github.com/grpc-ecosystem/grpc-gateway/v2@v2.19.1/internal/httprule/parse_test.go (about) 1 package httprule 2 3 import ( 4 "errors" 5 "fmt" 6 "reflect" 7 "testing" 8 9 "google.golang.org/grpc/grpclog" 10 ) 11 12 func TestTokenize(t *testing.T) { 13 for _, spec := range []struct { 14 src string 15 tokens []string 16 verb string 17 }{ 18 { 19 src: "", 20 tokens: []string{eof}, 21 }, 22 { 23 src: "v1", 24 tokens: []string{"v1", eof}, 25 }, 26 { 27 src: "v1/b", 28 tokens: []string{"v1", "/", "b", eof}, 29 }, 30 { 31 src: "v1/endpoint/*", 32 tokens: []string{"v1", "/", "endpoint", "/", "*", eof}, 33 }, 34 { 35 src: "v1/endpoint/**", 36 tokens: []string{"v1", "/", "endpoint", "/", "**", eof}, 37 }, 38 { 39 src: "v1/b/{bucket_name=*}", 40 tokens: []string{ 41 "v1", "/", 42 "b", "/", 43 "{", "bucket_name", "=", "*", "}", 44 eof, 45 }, 46 }, 47 { 48 src: "v1/b/{bucket_name=buckets/*}", 49 tokens: []string{ 50 "v1", "/", 51 "b", "/", 52 "{", "bucket_name", "=", "buckets", "/", "*", "}", 53 eof, 54 }, 55 }, 56 { 57 src: "v1/b/{bucket_name=buckets/*}/o", 58 tokens: []string{ 59 "v1", "/", 60 "b", "/", 61 "{", "bucket_name", "=", "buckets", "/", "*", "}", "/", 62 "o", 63 eof, 64 }, 65 }, 66 { 67 src: "v1/b/{bucket_name=buckets/*}/o/{name}", 68 tokens: []string{ 69 "v1", "/", 70 "b", "/", 71 "{", "bucket_name", "=", "buckets", "/", "*", "}", "/", 72 "o", "/", "{", "name", "}", 73 eof, 74 }, 75 }, 76 { 77 src: "v1/a=b&c=d;e=f:g/endpoint.rdf", 78 tokens: []string{ 79 "v1", "/", 80 "a=b&c=d;e=f:g", "/", 81 "endpoint.rdf", 82 eof, 83 }, 84 }, 85 { 86 src: "v1/a/{endpoint}:a", 87 tokens: []string{ 88 "v1", "/", 89 "a", "/", 90 "{", "endpoint", "}", 91 eof, 92 }, 93 verb: "a", 94 }, 95 { 96 src: "v1/a/{endpoint}:b:c", 97 tokens: []string{ 98 "v1", "/", 99 "a", "/", 100 "{", "endpoint", "}", 101 eof, 102 }, 103 verb: "b:c", 104 }, 105 } { 106 tokens, verb := tokenize(spec.src) 107 if got, want := tokens, spec.tokens; !reflect.DeepEqual(got, want) { 108 t.Errorf("tokenize(%q) = %q, _; want %q, _", spec.src, got, want) 109 } 110 111 switch { 112 case spec.verb != "": 113 if got, want := verb, spec.verb; !reflect.DeepEqual(got, want) { 114 t.Errorf("tokenize(%q) = %q, _; want %q, _", spec.src, got, want) 115 } 116 117 default: 118 if got, want := verb, ""; got != want { 119 t.Errorf("tokenize(%q) = _, %q; want _, %q", spec.src, got, want) 120 } 121 122 src := fmt.Sprintf("%s:%s", spec.src, "LOCK") 123 tokens, verb = tokenize(src) 124 if got, want := tokens, spec.tokens; !reflect.DeepEqual(got, want) { 125 t.Errorf("tokenize(%q) = %q, _; want %q, _", src, got, want) 126 } 127 if got, want := verb, "LOCK"; got != want { 128 t.Errorf("tokenize(%q) = _, %q; want _, %q", src, got, want) 129 } 130 } 131 } 132 } 133 134 func TestParseSegments(t *testing.T) { 135 for _, spec := range []struct { 136 tokens []string 137 want []segment 138 }{ 139 { 140 tokens: []string{eof}, 141 want: []segment{ 142 literal(eof), 143 }, 144 }, 145 { 146 // Note: this case will never arise as tokenize() will never return such a sequence of tokens 147 // and even if it does it will be treated as [eof] 148 tokens: []string{eof, "v1", eof}, 149 want: []segment{ 150 literal(eof), 151 }, 152 }, 153 { 154 tokens: []string{"v1", eof}, 155 want: []segment{ 156 literal("v1"), 157 }, 158 }, 159 { 160 tokens: []string{"/", eof}, 161 want: []segment{ 162 wildcard{}, 163 }, 164 }, 165 { 166 tokens: []string{"-._~!$&'()*+,;=:@", eof}, 167 want: []segment{ 168 literal("-._~!$&'()*+,;=:@"), 169 }, 170 }, 171 { 172 tokens: []string{"%e7%ac%ac%e4%b8%80%e7%89%88", eof}, 173 want: []segment{ 174 literal("%e7%ac%ac%e4%b8%80%e7%89%88"), 175 }, 176 }, 177 { 178 tokens: []string{"v1", "/", "*", eof}, 179 want: []segment{ 180 literal("v1"), 181 wildcard{}, 182 }, 183 }, 184 { 185 tokens: []string{"v1", "/", "**", eof}, 186 want: []segment{ 187 literal("v1"), 188 deepWildcard{}, 189 }, 190 }, 191 { 192 tokens: []string{"{", "name", "}", eof}, 193 want: []segment{ 194 variable{ 195 path: "name", 196 segments: []segment{ 197 wildcard{}, 198 }, 199 }, 200 }, 201 }, 202 { 203 tokens: []string{"{", "name", "=", "*", "}", eof}, 204 want: []segment{ 205 variable{ 206 path: "name", 207 segments: []segment{ 208 wildcard{}, 209 }, 210 }, 211 }, 212 }, 213 { 214 tokens: []string{"{", "field", ".", "nested", ".", "nested2", "=", "*", "}", eof}, 215 want: []segment{ 216 variable{ 217 path: "field.nested.nested2", 218 segments: []segment{ 219 wildcard{}, 220 }, 221 }, 222 }, 223 }, 224 { 225 tokens: []string{"{", "name", "=", "a", "/", "b", "/", "*", "}", eof}, 226 want: []segment{ 227 variable{ 228 path: "name", 229 segments: []segment{ 230 literal("a"), 231 literal("b"), 232 wildcard{}, 233 }, 234 }, 235 }, 236 }, 237 { 238 tokens: []string{ 239 "v1", "/", 240 "{", 241 "name", ".", "nested", ".", "nested2", 242 "=", 243 "a", "/", "b", "/", "*", 244 "}", "/", 245 "o", "/", 246 "{", 247 "another_name", 248 "=", 249 "a", "/", "b", "/", "*", "/", "c", 250 "}", "/", 251 "**", 252 eof, 253 }, 254 want: []segment{ 255 literal("v1"), 256 variable{ 257 path: "name.nested.nested2", 258 segments: []segment{ 259 literal("a"), 260 literal("b"), 261 wildcard{}, 262 }, 263 }, 264 literal("o"), 265 variable{ 266 path: "another_name", 267 segments: []segment{ 268 literal("a"), 269 literal("b"), 270 wildcard{}, 271 literal("c"), 272 }, 273 }, 274 deepWildcard{}, 275 }, 276 }, 277 } { 278 p := parser{tokens: spec.tokens} 279 segs, err := p.topLevelSegments() 280 if err != nil { 281 t.Errorf("parser{%q}.segments() failed with %v; want success", spec.tokens, err) 282 continue 283 } 284 if got, want := segs, spec.want; !reflect.DeepEqual(got, want) { 285 t.Errorf("parser{%q}.segments() = %#v; want %#v", spec.tokens, got, want) 286 } 287 if got := p.tokens; len(got) > 0 { 288 t.Errorf("p.tokens = %q; want []; spec.tokens=%q", got, spec.tokens) 289 } 290 } 291 } 292 293 func TestParse(t *testing.T) { 294 for _, spec := range []struct { 295 input string 296 wantFields []string 297 wantOpCodes []int 298 wantPool []string 299 wantVerb string 300 }{ 301 { 302 input: "/v1/{name}:bla:baa", 303 wantFields: []string{ 304 "name", 305 }, 306 wantPool: []string{"v1", "name"}, 307 wantVerb: "bla:baa", 308 }, 309 { 310 input: "/v1/{name}:", 311 wantFields: []string{ 312 "name", 313 }, 314 wantPool: []string{"v1", "name"}, 315 wantVerb: "", 316 }, 317 { 318 input: "/v1/{name=segment/wi:th}", 319 wantFields: []string{ 320 "name", 321 }, 322 wantPool: []string{"v1", "segment", "wi:th", "name"}, 323 wantVerb: "", 324 }, 325 } { 326 f, err := Parse(spec.input) 327 if err != nil { 328 t.Errorf("Parse(%q) failed with %v; want success", spec.input, err) 329 continue 330 } 331 tmpl := f.Compile() 332 if !reflect.DeepEqual(tmpl.Fields, spec.wantFields) { 333 t.Errorf("Parse(%q).Fields = %#v; want %#v", spec.input, tmpl.Fields, spec.wantFields) 334 } 335 if !reflect.DeepEqual(tmpl.Pool, spec.wantPool) { 336 t.Errorf("Parse(%q).Pool = %#v; want %#v", spec.input, tmpl.Pool, spec.wantPool) 337 } 338 if tmpl.Template != spec.input { 339 t.Errorf("Parse(%q).Template = %q; want %q", spec.input, tmpl.Template, spec.input) 340 } 341 if tmpl.Verb != spec.wantVerb { 342 t.Errorf("Parse(%q).Verb = %q; want %q", spec.input, tmpl.Verb, spec.wantVerb) 343 } 344 } 345 } 346 347 func TestParseError(t *testing.T) { 348 for _, spec := range []struct { 349 input string 350 wantError error 351 }{ 352 { 353 input: "v1/{name}", 354 wantError: InvalidTemplateError{ 355 tmpl: "v1/{name}", 356 msg: "no leading /", 357 }, 358 }, 359 } { 360 _, err := Parse(spec.input) 361 if err == nil { 362 t.Errorf("Parse(%q) unexpectedly did not fail", spec.input) 363 continue 364 } 365 if !errors.Is(err, spec.wantError) { 366 t.Errorf("Error did not match expected error: got %v wanted %v", err, spec.wantError) 367 } 368 } 369 } 370 371 func TestParseSegmentsWithErrors(t *testing.T) { 372 for _, spec := range []struct { 373 tokens []string 374 }{ 375 { 376 // double slash 377 tokens: []string{"//", eof}, 378 }, 379 { 380 // invalid literal 381 tokens: []string{"a?b", eof}, 382 }, 383 { 384 // invalid percent-encoding 385 tokens: []string{"%", eof}, 386 }, 387 { 388 // invalid percent-encoding 389 tokens: []string{"%2", eof}, 390 }, 391 { 392 // invalid percent-encoding 393 tokens: []string{"a%2z", eof}, 394 }, 395 { 396 // unterminated variable 397 tokens: []string{"{", "name", eof}, 398 }, 399 { 400 // unterminated variable 401 tokens: []string{"{", "name", "=", eof}, 402 }, 403 { 404 // unterminated variable 405 tokens: []string{"{", "name", "=", "*", eof}, 406 }, 407 { 408 // empty component in field path 409 tokens: []string{"{", "name", ".", "}", eof}, 410 }, 411 { 412 // empty component in field path 413 tokens: []string{"{", "name", ".", ".", "nested", "}", eof}, 414 }, 415 { 416 // invalid character in identifier 417 tokens: []string{"{", "field-name", "}", eof}, 418 }, 419 { 420 // no slash between segments 421 tokens: []string{"v1", "endpoint", eof}, 422 }, 423 { 424 // no slash between segments 425 tokens: []string{"v1", "{", "name", "}", eof}, 426 }, 427 } { 428 p := parser{tokens: spec.tokens} 429 segs, err := p.topLevelSegments() 430 if err == nil { 431 t.Errorf("parser{%q}.segments() succeeded; want InvalidTemplateError; accepted %#v", spec.tokens, segs) 432 continue 433 } 434 if grpclog.V(1) { 435 grpclog.Info(err) 436 } 437 } 438 }