test dynfunc package; standardize and refactor api #14

Merged
xdrm-brackets merged 19 commits from test/dynamic into 0.3.0 2020-04-04 10:09:20 +00:00
5 changed files with 103 additions and 110 deletions
Showing only changes of commit 1e0fb77d61 - Show all commits

View File

@ -80,7 +80,8 @@ func TestLegalServiceName(t *testing.T) {
for i, test := range tests { for i, test := range tests {
t.Run(fmt.Sprintf("service.%d", i), func(t *testing.T) { t.Run(fmt.Sprintf("service.%d", i), func(t *testing.T) {
_, err := Parse(strings.NewReader(test.Raw)) srv := &Server{}
err := srv.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())
@ -134,7 +135,8 @@ func TestAvailableMethods(t *testing.T) {
for i, test := range tests { for i, test := range tests {
t.Run(fmt.Sprintf("service.%d", i), func(t *testing.T) { t.Run(fmt.Sprintf("service.%d", i), func(t *testing.T) {
_, err := Parse(strings.NewReader(test.Raw)) srv := &Server{}
err := srv.Parse(strings.NewReader(test.Raw))
if test.ValidMethod && err != nil { if test.ValidMethod && err != nil {
t.Errorf("unexpected error: '%s'", err.Error()) t.Errorf("unexpected error: '%s'", err.Error())
@ -150,20 +152,22 @@ func TestAvailableMethods(t *testing.T) {
} }
func TestParseEmpty(t *testing.T) { func TestParseEmpty(t *testing.T) {
t.Parallel() t.Parallel()
reader := strings.NewReader(`[]`) r := strings.NewReader(`[]`)
_, err := Parse(reader) srv := &Server{}
err := srv.Parse(r)
if err != nil { if err != nil {
t.Errorf("unexpected error (got '%s')", err) t.Errorf("unexpected error (got '%s')", err)
t.FailNow() t.FailNow()
} }
} }
func TestParseJsonError(t *testing.T) { func TestParseJsonError(t *testing.T) {
reader := strings.NewReader(`{ r := strings.NewReader(`{
"GET": { "GET": {
"info": "info "info": "info
}, },
}`) // trailing ',' is invalid JSON }`) // trailing ',' is invalid JSON
_, err := Parse(reader) srv := &Server{}
err := srv.Parse(r)
if err == nil { if err == nil {
t.Errorf("expected error") t.Errorf("expected error")
t.FailNow() t.FailNow()
@ -205,7 +209,8 @@ func TestParseMissingMethodDescription(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)) srv := &Server{}
err := srv.Parse(strings.NewReader(test.Raw))
if test.ValidDescription && err != nil { if test.ValidDescription && err != nil {
t.Errorf("unexpected error: '%s'", err) t.Errorf("unexpected error: '%s'", err)
@ -223,7 +228,7 @@ func TestParseMissingMethodDescription(t *testing.T) {
func TestParamEmptyRenameNoRename(t *testing.T) { func TestParamEmptyRenameNoRename(t *testing.T) {
t.Parallel() t.Parallel()
reader := strings.NewReader(`[ r := strings.NewReader(`[
{ {
"method": "GET", "method": "GET",
"path": "/", "path": "/",
@ -233,7 +238,9 @@ func TestParamEmptyRenameNoRename(t *testing.T) {
} }
} }
]`) ]`)
srv, err := Parse(reader, builtin.AnyDataType{}) srv := &Server{}
srv.Types = append(srv.Types, builtin.AnyDataType{})
err := srv.Parse(r)
if err != nil { if err != nil {
t.Errorf("unexpected error: '%s'", err) t.Errorf("unexpected error: '%s'", err)
t.FailNow() t.FailNow()
@ -254,7 +261,7 @@ func TestParamEmptyRenameNoRename(t *testing.T) {
} }
func TestOptionalParam(t *testing.T) { func TestOptionalParam(t *testing.T) {
t.Parallel() t.Parallel()
reader := strings.NewReader(`[ r := strings.NewReader(`[
{ {
"method": "GET", "method": "GET",
"path": "/", "path": "/",
@ -267,7 +274,10 @@ func TestOptionalParam(t *testing.T) {
} }
} }
]`) ]`)
srv, err := Parse(reader, builtin.AnyDataType{}, builtin.BoolDataType{}) srv := &Server{}
srv.Types = append(srv.Types, builtin.AnyDataType{})
srv.Types = append(srv.Types, builtin.BoolDataType{})
err := srv.Parse(r)
if err != nil { if err != nil {
t.Errorf("unexpected error: '%s'", err) t.Errorf("unexpected error: '%s'", err)
t.FailNow() t.FailNow()
@ -577,7 +587,9 @@ 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{}) srv := &Server{}
srv.Types = append(srv.Types, builtin.AnyDataType{})
err := srv.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())
@ -814,7 +826,10 @@ func TestServiceCollision(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.Config), builtin.StringDataType{}, builtin.UintDataType{}) srv := &Server{}
srv.Types = append(srv.Types, builtin.StringDataType{})
srv.Types = append(srv.Types, builtin.UintDataType{})
err := srv.Parse(strings.NewReader(test.Config))
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())
@ -951,7 +966,11 @@ func TestMatchSimple(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) {
srv, err := Parse(strings.NewReader(test.Config), builtin.AnyDataType{}, builtin.IntDataType{}, builtin.BoolDataType{}) srv := &Server{}
srv.Types = append(srv.Types, builtin.AnyDataType{})
srv.Types = append(srv.Types, builtin.IntDataType{})
srv.Types = append(srv.Types, builtin.BoolDataType{})
err := srv.Parse(strings.NewReader(test.Config))
if err != nil { if err != nil {
t.Errorf("unexpected error: '%s'", err) t.Errorf("unexpected error: '%s'", err)

View File

@ -1,11 +1,26 @@
package config package config
import ( import (
"reflect"
"git.xdrm.io/go/aicra/datatype" "git.xdrm.io/go/aicra/datatype"
) )
// Validate implements the validator interface // Parameter represents a parameter definition (from api.json)
func (param *Parameter) Validate(datatypes ...datatype.T) error { type Parameter struct {
Description string `json:"info"`
Type string `json:"type"`
Rename string `json:"name,omitempty"`
// ExtractType is the type of data the datatype returns
ExtractType reflect.Type
// Optional is set to true when the type is prefixed with '?'
Optional bool
// Validator is inferred from @Type
Validator datatype.Validator
}
func (param *Parameter) validate(datatypes ...datatype.T) error {
// missing description // missing description
if len(param.Description) < 1 { if len(param.Description) < 1 {
return ErrMissingParamDesc return ErrMissingParamDesc

View File

@ -9,34 +9,30 @@ import (
"git.xdrm.io/go/aicra/datatype" "git.xdrm.io/go/aicra/datatype"
) )
// Parse builds a server configuration from a json reader and checks for most format errors. // Server definition
// you can provide additional DataTypes as variadic arguments type Server struct {
func Parse(r io.Reader, dtypes ...datatype.T) (*Server, error) { Types []datatype.T
server := &Server{ Services []*Service
Types: make([]datatype.T, 0),
Services: make([]*Service, 0),
} }
// add data types // Parse a reader into a server. Server.Types must be set beforehand to
for _, dtype := range dtypes { // make datatypes available when checking and formatting the read configuration.
server.Types = append(server.Types, dtype) func (srv *Server) Parse(r io.Reader) error {
if err := json.NewDecoder(r).Decode(&srv.Services); err != nil {
return fmt.Errorf("%s: %w", ErrRead, err)
} }
if err := json.NewDecoder(r).Decode(&server.Services); err != nil { if err := srv.validate(); err != nil {
return nil, fmt.Errorf("%s: %w", ErrRead, err) return fmt.Errorf("%s: %w", ErrFormat, err)
} }
if err := server.Validate(); err != nil { return nil
return nil, fmt.Errorf("%s: %w", ErrFormat, err)
} }
return server, nil // validate implements the validator interface
} func (server Server) validate(datatypes ...datatype.T) error {
// Validate implements the validator interface
func (server Server) Validate(datatypes ...datatype.T) error {
for _, service := range server.Services { for _, service := range server.Services {
err := service.Validate(server.Types...) err := service.validate(server.Types...)
if err != nil { if err != nil {
return fmt.Errorf("%s '%s': %w", service.Method, service.Pattern, err) return fmt.Errorf("%s '%s': %w", service.Method, service.Pattern, err)
} }

View File

@ -11,6 +11,35 @@ import (
var braceRegex = regexp.MustCompile(`^{([a-z_-]+)}$`) var braceRegex = regexp.MustCompile(`^{([a-z_-]+)}$`)
var queryRegex = regexp.MustCompile(`^GET@([a-z_-]+)$`) var queryRegex = regexp.MustCompile(`^GET@([a-z_-]+)$`)
var availableHTTPMethods = []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete}
// Service definition
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"`
Output map[string]*Parameter `json:"out"`
// references to url parameters
// format: '/uri/{param}'
Captures []*BraceCapture
// references to Query parameters
// format: 'GET@paranName'
Query map[string]*Parameter
// references for form parameters (all but Captures and Query)
Form map[string]*Parameter
}
// BraceCapture links to the related URI parameter
type BraceCapture struct {
Name string
Index int
Ref *Parameter
}
// 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 {
@ -24,9 +53,6 @@ func (svc *Service) Match(req *http.Request) bool {
return false return false
} }
// check and extract input
// todo: check if input match and extract models
return true return true
} }
@ -76,7 +102,7 @@ func (svc *Service) matchPattern(uri string) bool {
} }
// Validate implements the validator interface // Validate implements the validator interface
func (svc *Service) Validate(datatypes ...datatype.T) error { func (svc *Service) validate(datatypes ...datatype.T) error {
// check method // check method
err := svc.isMethodAvailable() err := svc.isMethodAvailable()
if err != nil { if err != nil {
@ -233,7 +259,7 @@ func (svc *Service) validateInput(types []datatype.T) error {
param.Rename = paramName param.Rename = paramName
} }
err := param.Validate(types...) err := param.validate(types...)
if err != nil { if err != nil {
return fmt.Errorf("%s: %w", paramName, err) return fmt.Errorf("%s: %w", paramName, err)
} }
@ -283,7 +309,7 @@ func (svc *Service) validateOutput(types []datatype.T) error {
param.Rename = paramName param.Rename = paramName
} }
err := param.Validate(types...) err := param.validate(types...)
if err != nil { if err != nil {
return fmt.Errorf("%s: %w", paramName, err) return fmt.Errorf("%s: %w", paramName, err)
} }

View File

@ -1,63 +0,0 @@
package config
import (
"net/http"
"reflect"
"git.xdrm.io/go/aicra/datatype"
)
var availableHTTPMethods = []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete}
// validator unifies the check and format routine
type validator interface {
Validate(...datatype.T) error
}
// Server represents a full server configuration
type Server struct {
Types []datatype.T
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"`
Output map[string]*Parameter `json:"out"`
// references to url parameters
// format: '/uri/{param}'
Captures []*BraceCapture
// references to Query parameters
// format: 'GET@paranName'
Query map[string]*Parameter
// references for form parameters (all but Captures and Query)
Form map[string]*Parameter
}
// Parameter represents a parameter definition (from api.json)
type Parameter struct {
Description string `json:"info"`
Type string `json:"type"`
Rename string `json:"name,omitempty"`
// ExtractType is the type of data the datatype returns
ExtractType reflect.Type
// Optional is set to true when the type is prefixed with '?'
Optional bool
// Validator is inferred from @Type
Validator datatype.Validator
}
// BraceCapture links to the related URI parameter
type BraceCapture struct {
Name string
Index int
Ref *Parameter
}