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 {
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 {
t.Errorf("expected an error: '%s'", test.Error.Error())
@ -134,7 +135,8 @@ func TestAvailableMethods(t *testing.T) {
for i, test := range tests {
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 {
t.Errorf("unexpected error: '%s'", err.Error())
@ -150,20 +152,22 @@ func TestAvailableMethods(t *testing.T) {
}
func TestParseEmpty(t *testing.T) {
t.Parallel()
reader := strings.NewReader(`[]`)
_, err := Parse(reader)
r := strings.NewReader(`[]`)
srv := &Server{}
err := srv.Parse(r)
if err != nil {
t.Errorf("unexpected error (got '%s')", err)
t.FailNow()
}
}
func TestParseJsonError(t *testing.T) {
reader := strings.NewReader(`{
r := strings.NewReader(`{
"GET": {
"info": "info
},
}`) // trailing ',' is invalid JSON
_, err := Parse(reader)
srv := &Server{}
err := srv.Parse(r)
if err == nil {
t.Errorf("expected error")
t.FailNow()
@ -205,7 +209,8 @@ func TestParseMissingMethodDescription(t *testing.T) {
for i, test := range tests {
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 {
t.Errorf("unexpected error: '%s'", err)
@ -223,7 +228,7 @@ func TestParseMissingMethodDescription(t *testing.T) {
func TestParamEmptyRenameNoRename(t *testing.T) {
t.Parallel()
reader := strings.NewReader(`[
r := strings.NewReader(`[
{
"method": "GET",
"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 {
t.Errorf("unexpected error: '%s'", err)
t.FailNow()
@ -254,7 +261,7 @@ func TestParamEmptyRenameNoRename(t *testing.T) {
}
func TestOptionalParam(t *testing.T) {
t.Parallel()
reader := strings.NewReader(`[
r := strings.NewReader(`[
{
"method": "GET",
"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 {
t.Errorf("unexpected error: '%s'", err)
t.FailNow()
@ -577,7 +587,9 @@ func TestParseParameters(t *testing.T) {
for i, test := range tests {
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 {
t.Errorf("expected an error: '%s'", test.Error.Error())
@ -814,7 +826,10 @@ func TestServiceCollision(t *testing.T) {
for i, test := range tests {
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 {
t.Errorf("expected an error: '%s'", test.Error.Error())
@ -951,7 +966,11 @@ func TestMatchSimple(t *testing.T) {
for i, test := range tests {
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 {
t.Errorf("unexpected error: '%s'", err)

View File

@ -1,11 +1,26 @@
package config
import (
"reflect"
"git.xdrm.io/go/aicra/datatype"
)
// Validate implements the validator interface
func (param *Parameter) Validate(datatypes ...datatype.T) error {
// 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
}
func (param *Parameter) validate(datatypes ...datatype.T) error {
// missing description
if len(param.Description) < 1 {
return ErrMissingParamDesc

View File

@ -9,34 +9,30 @@ import (
"git.xdrm.io/go/aicra/datatype"
)
// 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, dtypes ...datatype.T) (*Server, error) {
server := &Server{
Types: make([]datatype.T, 0),
Services: make([]*Service, 0),
}
// add data types
for _, dtype := range dtypes {
server.Types = append(server.Types, dtype)
}
if err := json.NewDecoder(r).Decode(&server.Services); err != nil {
return nil, fmt.Errorf("%s: %w", ErrRead, err)
}
if err := server.Validate(); err != nil {
return nil, fmt.Errorf("%s: %w", ErrFormat, err)
}
return server, nil
// Server definition
type Server struct {
Types []datatype.T
Services []*Service
}
// Validate implements the validator interface
func (server Server) Validate(datatypes ...datatype.T) error {
// Parse a reader into a server. Server.Types must be set beforehand to
// make datatypes available when checking and formatting the read configuration.
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 := srv.validate(); err != nil {
return fmt.Errorf("%s: %w", ErrFormat, err)
}
return nil
}
// validate implements the validator interface
func (server Server) validate(datatypes ...datatype.T) error {
for _, service := range server.Services {
err := service.Validate(server.Types...)
err := service.validate(server.Types...)
if err != nil {
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 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
func (svc *Service) Match(req *http.Request) bool {
@ -24,9 +53,6 @@ func (svc *Service) Match(req *http.Request) bool {
return false
}
// check and extract input
// todo: check if input match and extract models
return true
}
@ -76,7 +102,7 @@ func (svc *Service) matchPattern(uri string) bool {
}
// Validate implements the validator interface
func (svc *Service) Validate(datatypes ...datatype.T) error {
func (svc *Service) validate(datatypes ...datatype.T) error {
// check method
err := svc.isMethodAvailable()
if err != nil {
@ -233,7 +259,7 @@ func (svc *Service) validateInput(types []datatype.T) error {
param.Rename = paramName
}
err := param.Validate(types...)
err := param.validate(types...)
if err != nil {
return fmt.Errorf("%s: %w", paramName, err)
}
@ -283,7 +309,7 @@ func (svc *Service) validateOutput(types []datatype.T) error {
param.Rename = paramName
}
err := param.Validate(types...)
err := param.validate(types...)
if err != nil {
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
}