ref 0: big refactor of concerns between api request, server, etc
This commit is contained in:
parent
0a63e9afcc
commit
7e66b6ddd5
|
@ -1,93 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrUnknownKey is returned when a key does not exist using a getter
|
|
||||||
var ErrUnknownKey = errors.New("key does not exist")
|
|
||||||
|
|
||||||
// ErrInvalidType is returned when a typed getter tries to get a value that cannot be
|
|
||||||
// translated into the requested type
|
|
||||||
var ErrInvalidType = errors.New("invalid type")
|
|
||||||
|
|
||||||
// Has checks whether a key exists in the arguments
|
|
||||||
func (i Arguments) Has(key string) bool {
|
|
||||||
_, exists := i[key]
|
|
||||||
return exists
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get extracts a parameter as an interface{} value
|
|
||||||
func (i Arguments) Get(key string) (interface{}, error) {
|
|
||||||
val, ok := i[key]
|
|
||||||
if !ok {
|
|
||||||
return 0, ErrUnknownKey
|
|
||||||
}
|
|
||||||
|
|
||||||
return val, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFloat extracts a parameter as a float value
|
|
||||||
func (i Arguments) GetFloat(key string) (float64, error) {
|
|
||||||
val, err := i.Get(key)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
floatval, ok := val.(float64)
|
|
||||||
if !ok {
|
|
||||||
return 0, ErrInvalidType
|
|
||||||
}
|
|
||||||
|
|
||||||
return floatval, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetInt extracts a parameter as an int value
|
|
||||||
func (i Arguments) GetInt(key string) (int, error) {
|
|
||||||
floatval, err := i.GetFloat(key)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return int(floatval), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUint extracts a parameter as an uint value
|
|
||||||
func (i Arguments) GetUint(key string) (uint, error) {
|
|
||||||
floatval, err := i.GetFloat(key)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return uint(floatval), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetString extracts a parameter as a string value
|
|
||||||
func (i Arguments) GetString(key string) (string, error) {
|
|
||||||
val, ok := i[key]
|
|
||||||
if !ok {
|
|
||||||
return "", ErrUnknownKey
|
|
||||||
}
|
|
||||||
|
|
||||||
stringval, ok := val.(string)
|
|
||||||
if !ok {
|
|
||||||
return "", ErrInvalidType
|
|
||||||
}
|
|
||||||
|
|
||||||
return stringval, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBool extracts a parameter as a bool value
|
|
||||||
func (i Arguments) GetBool(key string) (bool, error) {
|
|
||||||
val, ok := i[key]
|
|
||||||
if !ok {
|
|
||||||
return false, ErrUnknownKey
|
|
||||||
}
|
|
||||||
|
|
||||||
boolval, ok := val.(bool)
|
|
||||||
if !ok {
|
|
||||||
return false, ErrInvalidType
|
|
||||||
}
|
|
||||||
|
|
||||||
return boolval, nil
|
|
||||||
}
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrorSuccess represents a generic successful controller execution
|
||||||
|
ErrorSuccess = func() Error { return Error{0, "all right", nil} }
|
||||||
|
|
||||||
|
// ErrorFailure is the most generic error
|
||||||
|
ErrorFailure = func() Error { return Error{1, "it failed", nil} }
|
||||||
|
|
||||||
|
// ErrorUnknown represents any error which cause is unknown.
|
||||||
|
// It might also be used for debug purposes as this error
|
||||||
|
// has to be used the less possible
|
||||||
|
ErrorUnknown = func() Error { return Error{-1, "", nil} }
|
||||||
|
|
||||||
|
// ErrorNoMatchFound has to be set when trying to fetch data and there is no result
|
||||||
|
ErrorNoMatchFound = func() Error { return Error{2, "no resource found", nil} }
|
||||||
|
|
||||||
|
// ErrorAlreadyExists has to be set when trying to insert data, but identifiers or
|
||||||
|
// unique fields already exists
|
||||||
|
ErrorAlreadyExists = func() Error { return Error{3, "resource already exists", nil} }
|
||||||
|
|
||||||
|
// ErrorConfig has to be set when there is a configuration error
|
||||||
|
ErrorConfig = func() Error { return Error{4, "configuration error", nil} }
|
||||||
|
|
||||||
|
// ErrorUpload has to be set when a file upload failed
|
||||||
|
ErrorUpload = func() Error { return Error{100, "upload failed", nil} }
|
||||||
|
|
||||||
|
// ErrorDownload has to be set when a file download failed
|
||||||
|
ErrorDownload = func() Error { return Error{101, "download failed", nil} }
|
||||||
|
|
||||||
|
// MissingDownloadHeaders has to be set when the implementation
|
||||||
|
// of a controller of type 'download' (which returns a file instead of
|
||||||
|
// a set or output fields) is missing its HEADER field
|
||||||
|
MissingDownloadHeaders = func() Error { return Error{102, "download headers are missing", nil} }
|
||||||
|
|
||||||
|
// ErrorMissingDownloadBody has to be set when the implementation
|
||||||
|
// of a controller of type 'download' (which returns a file instead of
|
||||||
|
// a set or output fields) is missing its BODY field
|
||||||
|
ErrorMissingDownloadBody = func() Error { return Error{103, "download body is missing", nil} }
|
||||||
|
|
||||||
|
// ErrorUnknownService is set when there is no controller matching
|
||||||
|
// the http request URI.
|
||||||
|
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 controller's
|
||||||
|
// implementation (plugin file) is not found/callable
|
||||||
|
// ErrorUncallableService = func() Error { return Error{202, "uncallable service", nil} }
|
||||||
|
|
||||||
|
// ErrorUncallableMethod is set when there the requested controller'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
|
||||||
|
// the api returns a permission error when the current scope (built
|
||||||
|
// by middlewares) does not match the scope required in the config.
|
||||||
|
// You can add your own permission policy and use this error
|
||||||
|
ErrorPermission = func() Error { return Error{300, "permission error", nil} }
|
||||||
|
|
||||||
|
// ErrorToken has to be set (usually in authentication middleware) to tell
|
||||||
|
// the user that this authentication token is expired or invalid
|
||||||
|
ErrorToken = func() Error { return Error{301, "token error", nil} }
|
||||||
|
|
||||||
|
// ErrorMissingParam is set when a *required* parameter is missing from the
|
||||||
|
// http request
|
||||||
|
ErrorMissingParam = func() Error { return Error{400, "missing parameter", nil} }
|
||||||
|
|
||||||
|
// ErrorInvalidParam is set when a given parameter fails its type check as
|
||||||
|
// defined in the config file.
|
||||||
|
ErrorInvalidParam = func() Error { return Error{401, "invalid parameter", nil} }
|
||||||
|
|
||||||
|
// ErrorInvalidDefaultParam is set when an optional parameter's default value
|
||||||
|
// does not match its type.
|
||||||
|
ErrorInvalidDefaultParam = func() Error { return Error{402, "invalid default param", nil} }
|
||||||
|
)
|
|
@ -0,0 +1,33 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error represents an http response error following the api format.
|
||||||
|
// These are used by the controllers to set the *execution status*
|
||||||
|
// directly into the response as JSON alongside response output fields.
|
||||||
|
type Error struct {
|
||||||
|
Code int `json:"error"`
|
||||||
|
Reason string `json:"reason"`
|
||||||
|
Arguments []interface{} `json:"error_args"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put adds an argument to the error
|
||||||
|
// to be displayed back to API caller
|
||||||
|
func (e *Error) Put(arg interface{}) {
|
||||||
|
|
||||||
|
/* (1) Make slice if not */
|
||||||
|
if e.Arguments == nil {
|
||||||
|
e.Arguments = make([]interface{}, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (2) Append argument */
|
||||||
|
e.Arguments = append(e.Arguments, arg)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements 'error'
|
||||||
|
func (e Error) Error() string {
|
||||||
|
return fmt.Sprintf("[%d] %s", e.Code, e.Reason)
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HandlerFunc manages an API request
|
||||||
|
type HandlerFunc func(Request, *Response)
|
||||||
|
|
||||||
|
// Handler is an API handler ready to be bound
|
||||||
|
type Handler struct {
|
||||||
|
path string
|
||||||
|
method string
|
||||||
|
handle HandlerFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHandler builds a handler from its http method and path
|
||||||
|
func NewHandler(method, path string, handlerFunc HandlerFunc) *Handler {
|
||||||
|
return &Handler{
|
||||||
|
path: path,
|
||||||
|
method: strings.ToUpper(method),
|
||||||
|
handle: handlerFunc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle fires a handler
|
||||||
|
func (h *Handler) Handle(req Request, res *Response) {
|
||||||
|
h.handle(req, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMethod returns the handler's HTTP method
|
||||||
|
func (h *Handler) GetMethod() string {
|
||||||
|
return h.method
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPath returns the handler's path
|
||||||
|
func (h *Handler) GetPath() string {
|
||||||
|
return h.path
|
||||||
|
}
|
|
@ -1,40 +1,40 @@
|
||||||
package request
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// RequestParam defines input parameters of an api request
|
||||||
|
type RequestParam map[string]interface{}
|
||||||
|
|
||||||
// Request represents an API request i.e. HTTP
|
// Request represents an API request i.e. HTTP
|
||||||
type Request struct {
|
type Request struct {
|
||||||
// corresponds to the list of uri components
|
// corresponds to the list of uri components
|
||||||
// featuring in the request URI
|
// featured in the request URI
|
||||||
URI []string
|
URI []string
|
||||||
|
|
||||||
// controller path (portion of 'Uri')
|
// original HTTP request
|
||||||
Path []string
|
Request *http.Request
|
||||||
|
|
||||||
// contains all data from URL, GET, and FORM
|
// input parameters
|
||||||
Data *DataSet
|
Param RequestParam
|
||||||
}
|
}
|
||||||
|
|
||||||
// New builds an interface request from a http.Request
|
// NewRequest builds an interface request from a http.Request
|
||||||
func New(req *http.Request) (*Request, error) {
|
func NewRequest(req *http.Request) (*Request, error) {
|
||||||
|
|
||||||
/* (1) Get useful data */
|
// 1. get useful data
|
||||||
uri := normaliseURI(req.URL.Path)
|
uri := normaliseURI(req.URL.Path)
|
||||||
uriparts := strings.Split(uri, "/")
|
uriparts := strings.Split(uri, "/")
|
||||||
|
|
||||||
/* (2) Init request */
|
// 3. Init request
|
||||||
inst := &Request{
|
inst := &Request{
|
||||||
URI: uriparts,
|
URI: uriparts,
|
||||||
Path: make([]string, 0, len(uriparts)),
|
Request: req,
|
||||||
Data: NewDataset(),
|
Param: make(RequestParam),
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (3) Build dataset */
|
|
||||||
inst.Data.Build(req)
|
|
||||||
|
|
||||||
return inst, nil
|
return inst, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +1,47 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.xdrm.io/go/aicra/err"
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
// New creates an empty response
|
// ResponseData defines format for response parameters to return
|
||||||
|
type ResponseData map[string]interface{}
|
||||||
|
|
||||||
|
// Response represents an API response to be sent
|
||||||
|
type Response struct {
|
||||||
|
Data ResponseData
|
||||||
|
Headers http.Header
|
||||||
|
Err Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewResponse creates an empty response
|
||||||
func NewResponse() *Response {
|
func NewResponse() *Response {
|
||||||
return &Response{
|
return &Response{
|
||||||
data: make(map[string]interface{}),
|
Data: make(ResponseData),
|
||||||
Err: err.Success,
|
Err: ErrorFailure(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set adds/overrides a new response field
|
// SetData adds/overrides a new response field
|
||||||
func (i *Response) Set(name string, value interface{}) {
|
func (i *Response) SetData(name string, value interface{}) {
|
||||||
i.data[name] = value
|
i.Data[name] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get gets a response field
|
// GetData gets a response field
|
||||||
func (i *Response) Get(name string) interface{} {
|
func (i *Response) GetData(name string) interface{} {
|
||||||
value, _ := i.data[name]
|
value, _ := i.Data[name]
|
||||||
|
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dump gets all key/value pairs
|
type jsonResponse struct {
|
||||||
func (i *Response) Dump() map[string]interface{} {
|
Error
|
||||||
return i.data
|
ResponseData
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements the 'json.Marshaler' interface and is used
|
||||||
|
// to generate the JSON representation of the response
|
||||||
|
func (i *Response) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(jsonResponse{i.Err, i.Data})
|
||||||
}
|
}
|
||||||
|
|
14
api/types.go
14
api/types.go
|
@ -1,14 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.xdrm.io/go/aicra/err"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Arguments contains all key-value arguments
|
|
||||||
type Arguments map[string]interface{}
|
|
||||||
|
|
||||||
// Response represents an API response to be sent
|
|
||||||
type Response struct {
|
|
||||||
data map[string]interface{}
|
|
||||||
Err err.Error
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
package driver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Generic tells the aicra instance to use the generic driver to load controller/middleware executables
|
|
||||||
//
|
|
||||||
// It will call an executable with the json input into the standard input (argument 1)
|
|
||||||
// the HTTP method is send as the key _HTTP_METHOD_ (in upper case)
|
|
||||||
// The standard output must be a json corresponding to the data
|
|
||||||
type Generic struct{}
|
|
||||||
|
|
||||||
// Name returns the driver name
|
|
||||||
func (d *Generic) Name() string { return "generic" }
|
|
||||||
|
|
||||||
// Path returns the universal path from the source path
|
|
||||||
func (d Generic) Path(_root, _folder, _src string) string {
|
|
||||||
return _src
|
|
||||||
}
|
|
||||||
|
|
||||||
// Source returns the source path from the universal path
|
|
||||||
func (d Generic) Source(_root, _folder, _path string) string {
|
|
||||||
return filepath.Join(_root, _folder, _path)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build returns the build path from the universal path
|
|
||||||
func (d Generic) Build(_root, _folder, _path string) string {
|
|
||||||
return filepath.Join(_root, _folder, _path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compiled returns whether the driver has to be build
|
|
||||||
func (d Generic) Compiled() bool { return false }
|
|
||||||
|
|
||||||
// LoadController implements the Driver interface
|
|
||||||
func (d *Generic) LoadController(_path string) (Controller, error) {
|
|
||||||
return genericController(_path), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadMiddleware returns a new middleware; it must be a
|
|
||||||
// valid and existing folder/filename file
|
|
||||||
func (d *Generic) LoadMiddleware(_path string) (Middleware, error) {
|
|
||||||
return genericMiddleware(_path), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadChecker returns a new middleware; it must be a
|
|
||||||
// valid and existing folder/filename file
|
|
||||||
func (d *Generic) LoadChecker(_path string) (Checker, error) {
|
|
||||||
return genericChecker(_path), nil
|
|
||||||
}
|
|
|
@ -1,178 +0,0 @@
|
||||||
package driver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"git.xdrm.io/go/aicra/api"
|
|
||||||
e "git.xdrm.io/go/aicra/err"
|
|
||||||
"net/http"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// genericController is the mockup for returning a controller with as a string the path
|
|
||||||
type genericController string
|
|
||||||
|
|
||||||
func (path genericController) Get(d api.Arguments) api.Response {
|
|
||||||
|
|
||||||
res := api.NewResponse()
|
|
||||||
|
|
||||||
/* (1) Prepare stdin data */
|
|
||||||
stdin, err := json.Marshal(d)
|
|
||||||
if err != nil {
|
|
||||||
res.Err = e.UncallableController
|
|
||||||
return *res
|
|
||||||
}
|
|
||||||
|
|
||||||
// extract HTTP method
|
|
||||||
rawMethod, ok := d["_HTTP_METHOD_"]
|
|
||||||
if !ok {
|
|
||||||
res.Err = e.UncallableController
|
|
||||||
return *res
|
|
||||||
}
|
|
||||||
method, ok := rawMethod.(string)
|
|
||||||
if !ok {
|
|
||||||
res.Err = e.UncallableController
|
|
||||||
return *res
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (2) Try to load command with <stdin> -> stdout */
|
|
||||||
cmd := exec.Command(string(path), method, string(stdin))
|
|
||||||
|
|
||||||
stdout, err := cmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
res.Err = e.UncallableController
|
|
||||||
return *res
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (3) Get output json */
|
|
||||||
var outputI interface{}
|
|
||||||
err = json.Unmarshal(stdout, &outputI)
|
|
||||||
if err != nil {
|
|
||||||
res.Err = e.UncallableController
|
|
||||||
return *res
|
|
||||||
}
|
|
||||||
|
|
||||||
output, ok := outputI.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
res.Err = e.UncallableController
|
|
||||||
return *res
|
|
||||||
}
|
|
||||||
|
|
||||||
res.Err = e.Success
|
|
||||||
|
|
||||||
// extract error (success by default or on error)
|
|
||||||
if outErr, ok := output["error"]; ok {
|
|
||||||
errCode, ok := outErr.(float64)
|
|
||||||
if ok {
|
|
||||||
res.Err = e.Error{Code: int(errCode), Reason: "unknown reason", Arguments: nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(output, "error")
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (4) fill response */
|
|
||||||
for k, v := range output {
|
|
||||||
res.Set(k, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return *res
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (path genericController) Post(d api.Arguments) api.Response {
|
|
||||||
return path.Get(d)
|
|
||||||
}
|
|
||||||
func (path genericController) Put(d api.Arguments) api.Response {
|
|
||||||
return path.Get(d)
|
|
||||||
}
|
|
||||||
func (path genericController) Delete(d api.Arguments) api.Response {
|
|
||||||
return path.Get(d)
|
|
||||||
}
|
|
||||||
|
|
||||||
// genericMiddleware is the mockup for returning a middleware as a string (its path)
|
|
||||||
type genericMiddleware string
|
|
||||||
|
|
||||||
func (path genericMiddleware) Inspect(_req http.Request, _scope *[]string) {
|
|
||||||
|
|
||||||
/* (1) Prepare stdin data */
|
|
||||||
stdin, err := json.Marshal(_scope)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (2) Try to load command with <stdin> -> stdout */
|
|
||||||
cmd := exec.Command(string(path), string(stdin))
|
|
||||||
|
|
||||||
stdout, err := cmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (3) Get output json */
|
|
||||||
var outputI interface{}
|
|
||||||
err = json.Unmarshal(stdout, &outputI)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (4) Get as []string */
|
|
||||||
scope, ok := outputI.([]interface{})
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (5) Try to add each value to the scope */
|
|
||||||
for _, v := range scope {
|
|
||||||
stringScope, ok := v.(string)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
*_scope = append(*_scope, stringScope)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// genericChecker is the mockup for returning a checker as a string (its path)
|
|
||||||
type genericChecker string
|
|
||||||
|
|
||||||
func (path genericChecker) Match(_type string) bool {
|
|
||||||
|
|
||||||
/* (1) Try to load command with <stdin> -> stdout */
|
|
||||||
cmd := exec.Command(string(path), "MATCH", _type)
|
|
||||||
|
|
||||||
stdout, err := cmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (2) Parse output */
|
|
||||||
output := strings.ToLower(strings.Trim(string(stdout), " \t\r\n"))
|
|
||||||
|
|
||||||
return output == "true" || output == "1"
|
|
||||||
|
|
||||||
}
|
|
||||||
func (path genericChecker) Check(_value interface{}) bool {
|
|
||||||
|
|
||||||
/* (1) Prepare stdin data */
|
|
||||||
indata := make(map[string]interface{})
|
|
||||||
indata["value"] = _value
|
|
||||||
|
|
||||||
stdin, err := json.Marshal(indata)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (2) Try to load command with <stdin> -> stdout */
|
|
||||||
cmd := exec.Command(string(path), "CHECK", string(stdin))
|
|
||||||
|
|
||||||
stdout, err := cmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (2) Parse output */
|
|
||||||
output := strings.ToLower(strings.Trim(string(stdout), " \t\r\n"))
|
|
||||||
|
|
||||||
return output == "true" || output == "1"
|
|
||||||
|
|
||||||
}
|
|
117
driver/plugin.go
117
driver/plugin.go
|
@ -1,117 +0,0 @@
|
||||||
package driver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"path/filepath"
|
|
||||||
"plugin"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Plugin tells the aicra instance to use the plugin driver to load controller/middleware executables
|
|
||||||
//
|
|
||||||
// It will load go .so plugins with the following interface :
|
|
||||||
//
|
|
||||||
// type Controller interface {
|
|
||||||
// Get(d i.Arguments, r *i.Response) i.Response
|
|
||||||
// Post(d i.Arguments, r *i.Response) i.Response
|
|
||||||
// Put(d i.Arguments, r *i.Response) i.Response
|
|
||||||
// Delete(d i.Arguments, r *i.Response) i.Response
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// The controllers are exported by calling the 'Export() Controller' method
|
|
||||||
type Plugin struct{}
|
|
||||||
|
|
||||||
// Name returns the driver name
|
|
||||||
func (d Plugin) Name() string { return "plugin" }
|
|
||||||
|
|
||||||
// Path returns the universal path from the source path
|
|
||||||
func (d Plugin) Path(_root, _folder, _src string) string {
|
|
||||||
return filepath.Dir(_src)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Source returns the source path from the universal path
|
|
||||||
func (d Plugin) Source(_root, _folder, _path string) string {
|
|
||||||
|
|
||||||
return filepath.Join(_root, _folder, _path, "main.go")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build returns the build path from the universal path
|
|
||||||
func (d Plugin) Build(_root, _folder, _path string) string {
|
|
||||||
if _path == "" {
|
|
||||||
return fmt.Sprintf("%s", filepath.Join(_root, ".build", _folder, "ROOT.so"))
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s.so", filepath.Join(_root, ".build", _folder, _path))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compiled returns whether the driver has to be build
|
|
||||||
func (d Plugin) Compiled() bool { return true }
|
|
||||||
|
|
||||||
// LoadController returns a new Controller
|
|
||||||
func (d *Plugin) LoadController(_path string) (Controller, error) {
|
|
||||||
|
|
||||||
/* 1. Try to load plugin */
|
|
||||||
p, err := plugin.Open(_path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 2. Try to extract exported field */
|
|
||||||
m, err := p.Lookup("Export")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
exporter, ok := m.(func() Controller)
|
|
||||||
if !ok {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 3. Controller */
|
|
||||||
return exporter(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadMiddleware returns a new Middleware
|
|
||||||
func (d *Plugin) LoadMiddleware(_path string) (Middleware, error) {
|
|
||||||
|
|
||||||
/* 1. Try to load plugin */
|
|
||||||
p, err := plugin.Open(_path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 2. Try to extract exported field */
|
|
||||||
m, err := p.Lookup("Export")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
exporter, ok := m.(func() Middleware)
|
|
||||||
if !ok {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return exporter(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadChecker returns a new Checker
|
|
||||||
func (d *Plugin) LoadChecker(_path string) (Checker, error) {
|
|
||||||
|
|
||||||
/* 1. Try to load plugin */
|
|
||||||
p, err := plugin.Open(_path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 2. Try to extract exported field */
|
|
||||||
m, err := p.Lookup("Export")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
exporter, ok := m.(func() Checker)
|
|
||||||
if !ok {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return exporter(), nil
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
package driver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.xdrm.io/go/aicra/api"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Driver defines the driver interface to load controller/middleware implementation or executables
|
|
||||||
type Driver interface {
|
|
||||||
Name() string
|
|
||||||
Path(string, string, string) string
|
|
||||||
Source(string, string, string) string
|
|
||||||
Build(string, string, string) string
|
|
||||||
Compiled() bool
|
|
||||||
|
|
||||||
LoadController(_path string) (Controller, error)
|
|
||||||
LoadMiddleware(_path string) (Middleware, error)
|
|
||||||
LoadChecker(_path string) (Checker, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checker is the interface that type checkers implementation must follow
|
|
||||||
type Checker interface {
|
|
||||||
Match(string) bool
|
|
||||||
Check(interface{}) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Controller is the interface that controller implementation must follow
|
|
||||||
// it is used by the 'Import' driver
|
|
||||||
type Controller interface {
|
|
||||||
Get(d api.Arguments) api.Response
|
|
||||||
Post(d api.Arguments) api.Response
|
|
||||||
Put(d api.Arguments) api.Response
|
|
||||||
Delete(d api.Arguments) api.Response
|
|
||||||
}
|
|
||||||
|
|
||||||
// Middleware is the interface that middleware implementation must follow
|
|
||||||
// it is used by the 'Import' driver
|
|
||||||
type Middleware interface {
|
|
||||||
Inspect(http.Request, *[]string)
|
|
||||||
}
|
|
|
@ -1,78 +0,0 @@
|
||||||
package err
|
|
||||||
|
|
||||||
var (
|
|
||||||
// Success represents a generic successful controller execution
|
|
||||||
Success = Error{0, "all right", nil}
|
|
||||||
|
|
||||||
// Failure is the most generic error
|
|
||||||
Failure = Error{1, "it failed", nil}
|
|
||||||
|
|
||||||
// Unknown represents any error which cause is unknown.
|
|
||||||
// It might also be used for debug purposes as this error
|
|
||||||
// has to be used the less possible
|
|
||||||
Unknown = Error{-1, "", nil}
|
|
||||||
|
|
||||||
// NoMatchFound has to be set when trying to fetch data and there is no result
|
|
||||||
NoMatchFound = Error{2, "no resource found", nil}
|
|
||||||
|
|
||||||
// AlreadyExists has to be set when trying to insert data, but identifiers or
|
|
||||||
// unique fields already exists
|
|
||||||
AlreadyExists = Error{3, "resource already exists", nil}
|
|
||||||
|
|
||||||
// Config has to be set when there is a configuration error
|
|
||||||
Config = Error{4, "configuration error", nil}
|
|
||||||
|
|
||||||
// Upload has to be set when a file upload failed
|
|
||||||
Upload = Error{100, "upload failed", nil}
|
|
||||||
|
|
||||||
// Download has to be set when a file download failed
|
|
||||||
Download = Error{101, "download failed", nil}
|
|
||||||
|
|
||||||
// MissingDownloadHeaders has to be set when the implementation
|
|
||||||
// of a controller of type 'download' (which returns a file instead of
|
|
||||||
// a set or output fields) is missing its HEADER field
|
|
||||||
MissingDownloadHeaders = Error{102, "download headers are missing", nil}
|
|
||||||
|
|
||||||
// MissingDownloadBody has to be set when the implementation
|
|
||||||
// of a controller of type 'download' (which returns a file instead of
|
|
||||||
// a set or output fields) is missing its BODY field
|
|
||||||
MissingDownloadBody = Error{103, "download body is missing", nil}
|
|
||||||
|
|
||||||
// UnknownController is set when there is no controller matching
|
|
||||||
// the http request URI.
|
|
||||||
UnknownController = Error{200, "unknown controller", nil}
|
|
||||||
|
|
||||||
// UnknownMethod is set when there is no method matching the
|
|
||||||
// request's http method
|
|
||||||
UnknownMethod = Error{201, "unknown method", nil}
|
|
||||||
|
|
||||||
// UncallableController is set when there the requested controller's
|
|
||||||
// implementation (plugin file) is not found/callable
|
|
||||||
UncallableController = Error{202, "uncallable controller", nil}
|
|
||||||
|
|
||||||
// UncallableMethod is set when there the requested controller's
|
|
||||||
// implementation does not features the requested method
|
|
||||||
UncallableMethod = Error{203, "uncallable method", nil}
|
|
||||||
|
|
||||||
// Permission is set when there is a permission error by default
|
|
||||||
// the api returns a permission error when the current scope (built
|
|
||||||
// by middlewares) does not match the scope required in the config.
|
|
||||||
// You can add your own permission policy and use this error
|
|
||||||
Permission = Error{300, "permission error", nil}
|
|
||||||
|
|
||||||
// Token has to be set (usually in authentication middleware) to tell
|
|
||||||
// the user that this authentication token is expired or invalid
|
|
||||||
Token = Error{301, "token error", nil}
|
|
||||||
|
|
||||||
// MissingParam is set when a *required* parameter is missing from the
|
|
||||||
// http request
|
|
||||||
MissingParam = Error{400, "missing parameter", nil}
|
|
||||||
|
|
||||||
// InvalidParam is set when a given parameter fails its type check as
|
|
||||||
// defined in the config file.
|
|
||||||
InvalidParam = Error{401, "invalid parameter", nil}
|
|
||||||
|
|
||||||
// InvalidDefaultParam is set when an optional parameter's default value
|
|
||||||
// does not match its type.
|
|
||||||
InvalidDefaultParam = Error{402, "invalid default param", nil}
|
|
||||||
)
|
|
|
@ -1,56 +0,0 @@
|
||||||
package err
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Error represents an http response error following the api format.
|
|
||||||
// These are used by the controllers to set the *execution status*
|
|
||||||
// directly into the response as JSON alongside response output fields.
|
|
||||||
type Error struct {
|
|
||||||
Code int
|
|
||||||
Reason string
|
|
||||||
Arguments []interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put adds an argument to the error
|
|
||||||
// to be displayed back to API caller
|
|
||||||
func (e *Error) Put(arg interface{}) {
|
|
||||||
|
|
||||||
/* (1) Make slice if not */
|
|
||||||
if e.Arguments == nil {
|
|
||||||
e.Arguments = make([]interface{}, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (2) Append argument */
|
|
||||||
e.Arguments = append(e.Arguments, arg)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implements 'error'
|
|
||||||
func (e Error) Error() string {
|
|
||||||
|
|
||||||
return fmt.Sprintf("[%d] %s", e.Code, e.Reason)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON implements the 'json.Marshaler' interface and is used
|
|
||||||
// to generate the JSON representation of the error data
|
|
||||||
func (e Error) MarshalJSON() ([]byte, error) {
|
|
||||||
|
|
||||||
var jsonArguments string
|
|
||||||
|
|
||||||
/* (1) Marshal 'Arguments' if set */
|
|
||||||
if e.Arguments != nil && len(e.Arguments) > 0 {
|
|
||||||
argRepresentation, err := json.Marshal(e.Arguments)
|
|
||||||
if err == nil {
|
|
||||||
jsonArguments = fmt.Sprintf(",\"arguments\":%s", argRepresentation)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (2) Render JSON manually */
|
|
||||||
return []byte(fmt.Sprintf("{\"error\":%d,\"reason\":\"%s\"%s}", e.Code, e.Reason, jsonArguments)), nil
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
package clifmt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Color returns a bash-formatted string representing
|
|
||||||
// the string @text with the color code @color and in bold
|
|
||||||
// if @bold (1 optional argument) is set to true
|
|
||||||
func Color(color byte, text string, bold ...bool) string {
|
|
||||||
b := "0"
|
|
||||||
if len(bold) > 0 && bold[0] {
|
|
||||||
b = "1"
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("\033[%s;%dm%s\033[0m", b, color, text)
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
package clifmt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var titleIndex = 0
|
|
||||||
var alignOffset = 30
|
|
||||||
|
|
||||||
// Warn returns a red warning ASCII sign. If a string is given
|
|
||||||
// as argument, it will print it after the warning sign
|
|
||||||
func Warn(s ...string) string {
|
|
||||||
if len(s) == 0 {
|
|
||||||
return Color(31, "/!\\")
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("%s %s", Warn(), s[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Info returns a blue info ASCII sign. If a string is given
|
|
||||||
// as argument, it will print it after the info sign
|
|
||||||
func Info(s ...string) string {
|
|
||||||
if len(s) == 0 {
|
|
||||||
return Color(34, "(!)")
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("%s %s", Info(), s[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Title prints a formatted title (auto-indexed from local counted)
|
|
||||||
func Title(s string) {
|
|
||||||
titleIndex++
|
|
||||||
fmt.Printf("\n%s |%d| %s %s\n", Color(33, ">>", false), titleIndex, s, Color(33, "<<", false))
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Align prints strings with space padding to align line ends (fixed width)
|
|
||||||
func Align(s string) {
|
|
||||||
|
|
||||||
// 1. print string
|
|
||||||
fmt.Printf("%s", s)
|
|
||||||
|
|
||||||
// 2. get actual size
|
|
||||||
size := len(s)
|
|
||||||
|
|
||||||
// 3. remove \033[XYm format characters
|
|
||||||
size -= (len(strings.Split(s, "\033")) - 0) * 6
|
|
||||||
|
|
||||||
// 3. add 1 char for each \033[0m
|
|
||||||
size += len(strings.Split(s, "\033[0m")) - 1
|
|
||||||
|
|
||||||
// 4. print trailing spaces
|
|
||||||
for i := size; i < alignOffset; i++ {
|
|
||||||
fmt.Printf(" ")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,8 +3,6 @@ package config
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.xdrm.io/go/aicra/middleware"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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.
|
||||||
|
@ -84,29 +82,6 @@ func (methodDef *Method) checkAndFormat(servicePath string, httpMethod string) e
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckScope returns whether a given scope matches the method configuration.
|
|
||||||
// The scope format is: `[ [a,b], [c], [d,e] ]` where the first level is a bitwise `OR` and the second a bitwise `AND`
|
|
||||||
func (methodDef *Method) CheckScope(scope middleware.Scope) bool {
|
|
||||||
|
|
||||||
for _, OR := range methodDef.Permission {
|
|
||||||
granted := true
|
|
||||||
|
|
||||||
for _, AND := range OR {
|
|
||||||
if !scopeHasPermission(AND, scope) {
|
|
||||||
granted = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if one is valid -> grant
|
|
||||||
if granted {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// scopeHasPermission returns whether the permission fulfills a given scope
|
// scopeHasPermission returns whether the permission fulfills a given scope
|
||||||
func scopeHasPermission(permission string, scope []string) bool {
|
func scopeHasPermission(permission string, scope []string) bool {
|
||||||
for _, s := range scope {
|
for _, s := range scope {
|
||||||
|
|
|
@ -1,30 +1,31 @@
|
||||||
package request
|
package reqdata
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.xdrm.io/go/aicra/internal/multipart"
|
"git.xdrm.io/go/aicra/internal/multipart"
|
||||||
|
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DataSet represents all data that can be caught:
|
// Store represents all data that can be caught:
|
||||||
// - URI (guessed from the URI by removing the controller path)
|
// - URI (guessed from the URI by removing the controller path)
|
||||||
// - GET (default url data)
|
// - GET (default url data)
|
||||||
// - POST (from json, form-data, url-encoded)
|
// - POST (from json, form-data, url-encoded)
|
||||||
type DataSet struct {
|
type Store struct {
|
||||||
|
|
||||||
// ordered values from the URI
|
// ordered values from the URI
|
||||||
// catches all after the controller path
|
// catches all after the controller path
|
||||||
//
|
//
|
||||||
// points to DataSet.Data
|
// points to Store.Data
|
||||||
URI []*Parameter
|
URI []*Parameter
|
||||||
|
|
||||||
// uri parameters following the QUERY format
|
// uri parameters following the QUERY format
|
||||||
//
|
//
|
||||||
// points to DataSet.Data
|
// points to Store.Data
|
||||||
Get map[string]*Parameter
|
Get map[string]*Parameter
|
||||||
|
|
||||||
// form data depending on the Content-Type:
|
// form data depending on the Content-Type:
|
||||||
|
@ -32,7 +33,7 @@ type DataSet struct {
|
||||||
// 'application/x-www-form-urlencoded' => standard parameters as QUERY parameters
|
// 'application/x-www-form-urlencoded' => standard parameters as QUERY parameters
|
||||||
// 'multipart/form-data' => parse form-data format
|
// 'multipart/form-data' => parse form-data format
|
||||||
//
|
//
|
||||||
// points to DataSet.Data
|
// points to Store.Data
|
||||||
Form map[string]*Parameter
|
Form map[string]*Parameter
|
||||||
|
|
||||||
// contains URL+GET+FORM data with prefixes:
|
// contains URL+GET+FORM data with prefixes:
|
||||||
|
@ -42,37 +43,33 @@ type DataSet struct {
|
||||||
Set map[string]*Parameter
|
Set map[string]*Parameter
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDataset creates an empty request dataset
|
// New creates a new store from an http request.
|
||||||
func NewDataset() *DataSet {
|
func New(req *http.Request) *Store {
|
||||||
return &DataSet{
|
ds := &Store{
|
||||||
URI: make([]*Parameter, 0),
|
URI: make([]*Parameter, 0),
|
||||||
Get: make(map[string]*Parameter),
|
Get: make(map[string]*Parameter),
|
||||||
Form: make(map[string]*Parameter),
|
Form: make(map[string]*Parameter),
|
||||||
Set: make(map[string]*Parameter),
|
Set: make(map[string]*Parameter),
|
||||||
}
|
}
|
||||||
}
|
// 1. GET (query) data
|
||||||
|
ds.fetchGet(req)
|
||||||
|
|
||||||
// Build builds a 'DataSet' from an http request
|
// 2. We are done if GET method
|
||||||
func (i *DataSet) Build(req *http.Request) {
|
if req.Method == http.MethodGet {
|
||||||
|
return ds
|
||||||
/* (1) GET (query) data */
|
|
||||||
i.fetchGet(req)
|
|
||||||
|
|
||||||
/* (2) We are done if GET method */
|
|
||||||
if req.Method == "GET" {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (3) POST (body) data */
|
// 2. POST (body) data
|
||||||
i.fetchForm(req)
|
ds.fetchForm(req)
|
||||||
|
|
||||||
|
return ds
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetURI stores URL data and fills 'Set'
|
// SetURIParameters stores URL orderedURIParams and fills 'Set'
|
||||||
// with creating pointers inside 'Url'
|
// with creating pointers inside 'Url'
|
||||||
func (i *DataSet) SetURI(data []string) {
|
func (i *Store) SetURIParameters(orderedUParams []string) {
|
||||||
|
|
||||||
for index, value := range data {
|
for index, value := range orderedUParams {
|
||||||
|
|
||||||
// create set index
|
// create set index
|
||||||
setindex := fmt.Sprintf("URL#%d", index)
|
setindex := fmt.Sprintf("URL#%d", index)
|
||||||
|
@ -91,7 +88,7 @@ func (i *DataSet) SetURI(data []string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetchGet stores data from the QUERY (in url parameters)
|
// fetchGet stores data from the QUERY (in url parameters)
|
||||||
func (i *DataSet) fetchGet(req *http.Request) {
|
func (i *Store) fetchGet(req *http.Request) {
|
||||||
|
|
||||||
for name, value := range req.URL.Query() {
|
for name, value := range req.URL.Query() {
|
||||||
|
|
||||||
|
@ -128,7 +125,7 @@ func (i *DataSet) fetchGet(req *http.Request) {
|
||||||
// - parse 'form-data' if not supported (not POST requests)
|
// - parse 'form-data' if not supported (not POST requests)
|
||||||
// - parse 'x-www-form-urlencoded'
|
// - parse 'x-www-form-urlencoded'
|
||||||
// - parse 'application/json'
|
// - parse 'application/json'
|
||||||
func (i *DataSet) fetchForm(req *http.Request) {
|
func (i *Store) fetchForm(req *http.Request) {
|
||||||
|
|
||||||
contentType := req.Header.Get("Content-Type")
|
contentType := req.Header.Get("Content-Type")
|
||||||
|
|
||||||
|
@ -155,7 +152,7 @@ func (i *DataSet) fetchForm(req *http.Request) {
|
||||||
|
|
||||||
// parseJSON parses JSON from the request body inside 'Form'
|
// parseJSON parses JSON from the request body inside 'Form'
|
||||||
// and 'Set'
|
// and 'Set'
|
||||||
func (i *DataSet) parseJSON(req *http.Request) {
|
func (i *Store) parseJSON(req *http.Request) {
|
||||||
|
|
||||||
parsed := make(map[string]interface{}, 0)
|
parsed := make(map[string]interface{}, 0)
|
||||||
|
|
||||||
|
@ -197,7 +194,7 @@ func (i *DataSet) parseJSON(req *http.Request) {
|
||||||
|
|
||||||
// parseUrlencoded parses urlencoded from the request body inside 'Form'
|
// parseUrlencoded parses urlencoded from the request body inside 'Form'
|
||||||
// and 'Set'
|
// and 'Set'
|
||||||
func (i *DataSet) parseUrlencoded(req *http.Request) {
|
func (i *Store) parseUrlencoded(req *http.Request) {
|
||||||
|
|
||||||
// use http.Request interface
|
// use http.Request interface
|
||||||
if err := req.ParseForm(); err != nil {
|
if err := req.ParseForm(); err != nil {
|
||||||
|
@ -233,7 +230,7 @@ func (i *DataSet) parseUrlencoded(req *http.Request) {
|
||||||
|
|
||||||
// parseMultipart parses multi-part from the request body inside 'Form'
|
// parseMultipart parses multi-part from the request body inside 'Form'
|
||||||
// and 'Set'
|
// and 'Set'
|
||||||
func (i *DataSet) parseMultipart(req *http.Request) {
|
func (i *Store) parseMultipart(req *http.Request) {
|
||||||
|
|
||||||
/* (1) Create reader */
|
/* (1) Create reader */
|
||||||
boundary := req.Header.Get("Content-Type")[len("multipart/form-data; boundary="):]
|
boundary := req.Header.Get("Content-Type")[len("multipart/form-data; boundary="):]
|
|
@ -1,4 +1,4 @@
|
||||||
package request
|
package reqdata
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
|
@ -1,4 +1,4 @@
|
||||||
package request
|
package reqdata
|
||||||
|
|
||||||
// Parameter represents an http request parameter
|
// Parameter represents an http request parameter
|
||||||
// that can be of type URL, GET, or FORM (multipart, json, urlencoded)
|
// that can be of type URL, GET, or FORM (multipart, json, urlencoded)
|
|
@ -1,31 +0,0 @@
|
||||||
package middleware
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.xdrm.io/go/aicra/driver"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CreateRegistry creates an empty registry
|
|
||||||
func CreateRegistry() Registry {
|
|
||||||
return make(Registry)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add adds a new middleware for a path
|
|
||||||
func (reg Registry) Add(_path string, _element driver.Middleware) {
|
|
||||||
reg[_path] = _element
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run executes all middlewares (default browse order)
|
|
||||||
func (reg Registry) Run(req http.Request) []string {
|
|
||||||
|
|
||||||
/* (1) Initialise scope */
|
|
||||||
scope := make([]string, 0)
|
|
||||||
|
|
||||||
/* (2) Execute each middleware */
|
|
||||||
for _, mw := range reg {
|
|
||||||
mw.Inspect(req, &scope)
|
|
||||||
}
|
|
||||||
|
|
||||||
return scope
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
package middleware
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.xdrm.io/go/aicra/driver"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Scope represents a list of scope processed by middlewares
|
|
||||||
// and used by the router to block/allow some uris
|
|
||||||
// it is also passed to controllers
|
|
||||||
//
|
|
||||||
// DISCLAIMER: it is used to help developers but for compatibility
|
|
||||||
// purposes, the type is always used as its definition ([]string)
|
|
||||||
type Scope []string
|
|
||||||
|
|
||||||
// Registry represents a registry containing all registered
|
|
||||||
// middlewares to be processed before routing any request
|
|
||||||
// The map is <name> => <middleware>
|
|
||||||
type Registry map[string]driver.Middleware
|
|
301
server.go
301
server.go
|
@ -1,106 +1,55 @@
|
||||||
package aicra
|
package aicra
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.xdrm.io/go/aicra/api"
|
"git.xdrm.io/go/aicra/api"
|
||||||
"git.xdrm.io/go/aicra/driver"
|
|
||||||
e "git.xdrm.io/go/aicra/err"
|
|
||||||
"git.xdrm.io/go/aicra/internal/apidef"
|
|
||||||
"git.xdrm.io/go/aicra/internal/checker"
|
|
||||||
"git.xdrm.io/go/aicra/internal/config"
|
"git.xdrm.io/go/aicra/internal/config"
|
||||||
apirequest "git.xdrm.io/go/aicra/internal/request"
|
"git.xdrm.io/go/aicra/internal/reqdata"
|
||||||
"git.xdrm.io/go/aicra/middleware"
|
checker "git.xdrm.io/go/aicra/typecheck"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Server represents an AICRA instance featuring:
|
// Server represents an AICRA instance featuring: type checkers, services
|
||||||
// * its type checkers
|
|
||||||
// * its middlewares
|
|
||||||
// * its controllers (api config)
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
controller *apidef.Controller // controllers
|
services *config.Service
|
||||||
checker checker.Registry // type checker registry
|
checkers *checker.Set
|
||||||
middleware middleware.Registry // middlewares
|
handlers []*api.Handler
|
||||||
schema *config.Schema
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a framework instance from a configuration file
|
// New creates a framework instance from a configuration file
|
||||||
// _path is the json configuration path
|
func New(configPath string) (*Server, error) {
|
||||||
// _driver is used to load/run the controllers and middlewares (default: )
|
|
||||||
//
|
|
||||||
func New(_path string) (*Server, error) {
|
|
||||||
|
|
||||||
/* 1. Load config */
|
var err error
|
||||||
schema, err := config.Parse("./aicra.json")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 2. Init instance */
|
// 1. init instance
|
||||||
var i = &Server{
|
var i = &Server{
|
||||||
controller: nil,
|
services: nil,
|
||||||
schema: schema,
|
checkers: checker.New(),
|
||||||
|
handlers: make([]*api.Handler, 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 3. Load configuration */
|
// 2. open config file
|
||||||
i.controller, err = apidef.Parse(_path)
|
configFile, err := os.Open(configPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer configFile.Close()
|
||||||
|
|
||||||
|
// 3. load configuration
|
||||||
|
i.services, err = config.Parse(configFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 4. Load type registry */
|
/* 3. Load type registry */
|
||||||
i.checker = checker.CreateRegistry()
|
// TODO: add methods on the checker to set types programmatically
|
||||||
|
|
||||||
// add default types if set
|
/* 4. Load middleware registry */
|
||||||
if schema.Types.Default {
|
// TODO: add methods to set them manually
|
||||||
|
|
||||||
// driver is Plugin for defaults (even if generic for the controllers etc)
|
|
||||||
defaultTypesDriver := new(driver.Plugin)
|
|
||||||
files, err := filepath.Glob(filepath.Join(schema.Root, ".build/DEFAULT_TYPES/*.so"))
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("cannot load default types")
|
|
||||||
}
|
|
||||||
for _, path := range files {
|
|
||||||
|
|
||||||
name := strings.TrimSuffix(filepath.Base(path), ".so")
|
|
||||||
|
|
||||||
mwFunc, err := defaultTypesDriver.LoadChecker(path)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("cannot load default type checker '%s' | %s", name, err)
|
|
||||||
}
|
|
||||||
i.checker.Add(name, mwFunc)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add custom types
|
|
||||||
for name, path := range schema.Types.Map {
|
|
||||||
|
|
||||||
fullpath := schema.Driver.Build(schema.Root, schema.Types.Folder, path)
|
|
||||||
mwFunc, err := schema.Driver.LoadChecker(fullpath)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("cannot load type checker '%s' | %s", name, err)
|
|
||||||
}
|
|
||||||
i.checker.Add(path, mwFunc)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 5. Load middleware registry */
|
|
||||||
i.middleware = middleware.CreateRegistry()
|
|
||||||
for name, path := range schema.Middlewares.Map {
|
|
||||||
|
|
||||||
fullpath := schema.Driver.Build(schema.Root, schema.Middlewares.Folder, path)
|
|
||||||
mwFunc, err := schema.Driver.LoadMiddleware(fullpath)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("cannot load middleware '%s' | %s", name, err)
|
|
||||||
}
|
|
||||||
i.middleware.Add(path, mwFunc)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return i, nil
|
return i, nil
|
||||||
|
|
||||||
|
@ -111,179 +60,91 @@ func (s *Server) ServeHTTP(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
defer req.Body.Close()
|
defer req.Body.Close()
|
||||||
|
|
||||||
/* (1) Build request */
|
// 1. build API request from HTTP request
|
||||||
apiRequest, err := apirequest.New(req)
|
apiRequest, err := api.NewRequest(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (2) Launch middlewares to build the scope */
|
// 2. find a matching service for this path in the config
|
||||||
scope := s.middleware.Run(*req)
|
serviceDef, pathIndex := s.services.Browse(apiRequest.URI)
|
||||||
|
if serviceDef == nil {
|
||||||
/* (3) Find a matching controller */
|
|
||||||
controller := s.matchController(apiRequest)
|
|
||||||
if controller == nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
servicePath := strings.Join(apiRequest.URI[:pathIndex], "/")
|
||||||
|
|
||||||
/* (4) Check if matching method exists */
|
// 3. check if matching method exists in config */
|
||||||
var method = controller.Method(req.Method)
|
var method = serviceDef.Method(req.Method)
|
||||||
|
|
||||||
if method == nil {
|
if method == nil {
|
||||||
httpError(res, e.UnknownMethod)
|
httpError(res, api.ErrorUnknownMethod())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (5) Check scope permissions */
|
// 4. parse every input data from the request
|
||||||
if !method.CheckScope(scope) {
|
store := reqdata.New(req)
|
||||||
httpError(res, e.Permission)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (4) Check parameters
|
/* (4) Check parameters
|
||||||
---------------------------------------------------------*/
|
---------------------------------------------------------*/
|
||||||
parameters, paramError := s.extractParameters(apiRequest, method.Parameters)
|
parameters, paramError := s.extractParameters(store, method.Parameters)
|
||||||
|
|
||||||
// Fail if argument check failed
|
// Fail if argument check failed
|
||||||
if paramError.Code != e.Success.Code {
|
if paramError.Code != api.ErrorSuccess().Code {
|
||||||
httpError(res, paramError)
|
httpError(res, paramError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (5) Load controller
|
apiRequest.Param = parameters
|
||||||
---------------------------------------------------------*/
|
|
||||||
// get paths
|
|
||||||
ctlBuildPath := strings.Join(apiRequest.Path, "/")
|
|
||||||
ctlBuildPath = s.schema.Driver.Build(s.schema.Root, s.schema.Controllers.Folder, ctlBuildPath)
|
|
||||||
|
|
||||||
// get controller
|
/* (5) Search a matching handler
|
||||||
ctlObject, err := s.schema.Driver.LoadController(ctlBuildPath)
|
---------------------------------------------------------*/
|
||||||
httpMethod := strings.ToUpper(req.Method)
|
var serviceHandler *api.Handler
|
||||||
if err != nil {
|
var serviceFound bool
|
||||||
httpErr := e.UncallableController
|
|
||||||
httpErr.Put(err)
|
for _, handler := range s.handlers {
|
||||||
httpError(res, httpErr)
|
if handler.GetPath() == servicePath {
|
||||||
log.Printf("err( %s )\n", err)
|
serviceFound = true
|
||||||
|
if handler.GetMethod() == req.Method {
|
||||||
|
serviceHandler = handler
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fail if found no handler
|
||||||
|
if serviceHandler == nil {
|
||||||
|
if serviceFound {
|
||||||
|
httpError(res, api.ErrorUnknownMethod())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
httpError(res, api.ErrorUnknownService())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var ctlMethod func(api.Arguments) api.Response
|
/* (6) Execute handler and return response
|
||||||
// select method
|
|
||||||
switch httpMethod {
|
|
||||||
case "GET":
|
|
||||||
ctlMethod = ctlObject.Get
|
|
||||||
case "POST":
|
|
||||||
ctlMethod = ctlObject.Post
|
|
||||||
case "PUT":
|
|
||||||
ctlMethod = ctlObject.Put
|
|
||||||
case "DELETE":
|
|
||||||
ctlMethod = ctlObject.Delete
|
|
||||||
default:
|
|
||||||
httpError(res, e.UnknownMethod)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (6) Execute and get response
|
|
||||||
---------------------------------------------------------*/
|
---------------------------------------------------------*/
|
||||||
/* (1) Give HTTP METHOD */
|
// 1. execute
|
||||||
parameters["_HTTP_METHOD_"] = httpMethod
|
apiResponse := api.NewResponse()
|
||||||
|
serviceHandler.Handle(*apiRequest, apiResponse)
|
||||||
|
|
||||||
/* (2) Give Authorization header into controller */
|
// 2. apply headers
|
||||||
parameters["_AUTHORIZATION_"] = req.Header.Get("Authorization")
|
for key, values := range apiResponse.Headers {
|
||||||
|
for _, value := range values {
|
||||||
/* (3) Give Scope into controller */
|
res.Header().Add(key, value)
|
||||||
parameters["_SCOPE_"] = scope
|
|
||||||
|
|
||||||
/* (4) Execute */
|
|
||||||
response := ctlMethod(parameters)
|
|
||||||
|
|
||||||
/* (5) Extract http headers */
|
|
||||||
for k, v := range response.Dump() {
|
|
||||||
if k == "_REDIRECT_" {
|
|
||||||
if newLocation, ok := v.(string); ok {
|
|
||||||
httpRedirect(res, newLocation)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (5) Build JSON response */
|
// 3. build JSON apiResponse
|
||||||
httpPrint(res, response)
|
httpPrint(res, apiResponse)
|
||||||
return
|
return
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// extractParameters extracts parameters for the request and checks
|
// HandleFunc sets a new handler for an HTTP method to a path
|
||||||
// every single one according to configuration options
|
func (s *Server) HandleFunc(httpMethod, path string, handlerFunc api.HandlerFunc) {
|
||||||
func (s *Server) extractParameters(req *apirequest.Request, methodParam map[string]*apidef.Parameter) (map[string]interface{}, e.Error) {
|
handler := api.NewHandler(httpMethod, path, handlerFunc)
|
||||||
|
s.handlers = append(s.handlers, handler)
|
||||||
// init vars
|
}
|
||||||
err := e.Success
|
|
||||||
parameters := make(map[string]interface{})
|
// Handle sets a new handler
|
||||||
|
func (s *Server) Handle(handler *api.Handler) {
|
||||||
// for each param of the config
|
s.handlers = append(s.handlers, handler)
|
||||||
for name, param := range methodParam {
|
|
||||||
|
|
||||||
/* (1) Extract value */
|
|
||||||
p, isset := req.Data.Set[name]
|
|
||||||
|
|
||||||
/* (2) Required & missing */
|
|
||||||
if !isset && !param.Optional {
|
|
||||||
err = e.MissingParam
|
|
||||||
err.Put(name)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (3) Optional & missing: set default value */
|
|
||||||
if !isset {
|
|
||||||
p = &apirequest.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 {
|
|
||||||
err = e.InvalidParam
|
|
||||||
err.Put(param.Rename)
|
|
||||||
err.Put("FILE")
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (6) Do not check if file */
|
|
||||||
if gotFile {
|
|
||||||
parameters[param.Rename] = p.Value
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (7) Check type */
|
|
||||||
if s.checker.Run(param.Type, p.Value) != nil {
|
|
||||||
|
|
||||||
err = e.InvalidParam
|
|
||||||
err.Put(param.Rename)
|
|
||||||
err.Put(param.Type)
|
|
||||||
err.Put(p.Value)
|
|
||||||
break
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
parameters[param.Rename] = p.Value
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return parameters, err
|
|
||||||
}
|
}
|
||||||
|
|
120
util.go
120
util.go
|
@ -4,59 +4,111 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"git.xdrm.io/go/aicra/api"
|
"git.xdrm.io/go/aicra/api"
|
||||||
"git.xdrm.io/go/aicra/err"
|
"git.xdrm.io/go/aicra/internal/config"
|
||||||
"git.xdrm.io/go/aicra/internal/apidef"
|
"git.xdrm.io/go/aicra/internal/reqdata"
|
||||||
apireq "git.xdrm.io/go/aicra/internal/request"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Server) matchController(req *apireq.Request) *apidef.Controller {
|
func (s *Server) findServiceDef(req *api.Request) (serviceDef *config.Service, servicePath string) {
|
||||||
|
|
||||||
/* (1) Try to browse by URI */
|
// 1. try to find definition
|
||||||
pathi, ctl := s.controller.Browse(req.URI)
|
serviceDef, pathi := s.services.Browse(req.URI)
|
||||||
|
|
||||||
/* (2) Set controller uri */
|
// 2. set service uri
|
||||||
req.Path = make([]string, 0, pathi)
|
servicePath = strings.Join(req.URI[:pathi], "/")
|
||||||
req.Path = append(req.Path, req.URI[:pathi]...)
|
|
||||||
|
|
||||||
/* (3) Extract & store URI params */
|
|
||||||
req.Data.SetURI(req.URI[pathi:])
|
|
||||||
|
|
||||||
/* (4) Return controller */
|
|
||||||
return ctl
|
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirects to another location (http protocol)
|
// extractParameters extracts parameters for the request and checks
|
||||||
func httpRedirect(r http.ResponseWriter, loc string) {
|
// every single one according to configuration options
|
||||||
r.Header().Add("Location", loc)
|
func (s *Server) extractParameters(store *reqdata.Store, methodParam map[string]*config.Parameter) (map[string]interface{}, api.Error) {
|
||||||
r.WriteHeader(308) // permanent redirect
|
|
||||||
|
// init vars
|
||||||
|
apiError := api.ErrorSuccess()
|
||||||
|
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) Required & missing */
|
||||||
|
if !isset && !param.Optional {
|
||||||
|
apiError = api.ErrorMissingParam()
|
||||||
|
apiError.Put(name)
|
||||||
|
return nil, apiError
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (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 {
|
||||||
|
apiError = api.ErrorInvalidParam()
|
||||||
|
apiError.Put(param.Rename)
|
||||||
|
apiError.Put("FILE")
|
||||||
|
return nil, apiError
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (6) Do not check if file */
|
||||||
|
if gotFile {
|
||||||
|
parameters[param.Rename] = p.Value
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (7) Check type */
|
||||||
|
if s.checkers.Run(param.Type, p.Value) != nil {
|
||||||
|
|
||||||
|
apiError = api.ErrorInvalidParam()
|
||||||
|
apiError.Put(param.Rename)
|
||||||
|
apiError.Put(param.Type)
|
||||||
|
apiError.Put(p.Value)
|
||||||
|
break
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
parameters[param.Rename] = p.Value
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return parameters, apiError
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prints an HTTP response
|
// Prints an HTTP response
|
||||||
func httpPrint(r http.ResponseWriter, res api.Response) {
|
func httpPrint(r http.ResponseWriter, res *api.Response) {
|
||||||
// get response data
|
|
||||||
formattedResponse := res.Dump()
|
|
||||||
|
|
||||||
// add error fields
|
|
||||||
formattedResponse["error"] = res.Err.Code
|
|
||||||
formattedResponse["reason"] = res.Err.Reason
|
|
||||||
|
|
||||||
// add arguments if any
|
|
||||||
if res.Err.Arguments != nil && len(res.Err.Arguments) > 0 {
|
|
||||||
formattedResponse["args"] = res.Err.Arguments
|
|
||||||
}
|
|
||||||
|
|
||||||
// write this json
|
// write this json
|
||||||
jsonResponse, _ := json.Marshal(formattedResponse)
|
jsonResponse, _ := json.Marshal(res)
|
||||||
r.Header().Add("Content-Type", "application/json")
|
r.Header().Add("Content-Type", "application/json")
|
||||||
r.Write(jsonResponse)
|
r.Write(jsonResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prints an error as HTTP response
|
// Prints an error as HTTP response
|
||||||
func httpError(r http.ResponseWriter, e err.Error) {
|
func httpError(r http.ResponseWriter, e api.Error) {
|
||||||
JSON, _ := e.MarshalJSON()
|
JSON, _ := json.Marshal(e)
|
||||||
r.Header().Add("Content-Type", "application/json")
|
r.Header().Add("Content-Type", "application/json")
|
||||||
r.Write(JSON)
|
r.Write(JSON)
|
||||||
log.Printf("[http.fail] %s\n", e.Reason)
|
log.Printf("[http.fail] %s\n", e.Reason)
|
||||||
|
|
Loading…
Reference in New Issue