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}" } ]`,
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"
}

View File

@ -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")

View File

@ -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
}
}

View File

@ -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
// for each slash-separated chunk
parts := splitURL(svc.Pattern)
for i, part := range parts {
if len(part) < 1 {
return ErrInvalidPattern
}
// not directly preceded by a slash
if svc.Pattern[c-1] != '/' {
return ErrInvalidPatternBracePosition
// 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)
}
depth++
svc.captures = append(svc.captures, &braceCapture{
Index: i,
Name: braceName,
Ref: nil,
})
continue
}
if char == '}' {
// closing brace when already closed
if depth != 1 {
return ErrInvalidPatternClosingBrace
}
// not directly followed by a slash or end of pattern
if c+1 < l && svc.Pattern[c+1] != '/' {
return ErrInvalidPatternBracePosition
}
depth--
// 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

View File

@ -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{}
}