diff --git a/config/config_test.go b/config/config_test.go index c7913d6..302702b 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -43,11 +43,11 @@ func TestLegalServiceName(t *testing.T) { }, { `[ { "method": "GET", "info": "a", "path": "/invalid/s{braces}" } ]`, - ErrInvalidPatternBracePosition, + ErrInvalidPatternBraceCapture, }, { `[ { "method": "GET", "info": "a", "path": "/invalid/{braces}a" } ]`, - ErrInvalidPatternBracePosition, + ErrInvalidPatternBraceCapture, }, { `[ { "method": "GET", "info": "a", "path": "/invalid/{braces}" } ]`, @@ -55,11 +55,11 @@ func TestLegalServiceName(t *testing.T) { }, { `[ { "method": "GET", "info": "a", "path": "/invalid/s{braces}/abc" } ]`, - ErrInvalidPatternBracePosition, + ErrInvalidPatternBraceCapture, }, { `[ { "method": "GET", "info": "a", "path": "/invalid/{braces}s/abc" } ]`, - ErrInvalidPatternBracePosition, + ErrInvalidPatternBraceCapture, }, { `[ { "method": "GET", "info": "a", "path": "/invalid/{braces}/abc" } ]`, @@ -67,11 +67,11 @@ func TestLegalServiceName(t *testing.T) { }, { `[ { "method": "GET", "info": "a", "path": "/invalid/{b{races}s/abc" } ]`, - ErrInvalidPatternOpeningBrace, + ErrInvalidPatternBraceCapture, }, { `[ { "method": "GET", "info": "a", "path": "/invalid/{braces}/}abc" } ]`, - ErrInvalidPatternClosingBrace, + ErrInvalidPatternBraceCapture, }, } @@ -303,7 +303,7 @@ func TestParseParameters(t *testing.T) { } } ]`, - ErrIllegalParamName, + ErrMissingParamDesc, }, { // invalid param name suffix `[ @@ -316,7 +316,7 @@ func TestParseParameters(t *testing.T) { } } ]`, - ErrIllegalParamName, + ErrMissingParamDesc, }, { // missing param description @@ -500,7 +500,6 @@ func TestParseParameters(t *testing.T) { } -// todo: rewrite with new api format func TestMatchSimple(t *testing.T) { tests := []struct { Config string @@ -583,7 +582,7 @@ func TestMatchSimple(t *testing.T) { "path": "/a/{valid}", "info": "info", "in": { - "{id}": { + "{valid}": { "info": "info", "type": "bool" } @@ -598,7 +597,7 @@ func TestMatchSimple(t *testing.T) { "path": "/a/{valid}", "info": "info", "in": { - "{id}": { + "{valid}": { "info": "info", "type": "bool" } diff --git a/config/errors.go b/config/errors.go index 4795e57..7a5b9cc 100644 --- a/config/errors.go +++ b/config/errors.go @@ -23,14 +23,11 @@ const ErrPatternCollision = Error("invalid config format") // ErrInvalidPattern - a service pattern is malformed const ErrInvalidPattern = Error("must begin with a '/' and not end with") -// ErrInvalidPatternBracePosition - a service pattern opening/closing brace is not directly between '/' -const ErrInvalidPatternBracePosition = Error("capturing braces must be alone between slashes") +// ErrInvalidPatternBraceCapture - a service pattern brace capture is invalid +const ErrInvalidPatternBraceCapture = Error("invalid uri capturing braces") -// ErrInvalidPatternOpeningBrace - a service pattern opening brace is invalid -const ErrInvalidPatternOpeningBrace = Error("opening brace already open") - -// ErrInvalidPatternClosingBrace - a service pattern closing brace is invalid -const ErrInvalidPatternClosingBrace = Error("closing brace already closed") +// ErrUnspecifiedBraceCapture - a parameter brace capture is not specified in the pattern +const ErrUnspecifiedBraceCapture = Error("capturing brace missing in the path") // ErrMissingDescription - a service is missing its description const ErrMissingDescription = Error("missing description") @@ -42,7 +39,7 @@ const ErrMissingParamDesc = Error("missing parameter description") const ErrUnknownDataType = Error("unknown data type") // ErrIllegalParamName - a parameter has an illegal name -const ErrIllegalParamName = Error("parameter name must not begin/end with '_'") +const ErrIllegalParamName = Error("illegal parameter name") // ErrMissingParamType - a parameter has an illegal type const ErrMissingParamType = Error("missing parameter type") diff --git a/config/services.go b/config/server.go similarity index 98% rename from config/services.go rename to config/server.go index 625570e..91cdad2 100644 --- a/config/services.go +++ b/config/server.go @@ -123,7 +123,7 @@ func (server *Server) collide() error { // Find a service matching an incoming HTTP request func (server Server) Find(r *http.Request) *Service { for _, service := range server.services { - if service.Match(r) { + if matches := service.Match(r); matches { return service } } diff --git a/config/service.go b/config/service.go index 808f8bb..10f4c1f 100644 --- a/config/service.go +++ b/config/service.go @@ -3,11 +3,14 @@ package config import ( "fmt" "net/http" + "regexp" "strings" "git.xdrm.io/go/aicra/config/datatype" ) +var braceRegex = regexp.MustCompile(`^{([a-z_-]+)}$`) + // Match returns if this service would handle this HTTP request func (svc *Service) Match(req *http.Request) bool { // method @@ -21,7 +24,7 @@ func (svc *Service) Match(req *http.Request) bool { } // check and extract input - // todo: check if input match + // todo: check if input match and extract models return true } @@ -50,34 +53,34 @@ func (svc *Service) checkPattern() error { } } - // check capturing braces - depth := 0 - for c, l := 1, length; c < l; c++ { - char := svc.Pattern[c] - - if char == '{' { - // opening brace when already opened - if depth != 0 { - return ErrInvalidPatternOpeningBrace - } - - // not directly preceded by a slash - if svc.Pattern[c-1] != '/' { - return ErrInvalidPatternBracePosition - } - depth++ + // for each slash-separated chunk + parts := splitURL(svc.Pattern) + for i, part := range parts { + if len(part) < 1 { + return ErrInvalidPattern } - if char == '}' { - // closing brace when already closed - if depth != 1 { - return ErrInvalidPatternClosingBrace + + // if brace capture + if matches := braceRegex.FindAllStringSubmatch(part, -1); len(matches) > 0 && len(matches[0]) > 1 { + braceName := matches[0][1] + + // append + if svc.captures == nil { + svc.captures = make([]*braceCapture, 0) } - // not directly followed by a slash or end of pattern - if c+1 < l && svc.Pattern[c+1] != '/' { - return ErrInvalidPatternBracePosition - } - depth-- + svc.captures = append(svc.captures, &braceCapture{ + Index: i, + Name: braceName, + Ref: nil, + }) + continue } + + // fail on invalid format + if strings.ContainsAny(part, "{}") { + return ErrInvalidPatternBraceCapture + } + } return nil @@ -93,12 +96,27 @@ func (svc *Service) checkAndFormatInput(types []datatype.DataType) error { // for each parameter for paramName, param := range svc.Input { - - // fail on invalid name - if strings.Trim(paramName, "_") != paramName { + if len(paramName) < 1 { return fmt.Errorf("%s: %w", paramName, ErrIllegalParamName) } + // fail if brace does not exists in pattern + if matches := braceRegex.FindAllStringSubmatch(paramName, -1); len(matches) > 0 && len(matches[0]) > 1 { + braceName := matches[0][1] + + found := false + for _, capture := range svc.captures { + if capture.Name == braceName { + capture.Ref = param + found = true + break + } + } + if !found { + return fmt.Errorf("%s: %w", paramName, ErrUnspecifiedBraceCapture) + } + } + // use param name if no rename if len(param.Rename) < 1 { param.Rename = paramName diff --git a/config/types.go b/config/types.go index b3da173..fb292be 100644 --- a/config/types.go +++ b/config/types.go @@ -8,6 +8,25 @@ import ( var availableHTTPMethods = []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete} +// Server represents a full server configuration +type Server struct { + types []datatype.DataType + services []*Service +} + +// Service represents a service definition (from api.json) +type Service struct { + Method string `json:"method"` + Pattern string `json:"path"` + Scope [][]string `json:"scope"` + Description string `json:"info"` + Input map[string]*Parameter `json:"in"` + // Download *bool `json:"download"` + // Output map[string]*Parameter `json:"out"` + + captures []*braceCapture +} + // Parameter represents a parameter definition (from api.json) type Parameter struct { Description string `json:"info"` @@ -20,19 +39,15 @@ type Parameter struct { Validator datatype.Validator } -// Service represents a service definition (from api.json) -type Service struct { - Method string `json:"method"` - Pattern string `json:"path"` - Scope [][]string `json:"scope"` - Description string `json:"info"` - Input map[string]*Parameter `json:"in"` - // Download *bool `json:"download"` - // Output map[string]*Parameter `json:"out"` +// links to the related URI parameter +type braceCapture struct { + Name string + Index int + Ref *Parameter } -// Server represents a full server configuration -type Server struct { - types []datatype.DataType - services []*Service +// links to the related URI parameter and hold a value +type braceCaptureValue struct { + braceCapture + Value interface{} }