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:
parent
32aff3e07f
commit
d1ab4fefb0
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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{}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue