From 8237e6a08e7600e521382f6fc469e7a18ba32377 Mon Sep 17 00:00:00 2001 From: Vasiliy Tolstov Date: Wed, 29 Sep 2021 13:09:22 +0300 Subject: [PATCH] util/router: drop google copy of pattern matcher in favour of util/http trie Signed-off-by: Vasiliy Tolstov --- util/router/LICENSE.txt | 27 --- util/router/compile.go | 119 ------------ util/router/compile_test.go | 129 ------------- util/router/parse.go | 371 ------------------------------------ util/router/parse_test.go | 360 ---------------------------------- util/router/pattern.go | 24 --- util/router/router.go | 32 ---- util/router/runtime.go | 259 ------------------------- util/router/types.go | 62 ------ util/router/types_test.go | 93 --------- 10 files changed, 1476 deletions(-) delete mode 100644 util/router/LICENSE.txt delete mode 100644 util/router/compile.go delete mode 100644 util/router/compile_test.go delete mode 100644 util/router/parse.go delete mode 100644 util/router/parse_test.go delete mode 100644 util/router/pattern.go delete mode 100644 util/router/router.go delete mode 100644 util/router/runtime.go delete mode 100644 util/router/types.go delete mode 100644 util/router/types_test.go diff --git a/util/router/LICENSE.txt b/util/router/LICENSE.txt deleted file mode 100644 index 36451625..00000000 --- a/util/router/LICENSE.txt +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2015, Gengo, Inc. -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - * Neither the name of Gengo, Inc. nor the names of its - contributors may be used to endorse or promote products derived from this - software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/util/router/compile.go b/util/router/compile.go deleted file mode 100644 index 22f7d874..00000000 --- a/util/router/compile.go +++ /dev/null @@ -1,119 +0,0 @@ -package router - -// download from https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/master/protoc-gen-grpc-gateway/httprule/compile.go - -const ( - opcodeVersion = 1 -) - -// Template is a compiled representation of path templates. -type Template struct { - // Version is the version number of the format. - Version int - // OpCodes is a sequence of operations. - OpCodes []int - // Pool is a constant pool - Pool []string - // Verb is a VERB part in the template. - Verb string - // Fields is a list of field paths bound in this template. - Fields []string - // Original template (example: /v1/a_bit_of_everything) - Template string -} - -// Compiler compiles utilities representation of path templates into marshallable operations. -// They can be unmarshalled by runtime.NewPattern. -type Compiler interface { - Compile() Template -} - -type op struct { - // code is the opcode of the operation - code OpCode - - // str is a string operand of the code. - // num is ignored if str is not empty. - str string - - // num is a numeric operand of the code. - num int -} - -func (w wildcard) compile() []op { - return []op{ - {code: OpPush}, - } -} - -func (w deepWildcard) compile() []op { - return []op{ - {code: OpPushM}, - } -} - -func (l literal) compile() []op { - return []op{ - { - code: OpLitPush, - str: string(l), - }, - } -} - -func (v variable) compile() []op { - ops := make([]op, 0, len(v.segments)) - for _, s := range v.segments { - ops = append(ops, s.compile()...) - } - ops = append(ops, op{ - code: OpConcatN, - num: len(v.segments), - }, op{ - code: OpCapture, - str: v.path, - }) - - return ops -} - -func (t template) Compile() Template { - rawOps := make([]op, 0, len(t.segments)) - for _, s := range t.segments { - rawOps = append(rawOps, s.compile()...) - } - - var ( - ops []int - pool []string - fields []string - ) - consts := make(map[string]int) - for _, op := range rawOps { - ops = append(ops, int(op.code)) - if op.str == "" { - ops = append(ops, op.num) - } else { - // eof segment literal represents the "/" path pattern - if op.str == eof { - op.str = "" - } - if _, ok := consts[op.str]; !ok { - consts[op.str] = len(pool) - pool = append(pool, op.str) - } - ops = append(ops, consts[op.str]) - } - if op.code == OpCapture { - fields = append(fields, op.str) - } - } - return Template{ - Version: opcodeVersion, - OpCodes: ops, - Pool: pool, - Verb: t.verb, - Fields: fields, - Template: t.template, - } -} diff --git a/util/router/compile_test.go b/util/router/compile_test.go deleted file mode 100644 index 63afe625..00000000 --- a/util/router/compile_test.go +++ /dev/null @@ -1,129 +0,0 @@ -package router - -// download from https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/master/protoc-gen-grpc-gateway/httprule/compile_test.go - -import ( - "reflect" - "testing" -) - -const ( - operandFiller = 0 -) - -func TestCompile(t *testing.T) { - for _, spec := range []struct { - segs []segment - verb string - - ops []int - pool []string - fields []string - }{ - {}, - { - segs: []segment{ - literal(eof), - }, - ops: []int{int(OpLitPush), 0}, - pool: []string{""}, - }, - { - segs: []segment{ - wildcard{}, - }, - ops: []int{int(OpPush), operandFiller}, - }, - { - segs: []segment{ - deepWildcard{}, - }, - ops: []int{int(OpPushM), operandFiller}, - }, - { - segs: []segment{ - literal("v1"), - }, - ops: []int{int(OpLitPush), 0}, - pool: []string{"v1"}, - }, - { - segs: []segment{ - literal("v1"), - }, - verb: "LOCK", - ops: []int{int(OpLitPush), 0}, - pool: []string{"v1"}, - }, - { - segs: []segment{ - variable{ - path: "name.nested", - segments: []segment{ - wildcard{}, - }, - }, - }, - ops: []int{ - int(OpPush), operandFiller, - int(OpConcatN), 1, - int(OpCapture), 0, - }, - pool: []string{"name.nested"}, - fields: []string{"name.nested"}, - }, - { - segs: []segment{ - literal("obj"), - variable{ - path: "name.nested", - segments: []segment{ - literal("a"), - wildcard{}, - literal("b"), - }, - }, - variable{ - path: "obj", - segments: []segment{ - deepWildcard{}, - }, - }, - }, - ops: []int{ - int(OpLitPush), 0, - int(OpLitPush), 1, - int(OpPush), operandFiller, - int(OpLitPush), 2, - int(OpConcatN), 3, - int(OpCapture), 3, - int(OpPushM), operandFiller, - int(OpConcatN), 1, - int(OpCapture), 0, - }, - pool: []string{"obj", "a", "b", "name.nested"}, - fields: []string{"name.nested", "obj"}, - }, - } { - tmpl := template{ - segments: spec.segs, - verb: spec.verb, - } - compiled := tmpl.Compile() - if got, want := compiled.Version, opcodeVersion; got != want { - t.Errorf("tmpl.Compile().Version = %d; want %d; segs=%#v, verb=%q", got, want, spec.segs, spec.verb) - } - if got, want := compiled.OpCodes, spec.ops; !reflect.DeepEqual(got, want) { - t.Errorf("tmpl.Compile().OpCodes = %v; want %v; segs=%#v, verb=%q", got, want, spec.segs, spec.verb) - } - if got, want := compiled.Pool, spec.pool; !reflect.DeepEqual(got, want) { - t.Errorf("tmpl.Compile().Pool = %q; want %q; segs=%#v, verb=%q", got, want, spec.segs, spec.verb) - } - if got, want := compiled.Verb, spec.verb; got != want { - t.Errorf("tmpl.Compile().Verb = %q; want %q; segs=%#v, verb=%q", got, want, spec.segs, spec.verb) - } - if got, want := compiled.Fields, spec.fields; !reflect.DeepEqual(got, want) { - t.Errorf("tmpl.Compile().Fields = %q; want %q; segs=%#v, verb=%q", got, want, spec.segs, spec.verb) - } - } -} diff --git a/util/router/parse.go b/util/router/parse.go deleted file mode 100644 index 846ba0d1..00000000 --- a/util/router/parse.go +++ /dev/null @@ -1,371 +0,0 @@ -package router - -// download from https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/master/protoc-gen-grpc-gateway/httprule/parse.go - -import ( - "fmt" - "strings" -) - -// InvalidTemplateError indicates that the path template is not valid. -type InvalidTemplateError struct { - tmpl string - msg string -} - -func (e InvalidTemplateError) Error() string { - return fmt.Sprintf("%s: %s", e.msg, e.tmpl) -} - -// Parse parses the string representation of path template -func Parse(tmpl string) (Compiler, error) { - if !strings.HasPrefix(tmpl, "/") { - return template{}, InvalidTemplateError{tmpl: tmpl, msg: "no leading /"} - } - tokens, verb := tokenize(tmpl[1:]) - - p := parser{tokens: tokens} - segs, err := p.topLevelSegments() - if err != nil { - return template{}, InvalidTemplateError{tmpl: tmpl, msg: err.Error()} - } - - return template{ - segments: segs, - verb: verb, - template: tmpl, - }, nil -} - -func tokenize(path string) (tokens []string, verb string) { - if path == "" { - return []string{eof}, "" - } - - const ( - init = iota - field - nested - ) - st := init - for path != "" { - var idx int - switch st { - case init: - idx = strings.IndexAny(path, "/{") - case field: - idx = strings.IndexAny(path, ".=}") - case nested: - idx = strings.IndexAny(path, "/}") - } - if idx < 0 { - tokens = append(tokens, path) - break - } - switch r := path[idx]; r { - case '/', '.': - case '{': - st = field - case '=': - st = nested - case '}': - st = init - } - if idx == 0 { - tokens = append(tokens, path[idx:idx+1]) - } else { - tokens = append(tokens, path[:idx], path[idx:idx+1]) - } - path = path[idx+1:] - } - - l := len(tokens) - // See - // https://github.com/grpc-ecosystem/grpc-gateway/pull/1947#issuecomment-774523693 ; - // although normal and backwards-compat logic here is to use the last index - // of a colon, if the final segment is a variable followed by a colon, the - // part following the colon must be a verb. Hence if the previous token is - // an end var marker, we switch the index we're looking for to Index instead - // of LastIndex, so that we correctly grab the remaining part of the path as - // the verb. - var penultimateTokenIsEndVar bool - switch l { - case 0, 1: - // Not enough to be variable so skip this logic and don't result in an - // invalid index - default: - penultimateTokenIsEndVar = tokens[l-2] == "}" - } - t := tokens[l-1] - var idx int - if penultimateTokenIsEndVar { - idx = strings.Index(t, ":") - } else { - idx = strings.LastIndex(t, ":") - } - if idx == 0 { - tokens, verb = tokens[:l-1], t[1:] - } else if idx > 0 { - tokens[l-1], verb = t[:idx], t[idx+1:] - } - tokens = append(tokens, eof) - return tokens, verb -} - -// parser is a parser of the template syntax defined in github.com/googleapis/googleapis/google/api/http.proto. -type parser struct { - tokens []string - accepted []string -} - -// topLevelSegments is the target of this parser. -func (p *parser) topLevelSegments() ([]segment, error) { - if _, err := p.accept(typeEOF); err == nil { - p.tokens = p.tokens[:0] - return []segment{literal(eof)}, nil - } - segs, err := p.segments() - if err != nil { - return nil, err - } - if _, err := p.accept(typeEOF); err != nil { - return nil, fmt.Errorf("unexpected token %q after segments %q", p.tokens[0], strings.Join(p.accepted, "")) - } - return segs, nil -} - -func (p *parser) segments() ([]segment, error) { - s, err := p.segment() - if err != nil { - return nil, err - } - - segs := []segment{s} - for { - if _, err := p.accept("/"); err != nil { - return segs, nil - } - s, err := p.segment() - if err != nil { - return segs, err - } - segs = append(segs, s) - } -} - -func (p *parser) segment() (segment, error) { - if _, err := p.accept("*"); err == nil { - return wildcard{}, nil - } - if _, err := p.accept("**"); err == nil { - return deepWildcard{}, nil - } - if l, err := p.literal(); err == nil { - return l, nil - } - - v, err := p.variable() - if err != nil { - return nil, fmt.Errorf("segment neither wildcards, literal or variable: %v", err) - } - return v, err -} - -func (p *parser) literal() (segment, error) { - lit, err := p.accept(typeLiteral) - if err != nil { - return nil, err - } - return literal(lit), nil -} - -func (p *parser) variable() (segment, error) { - if _, err := p.accept("{"); err != nil { - return nil, err - } - - path, err := p.fieldPath() - if err != nil { - return nil, err - } - - var segs []segment - if _, err := p.accept("="); err == nil { - segs, err = p.segments() - if err != nil { - return nil, fmt.Errorf("invalid segment in variable %q: %v", path, err) - } - } else { - segs = []segment{wildcard{}} - } - - if _, err := p.accept("}"); err != nil { - return nil, fmt.Errorf("unterminated variable segment: %s", path) - } - return variable{ - path: path, - segments: segs, - }, nil -} - -func (p *parser) fieldPath() (string, error) { - c, err := p.accept(typeIdent) - if err != nil { - return "", err - } - components := []string{c} - for { - if _, err = p.accept("."); err != nil { - return strings.Join(components, "."), nil - } - c, err := p.accept(typeIdent) - if err != nil { - return "", fmt.Errorf("invalid field path component: %v", err) - } - components = append(components, c) - } -} - -// A termType is a type of terminal symbols. -type termType string - -// These constants define some of valid values of termType. -// They improve readability of parse functions. -// -// You can also use "/", "*", "**", "." or "=" as valid values. -const ( - typeIdent = termType("ident") - typeLiteral = termType("literal") - typeEOF = termType("$") -) - -const ( - // eof is the terminal symbol which always appears at the end of token sequence. - eof = "\u0000" -) - -// accept tries to accept a token in "p". -// This function consumes a token and returns it if it matches to the specified "term". -// If it doesn't match, the function does not consume any tokens and return an error. -func (p *parser) accept(term termType) (string, error) { - t := p.tokens[0] - switch term { - case "/", "*", "**", ".", "=", "{", "}": - if t != string(term) && t != "/" { - return "", fmt.Errorf("expected %q but got %q", term, t) - } - case typeEOF: - if t != eof { - return "", fmt.Errorf("expected EOF but got %q", t) - } - case typeIdent: - if err := expectIdent(t); err != nil { - return "", err - } - case typeLiteral: - if err := expectPChars(t); err != nil { - return "", err - } - default: - return "", fmt.Errorf("unknown termType %q", term) - } - p.tokens = p.tokens[1:] - p.accepted = append(p.accepted, t) - return t, nil -} - -// expectPChars determines if "t" consists of only pchars defined in RFC3986. -// -// https://www.ietf.org/rfc/rfc3986.txt, P.49 -// pchar = unreserved / pct-encoded / sub-delims / ":" / "@" -// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" -// sub-delims = "!" / "$" / "&" / "'" / "(" / ")" -// / "*" / "+" / "," / ";" / "=" -// pct-encoded = "%" HEXDIG HEXDIG -//nolint:gocyclo -func expectPChars(t string) error { - const ( - init = iota - pct1 - pct2 - ) - st := init - for _, r := range t { - if st != init { - if !isHexDigit(r) { - return fmt.Errorf("invalid hexdigit: %c(%U)", r, r) - } - switch st { - case pct1: - st = pct2 - case pct2: - st = init - } - continue - } - - // unreserved - switch { - case 'A' <= r && r <= 'Z': - continue - case 'a' <= r && r <= 'z': - continue - case '0' <= r && r <= '9': - continue - } - switch r { - case '-', '.', '_', '~': - // unreserved - case '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=': - // sub-delims - case ':', '@': - // rest of pchar - case '%': - // pct-encoded - st = pct1 - default: - return fmt.Errorf("invalid character in path segment: %q(%U)", r, r) - } - } - if st != init { - return fmt.Errorf("invalid percent-encoding in %q", t) - } - return nil -} - -// expectIdent determines if "ident" is a valid identifier in .proto schema ([[:alpha:]_][[:alphanum:]_]*). -func expectIdent(ident string) error { - if ident == "" { - return fmt.Errorf("empty identifier") - } - for pos, r := range ident { - switch { - case '0' <= r && r <= '9': - if pos == 0 { - return fmt.Errorf("identifier starting with digit: %s", ident) - } - continue - case 'A' <= r && r <= 'Z': - continue - case 'a' <= r && r <= 'z': - continue - case r == '_': - continue - default: - return fmt.Errorf("invalid character %q(%U) in identifier: %s", r, r, ident) - } - } - return nil -} - -func isHexDigit(r rune) bool { - switch { - case '0' <= r && r <= '9': - return true - case 'A' <= r && r <= 'F': - return true - case 'a' <= r && r <= 'f': - return true - } - return false -} diff --git a/util/router/parse_test.go b/util/router/parse_test.go deleted file mode 100644 index fb13b444..00000000 --- a/util/router/parse_test.go +++ /dev/null @@ -1,360 +0,0 @@ -package router - -// download from https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/master/protoc-gen-grpc-gateway/httprule/parse_test.go - -import ( - "context" - "fmt" - "reflect" - "testing" - - "github.com/unistack-org/micro/v3/logger" -) - -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 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 - } - logger.Info(context.TODO(), err.Error()) - } -} diff --git a/util/router/pattern.go b/util/router/pattern.go deleted file mode 100644 index 394b4b45..00000000 --- a/util/router/pattern.go +++ /dev/null @@ -1,24 +0,0 @@ -package router - -// download from https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/master/utilities/pattern.go - -// An OpCode is a opcode of compiled path patterns. -type OpCode int - -// These constants are the valid values of OpCode. -const ( - // OpNop does nothing - OpNop = OpCode(iota) - // OpPush pushes a component to stack - OpPush - // OpLitPush pushes a component to stack if it matches to the literal - OpLitPush - // OpPushM concatenates the remaining components and pushes it to stack - OpPushM - // OpConcatN pops N items from stack, concatenates them and pushes it back to stack - OpConcatN - // OpCapture pops an item and binds it to the variable - OpCapture - // OpEnd is the least positive invalid opcode. - OpEnd -) diff --git a/util/router/router.go b/util/router/router.go deleted file mode 100644 index 2bf6dcc5..00000000 --- a/util/router/router.go +++ /dev/null @@ -1,32 +0,0 @@ -package router - -import ( - "github.com/unistack-org/micro/v3/register" - "github.com/unistack-org/micro/v3/router" -) - -type apiRouter struct { - router.Router - routes []router.Route -} - -func (r *apiRouter) Lookup(...router.QueryOption) ([]router.Route, error) { - return r.routes, nil -} - -func (r *apiRouter) String() string { - return "api" -} - -// New router is a hack for API routing -func New(srvs []*register.Service) router.Router { - var routes []router.Route - - for _, srv := range srvs { - for _, n := range srv.Nodes { - routes = append(routes, router.Route{Address: n.Address, Metadata: n.Metadata}) - } - } - - return &apiRouter{routes: routes} -} diff --git a/util/router/runtime.go b/util/router/runtime.go deleted file mode 100644 index 54d15f71..00000000 --- a/util/router/runtime.go +++ /dev/null @@ -1,259 +0,0 @@ -package router - -// download from https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/master/runtime/pattern.go - -import ( - "context" - "errors" - "fmt" - "strings" - - "github.com/unistack-org/micro/v3/logger" -) - -var ( - // ErrNotMatch indicates that the given HTTP request path does not match to the pattern. - ErrNotMatch = errors.New("not match to the path pattern") - // ErrInvalidPattern indicates that the given definition of Pattern is not valid. - ErrInvalidPattern = errors.New("invalid pattern") -) - -type rop struct { - code OpCode - operand int -} - -// Pattern is a template pattern of http request paths defined in -// https://github.com/googleapis/googleapis/blob/master/google/api/http.proto -type Pattern struct { - // ops is a list of operations - ops []rop - // pool is a constant pool indexed by the operands or vars - pool []string - // vars is a list of variables names to be bound by this pattern - vars []string - // stacksize is the max depth of the stack - stacksize int - // tailLen is the length of the fixed-size segments after a deep wildcard - tailLen int - // verb is the VERB part of the path pattern. It is empty if the pattern does not have VERB part. - verb string -} - -// NewPattern returns a new Pattern from the given definition values. -// "ops" is a sequence of op codes. "pool" is a constant pool. -// "verb" is the verb part of the pattern. It is empty if the pattern does not have the part. -// "version" must be 1 for now. -// It returns an error if the given definition is invalid. -func NewPattern(version int, ops []int, pool []string, verb string) (Pattern, error) { - if version != 1 { - if logger.V(logger.TraceLevel) { - logger.Trace(context.TODO(), "unsupported version: %d", version) - } - return Pattern{}, ErrInvalidPattern - } - - l := len(ops) - if l%2 != 0 { - if logger.V(logger.TraceLevel) { - logger.Trace(context.TODO(), "odd number of ops codes: %d", l) - } - return Pattern{}, ErrInvalidPattern - } - - var ( - typedOps []rop - stack, maxstack int - tailLen int - pushMSeen bool - vars []string - ) - for i := 0; i < l; i += 2 { - op := rop{code: OpCode(ops[i]), operand: ops[i+1]} - switch op.code { - case OpNop: - continue - case OpPush: - if pushMSeen { - tailLen++ - } - stack++ - case OpPushM: - if pushMSeen { - if logger.V(logger.TraceLevel) { - logger.Trace(context.TODO(), "pushM appears twice") - } - return Pattern{}, ErrInvalidPattern - } - pushMSeen = true - stack++ - case OpLitPush: - if op.operand < 0 || len(pool) <= op.operand { - if logger.V(logger.TraceLevel) { - logger.Trace(context.TODO(), "negative literal index: %d", op.operand) - } - return Pattern{}, ErrInvalidPattern - } - if pushMSeen { - tailLen++ - } - stack++ - case OpConcatN: - if op.operand <= 0 { - if logger.V(logger.TraceLevel) { - logger.Trace(context.TODO(), "negative concat size: %d", op.operand) - } - return Pattern{}, ErrInvalidPattern - } - stack -= op.operand - if stack < 0 { - if logger.V(logger.TraceLevel) { - logger.Trace(context.TODO(), "stack underflow") - } - return Pattern{}, ErrInvalidPattern - } - stack++ - case OpCapture: - if op.operand < 0 || len(pool) <= op.operand { - if logger.V(logger.TraceLevel) { - logger.Trace(context.TODO(), "variable name index out of bound: %d", op.operand) - } - return Pattern{}, ErrInvalidPattern - } - v := pool[op.operand] - op.operand = len(vars) - vars = append(vars, v) - stack-- - if stack < 0 { - if logger.V(logger.TraceLevel) { - logger.Trace(context.TODO(), "stack underflow") - } - return Pattern{}, ErrInvalidPattern - } - default: - if logger.V(logger.TraceLevel) { - logger.Trace(context.TODO(), "invalid opcode: %d", op.code) - } - return Pattern{}, ErrInvalidPattern - } - - if maxstack < stack { - maxstack = stack - } - typedOps = append(typedOps, op) - } - return Pattern{ - ops: typedOps, - pool: pool, - vars: vars, - stacksize: maxstack, - tailLen: tailLen, - verb: verb, - }, nil -} - -// MustPattern is a helper function which makes it easier to call NewPattern in variable initialization. -func MustPattern(p Pattern, err error) Pattern { - if err != nil { - if logger.V(logger.FatalLevel) { - logger.Fatal(context.TODO(), "Pattern initialization failed: %v", err) - } - } - return p -} - -// Match examines components if it matches to the Pattern. -// If it matches, the function returns a mapping from field paths to their captured values. -// If otherwise, the function returns an error. -//nolint:gocyclo -func (p Pattern) Match(components []string, verb string) (map[string]string, error) { - if p.verb != verb { - if p.verb != "" { - return nil, ErrNotMatch - } - if len(components) == 0 { - components = []string{":" + verb} - } else { - components = append([]string{}, components...) - components[len(components)-1] += ":" + verb - } - } - - var pos int - stack := make([]string, 0, p.stacksize) - captured := make([]string, len(p.vars)) - l := len(components) - for _, op := range p.ops { - switch op.code { - case OpNop: - continue - case OpPush, OpLitPush: - if pos >= l { - return nil, ErrNotMatch - } - c := components[pos] - if op.code == OpLitPush { - if lit := p.pool[op.operand]; c != lit { - return nil, ErrNotMatch - } - } - stack = append(stack, c) - pos++ - case OpPushM: - end := len(components) - if end < pos+p.tailLen { - return nil, ErrNotMatch - } - end -= p.tailLen - stack = append(stack, strings.Join(components[pos:end], "/")) - pos = end - case OpConcatN: - n := op.operand - l := len(stack) - n - stack = append(stack[:l], strings.Join(stack[l:], "/")) - case OpCapture: - n := len(stack) - 1 - captured[op.operand] = stack[n] - stack = stack[:n] - } - } - if pos < l { - return nil, ErrNotMatch - } - bindings := make(map[string]string, len(captured)) - for i, val := range captured { - bindings[p.vars[i]] = val - } - return bindings, nil -} - -// Verb returns the verb part of the Pattern. -func (p Pattern) Verb() string { return p.verb } - -func (p Pattern) String() string { - var stack []string - for _, op := range p.ops { - switch op.code { - case OpNop: - continue - case OpPush: - stack = append(stack, "*") - case OpLitPush: - stack = append(stack, p.pool[op.operand]) - case OpPushM: - stack = append(stack, "**") - case OpConcatN: - n := op.operand - l := len(stack) - n - stack = append(stack[:l], strings.Join(stack[l:], "/")) - case OpCapture: - n := len(stack) - 1 - stack[n] = fmt.Sprintf("{%s=%s}", p.vars[op.operand], stack[n]) - } - } - segs := strings.Join(stack, "/") - if p.verb != "" { - return fmt.Sprintf("/%s:%s", segs, p.verb) - } - return "/" + segs -} diff --git a/util/router/types.go b/util/router/types.go deleted file mode 100644 index 183eeb7c..00000000 --- a/util/router/types.go +++ /dev/null @@ -1,62 +0,0 @@ -package router - -// download from https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/master/protoc-gen-grpc-gateway/httprule/types.go - -import ( - "fmt" - "strings" -) - -type template struct { - segments []segment - verb string - template string -} - -type segment interface { - fmt.Stringer - compile() (ops []op) -} - -type wildcard struct{} - -type deepWildcard struct{} - -type literal string - -type variable struct { - path string - segments []segment -} - -func (wildcard) String() string { - return "*" -} - -func (deepWildcard) String() string { - return "**" -} - -func (l literal) String() string { - return string(l) -} - -func (v variable) String() string { - segs := make([]string, 0, len(v.segments)) - for _, s := range v.segments { - segs = append(segs, s.String()) - } - return fmt.Sprintf("{%s=%s}", v.path, strings.Join(segs, "/")) -} - -func (t template) String() string { - segs := make([]string, 0, len(t.segments)) - for _, s := range t.segments { - segs = append(segs, s.String()) - } - str := strings.Join(segs, "/") - if t.verb != "" { - str = fmt.Sprintf("%s:%s", str, t.verb) - } - return "/" + str -} diff --git a/util/router/types_test.go b/util/router/types_test.go deleted file mode 100644 index cb9eb8e7..00000000 --- a/util/router/types_test.go +++ /dev/null @@ -1,93 +0,0 @@ -package router - -// download from https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/master/protoc-gen-grpc-gateway/httprule/types_test.go - -import ( - "fmt" - "testing" -) - -func TestTemplateStringer(t *testing.T) { - for _, spec := range []struct { - segs []segment - want string - }{ - { - segs: []segment{ - literal("v1"), - }, - want: "/v1", - }, - { - segs: []segment{ - wildcard{}, - }, - want: "/*", - }, - { - segs: []segment{ - deepWildcard{}, - }, - want: "/**", - }, - { - segs: []segment{ - variable{ - path: "name", - segments: []segment{ - literal("a"), - }, - }, - }, - want: "/{name=a}", - }, - { - segs: []segment{ - variable{ - path: "name", - segments: []segment{ - literal("a"), - wildcard{}, - literal("b"), - }, - }, - }, - want: "/{name=a/*/b}", - }, - { - segs: []segment{ - literal("v1"), - variable{ - path: "name", - segments: []segment{ - literal("a"), - wildcard{}, - literal("b"), - }, - }, - literal("c"), - variable{ - path: "field.nested", - segments: []segment{ - wildcard{}, - literal("d"), - }, - }, - wildcard{}, - literal("e"), - deepWildcard{}, - }, - want: "/v1/{name=a/*/b}/c/{field.nested=*/d}/*/e/**", - }, - } { - tmpl := template{segments: spec.segs} - if got, want := tmpl.String(), spec.want; got != want { - t.Errorf("%#v.String() = %q; want %q", tmpl, got, want) - } - - tmpl.verb = "LOCK" - if got, want := tmpl.String(), fmt.Sprintf("%s:LOCK", spec.want); got != want { - t.Errorf("%#v.String() = %q; want %q", tmpl, got, want) - } - } -}