add brace captures and check between param and pattern (keep them so no need to check them at each req)

This commit is contained in:
Adrien Marquès 2020-03-15 01:37:28 +01:00
parent 32aff3e07f
commit d1ab4fefb0
Signed by: xdrm-brackets
GPG Key ID: D75243CA236D825E
5 changed files with 91 additions and 62 deletions

View File

@ -43,11 +43,11 @@ func TestLegalServiceName(t *testing.T) {
}, },
{ {
`[ { "method": "GET", "info": "a", "path": "/invalid/s{braces}" } ]`, `[ { "method": "GET", "info": "a", "path": "/invalid/s{braces}" } ]`,
ErrInvalidPatternBracePosition, ErrInvalidPatternBraceCapture,
}, },
{ {
`[ { "method": "GET", "info": "a", "path": "/invalid/{braces}a" } ]`, `[ { "method": "GET", "info": "a", "path": "/invalid/{braces}a" } ]`,
ErrInvalidPatternBracePosition, ErrInvalidPatternBraceCapture,
}, },
{ {
`[ { "method": "GET", "info": "a", "path": "/invalid/{braces}" } ]`, `[ { "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" } ]`, `[ { "method": "GET", "info": "a", "path": "/invalid/s{braces}/abc" } ]`,
ErrInvalidPatternBracePosition, ErrInvalidPatternBraceCapture,
}, },
{ {
`[ { "method": "GET", "info": "a", "path": "/invalid/{braces}s/abc" } ]`, `[ { "method": "GET", "info": "a", "path": "/invalid/{braces}s/abc" } ]`,
ErrInvalidPatternBracePosition, ErrInvalidPatternBraceCapture,
}, },
{ {
`[ { "method": "GET", "info": "a", "path": "/invalid/{braces}/abc" } ]`, `[ { "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" } ]`, `[ { "method": "GET", "info": "a", "path": "/invalid/{b{races}s/abc" } ]`,
ErrInvalidPatternOpeningBrace, ErrInvalidPatternBraceCapture,
}, },
{ {
`[ { "method": "GET", "info": "a", "path": "/invalid/{braces}/}abc" } ]`, `[ { "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 { // invalid param name suffix
`[ `[
@ -316,7 +316,7 @@ func TestParseParameters(t *testing.T) {
} }
} }
]`, ]`,
ErrIllegalParamName, ErrMissingParamDesc,
}, },
{ // missing param description { // missing param description
@ -500,7 +500,6 @@ func TestParseParameters(t *testing.T) {
} }
// todo: rewrite with new api format
func TestMatchSimple(t *testing.T) { func TestMatchSimple(t *testing.T) {
tests := []struct { tests := []struct {
Config string Config string
@ -583,7 +582,7 @@ func TestMatchSimple(t *testing.T) {
"path": "/a/{valid}", "path": "/a/{valid}",
"info": "info", "info": "info",
"in": { "in": {
"{id}": { "{valid}": {
"info": "info", "info": "info",
"type": "bool" "type": "bool"
} }
@ -598,7 +597,7 @@ func TestMatchSimple(t *testing.T) {
"path": "/a/{valid}", "path": "/a/{valid}",
"info": "info", "info": "info",
"in": { "in": {
"{id}": { "{valid}": {
"info": "info", "info": "info",
"type": "bool" "type": "bool"
} }

View File

@ -23,14 +23,11 @@ const ErrPatternCollision = Error("invalid config format")
// ErrInvalidPattern - a service pattern is malformed // ErrInvalidPattern - a service pattern is malformed
const ErrInvalidPattern = Error("must begin with a '/' and not end with") const ErrInvalidPattern = Error("must begin with a '/' and not end with")
// ErrInvalidPatternBracePosition - a service pattern opening/closing brace is not directly between '/' // ErrInvalidPatternBraceCapture - a service pattern brace capture is invalid
const ErrInvalidPatternBracePosition = Error("capturing braces must be alone between slashes") const ErrInvalidPatternBraceCapture = Error("invalid uri capturing braces")
// ErrInvalidPatternOpeningBrace - a service pattern opening brace is invalid // ErrUnspecifiedBraceCapture - a parameter brace capture is not specified in the pattern
const ErrInvalidPatternOpeningBrace = Error("opening brace already open") const ErrUnspecifiedBraceCapture = Error("capturing brace missing in the path")
// ErrInvalidPatternClosingBrace - a service pattern closing brace is invalid
const ErrInvalidPatternClosingBrace = Error("closing brace already closed")
// ErrMissingDescription - a service is missing its description // ErrMissingDescription - a service is missing its description
const ErrMissingDescription = Error("missing description") const ErrMissingDescription = Error("missing description")
@ -42,7 +39,7 @@ const ErrMissingParamDesc = Error("missing parameter description")
const ErrUnknownDataType = Error("unknown data type") const ErrUnknownDataType = Error("unknown data type")
// ErrIllegalParamName - a parameter has an illegal name // 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 // ErrMissingParamType - a parameter has an illegal type
const ErrMissingParamType = Error("missing parameter type") const ErrMissingParamType = Error("missing parameter type")

View File

@ -123,7 +123,7 @@ func (server *Server) collide() error {
// Find a service matching an incoming HTTP request // Find a service matching an incoming HTTP request
func (server Server) Find(r *http.Request) *Service { func (server Server) Find(r *http.Request) *Service {
for _, service := range server.services { for _, service := range server.services {
if service.Match(r) { if matches := service.Match(r); matches {
return service return service
} }
} }

View File

@ -3,11 +3,14 @@ package config
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"regexp"
"strings" "strings"
"git.xdrm.io/go/aicra/config/datatype" "git.xdrm.io/go/aicra/config/datatype"
) )
var braceRegex = regexp.MustCompile(`^{([a-z_-]+)}$`)
// Match returns if this service would handle this HTTP request // Match returns if this service would handle this HTTP request
func (svc *Service) Match(req *http.Request) bool { func (svc *Service) Match(req *http.Request) bool {
// method // method
@ -21,7 +24,7 @@ func (svc *Service) Match(req *http.Request) bool {
} }
// check and extract input // check and extract input
// todo: check if input match // todo: check if input match and extract models
return true return true
} }
@ -50,34 +53,34 @@ func (svc *Service) checkPattern() error {
} }
} }
// check capturing braces // for each slash-separated chunk
depth := 0 parts := splitURL(svc.Pattern)
for c, l := 1, length; c < l; c++ { for i, part := range parts {
char := svc.Pattern[c] if len(part) < 1 {
return ErrInvalidPattern
if char == '{' {
// opening brace when already opened
if depth != 0 {
return ErrInvalidPatternOpeningBrace
} }
// not directly preceded by a slash // if brace capture
if svc.Pattern[c-1] != '/' { if matches := braceRegex.FindAllStringSubmatch(part, -1); len(matches) > 0 && len(matches[0]) > 1 {
return ErrInvalidPatternBracePosition braceName := matches[0][1]
// append
if svc.captures == nil {
svc.captures = make([]*braceCapture, 0)
} }
depth++ svc.captures = append(svc.captures, &braceCapture{
Index: i,
Name: braceName,
Ref: nil,
})
continue
} }
if char == '}' {
// closing brace when already closed // fail on invalid format
if depth != 1 { if strings.ContainsAny(part, "{}") {
return ErrInvalidPatternClosingBrace return ErrInvalidPatternBraceCapture
}
// not directly followed by a slash or end of pattern
if c+1 < l && svc.Pattern[c+1] != '/' {
return ErrInvalidPatternBracePosition
}
depth--
} }
} }
return nil return nil
@ -93,12 +96,27 @@ func (svc *Service) checkAndFormatInput(types []datatype.DataType) error {
// for each parameter // for each parameter
for paramName, param := range svc.Input { for paramName, param := range svc.Input {
if len(paramName) < 1 {
// fail on invalid name
if strings.Trim(paramName, "_") != paramName {
return fmt.Errorf("%s: %w", paramName, ErrIllegalParamName) 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 // use param name if no rename
if len(param.Rename) < 1 { if len(param.Rename) < 1 {
param.Rename = paramName param.Rename = paramName

View File

@ -8,6 +8,25 @@ import (
var availableHTTPMethods = []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete} 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) // Parameter represents a parameter definition (from api.json)
type Parameter struct { type Parameter struct {
Description string `json:"info"` Description string `json:"info"`
@ -20,19 +39,15 @@ type Parameter struct {
Validator datatype.Validator Validator datatype.Validator
} }
// Service represents a service definition (from api.json) // links to the related URI parameter
type Service struct { type braceCapture struct {
Method string `json:"method"` Name string
Pattern string `json:"path"` Index int
Scope [][]string `json:"scope"` Ref *Parameter
Description string `json:"info"`
Input map[string]*Parameter `json:"in"`
// Download *bool `json:"download"`
// Output map[string]*Parameter `json:"out"`
} }
// Server represents a full server configuration // links to the related URI parameter and hold a value
type Server struct { type braceCaptureValue struct {
types []datatype.DataType braceCapture
services []*Service Value interface{}
} }