Compare commits
No commits in common. "32aff3e07fa597fb2e50efdf94749cf407f9d5ca" and "511070196b2ffbf14868190d5f0c3e1dc12f71ac" have entirely different histories.
32aff3e07f
...
511070196b
|
@ -3,12 +3,8 @@ package config
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.xdrm.io/go/aicra/config/datatype/builtin"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLegalServiceName(t *testing.T) {
|
func TestLegalServiceName(t *testing.T) {
|
||||||
|
@ -223,22 +219,22 @@ func TestParamEmptyRenameNoRename(t *testing.T) {
|
||||||
"path": "/",
|
"path": "/",
|
||||||
"info": "valid-description",
|
"info": "valid-description",
|
||||||
"in": {
|
"in": {
|
||||||
"original": { "info": "valid-desc", "type": "any", "name": "" }
|
"original": { "info": "valid-desc", "type": "valid-type", "name": "" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]`)
|
]`)
|
||||||
srv, err := Parse(reader, builtin.AnyDataType{})
|
srv, err := Parse(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unexpected error: '%s'", err)
|
t.Errorf("unexpected error: '%s'", err)
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(srv.services) < 1 {
|
if len(srv) < 1 {
|
||||||
t.Errorf("expected a service")
|
t.Errorf("expected a service")
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, param := range srv.services[0].Input {
|
for _, param := range (srv)[0].Input {
|
||||||
if param.Rename != "original" {
|
if param.Rename != "original" {
|
||||||
t.Errorf("expected the parameter 'original' not to be renamed to '%s'", param.Rename)
|
t.Errorf("expected the parameter 'original' not to be renamed to '%s'", param.Rename)
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
|
@ -253,24 +249,24 @@ func TestOptionalParam(t *testing.T) {
|
||||||
"path": "/",
|
"path": "/",
|
||||||
"info": "valid-description",
|
"info": "valid-description",
|
||||||
"in": {
|
"in": {
|
||||||
"optional": { "info": "optional-type", "type": "?bool" },
|
"optional": { "info": "valid-desc", "type": "?optional-type" },
|
||||||
"required": { "info": "required-type", "type": "bool" },
|
"required": { "info": "valid-desc", "type": "required-type" },
|
||||||
"required2": { "info": "required", "type": "any" },
|
"required2": { "info": "valid-desc", "type": "a" },
|
||||||
"optional2": { "info": "optional", "type": "?any" }
|
"optional2": { "info": "valid-desc", "type": "?a" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]`)
|
]`)
|
||||||
srv, err := Parse(reader, builtin.AnyDataType{}, builtin.BoolDataType{})
|
srv, err := Parse(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unexpected error: '%s'", err)
|
t.Errorf("unexpected error: '%s'", err)
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(srv.services) < 1 {
|
if len(srv) < 1 {
|
||||||
t.Errorf("expected a service")
|
t.Errorf("expected a service")
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
for pName, param := range srv.services[0].Input {
|
for pName, param := range (srv)[0].Input {
|
||||||
|
|
||||||
if pName == "optional" || pName == "optional2" {
|
if pName == "optional" || pName == "optional2" {
|
||||||
if !param.Optional {
|
if !param.Optional {
|
||||||
|
@ -393,7 +389,7 @@ func TestParseParameters(t *testing.T) {
|
||||||
"path": "/",
|
"path": "/",
|
||||||
"info": "info",
|
"info": "info",
|
||||||
"in": {
|
"in": {
|
||||||
"param1": { "info": "valid", "type": "any" }
|
"param1": { "info": "valid", "type": "a" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]`,
|
]`,
|
||||||
|
@ -406,7 +402,7 @@ func TestParseParameters(t *testing.T) {
|
||||||
"path": "/",
|
"path": "/",
|
||||||
"info": "info",
|
"info": "info",
|
||||||
"in": {
|
"in": {
|
||||||
"param1": { "info": "valid", "type": "?any" }
|
"param1": { "info": "valid", "type": "?valid" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]`,
|
]`,
|
||||||
|
@ -420,8 +416,8 @@ func TestParseParameters(t *testing.T) {
|
||||||
"path": "/",
|
"path": "/",
|
||||||
"info": "info",
|
"info": "info",
|
||||||
"in": {
|
"in": {
|
||||||
"param1": { "info": "valid", "type": "any" },
|
"param1": { "info": "valid", "type": "valid" },
|
||||||
"param2": { "info": "valid", "type": "any", "name": "param1" }
|
"param2": { "info": "valid", "type": "valid", "name": "param1" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]`,
|
]`,
|
||||||
|
@ -435,8 +431,8 @@ func TestParseParameters(t *testing.T) {
|
||||||
"path": "/",
|
"path": "/",
|
||||||
"info": "info",
|
"info": "info",
|
||||||
"in": {
|
"in": {
|
||||||
"param1": { "info": "valid", "type": "any", "name": "param2" },
|
"param1": { "info": "valid", "type": "valid", "name": "param2" },
|
||||||
"param2": { "info": "valid", "type": "any" }
|
"param2": { "info": "valid", "type": "valid" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]`,
|
]`,
|
||||||
|
@ -450,8 +446,8 @@ func TestParseParameters(t *testing.T) {
|
||||||
"path": "/",
|
"path": "/",
|
||||||
"info": "info",
|
"info": "info",
|
||||||
"in": {
|
"in": {
|
||||||
"param1": { "info": "valid", "type": "any", "name": "conflict" },
|
"param1": { "info": "valid", "type": "valid", "name": "conflict" },
|
||||||
"param2": { "info": "valid", "type": "any", "name": "conflict" }
|
"param2": { "info": "valid", "type": "valid", "name": "conflict" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]`,
|
]`,
|
||||||
|
@ -466,8 +462,8 @@ func TestParseParameters(t *testing.T) {
|
||||||
"path": "/",
|
"path": "/",
|
||||||
"info": "info",
|
"info": "info",
|
||||||
"in": {
|
"in": {
|
||||||
"param1": { "info": "valid", "type": "any", "name": "freename" },
|
"param1": { "info": "valid", "type": "valid", "name": "freename" },
|
||||||
"param2": { "info": "valid", "type": "any", "name": "freename2" }
|
"param2": { "info": "valid", "type": "valid", "name": "freename2" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]`,
|
]`,
|
||||||
|
@ -478,7 +474,7 @@ func TestParseParameters(t *testing.T) {
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
|
|
||||||
t.Run(fmt.Sprintf("method.%d", i), func(t *testing.T) {
|
t.Run(fmt.Sprintf("method.%d", i), func(t *testing.T) {
|
||||||
_, err := Parse(strings.NewReader(test.Raw), builtin.AnyDataType{})
|
_, err := Parse(strings.NewReader(test.Raw))
|
||||||
|
|
||||||
if err == nil && test.Error != nil {
|
if err == nil && test.Error != nil {
|
||||||
t.Errorf("expected an error: '%s'", test.Error.Error())
|
t.Errorf("expected an error: '%s'", test.Error.Error())
|
||||||
|
@ -501,141 +497,136 @@ func TestParseParameters(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: rewrite with new api format
|
// todo: rewrite with new api format
|
||||||
func TestMatchSimple(t *testing.T) {
|
// func TestMatchSimple(t *testing.T) {
|
||||||
tests := []struct {
|
// tests := []struct {
|
||||||
Config string
|
// Raw string
|
||||||
URL string
|
// Path []string
|
||||||
Match bool
|
// BrowseDepth int
|
||||||
}{
|
// ValidDepth bool
|
||||||
{ // false positive -1
|
// }{
|
||||||
`[ {
|
// { // false positive -1
|
||||||
"method": "GET",
|
// `{
|
||||||
"path": "/a",
|
// "/" : {
|
||||||
"info": "info",
|
// "parent": {
|
||||||
"in": {}
|
// "/": {
|
||||||
} ]`,
|
// "subdir": {}
|
||||||
"/",
|
// }
|
||||||
false,
|
// }
|
||||||
},
|
// }
|
||||||
{ // false positive +1
|
// }`,
|
||||||
`[ {
|
// []string{"parent", "subdir"},
|
||||||
"method": "GET",
|
// 1,
|
||||||
"path": "/",
|
// false,
|
||||||
"info": "info",
|
// },
|
||||||
"in": {}
|
// { // false positive +1
|
||||||
} ]`,
|
// `{
|
||||||
"/a",
|
// "/" : {
|
||||||
false,
|
// "parent": {
|
||||||
},
|
// "/": {
|
||||||
{
|
// "subdir": {}
|
||||||
`[ {
|
// }
|
||||||
"method": "GET",
|
// }
|
||||||
"path": "/a",
|
// }
|
||||||
"info": "info",
|
// }`,
|
||||||
"in": {}
|
// []string{"parent", "subdir"},
|
||||||
} ]`,
|
// 3,
|
||||||
"/a",
|
// false,
|
||||||
true,
|
// },
|
||||||
},
|
|
||||||
{
|
|
||||||
`[ {
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/a",
|
|
||||||
"info": "info",
|
|
||||||
"in": {}
|
|
||||||
} ]`,
|
|
||||||
"/a/",
|
|
||||||
true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`[ {
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/a/{id}",
|
|
||||||
"info": "info",
|
|
||||||
"in": {
|
|
||||||
"{id}": {
|
|
||||||
"info": "info",
|
|
||||||
"type": "bool"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} ]`,
|
|
||||||
"/a/12/",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`[ {
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/a/{id}",
|
|
||||||
"info": "info",
|
|
||||||
"in": {
|
|
||||||
"{id}": {
|
|
||||||
"info": "info",
|
|
||||||
"type": "int"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} ]`,
|
|
||||||
"/a/12/",
|
|
||||||
true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`[ {
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/a/{valid}",
|
|
||||||
"info": "info",
|
|
||||||
"in": {
|
|
||||||
"{id}": {
|
|
||||||
"info": "info",
|
|
||||||
"type": "bool"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} ]`,
|
|
||||||
"/a/12/",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`[ {
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/a/{valid}",
|
|
||||||
"info": "info",
|
|
||||||
"in": {
|
|
||||||
"{id}": {
|
|
||||||
"info": "info",
|
|
||||||
"type": "bool"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} ]`,
|
|
||||||
"/a/true/",
|
|
||||||
true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, test := range tests {
|
// {
|
||||||
|
// `{
|
||||||
|
// "/" : {
|
||||||
|
// "parent": {
|
||||||
|
// "/": {
|
||||||
|
// "subdir": {}
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }`,
|
||||||
|
// []string{"parent", "subdir"},
|
||||||
|
// 2,
|
||||||
|
// true,
|
||||||
|
// },
|
||||||
|
// { // unknown path
|
||||||
|
// `{
|
||||||
|
// "/" : {
|
||||||
|
// "parent": {
|
||||||
|
// "/": {
|
||||||
|
// "subdir": {}
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }`,
|
||||||
|
// []string{"x", "y"},
|
||||||
|
// 2,
|
||||||
|
// false,
|
||||||
|
// },
|
||||||
|
// { // unknown path
|
||||||
|
// `{
|
||||||
|
// "/" : {
|
||||||
|
// "parent": {
|
||||||
|
// "/": {
|
||||||
|
// "subdir": {}
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }`,
|
||||||
|
// []string{"parent", "y"},
|
||||||
|
// 1,
|
||||||
|
// true,
|
||||||
|
// },
|
||||||
|
// { // Warning: this case is important to understand the precedence of service paths over
|
||||||
|
// // the value of some variables. Here if we send a string parameter in the GET method that
|
||||||
|
// // unfortunately is equal to 'subdir', it will call the sub-service /parent/subdir' instead
|
||||||
|
// // of the service /parent with its parameter set to the value 'subdir'.
|
||||||
|
// `{
|
||||||
|
// "/" : {
|
||||||
|
// "parent": {
|
||||||
|
// "/": {
|
||||||
|
// "subdir": {}
|
||||||
|
// },
|
||||||
|
// "GET": {
|
||||||
|
// "info": "valid-desc",
|
||||||
|
// "in": {
|
||||||
|
// "some-value": {
|
||||||
|
// "info": "valid-desc",
|
||||||
|
// "type": "valid-type"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }`,
|
||||||
|
// []string{"parent", "subdir"},
|
||||||
|
// 2,
|
||||||
|
// true,
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
|
||||||
t.Run(fmt.Sprintf("method.%d", i), func(t *testing.T) {
|
// for i, test := range tests {
|
||||||
srv, err := Parse(strings.NewReader(test.Config), builtin.AnyDataType{}, builtin.IntDataType{}, builtin.BoolDataType{})
|
|
||||||
|
|
||||||
if err != nil {
|
// t.Run(fmt.Sprintf("method.%d", i), func(t *testing.T) {
|
||||||
t.Errorf("unexpected error: '%s'", err)
|
// srv, err := Parse(strings.NewReader(test.Raw))
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(srv.services) != 1 {
|
// if err != nil {
|
||||||
t.Errorf("expected to have 1 service, got %d", len(srv.services))
|
// t.Errorf("unexpected error: '%s'", err)
|
||||||
t.FailNow()
|
// t.FailNow()
|
||||||
}
|
// }
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodGet, test.URL, nil)
|
// _, depth := srv.Match(test.Path)
|
||||||
|
// if test.ValidDepth {
|
||||||
|
// if depth != test.BrowseDepth {
|
||||||
|
// t.Errorf("expected a depth of %d (got %d)", test.BrowseDepth, depth)
|
||||||
|
// t.FailNow()
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// if depth == test.BrowseDepth {
|
||||||
|
// t.Errorf("expected a depth NOT %d (got %d)", test.BrowseDepth, depth)
|
||||||
|
// t.FailNow()
|
||||||
|
// }
|
||||||
|
|
||||||
match := srv.services[0].Match(req)
|
// }
|
||||||
if test.Match && !match {
|
// })
|
||||||
t.Errorf("expected '%s' to match", test.URL)
|
// }
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if !test.Match && match {
|
|
||||||
t.Errorf("expected '%s' NOT to match", test.URL)
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
// }
|
||||||
|
|
|
@ -4,9 +4,9 @@ package datatype
|
||||||
// and casts the value into a compatible type
|
// and casts the value into a compatible type
|
||||||
type Validator func(value interface{}) (cast interface{}, valid bool)
|
type Validator func(value interface{}) (cast interface{}, valid bool)
|
||||||
|
|
||||||
// DataType builds a DataType from the type definition (from the
|
// Builder builds a DataType from the type definition (from the
|
||||||
// configuration field "type") and returns NIL if the type
|
// configuration field "type") and returns NIL if the type
|
||||||
// definition does not match this DataType
|
// definition does not match this DataType
|
||||||
type DataType interface {
|
type Builder interface {
|
||||||
Build(typeDefinition string) Validator
|
Build(typeDefinition string) Validator
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,9 +38,6 @@ const ErrMissingDescription = Error("missing description")
|
||||||
// ErrMissingParamDesc - a parameter is missing its description
|
// ErrMissingParamDesc - a parameter is missing its description
|
||||||
const ErrMissingParamDesc = Error("missing parameter description")
|
const ErrMissingParamDesc = Error("missing parameter description")
|
||||||
|
|
||||||
// ErrUnknownDataType - a parameter has an unknown datatype name
|
|
||||||
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("parameter name must not begin/end with '_'")
|
||||||
|
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
package config
|
|
||||||
|
|
||||||
import "strings"
|
|
||||||
|
|
||||||
// splits an URL without empty sets
|
|
||||||
func splitURL(url string) []string {
|
|
||||||
trimmed := strings.Trim(url, " /\t\r\n")
|
|
||||||
split := strings.Split(trimmed, "/")
|
|
||||||
|
|
||||||
// remove empty set when empty url
|
|
||||||
if len(split) == 1 && len(split[0]) == 0 {
|
|
||||||
return []string{}
|
|
||||||
}
|
|
||||||
return split
|
|
||||||
}
|
|
|
@ -1,7 +1,5 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import "git.xdrm.io/go/aicra/config/datatype"
|
|
||||||
|
|
||||||
func (param *Parameter) checkAndFormat() error {
|
func (param *Parameter) checkAndFormat() error {
|
||||||
|
|
||||||
// missing description
|
// missing description
|
||||||
|
@ -22,14 +20,3 @@ func (param *Parameter) checkAndFormat() error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// assigns the first matching data type from the type definition
|
|
||||||
func (param *Parameter) assignDataType(types []datatype.DataType) bool {
|
|
||||||
for _, dtype := range types {
|
|
||||||
param.Validator = dtype.Build(param.Type)
|
|
||||||
if param.Validator != nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
|
@ -4,26 +4,11 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.xdrm.io/go/aicra/config/datatype"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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
|
return false
|
||||||
if req.Method != svc.Method {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// check path
|
|
||||||
if !svc.matchPattern(req.RequestURI) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// check and extract input
|
|
||||||
// todo: check if input match
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *Service) checkMethod() error {
|
func (svc *Service) checkMethod() error {
|
||||||
|
@ -83,7 +68,7 @@ func (svc *Service) checkPattern() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *Service) checkAndFormatInput(types []datatype.DataType) error {
|
func (svc *Service) checkAndFormatInput() error {
|
||||||
|
|
||||||
// ignore no parameter
|
// ignore no parameter
|
||||||
if svc.Input == nil || len(svc.Input) < 1 {
|
if svc.Input == nil || len(svc.Input) < 1 {
|
||||||
|
@ -109,10 +94,6 @@ func (svc *Service) checkAndFormatInput(types []datatype.DataType) error {
|
||||||
return fmt.Errorf("%s: %w", paramName, err)
|
return fmt.Errorf("%s: %w", paramName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !param.assignDataType(types) {
|
|
||||||
return fmt.Errorf("%s: %w", paramName, ErrUnknownDataType)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for name/rename conflict
|
// check for name/rename conflict
|
||||||
for paramName2, param2 := range svc.Input {
|
for paramName2, param2 := range svc.Input {
|
||||||
// ignore self
|
// ignore self
|
||||||
|
@ -133,48 +114,3 @@ func (svc *Service) checkAndFormatInput(types []datatype.DataType) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// checks if an uri matches the service's pattern
|
|
||||||
func (svc *Service) matchPattern(uri string) bool {
|
|
||||||
uriparts := splitURL(uri)
|
|
||||||
parts := splitURL(svc.Pattern)
|
|
||||||
|
|
||||||
// fail if size differ
|
|
||||||
if len(uriparts) != len(parts) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// root url '/'
|
|
||||||
if len(parts) == 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// check part by part
|
|
||||||
for i, part := range parts {
|
|
||||||
uripart := uriparts[i]
|
|
||||||
|
|
||||||
isCapture := len(part) > 0 && part[0] == '{'
|
|
||||||
|
|
||||||
// if no capture -> check equality
|
|
||||||
if !isCapture {
|
|
||||||
if part != uripart {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
param, exists := svc.Input[part]
|
|
||||||
|
|
||||||
// fail if no validator
|
|
||||||
if !exists || param.Validator == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// fail if not type-valid
|
|
||||||
if _, valid := param.Validator(uripart); !valid {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
|
@ -6,123 +6,38 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.xdrm.io/go/aicra/config/datatype"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Parse builds a server configuration from a json reader and checks for most format errors.
|
// Parse builds a server configuration from a json reader and checks for most format errors.
|
||||||
// you can provide additional DataTypes as variadic arguments
|
func Parse(r io.Reader) (Services, error) {
|
||||||
func Parse(r io.Reader, dtypes ...datatype.DataType) (*Server, error) {
|
services := make(Services, 0)
|
||||||
server := &Server{
|
|
||||||
types: make([]datatype.DataType, 0),
|
|
||||||
services: make([]*Service, 0),
|
|
||||||
}
|
|
||||||
// add data types
|
|
||||||
for _, dtype := range dtypes {
|
|
||||||
server.types = append(server.types, dtype)
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse JSON
|
err := json.NewDecoder(r).Decode(&services)
|
||||||
if err := json.NewDecoder(r).Decode(&server.services); err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s: %w", ErrRead, err)
|
return nil, fmt.Errorf("%s: %w", ErrRead, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check services
|
err = services.checkAndFormat()
|
||||||
if err := server.checkAndFormat(); err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s: %w", ErrFormat, err)
|
return nil, fmt.Errorf("%s: %w", ErrFormat, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check collisions
|
if services.collide() {
|
||||||
if err := server.collide(); err != nil {
|
return nil, fmt.Errorf("%s: %w", ErrFormat, ErrPatternCollision)
|
||||||
return nil, fmt.Errorf("%s: %w", ErrFormat, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return server, nil
|
return services, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// collide returns if there is collision between services
|
// collide returns if there is collision between services
|
||||||
func (server *Server) collide() error {
|
func (svc Services) collide() bool {
|
||||||
length := len(server.services)
|
// todo: implement pattern collision using types to check if braces can be equal to fixed uri parts
|
||||||
|
return false
|
||||||
// for each service combination
|
|
||||||
for a := 0; a < length; a++ {
|
|
||||||
for b := a + 1; b < length; b++ {
|
|
||||||
aService := server.services[a]
|
|
||||||
bService := server.services[b]
|
|
||||||
|
|
||||||
// ignore different method
|
|
||||||
if aService.Method != bService.Method {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
aParts := splitURL(aService.Pattern)
|
|
||||||
bParts := splitURL(bService.Pattern)
|
|
||||||
|
|
||||||
// not same size
|
|
||||||
if len(aParts) != len(bParts) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// for each part
|
|
||||||
for pi, aPart := range aParts {
|
|
||||||
bPart := bParts[pi]
|
|
||||||
|
|
||||||
aIsCapture := len(aPart) > 1 && aPart[0] == '{'
|
|
||||||
bIsCapture := len(bPart) > 1 && bPart[0] == '{'
|
|
||||||
|
|
||||||
// both captures -> as we cannot check, consider a collision
|
|
||||||
if aIsCapture && bIsCapture {
|
|
||||||
return fmt.Errorf("%s: %s '%s'", ErrPatternCollision, aService.Method, aService.Pattern)
|
|
||||||
}
|
|
||||||
|
|
||||||
// no capture -> check equal
|
|
||||||
if !aIsCapture && !bIsCapture {
|
|
||||||
if aPart == bPart {
|
|
||||||
return fmt.Errorf("%s: %s '%s'", ErrPatternCollision, aService.Method, aService.Pattern)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// A captures B -> check type (B is A ?)
|
|
||||||
if aIsCapture {
|
|
||||||
input, exists := aService.Input[aPart]
|
|
||||||
|
|
||||||
// fail if no type or no validator
|
|
||||||
if !exists || input.Validator == nil {
|
|
||||||
return fmt.Errorf("%s: %s '%s'", ErrPatternCollision, aService.Method, aService.Pattern)
|
|
||||||
}
|
|
||||||
|
|
||||||
// fail if not valid
|
|
||||||
if _, valid := input.Validator(aPart); !valid {
|
|
||||||
return fmt.Errorf("%s: %s '%s'", ErrPatternCollision, aService.Method, aService.Pattern)
|
|
||||||
}
|
|
||||||
|
|
||||||
// B captures A -> check type (A is B ?)
|
|
||||||
} else {
|
|
||||||
input, exists := bService.Input[bPart]
|
|
||||||
|
|
||||||
// fail if no type or no validator
|
|
||||||
if !exists || input.Validator == nil {
|
|
||||||
return fmt.Errorf("%s: %s '%s'", ErrPatternCollision, aService.Method, aService.Pattern)
|
|
||||||
}
|
|
||||||
|
|
||||||
// fail if not valid
|
|
||||||
if _, valid := input.Validator(bPart); !valid {
|
|
||||||
return fmt.Errorf("%s: %s '%s'", ErrPatternCollision, aService.Method, aService.Pattern)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 (svc Services) Find(r *http.Request) *Service {
|
||||||
for _, service := range server.services {
|
for _, service := range svc {
|
||||||
if service.Match(r) {
|
if service.Match(r) {
|
||||||
return service
|
return service
|
||||||
}
|
}
|
||||||
|
@ -132,8 +47,8 @@ func (server Server) Find(r *http.Request) *Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkAndFormat checks for errors and missing fields and sets default values for optional fields.
|
// checkAndFormat checks for errors and missing fields and sets default values for optional fields.
|
||||||
func (server Server) checkAndFormat() error {
|
func (svc Services) checkAndFormat() error {
|
||||||
for _, service := range server.services {
|
for _, service := range svc {
|
||||||
|
|
||||||
// check method
|
// check method
|
||||||
err := service.checkMethod()
|
err := service.checkMethod()
|
||||||
|
@ -154,7 +69,7 @@ func (server Server) checkAndFormat() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// check input parameters
|
// check input parameters
|
||||||
err = service.checkAndFormatInput(server.types)
|
err = service.checkAndFormatInput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s '%s' [in]: %w", service.Method, service.Pattern, err)
|
return fmt.Errorf("%s '%s' [in]: %w", service.Method, service.Pattern, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,11 +13,10 @@ type Parameter struct {
|
||||||
Description string `json:"info"`
|
Description string `json:"info"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Rename string `json:"name,omitempty"`
|
Rename string `json:"name,omitempty"`
|
||||||
// Optional is set to true when the type is prefixed with '?'
|
Optional bool
|
||||||
Optional bool
|
|
||||||
|
|
||||||
// Validator is inferred from @Type
|
// validator is set from the @Type
|
||||||
Validator datatype.Validator
|
validator datatype.Validator
|
||||||
}
|
}
|
||||||
|
|
||||||
// Service represents a service definition (from api.json)
|
// Service represents a service definition (from api.json)
|
||||||
|
@ -26,13 +25,10 @@ type Service struct {
|
||||||
Pattern string `json:"path"`
|
Pattern string `json:"path"`
|
||||||
Scope [][]string `json:"scope"`
|
Scope [][]string `json:"scope"`
|
||||||
Description string `json:"info"`
|
Description string `json:"info"`
|
||||||
|
Download *bool `json:"download"`
|
||||||
Input map[string]*Parameter `json:"in"`
|
Input map[string]*Parameter `json:"in"`
|
||||||
// Download *bool `json:"download"`
|
|
||||||
// Output map[string]*Parameter `json:"out"`
|
// Output map[string]*Parameter `json:"out"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Server represents a full server configuration
|
// Services contains every service that represents a server configuration
|
||||||
type Server struct {
|
type Services []*Service
|
||||||
types []datatype.DataType
|
|
||||||
services []*Service
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue