package httpruleimport ()// InvalidTemplateError indicates that the path template is not valid.typeInvalidTemplateErrorstruct { tmpl string msg string}func ( InvalidTemplateError) () string {returnfmt.Sprintf("%s: %s", .msg, .tmpl)}// Parse parses the string representation of path templatefunc ( string) (Compiler, error) {if !strings.HasPrefix(, "/") {returntemplate{}, InvalidTemplateError{tmpl: , msg: "no leading /"} } , := tokenize([1:]) := parser{tokens: } , := .topLevelSegments()if != nil {returntemplate{}, InvalidTemplateError{tmpl: , msg: .Error()} }returntemplate{segments: ,verb: ,template: , }, nil}func tokenize( string) ( []string, string) {if == "" {return []string{eof}, "" }const ( = iota ) := for != "" {varintswitch {case : = strings.IndexAny(, "/{")case : = strings.IndexAny(, ".=}")case : = strings.IndexAny(, "/}") }if < 0 { = append(, )break }switch := []; {case'/', '.':case'{': = case'=': = case'}': = }if == 0 { = append(, [:+1]) } else { = append(, [:], [:+1]) } = [+1:] } := len()// 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.varboolswitch {case0, 1:// Not enough to be variable so skip this logic and don't result in an // invalid indexdefault: = [-2] == "}" } := [-1]varintif { = strings.Index(, ":") } else { = strings.LastIndex(, ":") }if == 0 { , = [:-1], [1:] } elseif > 0 { [-1], = [:], [+1:] } = append(, eof)return , }// 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 ( *parser) () ([]segment, error) {if , := .accept(typeEOF); == nil { .tokens = .tokens[:0]return []segment{literal(eof)}, nil } , := .segments()if != nil {returnnil, }if , := .accept(typeEOF); != nil {returnnil, fmt.Errorf("unexpected token %q after segments %q", .tokens[0], strings.Join(.accepted, "")) }return , nil}func ( *parser) () ([]segment, error) { , := .segment()if != nil {returnnil, } := []segment{}for {if , := .accept("/"); != nil {return , nil } , := .segment()if != nil {return , } = append(, ) }}func ( *parser) () (segment, error) {if , := .accept("*"); == nil {returnwildcard{}, nil }if , := .accept("**"); == nil {returndeepWildcard{}, nil }if , := .literal(); == nil {return , nil } , := .variable()if != nil {returnnil, fmt.Errorf("segment neither wildcards, literal or variable: %w", ) }return , nil}func ( *parser) () (segment, error) { , := .accept(typeLiteral)if != nil {returnnil, }returnliteral(), nil}func ( *parser) () (segment, error) {if , := .accept("{"); != nil {returnnil, } , := .fieldPath()if != nil {returnnil, }var []segmentif , := .accept("="); == nil { , = .segments()if != nil {returnnil, fmt.Errorf("invalid segment in variable %q: %w", , ) } } else { = []segment{wildcard{}} }if , := .accept("}"); != nil {returnnil, fmt.Errorf("unterminated variable segment: %s", ) }returnvariable{path: ,segments: , }, nil}func ( *parser) () (string, error) { , := .accept(typeIdent)if != nil {return"", } := []string{}for {if , := .accept("."); != nil {returnstrings.Join(, "."), nil } , := .accept(typeIdent)if != nil {return"", fmt.Errorf("invalid field path component: %w", ) } = append(, ) }}// 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("$"))// eof is the terminal symbol which always appears at the end of token sequence.const 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 ( *parser) ( termType) (string, error) { := .tokens[0]switch {case"/", "*", "**", ".", "=", "{", "}":if != string() && != "/" {return"", fmt.Errorf("expected %q but got %q", , ) }casetypeEOF:if != eof {return"", fmt.Errorf("expected EOF but got %q", ) }casetypeIdent:if := expectIdent(); != nil {return"", }casetypeLiteral:if := expectPChars(); != nil {return"", }default:return"", fmt.Errorf("unknown termType %q", ) } .tokens = .tokens[1:] .accepted = append(.accepted, )return , 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 HEXDIGfunc expectPChars( string) error {const ( = iota ) := for , := range {if != {if !isHexDigit() {returnfmt.Errorf("invalid hexdigit: %c(%U)", , ) }switch {case : = case : = }continue }// unreservedswitch {case'A' <= && <= 'Z':continuecase'a' <= && <= 'z':continuecase'0' <= && <= '9':continue }switch {case'-', '.', '_', '~':// unreservedcase'!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=':// sub-delimscase':', '@':// rest of pcharcase'%':// pct-encoded = default:returnfmt.Errorf("invalid character in path segment: %q(%U)", , ) } }if != {returnfmt.Errorf("invalid percent-encoding in %q", ) }returnnil}// expectIdent determines if "ident" is a valid identifier in .proto schema ([[:alpha:]_][[:alphanum:]_]*).func expectIdent( string) error {if == "" {returnerrors.New("empty identifier") }for , := range {switch {case'0' <= && <= '9':if == 0 {returnfmt.Errorf("identifier starting with digit: %s", ) }continuecase'A' <= && <= 'Z':continuecase'a' <= && <= 'z':continuecase == '_':continuedefault:returnfmt.Errorf("invalid character %q(%U) in identifier: %s", , , ) } }returnnil}func isHexDigit( rune) bool {switch {case'0' <= && <= '9':returntruecase'A' <= && <= 'F':returntruecase'a' <= && <= 'f':returntrue }returnfalse}
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.