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}" } ]`,
|
`[ { "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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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 svc.Pattern[c-1] != '/' {
|
|
||||||
return ErrInvalidPatternBracePosition
|
|
||||||
}
|
|
||||||
depth++
|
|
||||||
}
|
}
|
||||||
if char == '}' {
|
|
||||||
// closing brace when already closed
|
// if brace capture
|
||||||
if depth != 1 {
|
if matches := braceRegex.FindAllStringSubmatch(part, -1); len(matches) > 0 && len(matches[0]) > 1 {
|
||||||
return ErrInvalidPatternClosingBrace
|
braceName := matches[0][1]
|
||||||
|
|
||||||
|
// append
|
||||||
|
if svc.captures == nil {
|
||||||
|
svc.captures = make([]*braceCapture, 0)
|
||||||
}
|
}
|
||||||
// not directly followed by a slash or end of pattern
|
svc.captures = append(svc.captures, &braceCapture{
|
||||||
if c+1 < l && svc.Pattern[c+1] != '/' {
|
Index: i,
|
||||||
return ErrInvalidPatternBracePosition
|
Name: braceName,
|
||||||
}
|
Ref: nil,
|
||||||
depth--
|
})
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fail on invalid format
|
||||||
|
if strings.ContainsAny(part, "{}") {
|
||||||
|
return ErrInvalidPatternBraceCapture
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -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{}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue