package httprule import ( "errors" "fmt" "reflect" "testing" "google.golang.org/grpc/grpclog" ) func TestTokenize(t *testing.T) { for _, spec := range []struct { src string tokens []string verb string }{ { src: "", tokens: []string{eof}, }, { src: "v1", tokens: []string{"v1", eof}, }, { src: "v1/b", tokens: []string{"v1", "/", "b", eof}, }, { src: "v1/endpoint/*", tokens: []string{"v1", "/", "endpoint", "/", "*", eof}, }, { src: "v1/endpoint/**", tokens: []string{"v1", "/", "endpoint", "/", "**", eof}, }, { src: "v1/b/{bucket_name=*}", tokens: []string{ "v1", "/", "b", "/", "{", "bucket_name", "=", "*", "}", eof, }, }, { src: "v1/b/{bucket_name=buckets/*}", tokens: []string{ "v1", "/", "b", "/", "{", "bucket_name", "=", "buckets", "/", "*", "}", eof, }, }, { src: "v1/b/{bucket_name=buckets/*}/o", tokens: []string{ "v1", "/", "b", "/", "{", "bucket_name", "=", "buckets", "/", "*", "}", "/", "o", eof, }, }, { src: "v1/b/{bucket_name=buckets/*}/o/{name}", tokens: []string{ "v1", "/", "b", "/", "{", "bucket_name", "=", "buckets", "/", "*", "}", "/", "o", "/", "{", "name", "}", eof, }, }, { src: "v1/a=b&c=d;e=f:g/endpoint.rdf", tokens: []string{ "v1", "/", "a=b&c=d;e=f:g", "/", "endpoint.rdf", eof, }, }, { src: "v1/a/{endpoint}:a", tokens: []string{ "v1", "/", "a", "/", "{", "endpoint", "}", eof, }, verb: "a", }, { src: "v1/a/{endpoint}:b:c", tokens: []string{ "v1", "/", "a", "/", "{", "endpoint", "}", eof, }, verb: "b:c", }, } { tokens, verb := tokenize(spec.src) if got, want := tokens, spec.tokens; !reflect.DeepEqual(got, want) { t.Errorf("tokenize(%q) = %q, _; want %q, _", spec.src, got, want) } switch { case spec.verb != "": if got, want := verb, spec.verb; !reflect.DeepEqual(got, want) { t.Errorf("tokenize(%q) = %q, _; want %q, _", spec.src, got, want) } default: if got, want := verb, ""; got != want { t.Errorf("tokenize(%q) = _, %q; want _, %q", spec.src, got, want) } src := fmt.Sprintf("%s:%s", spec.src, "LOCK") tokens, verb = tokenize(src) if got, want := tokens, spec.tokens; !reflect.DeepEqual(got, want) { t.Errorf("tokenize(%q) = %q, _; want %q, _", src, got, want) } if got, want := verb, "LOCK"; got != want { t.Errorf("tokenize(%q) = _, %q; want _, %q", src, got, want) } } } } func TestParseSegments(t *testing.T) { for _, spec := range []struct { tokens []string want []segment }{ { tokens: []string{eof}, want: []segment{ literal(eof), }, }, { // Note: this case will never arise as tokenize() will never return such a sequence of tokens // and even if it does it will be treated as [eof] tokens: []string{eof, "v1", eof}, want: []segment{ literal(eof), }, }, { tokens: []string{"v1", eof}, want: []segment{ literal("v1"), }, }, { tokens: []string{"/", eof}, want: []segment{ wildcard{}, }, }, { tokens: []string{"-._~!$&'()*+,;=:@", eof}, want: []segment{ literal("-._~!$&'()*+,;=:@"), }, }, { tokens: []string{"%e7%ac%ac%e4%b8%80%e7%89%88", eof}, want: []segment{ literal("%e7%ac%ac%e4%b8%80%e7%89%88"), }, }, { tokens: []string{"v1", "/", "*", eof}, want: []segment{ literal("v1"), wildcard{}, }, }, { tokens: []string{"v1", "/", "**", eof}, want: []segment{ literal("v1"), deepWildcard{}, }, }, { tokens: []string{"{", "name", "}", eof}, want: []segment{ variable{ path: "name", segments: []segment{ wildcard{}, }, }, }, }, { tokens: []string{"{", "name", "=", "*", "}", eof}, want: []segment{ variable{ path: "name", segments: []segment{ wildcard{}, }, }, }, }, { tokens: []string{"{", "field", ".", "nested", ".", "nested2", "=", "*", "}", eof}, want: []segment{ variable{ path: "field.nested.nested2", segments: []segment{ wildcard{}, }, }, }, }, { tokens: []string{"{", "name", "=", "a", "/", "b", "/", "*", "}", eof}, want: []segment{ variable{ path: "name", segments: []segment{ literal("a"), literal("b"), wildcard{}, }, }, }, }, { tokens: []string{ "v1", "/", "{", "name", ".", "nested", ".", "nested2", "=", "a", "/", "b", "/", "*", "}", "/", "o", "/", "{", "another_name", "=", "a", "/", "b", "/", "*", "/", "c", "}", "/", "**", eof, }, want: []segment{ literal("v1"), variable{ path: "name.nested.nested2", segments: []segment{ literal("a"), literal("b"), wildcard{}, }, }, literal("o"), variable{ path: "another_name", segments: []segment{ literal("a"), literal("b"), wildcard{}, literal("c"), }, }, deepWildcard{}, }, }, } { p := parser{tokens: spec.tokens} segs, err := p.topLevelSegments() if err != nil { t.Errorf("parser{%q}.segments() failed with %v; want success", spec.tokens, err) continue } if got, want := segs, spec.want; !reflect.DeepEqual(got, want) { t.Errorf("parser{%q}.segments() = %#v; want %#v", spec.tokens, got, want) } if got := p.tokens; len(got) > 0 { t.Errorf("p.tokens = %q; want []; spec.tokens=%q", got, spec.tokens) } } } func TestParse(t *testing.T) { for _, spec := range []struct { input string wantFields []string wantOpCodes []int wantPool []string wantVerb string }{ { input: "/v1/{name}:bla:baa", wantFields: []string{ "name", }, wantPool: []string{"v1", "name"}, wantVerb: "bla:baa", }, { input: "/v1/{name}:", wantFields: []string{ "name", }, wantPool: []string{"v1", "name"}, wantVerb: "", }, { input: "/v1/{name=segment/wi:th}", wantFields: []string{ "name", }, wantPool: []string{"v1", "segment", "wi:th", "name"}, wantVerb: "", }, } { f, err := Parse(spec.input) if err != nil { t.Errorf("Parse(%q) failed with %v; want success", spec.input, err) continue } tmpl := f.Compile() if !reflect.DeepEqual(tmpl.Fields, spec.wantFields) { t.Errorf("Parse(%q).Fields = %#v; want %#v", spec.input, tmpl.Fields, spec.wantFields) } if !reflect.DeepEqual(tmpl.Pool, spec.wantPool) { t.Errorf("Parse(%q).Pool = %#v; want %#v", spec.input, tmpl.Pool, spec.wantPool) } if tmpl.Template != spec.input { t.Errorf("Parse(%q).Template = %q; want %q", spec.input, tmpl.Template, spec.input) } if tmpl.Verb != spec.wantVerb { t.Errorf("Parse(%q).Verb = %q; want %q", spec.input, tmpl.Verb, spec.wantVerb) } } } func TestParseError(t *testing.T) { for _, spec := range []struct { input string wantError error }{ { input: "v1/{name}", wantError: InvalidTemplateError{ tmpl: "v1/{name}", msg: "no leading /", }, }, } { _, err := Parse(spec.input) if err == nil { t.Errorf("Parse(%q) unexpectedly did not fail", spec.input) continue } if !errors.Is(err, spec.wantError) { t.Errorf("Error did not match expected error: got %v wanted %v", err, spec.wantError) } } } func TestParseSegmentsWithErrors(t *testing.T) { for _, spec := range []struct { tokens []string }{ { // double slash tokens: []string{"//", eof}, }, { // invalid literal tokens: []string{"a?b", eof}, }, { // invalid percent-encoding tokens: []string{"%", eof}, }, { // invalid percent-encoding tokens: []string{"%2", eof}, }, { // invalid percent-encoding tokens: []string{"a%2z", eof}, }, { // unterminated variable tokens: []string{"{", "name", eof}, }, { // unterminated variable tokens: []string{"{", "name", "=", eof}, }, { // unterminated variable tokens: []string{"{", "name", "=", "*", eof}, }, { // empty component in field path tokens: []string{"{", "name", ".", "}", eof}, }, { // empty component in field path tokens: []string{"{", "name", ".", ".", "nested", "}", eof}, }, { // invalid character in identifier tokens: []string{"{", "field-name", "}", eof}, }, { // no slash between segments tokens: []string{"v1", "endpoint", eof}, }, { // no slash between segments tokens: []string{"v1", "{", "name", "}", eof}, }, } { p := parser{tokens: spec.tokens} segs, err := p.topLevelSegments() if err == nil { t.Errorf("parser{%q}.segments() succeeded; want InvalidTemplateError; accepted %#v", spec.tokens, segs) continue } if grpclog.V(1) { grpclog.Info(err) } } }