Compare commits
No commits in common. "d6c22b5ff0c1a8ea6003f56fd081caadda735d2c" and "3606f9984d8499442b5c7b37240fb7fc8163ce4d" have entirely different histories.
d6c22b5ff0
...
3606f9984d
|
@ -42,10 +42,18 @@ var (
|
||||||
// the http request URI.
|
// the http request URI.
|
||||||
ErrorUnknownService = func() Error { return Error{200, "unknown service", nil} }
|
ErrorUnknownService = func() Error { return Error{200, "unknown service", nil} }
|
||||||
|
|
||||||
|
// ErrorUnknownMethod is set when there is no method matching the
|
||||||
|
// request's http method
|
||||||
|
ErrorUnknownMethod = func() Error { return Error{201, "unknown method", nil} }
|
||||||
|
|
||||||
// ErrorUncallableService is set when there the requested service's
|
// ErrorUncallableService is set when there the requested service's
|
||||||
// implementation (plugin file) is not found/callable
|
// implementation (plugin file) is not found/callable
|
||||||
ErrorUncallableService = func() Error { return Error{202, "uncallable service", nil} }
|
ErrorUncallableService = func() Error { return Error{202, "uncallable service", nil} }
|
||||||
|
|
||||||
|
// ErrorUncallableMethod is set when there the requested service's
|
||||||
|
// implementation does not features the requested method
|
||||||
|
ErrorUncallableMethod = func() Error { return Error{203, "uncallable method", nil} }
|
||||||
|
|
||||||
// ErrorPermission is set when there is a permission error by default
|
// ErrorPermission is set when there is a permission error by default
|
||||||
// the api returns a permission error when the current scope (built
|
// the api returns a permission error when the current scope (built
|
||||||
// by middlewares) does not match the scope required in the config.
|
// by middlewares) does not match the scope required in the config.
|
||||||
|
|
|
@ -4,19 +4,19 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// cerr allows you to create constant "const" error with type boxing.
|
// Error allows you to create constant "const" error with type boxing.
|
||||||
type cerr string
|
type Error string
|
||||||
|
|
||||||
// Error implements the error builtin interface.
|
// Error implements the error builtin interface.
|
||||||
func (err cerr) Error() string {
|
func (err Error) Error() string {
|
||||||
return string(err)
|
return string(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrReqParamNotFound is thrown when a request parameter is not found
|
// ErrReqParamNotFound is thrown when a request parameter is not found
|
||||||
const ErrReqParamNotFound = cerr("request parameter not found")
|
const ErrReqParamNotFound = Error("request parameter not found")
|
||||||
|
|
||||||
// ErrReqParamNotType is thrown when a request parameter is not asked with the right type
|
// ErrReqParamNotType is thrown when a request parameter is not asked with the right type
|
||||||
const ErrReqParamNotType = cerr("request parameter does not fulfills type")
|
const ErrReqParamNotType = Error("request parameter does not fulfills type")
|
||||||
|
|
||||||
// RequestParam defines input parameters of an api request
|
// RequestParam defines input parameters of an api request
|
||||||
type RequestParam map[string]interface{}
|
type RequestParam map[string]interface{}
|
||||||
|
|
172
http.go
172
http.go
|
@ -3,6 +3,7 @@ package aicra
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"git.xdrm.io/go/aicra/api"
|
"git.xdrm.io/go/aicra/api"
|
||||||
"git.xdrm.io/go/aicra/internal/reqdata"
|
"git.xdrm.io/go/aicra/internal/reqdata"
|
||||||
|
@ -12,96 +13,103 @@ import (
|
||||||
type httpServer Server
|
type httpServer Server
|
||||||
|
|
||||||
// ServeHTTP implements http.Handler and has to be called on each request
|
// ServeHTTP implements http.Handler and has to be called on each request
|
||||||
func (server httpServer) ServeHTTP(res http.ResponseWriter, req *http.Request) {
|
func (server httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
defer req.Body.Close()
|
defer r.Body.Close()
|
||||||
|
|
||||||
// 1. find a matching service in the config
|
/* (1) create api.Request from http.Request
|
||||||
service := server.config.Find(req)
|
---------------------------------------------------------*/
|
||||||
if service == nil {
|
request, err := api.NewRequest(r)
|
||||||
response := api.NewResponse(api.ErrorUnknownService())
|
|
||||||
response.ServeHTTP(res, req)
|
|
||||||
logError(response)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. build input parameter receiver
|
|
||||||
dataset := reqdata.New(service)
|
|
||||||
|
|
||||||
// 3. extract URI data
|
|
||||||
err := dataset.ExtractURI(req)
|
|
||||||
if err != nil {
|
|
||||||
response := api.NewResponse(api.ErrorMissingParam())
|
|
||||||
response.ServeHTTP(res, req)
|
|
||||||
logError(response)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. extract query data
|
|
||||||
err = dataset.ExtractQuery(req)
|
|
||||||
if err != nil {
|
|
||||||
response := api.NewResponse(api.ErrorMissingParam())
|
|
||||||
response.ServeHTTP(res, req)
|
|
||||||
logError(response)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. extract form/json data
|
|
||||||
err = dataset.ExtractForm(req)
|
|
||||||
if err != nil {
|
|
||||||
response := api.NewResponse(api.ErrorMissingParam())
|
|
||||||
response.ServeHTTP(res, req)
|
|
||||||
logError(response)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6. find a matching handler
|
|
||||||
var foundHandler *api.Handler
|
|
||||||
var found bool
|
|
||||||
|
|
||||||
for _, handler := range server.handlers {
|
|
||||||
if handler.GetMethod() == service.Method && handler.GetPath() == service.Pattern {
|
|
||||||
found = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 7. fail if found no handler
|
|
||||||
if foundHandler == nil {
|
|
||||||
if found {
|
|
||||||
r := api.NewResponse()
|
|
||||||
r.SetError(api.ErrorUncallableService(), service.Method, service.Pattern)
|
|
||||||
r.ServeHTTP(res, req)
|
|
||||||
logError(r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
r := api.NewResponse()
|
|
||||||
r.SetError(api.ErrorUnknownService(), service.Method, service.Pattern)
|
|
||||||
r.ServeHTTP(res, req)
|
|
||||||
logError(r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 8. build api.Request from http.Request
|
|
||||||
apireq, err := api.NewRequest(req)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 9. feed request with scope & parameters
|
// 2. find a matching service for this path in the config
|
||||||
apireq.Scope = service.Scope
|
serviceConf, pathIndex := server.config.Browse(request.URI)
|
||||||
apireq.Param = dataset.Data
|
if serviceConf == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 10. execute
|
// 3. extract the service path from request URI
|
||||||
response := api.NewResponse()
|
servicePath := strings.Join(request.URI[:pathIndex], "/")
|
||||||
foundHandler.Handle(*apireq, response)
|
if !strings.HasPrefix(servicePath, "/") {
|
||||||
|
servicePath = "/" + servicePath
|
||||||
|
}
|
||||||
|
|
||||||
// 11. apply headers
|
// 4. find method configuration from http method */
|
||||||
for key, values := range response.Headers {
|
var methodConf = serviceConf.Method(r.Method)
|
||||||
for _, value := range values {
|
if methodConf == nil {
|
||||||
res.Header().Add(key, value)
|
res := api.NewResponse(api.ErrorUnknownMethod())
|
||||||
|
res.ServeHTTP(w, r)
|
||||||
|
logError(res)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. parse data from the request (uri, query, form, json)
|
||||||
|
data := reqdata.New(request.URI[pathIndex:], r)
|
||||||
|
|
||||||
|
/* (2) check parameters
|
||||||
|
---------------------------------------------------------*/
|
||||||
|
parameters, paramError := server.extractParameters(data, methodConf.Parameters)
|
||||||
|
|
||||||
|
// Fail if argument check failed
|
||||||
|
if paramError.Code != api.ErrorSuccess().Code {
|
||||||
|
res := api.NewResponse(paramError)
|
||||||
|
res.ServeHTTP(w, r)
|
||||||
|
logError(res)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
request.Param = parameters
|
||||||
|
|
||||||
|
/* (3) search for the handler
|
||||||
|
---------------------------------------------------------*/
|
||||||
|
var foundHandler *api.Handler
|
||||||
|
var found bool
|
||||||
|
|
||||||
|
for _, handler := range server.handlers {
|
||||||
|
if handler.GetPath() == servicePath {
|
||||||
|
found = true
|
||||||
|
if handler.GetMethod() == r.Method {
|
||||||
|
foundHandler = handler
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 12. write to response
|
// fail if found no handler
|
||||||
response.ServeHTTP(res, req)
|
if foundHandler == nil {
|
||||||
|
if found {
|
||||||
|
res := api.NewResponse()
|
||||||
|
res.SetError(api.ErrorUncallableMethod(), servicePath, r.Method)
|
||||||
|
res.ServeHTTP(w, r)
|
||||||
|
logError(res)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res := api.NewResponse()
|
||||||
|
res.SetError(api.ErrorUncallableService(), servicePath)
|
||||||
|
res.ServeHTTP(w, r)
|
||||||
|
logError(res)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (4) execute handler and return response
|
||||||
|
---------------------------------------------------------*/
|
||||||
|
// 1. feed request with configuration scope
|
||||||
|
request.Scope = methodConf.Scope
|
||||||
|
|
||||||
|
// 2. execute
|
||||||
|
res := api.NewResponse()
|
||||||
|
foundHandler.Handle(*request, res)
|
||||||
|
|
||||||
|
// 3. apply headers
|
||||||
|
for key, values := range res.Headers {
|
||||||
|
for _, value := range values {
|
||||||
|
w.Header().Add(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. write to response
|
||||||
|
res.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,54 +1,54 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
// cerr allows you to create constant "const" error with type boxing.
|
// Error allows you to create constant "const" error with type boxing.
|
||||||
type cerr string
|
type Error string
|
||||||
|
|
||||||
// Error implements the error builtin interface.
|
// Error implements the error builtin interface.
|
||||||
func (err cerr) Error() string {
|
func (err Error) Error() string {
|
||||||
return string(err)
|
return string(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrRead - a problem ocurred when trying to read the configuration file
|
// ErrRead - a problem ocurred when trying to read the configuration file
|
||||||
const ErrRead = cerr("cannot read config")
|
const ErrRead = Error("cannot read config")
|
||||||
|
|
||||||
// ErrUnknownMethod - invalid http method
|
// ErrUnknownMethod - invalid http method
|
||||||
const ErrUnknownMethod = cerr("unknown HTTP method")
|
const ErrUnknownMethod = Error("unknown HTTP method")
|
||||||
|
|
||||||
// ErrFormat - a invalid format has been detected
|
// ErrFormat - a invalid format has been detected
|
||||||
const ErrFormat = cerr("invalid config format")
|
const ErrFormat = Error("invalid config format")
|
||||||
|
|
||||||
// ErrPatternCollision - there is a collision between 2 services' patterns (same method)
|
// ErrPatternCollision - there is a collision between 2 services' patterns (same method)
|
||||||
const ErrPatternCollision = cerr("invalid config format")
|
const ErrPatternCollision = Error("invalid config format")
|
||||||
|
|
||||||
// ErrInvalidPattern - a service pattern is malformed
|
// ErrInvalidPattern - a service pattern is malformed
|
||||||
const ErrInvalidPattern = cerr("must begin with a '/' and not end with")
|
const ErrInvalidPattern = Error("must begin with a '/' and not end with")
|
||||||
|
|
||||||
// ErrInvalidPatternBraceCapture - a service pattern brace capture is invalid
|
// ErrInvalidPatternBraceCapture - a service pattern brace capture is invalid
|
||||||
const ErrInvalidPatternBraceCapture = cerr("invalid uri capturing braces")
|
const ErrInvalidPatternBraceCapture = Error("invalid uri capturing braces")
|
||||||
|
|
||||||
// ErrUnspecifiedBraceCapture - a parameter brace capture is not specified in the pattern
|
// ErrUnspecifiedBraceCapture - a parameter brace capture is not specified in the pattern
|
||||||
const ErrUnspecifiedBraceCapture = cerr("capturing brace missing in the path")
|
const ErrUnspecifiedBraceCapture = Error("capturing brace missing in the path")
|
||||||
|
|
||||||
// ErrUndefinedBraceCapture - a parameter brace capture in the pattern is not defined in parameters
|
// ErrUndefinedBraceCapture - a parameter brace capture in the pattern is not defined in parameters
|
||||||
const ErrUndefinedBraceCapture = cerr("capturing brace missing input definition")
|
const ErrUndefinedBraceCapture = Error("capturing brace missing input definition")
|
||||||
|
|
||||||
// ErrMissingDescription - a service is missing its description
|
// ErrMissingDescription - a service is missing its description
|
||||||
const ErrMissingDescription = cerr("missing description")
|
const ErrMissingDescription = Error("missing description")
|
||||||
|
|
||||||
// ErrIllegalOptionalURIParam - an URI parameter cannot be optional
|
// ErrIllegalOptionalURIParam - an URI parameter cannot be optional
|
||||||
const ErrIllegalOptionalURIParam = cerr("URI parameter cannot be optional")
|
const ErrIllegalOptionalURIParam = Error("URI parameter cannot be optional")
|
||||||
|
|
||||||
// ErrMissingParamDesc - a parameter is missing its description
|
// ErrMissingParamDesc - a parameter is missing its description
|
||||||
const ErrMissingParamDesc = cerr("missing parameter description")
|
const ErrMissingParamDesc = Error("missing parameter description")
|
||||||
|
|
||||||
// ErrUnknownDataType - a parameter has an unknown datatype name
|
// ErrUnknownDataType - a parameter has an unknown datatype name
|
||||||
const ErrUnknownDataType = cerr("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 = cerr("illegal parameter name")
|
const ErrIllegalParamName = Error("illegal parameter name")
|
||||||
|
|
||||||
// ErrMissingParamType - a parameter has an illegal type
|
// ErrMissingParamType - a parameter has an illegal type
|
||||||
const ErrMissingParamType = cerr("missing parameter type")
|
const ErrMissingParamType = Error("missing parameter type")
|
||||||
|
|
||||||
// ErrParamNameConflict - a parameter has a conflict with its name/rename field
|
// ErrParamNameConflict - a parameter has a conflict with its name/rename field
|
||||||
const ErrParamNameConflict = cerr("name conflict for parameter")
|
const ErrParamNameConflict = Error("name conflict for parameter")
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
package multipart
|
package multipart
|
||||||
|
|
||||||
// cerr allows you to create constant "const" error with type boxing.
|
// Error allows you to create constant "const" error with type boxing.
|
||||||
type cerr string
|
type Error string
|
||||||
|
|
||||||
// Error implements the error builtin interface.
|
// Error implements the error builtin interface.
|
||||||
func (err cerr) Error() string {
|
func (err Error) Error() string {
|
||||||
return string(err)
|
return string(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrMissingDataName is set when a multipart variable/file has no name="..."
|
// ErrMissingDataName is set when a multipart variable/file has no name="..."
|
||||||
const ErrMissingDataName = cerr("data has no name")
|
const ErrMissingDataName = Error("data has no name")
|
||||||
|
|
||||||
// ErrDataNameConflict is set when a multipart variable/file name is already used
|
// ErrDataNameConflict is set when a multipart variable/file name is already used
|
||||||
const ErrDataNameConflict = cerr("data name conflict")
|
const ErrDataNameConflict = Error("data name conflict")
|
||||||
|
|
||||||
// ErrNoHeader is set when a multipart variable/file has no (valid) header
|
// ErrNoHeader is set when a multipart variable/file has no (valid) header
|
||||||
const ErrNoHeader = cerr("data has no header")
|
const ErrNoHeader = Error("data has no header")
|
||||||
|
|
||||||
// Component represents a multipart variable/file
|
// Component represents a multipart variable/file
|
||||||
type Component struct {
|
type Component struct {
|
||||||
|
|
|
@ -1,30 +1,30 @@
|
||||||
package reqdata
|
package reqdata
|
||||||
|
|
||||||
// cerr allows you to create constant "const" error with type boxing.
|
// Error allows you to create constant "const" error with type boxing.
|
||||||
type cerr string
|
type Error string
|
||||||
|
|
||||||
// Error implements the error builtin interface.
|
// Error implements the error builtin interface.
|
||||||
func (err cerr) Error() string {
|
func (err Error) Error() string {
|
||||||
return string(err)
|
return string(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrUnknownType is returned when encountering an unknown type
|
// ErrUnknownType is returned when encountering an unknown type
|
||||||
const ErrUnknownType = cerr("unknown type")
|
const ErrUnknownType = Error("unknown type")
|
||||||
|
|
||||||
// ErrInvalidMultipart is returned when multipart parse failed
|
|
||||||
const ErrInvalidMultipart = cerr("invalid multipart")
|
|
||||||
|
|
||||||
// ErrParseParameter is returned when a parameter fails when parsing
|
|
||||||
const ErrParseParameter = cerr("cannot parse parameter")
|
|
||||||
|
|
||||||
// ErrInvalidJSON is returned when json parse failed
|
// ErrInvalidJSON is returned when json parse failed
|
||||||
const ErrInvalidJSON = cerr("invalid json")
|
const ErrInvalidJSON = Error("invalid json")
|
||||||
|
|
||||||
|
// ErrInvalidRootType is returned when json is a map
|
||||||
|
const ErrInvalidRootType = Error("invalid json root type")
|
||||||
|
|
||||||
|
// ErrInvalidParamName - parameter has an invalid
|
||||||
|
const ErrInvalidParamName = Error("invalid parameter name")
|
||||||
|
|
||||||
// ErrMissingRequiredParam - required param is missing
|
// ErrMissingRequiredParam - required param is missing
|
||||||
const ErrMissingRequiredParam = cerr("missing required param")
|
const ErrMissingRequiredParam = Error("missing required param")
|
||||||
|
|
||||||
// ErrInvalidType - parameter value does not satisfy its type
|
// ErrInvalidType - parameter value does not satisfy its type
|
||||||
const ErrInvalidType = cerr("invalid type")
|
const ErrInvalidType = Error("invalid type")
|
||||||
|
|
||||||
// ErrMissingURIParameter - missing an URI parameter
|
// ErrMissingURIParameter - missing an URI parameter
|
||||||
const ErrMissingURIParameter = cerr("missing URI parameter")
|
const ErrMissingURIParameter = Error("missing URI parameter")
|
||||||
|
|
|
@ -0,0 +1,117 @@
|
||||||
|
package reqdata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parameter represents an http request parameter
|
||||||
|
// that can be of type URL, GET, or FORM (multipart, json, urlencoded)
|
||||||
|
type Parameter struct {
|
||||||
|
// whether the value has been json-parsed
|
||||||
|
// for optimisation purpose, parameters are only parsed
|
||||||
|
// if they are required by the current service
|
||||||
|
Parsed bool
|
||||||
|
|
||||||
|
// whether the value is a file
|
||||||
|
File bool
|
||||||
|
|
||||||
|
// the actual parameter value
|
||||||
|
Value interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parameter (json-like) if not already done
|
||||||
|
func (i *Parameter) Parse() error {
|
||||||
|
|
||||||
|
/* (1) Stop if already parsed or nil*/
|
||||||
|
if i.Parsed || i.Value == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (2) Try to parse value */
|
||||||
|
parsed, err := parseParameter(i.Value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
i.Parsed = true
|
||||||
|
i.Value = parsed
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseParameter parses http GET/POST data
|
||||||
|
// - []string
|
||||||
|
// - size = 1 : return json of first element
|
||||||
|
// - size > 1 : return array of json elements
|
||||||
|
// - string : return json if valid, else return raw string
|
||||||
|
func parseParameter(data interface{}) (interface{}, error) {
|
||||||
|
dtype := reflect.TypeOf(data)
|
||||||
|
dvalue := reflect.ValueOf(data)
|
||||||
|
|
||||||
|
switch dtype.Kind() {
|
||||||
|
|
||||||
|
/* (1) []string -> recursive */
|
||||||
|
case reflect.Slice:
|
||||||
|
|
||||||
|
// 1. ignore empty
|
||||||
|
if dvalue.Len() == 0 {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. parse each element recursively
|
||||||
|
result := make([]interface{}, dvalue.Len())
|
||||||
|
|
||||||
|
for i, l := 0, dvalue.Len(); i < l; i++ {
|
||||||
|
element := dvalue.Index(i)
|
||||||
|
|
||||||
|
// ignore non-string
|
||||||
|
if element.Kind() != reflect.String {
|
||||||
|
result[i] = element.Interface()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed, err := parseParameter(element.String())
|
||||||
|
if err != nil {
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
result[i] = parsed
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
|
||||||
|
/* (2) string -> parse */
|
||||||
|
case reflect.String:
|
||||||
|
|
||||||
|
// build json wrapper
|
||||||
|
wrapper := fmt.Sprintf("{\"wrapped\":%s}", dvalue.String())
|
||||||
|
|
||||||
|
// try to parse as json
|
||||||
|
var result interface{}
|
||||||
|
err := json.Unmarshal([]byte(wrapper), &result)
|
||||||
|
|
||||||
|
// return if success
|
||||||
|
if err == nil {
|
||||||
|
|
||||||
|
mapval, ok := result.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return dvalue.String(), ErrInvalidRootType
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapped, ok := mapval["wrapped"]
|
||||||
|
if !ok {
|
||||||
|
return dvalue.String(), ErrInvalidJSON
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrapped, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// else return as string
|
||||||
|
return dvalue.String(), nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (3) NIL if unknown type */
|
||||||
|
return dvalue.Interface(), nil
|
||||||
|
|
||||||
|
}
|
|
@ -6,9 +6,21 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSimpleString(t *testing.T) {
|
func TestSimpleString(t *testing.T) {
|
||||||
p := parseParameter("some-string")
|
p := Parameter{Parsed: false, File: false, Value: "some-string"}
|
||||||
|
|
||||||
cast, canCast := p.(string)
|
err := p.Parse()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: <%s>", err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !p.Parsed {
|
||||||
|
t.Errorf("expected parameter to be parsed")
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
cast, canCast := p.Value.(string)
|
||||||
if !canCast {
|
if !canCast {
|
||||||
t.Errorf("expected parameter to be a string")
|
t.Errorf("expected parameter to be a string")
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
|
@ -25,9 +37,19 @@ func TestSimpleFloat(t *testing.T) {
|
||||||
|
|
||||||
for i, tcase := range tcases {
|
for i, tcase := range tcases {
|
||||||
t.Run("case "+string(i), func(t *testing.T) {
|
t.Run("case "+string(i), func(t *testing.T) {
|
||||||
p := parseParameter(tcase)
|
p := Parameter{Parsed: false, File: false, Value: tcase}
|
||||||
|
|
||||||
cast, canCast := p.(float64)
|
if err := p.Parse(); err != nil {
|
||||||
|
t.Errorf("unexpected error: <%s>", err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !p.Parsed {
|
||||||
|
t.Errorf("expected parameter to be parsed")
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
cast, canCast := p.Value.(float64)
|
||||||
if !canCast {
|
if !canCast {
|
||||||
t.Errorf("expected parameter to be a float64")
|
t.Errorf("expected parameter to be a float64")
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
|
@ -46,9 +68,19 @@ func TestSimpleBool(t *testing.T) {
|
||||||
|
|
||||||
for i, tcase := range tcases {
|
for i, tcase := range tcases {
|
||||||
t.Run("case "+string(i), func(t *testing.T) {
|
t.Run("case "+string(i), func(t *testing.T) {
|
||||||
p := parseParameter(tcase)
|
p := Parameter{Parsed: false, File: false, Value: tcase}
|
||||||
|
|
||||||
cast, canCast := p.(bool)
|
if err := p.Parse(); err != nil {
|
||||||
|
t.Errorf("unexpected error: <%s>", err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !p.Parsed {
|
||||||
|
t.Errorf("expected parameter to be parsed")
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
cast, canCast := p.Value.(bool)
|
||||||
if !canCast {
|
if !canCast {
|
||||||
t.Errorf("expected parameter to be a bool")
|
t.Errorf("expected parameter to be a bool")
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
|
@ -63,9 +95,21 @@ func TestSimpleBool(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJsonStringSlice(t *testing.T) {
|
func TestJsonStringSlice(t *testing.T) {
|
||||||
p := parseParameter(`["str1", "str2"]`)
|
p := Parameter{Parsed: false, File: false, Value: `["str1", "str2"]`}
|
||||||
|
|
||||||
slice, canCast := p.([]interface{})
|
err := p.Parse()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: <%s>", err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !p.Parsed {
|
||||||
|
t.Errorf("expected parameter to be parsed")
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
slice, canCast := p.Value.([]interface{})
|
||||||
if !canCast {
|
if !canCast {
|
||||||
t.Errorf("expected parameter to be a []interface{}")
|
t.Errorf("expected parameter to be a []interface{}")
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
|
@ -95,9 +139,21 @@ func TestJsonStringSlice(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStringSlice(t *testing.T) {
|
func TestStringSlice(t *testing.T) {
|
||||||
p := parseParameter([]string{"str1", "str2"})
|
p := Parameter{Parsed: false, File: false, Value: []string{"str1", "str2"}}
|
||||||
|
|
||||||
slice, canCast := p.([]interface{})
|
err := p.Parse()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: <%s>", err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !p.Parsed {
|
||||||
|
t.Errorf("expected parameter to be parsed")
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
slice, canCast := p.Value.([]interface{})
|
||||||
if !canCast {
|
if !canCast {
|
||||||
t.Errorf("expected parameter to be a []interface{}")
|
t.Errorf("expected parameter to be a []interface{}")
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
|
@ -137,9 +193,20 @@ func TestJsonPrimitiveBool(t *testing.T) {
|
||||||
|
|
||||||
for i, tcase := range tcases {
|
for i, tcase := range tcases {
|
||||||
t.Run("case "+string(i), func(t *testing.T) {
|
t.Run("case "+string(i), func(t *testing.T) {
|
||||||
p := parseParameter(tcase.Raw)
|
p := Parameter{Parsed: false, File: false, Value: tcase.Raw}
|
||||||
|
|
||||||
cast, canCast := p.(bool)
|
err := p.Parse()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: <%s>", err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !p.Parsed {
|
||||||
|
t.Errorf("expected parameter to be parsed")
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
cast, canCast := p.Value.(bool)
|
||||||
if !canCast {
|
if !canCast {
|
||||||
t.Errorf("expected parameter to be a bool")
|
t.Errorf("expected parameter to be a bool")
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
|
@ -174,9 +241,20 @@ func TestJsonPrimitiveFloat(t *testing.T) {
|
||||||
|
|
||||||
for i, tcase := range tcases {
|
for i, tcase := range tcases {
|
||||||
t.Run("case "+string(i), func(t *testing.T) {
|
t.Run("case "+string(i), func(t *testing.T) {
|
||||||
p := parseParameter(tcase.Raw)
|
p := Parameter{Parsed: false, File: false, Value: tcase.Raw}
|
||||||
|
|
||||||
cast, canCast := p.(float64)
|
err := p.Parse()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: <%s>", err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !p.Parsed {
|
||||||
|
t.Errorf("expected parameter to be parsed")
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
cast, canCast := p.Value.(float64)
|
||||||
if !canCast {
|
if !canCast {
|
||||||
t.Errorf("expected parameter to be a float64")
|
t.Errorf("expected parameter to be a float64")
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
|
@ -192,9 +270,21 @@ func TestJsonPrimitiveFloat(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJsonBoolSlice(t *testing.T) {
|
func TestJsonBoolSlice(t *testing.T) {
|
||||||
p := parseParameter([]string{"true", "false"})
|
p := Parameter{Parsed: false, File: false, Value: []string{"true", "false"}}
|
||||||
|
|
||||||
slice, canCast := p.([]interface{})
|
err := p.Parse()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: <%s>", err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !p.Parsed {
|
||||||
|
t.Errorf("expected parameter to be parsed")
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
slice, canCast := p.Value.([]interface{})
|
||||||
if !canCast {
|
if !canCast {
|
||||||
t.Errorf("expected parameter to be a []interface{}")
|
t.Errorf("expected parameter to be a []interface{}")
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
|
@ -224,9 +314,21 @@ func TestJsonBoolSlice(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBoolSlice(t *testing.T) {
|
func TestBoolSlice(t *testing.T) {
|
||||||
p := parseParameter([]bool{true, false})
|
p := Parameter{Parsed: false, File: false, Value: []bool{true, false}}
|
||||||
|
|
||||||
slice, canCast := p.([]interface{})
|
err := p.Parse()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: <%s>", err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !p.Parsed {
|
||||||
|
t.Errorf("expected parameter to be parsed")
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
slice, canCast := p.Value.([]interface{})
|
||||||
if !canCast {
|
if !canCast {
|
||||||
t.Errorf("expected parameter to be a []interface{}")
|
t.Errorf("expected parameter to be a []interface{}")
|
||||||
t.FailNow()
|
t.FailNow()
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"git.xdrm.io/go/aicra/internal/config"
|
"git.xdrm.io/go/aicra/internal/config"
|
||||||
"git.xdrm.io/go/aicra/internal/multipart"
|
"git.xdrm.io/go/aicra/internal/multipart"
|
||||||
|
@ -27,14 +26,14 @@ type Set struct {
|
||||||
// - FORM: no prefix
|
// - FORM: no prefix
|
||||||
// - URL: '{uri_var}'
|
// - URL: '{uri_var}'
|
||||||
// - GET: 'GET@' followed by the key in GET
|
// - GET: 'GET@' followed by the key in GET
|
||||||
Data map[string]interface{}
|
Data map[string]*Parameter
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new empty store.
|
// New creates a new empty store.
|
||||||
func New(service *config.Service) *Set {
|
func New(service *config.Service) *Set {
|
||||||
return &Set{
|
return &Set{
|
||||||
service: service,
|
service: service,
|
||||||
Data: make(map[string]interface{}),
|
Data: make(map[string]*Parameter),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,17 +53,17 @@ func (i *Set) ExtractURI(req *http.Request) error {
|
||||||
return fmt.Errorf("%s: %w", capture.Name, ErrUnknownType)
|
return fmt.Errorf("%s: %w", capture.Name, ErrUnknownType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse parameter
|
|
||||||
parsed := parseParameter(value)
|
|
||||||
|
|
||||||
// check type
|
// check type
|
||||||
cast, valid := capture.Ref.Validator(parsed)
|
cast, valid := capture.Ref.Validator(value)
|
||||||
if !valid {
|
if !valid {
|
||||||
return fmt.Errorf("%s: %w", capture.Name, ErrInvalidType)
|
return fmt.Errorf("%s: %w", capture.Name, ErrInvalidType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// store cast value in 'Set'
|
// store cast value in 'Set'
|
||||||
i.Data[capture.Ref.Rename] = cast
|
i.Data[capture.Ref.Rename] = &Parameter{
|
||||||
|
Value: cast,
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -87,17 +86,16 @@ func (i *Set) ExtractQuery(req *http.Request) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse parameter
|
|
||||||
parsed := parseParameter(value)
|
|
||||||
|
|
||||||
// check type
|
// check type
|
||||||
cast, valid := param.Validator(parsed)
|
cast, valid := param.Validator(value)
|
||||||
if !valid {
|
if !valid {
|
||||||
return fmt.Errorf("%s: %w", name, ErrInvalidType)
|
return fmt.Errorf("%s: %w", name, ErrInvalidType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// store cast value
|
// store value
|
||||||
i.Data[param.Rename] = cast
|
i.Data[param.Rename] = &Parameter{
|
||||||
|
Value: cast,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -169,8 +167,10 @@ func (i *Set) parseJSON(req *http.Request) error {
|
||||||
return fmt.Errorf("%s: %w", name, ErrInvalidType)
|
return fmt.Errorf("%s: %w", name, ErrInvalidType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// store cast value
|
// store value
|
||||||
i.Data[param.Rename] = cast
|
i.Data[param.Rename] = &Parameter{
|
||||||
|
Value: cast,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -197,17 +197,16 @@ func (i *Set) parseUrlencoded(req *http.Request) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse parameter
|
|
||||||
parsed := parseParameter(value)
|
|
||||||
|
|
||||||
// check type
|
// check type
|
||||||
cast, valid := param.Validator(parsed)
|
cast, valid := param.Validator(value)
|
||||||
if !valid {
|
if !valid {
|
||||||
return fmt.Errorf("%s: %w", name, ErrInvalidType)
|
return fmt.Errorf("%s: %w", name, ErrInvalidType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// store cast value
|
// store value
|
||||||
i.Data[param.Rename] = cast
|
i.Data[param.Rename] = &Parameter{
|
||||||
|
Value: cast,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -245,79 +244,18 @@ func (i *Set) parseMultipart(req *http.Request) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse parameter
|
|
||||||
parsed := parseParameter(string(component.Data))
|
|
||||||
|
|
||||||
// fail on invalid type
|
// fail on invalid type
|
||||||
cast, valid := param.Validator(parsed)
|
cast, valid := param.Validator(string(component.Data))
|
||||||
if !valid {
|
if !valid {
|
||||||
return fmt.Errorf("%s: %w", name, ErrInvalidType)
|
return fmt.Errorf("%s: %w", name, ErrInvalidType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// store cast value
|
// store value
|
||||||
i.Data[param.Rename] = cast
|
i.Data[param.Rename] = &Parameter{
|
||||||
|
Value: cast,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseParameter parses http URI/GET/POST data
|
|
||||||
// - []string : return array of json elements
|
|
||||||
// - string : return json if valid, else return raw string
|
|
||||||
func parseParameter(data interface{}) interface{} {
|
|
||||||
dtype := reflect.TypeOf(data)
|
|
||||||
dvalue := reflect.ValueOf(data)
|
|
||||||
|
|
||||||
switch dtype.Kind() {
|
|
||||||
|
|
||||||
/* (1) []string -> recursive */
|
|
||||||
case reflect.Slice:
|
|
||||||
|
|
||||||
// 1. ignore empty
|
|
||||||
if dvalue.Len() == 0 {
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. parse each element recursively
|
|
||||||
result := make([]interface{}, dvalue.Len())
|
|
||||||
|
|
||||||
for i, l := 0, dvalue.Len(); i < l; i++ {
|
|
||||||
element := dvalue.Index(i)
|
|
||||||
result[i] = parseParameter(element.Interface())
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
|
|
||||||
/* (2) string -> parse */
|
|
||||||
case reflect.String:
|
|
||||||
|
|
||||||
// build json wrapper
|
|
||||||
wrapper := fmt.Sprintf("{\"wrapped\":%s}", dvalue.String())
|
|
||||||
|
|
||||||
// try to parse as json
|
|
||||||
var result interface{}
|
|
||||||
err := json.Unmarshal([]byte(wrapper), &result)
|
|
||||||
|
|
||||||
// return if success
|
|
||||||
if err != nil {
|
|
||||||
return dvalue.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
mapval, ok := result.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return dvalue.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
wrapped, ok := mapval["wrapped"]
|
|
||||||
if !ok {
|
|
||||||
return dvalue.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
return wrapped
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (3) NIL if unknown type */
|
|
||||||
return dvalue.Interface()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -280,7 +280,7 @@ func TestExtractQuery(t *testing.T) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
cast, canCast := param.([]interface{})
|
cast, canCast := param.Value.([]string)
|
||||||
if !canCast {
|
if !canCast {
|
||||||
t.Errorf("should return a []string (got '%v')", cast)
|
t.Errorf("should return a []string (got '%v')", cast)
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
|
@ -458,9 +458,9 @@ func TestExtractFormUrlEncoded(t *testing.T) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
cast, canCast := param.([]interface{})
|
cast, canCast := param.Value.([]string)
|
||||||
if !canCast {
|
if !canCast {
|
||||||
t.Errorf("should return a []interface{} (got '%v')", cast)
|
t.Errorf("should return a []string (got '%v')", cast)
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -606,8 +606,8 @@ func TestJsonParameters(t *testing.T) {
|
||||||
|
|
||||||
valueType := reflect.TypeOf(value)
|
valueType := reflect.TypeOf(value)
|
||||||
|
|
||||||
paramValue := param
|
paramValue := param.Value
|
||||||
paramValueType := reflect.TypeOf(param)
|
paramValueType := reflect.TypeOf(param.Value)
|
||||||
|
|
||||||
if valueType != paramValueType {
|
if valueType != paramValueType {
|
||||||
t.Errorf("should be of type %v (got '%v')", valueType, paramValueType)
|
t.Errorf("should be of type %v (got '%v')", valueType, paramValueType)
|
||||||
|
@ -762,8 +762,8 @@ x
|
||||||
|
|
||||||
valueType := reflect.TypeOf(value)
|
valueType := reflect.TypeOf(value)
|
||||||
|
|
||||||
paramValue := param
|
paramValue := param.Value
|
||||||
paramValueType := reflect.TypeOf(param)
|
paramValueType := reflect.TypeOf(param.Value)
|
||||||
|
|
||||||
if valueType != paramValueType {
|
if valueType != paramValueType {
|
||||||
t.Errorf("should be of type %v (got '%v')", valueType, paramValueType)
|
t.Errorf("should be of type %v (got '%v')", valueType, paramValueType)
|
||||||
|
|
15
server.go
15
server.go
|
@ -6,18 +6,20 @@ import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"git.xdrm.io/go/aicra/api"
|
"git.xdrm.io/go/aicra/api"
|
||||||
"git.xdrm.io/go/aicra/datatype"
|
|
||||||
"git.xdrm.io/go/aicra/internal/config"
|
"git.xdrm.io/go/aicra/internal/config"
|
||||||
|
checker "git.xdrm.io/go/aicra/typecheck"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Server represents an AICRA instance featuring: type checkers, services
|
// Server represents an AICRA instance featuring: type checkers, services
|
||||||
type Server struct {
|
type Server struct {
|
||||||
config *config.Server
|
config *config.Service
|
||||||
|
Checkers *checker.Set
|
||||||
handlers []*api.Handler
|
handlers []*api.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a framework instance from a configuration file
|
// New creates a framework instance from a configuration file
|
||||||
func New(configPath string, dtypes ...datatype.T) (*Server, error) {
|
func New(configPath string) (*Server, error) {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
configFile io.ReadCloser
|
configFile io.ReadCloser
|
||||||
|
@ -26,6 +28,7 @@ func New(configPath string, dtypes ...datatype.T) (*Server, error) {
|
||||||
// 1. init instance
|
// 1. init instance
|
||||||
var i = &Server{
|
var i = &Server{
|
||||||
config: nil,
|
config: nil,
|
||||||
|
Checkers: checker.New(),
|
||||||
handlers: make([]*api.Handler, 0),
|
handlers: make([]*api.Handler, 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,16 +40,14 @@ func New(configPath string, dtypes ...datatype.T) (*Server, error) {
|
||||||
defer configFile.Close()
|
defer configFile.Close()
|
||||||
|
|
||||||
// 3. load configuration
|
// 3. load configuration
|
||||||
i.config, err = config.Parse(configFile, dtypes...)
|
i.config, err = config.Parse(configFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. log configuration services
|
// 4. log configuration services
|
||||||
log.Printf("🔧 Reading configuration '%s'\n", configPath)
|
log.Printf("🔧 Reading configuration '%s'\n", configPath)
|
||||||
for _, service := range i.config.Services {
|
logService(*i.config, "")
|
||||||
log.Printf(" ->\t%s\t'%s'\n", service.Method, service.Pattern)
|
|
||||||
}
|
|
||||||
|
|
||||||
return i, nil
|
return i, nil
|
||||||
|
|
||||||
|
|
90
util.go
90
util.go
|
@ -5,11 +5,101 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.xdrm.io/go/aicra/api"
|
"git.xdrm.io/go/aicra/api"
|
||||||
|
"git.xdrm.io/go/aicra/internal/config"
|
||||||
|
"git.xdrm.io/go/aicra/internal/reqdata"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// extractParameters extracts parameters for the request and checks
|
||||||
|
// every single one according to configuration options
|
||||||
|
func (s *httpServer) extractParameters(store *reqdata.Store, methodParam map[string]*config.Parameter) (map[string]interface{}, api.Error) {
|
||||||
|
|
||||||
|
// init vars
|
||||||
|
parameters := make(map[string]interface{})
|
||||||
|
|
||||||
|
// for each param of the config
|
||||||
|
for name, param := range methodParam {
|
||||||
|
|
||||||
|
// 1. extract value
|
||||||
|
p, isset := store.Set[name]
|
||||||
|
|
||||||
|
// 2. fail if required & missing
|
||||||
|
if !isset && !param.Optional {
|
||||||
|
apiErr := api.ErrorMissingParam()
|
||||||
|
apiErr.SetArguments(name)
|
||||||
|
return nil, apiErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. optional & missing: set default value
|
||||||
|
if !isset {
|
||||||
|
p = &reqdata.Parameter{
|
||||||
|
Parsed: true,
|
||||||
|
File: param.Type == "FILE",
|
||||||
|
Value: nil,
|
||||||
|
}
|
||||||
|
if param.Default != nil {
|
||||||
|
p.Value = *param.Default
|
||||||
|
}
|
||||||
|
|
||||||
|
// we are done
|
||||||
|
parameters[param.Rename] = p.Value
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. parse parameter if not file
|
||||||
|
if !p.File {
|
||||||
|
p.Parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. fail on unexpected multipart file
|
||||||
|
waitFile, gotFile := param.Type == "FILE", p.File
|
||||||
|
if gotFile && !waitFile || !gotFile && waitFile {
|
||||||
|
apiErr := api.ErrorInvalidParam()
|
||||||
|
apiErr.SetArguments(param.Rename, "FILE")
|
||||||
|
return nil, apiErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. ignore type check if file
|
||||||
|
if gotFile {
|
||||||
|
parameters[param.Rename] = p.Value
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. check type
|
||||||
|
if s.Checkers.Run(param.Type, p.Value) != nil {
|
||||||
|
apiErr := api.ErrorInvalidParam()
|
||||||
|
apiErr.SetArguments(param.Rename, param.Type, p.Value)
|
||||||
|
return nil, apiErr
|
||||||
|
}
|
||||||
|
|
||||||
|
parameters[param.Rename] = p.Value
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return parameters, api.ErrorSuccess()
|
||||||
|
}
|
||||||
|
|
||||||
var handledMethods = []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete}
|
var handledMethods = []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete}
|
||||||
|
|
||||||
// Prints an error as HTTP response
|
// Prints an error as HTTP response
|
||||||
func logError(res *api.Response) {
|
func logError(res *api.Response) {
|
||||||
log.Printf("[http.fail] %v\n", res)
|
log.Printf("[http.fail] %v\n", res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// logService logs a service details
|
||||||
|
func logService(s config.Service, path string) {
|
||||||
|
for _, method := range handledMethods {
|
||||||
|
if m := s.Method(method); m != nil {
|
||||||
|
if path == "" {
|
||||||
|
log.Printf(" ->\t%s\t'/'\n", method)
|
||||||
|
} else {
|
||||||
|
log.Printf(" ->\t%s\t'%s'\n", method, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Children != nil {
|
||||||
|
for subPath, child := range s.Children {
|
||||||
|
logService(*child, path+"/"+subPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue