ref 0: internal.apidef becomes internal.config + refactoe
This commit is contained in:
parent
a63e227538
commit
8109f57d15
|
@ -1,232 +0,0 @@
|
||||||
package apidef
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Parse builds a representation of the configuration
|
|
||||||
// The struct definition checks for most format errors
|
|
||||||
//
|
|
||||||
// path<string> The path to the configuration
|
|
||||||
//
|
|
||||||
// @return<controller> The parsed configuration root controller
|
|
||||||
// @return<err> The error if occurred
|
|
||||||
//
|
|
||||||
func Parse(path string) (*Controller, error) {
|
|
||||||
|
|
||||||
/* (1) Extract data
|
|
||||||
---------------------------------------------------------*/
|
|
||||||
/* (1) Open file */
|
|
||||||
file, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
/* (2) Init receiver dataset */
|
|
||||||
receiver := &Controller{}
|
|
||||||
|
|
||||||
/* (3) Decode json */
|
|
||||||
decoder := json.NewDecoder(file)
|
|
||||||
err = decoder.Decode(receiver)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (4) Format result */
|
|
||||||
err = receiver.format("/")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return receiver, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Method returns a controller's method if exists
|
|
||||||
//
|
|
||||||
// @method<string> The wanted method (case insensitive)
|
|
||||||
//
|
|
||||||
// @return<*Method> The requested method
|
|
||||||
// NIL if not found
|
|
||||||
//
|
|
||||||
func (c Controller) Method(method string) *Method {
|
|
||||||
method = strings.ToUpper(method)
|
|
||||||
|
|
||||||
switch method {
|
|
||||||
|
|
||||||
case "GET":
|
|
||||||
return c.GET
|
|
||||||
case "POST":
|
|
||||||
return c.POST
|
|
||||||
case "PUT":
|
|
||||||
return c.PUT
|
|
||||||
case "DELETE":
|
|
||||||
return c.DELETE
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Browse tries to browse the controller childtree and
|
|
||||||
// returns the farthest matching child
|
|
||||||
//
|
|
||||||
// @path the path to browse
|
|
||||||
//
|
|
||||||
// @return<int> The index in 'path' used to find the controller
|
|
||||||
// @return<*Controller> The farthest match
|
|
||||||
func (c *Controller) Browse(path []string) (int, *Controller) {
|
|
||||||
|
|
||||||
/* (1) initialise cursors */
|
|
||||||
current := c
|
|
||||||
i := 0 // index in path
|
|
||||||
|
|
||||||
/* (2) Browse while there is uri parts */
|
|
||||||
for i < len(path) {
|
|
||||||
|
|
||||||
// 1. Try to get child for this name
|
|
||||||
child, exists := current.Children[path[i]]
|
|
||||||
|
|
||||||
// 2. Stop if no matching child
|
|
||||||
if !exists {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Increment cursors
|
|
||||||
current = child
|
|
||||||
i++
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (3) Return matches */
|
|
||||||
return i, current
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// format checks for format errors and missing required fields
|
|
||||||
// it also sets default values to optional fields
|
|
||||||
func (c *Controller) format(controllerName string) error {
|
|
||||||
|
|
||||||
/* (1) Check each method
|
|
||||||
---------------------------------------------------------*/
|
|
||||||
methods := []struct {
|
|
||||||
Name string
|
|
||||||
Ptr *Method
|
|
||||||
}{
|
|
||||||
{"GET", c.GET},
|
|
||||||
{"POST", c.POST},
|
|
||||||
{"PUT", c.PUT},
|
|
||||||
{"DELETE", c.DELETE},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, method := range methods {
|
|
||||||
|
|
||||||
/* (1) ignore non-defined method */
|
|
||||||
if method.Ptr == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (2) Fail on missing description */
|
|
||||||
if len(method.Ptr.Description) < 1 {
|
|
||||||
return fmt.Errorf("Missing %s.%s description", controllerName, method.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (3) stop if no parameter */
|
|
||||||
if method.Ptr.Parameters == nil || len(method.Ptr.Parameters) < 1 {
|
|
||||||
method.Ptr.Parameters = make(map[string]*Parameter, 0)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
/* check parameters */
|
|
||||||
for pName, pData := range method.Ptr.Parameters {
|
|
||||||
|
|
||||||
// check name
|
|
||||||
if strings.Trim(pName, "_") != pName {
|
|
||||||
return fmt.Errorf("Invalid name '%s' must not begin/end with '_'", pName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(pData.Rename) < 1 {
|
|
||||||
pData.Rename = pName
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (5) Check for name/rename conflict */
|
|
||||||
for paramName, param := range method.Ptr.Parameters {
|
|
||||||
|
|
||||||
// ignore self
|
|
||||||
if pName == paramName {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. Same rename field
|
|
||||||
if pData.Rename == param.Rename {
|
|
||||||
return fmt.Errorf("Rename conflict for %s.%s parameter '%s'", controllerName, method.Name, pData.Rename)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Not-renamed field matches a renamed field
|
|
||||||
if pName == param.Rename {
|
|
||||||
return fmt.Errorf("Name conflict for %s.%s parameter '%s'", controllerName, method.Name, pName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Renamed field matches name
|
|
||||||
if pData.Rename == paramName {
|
|
||||||
return fmt.Errorf("Name conflict for %s.%s parameter '%s'", controllerName, method.Name, pName)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (6) Manage invalid type */
|
|
||||||
if len(pData.Type) < 1 {
|
|
||||||
return fmt.Errorf("Invalid type for %s.%s parameter '%s'", controllerName, method.Name, pName)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (7) Fail on missing description */
|
|
||||||
if len(pData.Description) < 1 {
|
|
||||||
return fmt.Errorf("Missing description for %s.%s parameter '%s'", controllerName, method.Name, pName)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (8) Fail on missing type */
|
|
||||||
if len(pData.Type) < 1 {
|
|
||||||
return fmt.Errorf("Missing type for %s.%s parameter '%s'", controllerName, method.Name, pName)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (9) Set optional + type */
|
|
||||||
if pData.Type[0] == '?' {
|
|
||||||
pData.Optional = true
|
|
||||||
pData.Type = pData.Type[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (2) Check child controllers
|
|
||||||
---------------------------------------------------------*/
|
|
||||||
/* (1) Stop if no child */
|
|
||||||
if c.Children == nil || len(c.Children) < 1 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (2) For each controller */
|
|
||||||
for ctlName, ctl := range c.Children {
|
|
||||||
|
|
||||||
/* (3) Invalid name */
|
|
||||||
if strings.ContainsAny(ctlName, "/-") {
|
|
||||||
return fmt.Errorf("Controller '%s' must not contain any slash '/' nor '-' symbols", ctlName)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (4) Check recursively */
|
|
||||||
err := ctl.format(ctlName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
package apidef
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.xdrm.io/go/aicra/middleware"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CheckScope returns whether a given scope matches the
|
|
||||||
// method configuration
|
|
||||||
//
|
|
||||||
// format is: [ [a,b], [c], [d,e] ]
|
|
||||||
// > level 1 is OR
|
|
||||||
// > level 2 is AND
|
|
||||||
func (m *Method) CheckScope(scope middleware.Scope) bool {
|
|
||||||
|
|
||||||
for _, OR := range m.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 @perm is present in a given @scope
|
|
||||||
func scopeHasPermission(perm string, scope []string) bool {
|
|
||||||
for _, s := range scope {
|
|
||||||
if perm == s {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
package apidef
|
|
||||||
|
|
||||||
/* (1) Configuration
|
|
||||||
---------------------------------------------------------*/
|
|
||||||
// Parameter represents a parameter definition (from api.json)
|
|
||||||
type Parameter struct {
|
|
||||||
Description string `json:"info"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Rename string `json:"name,omitempty"`
|
|
||||||
Optional bool
|
|
||||||
Default *interface{} `json:"default"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Method represents a method definition (from api.json)
|
|
||||||
type Method struct {
|
|
||||||
Description string `json:"info"`
|
|
||||||
Permission [][]string `json:"scope"`
|
|
||||||
Parameters map[string]*Parameter `json:"in"`
|
|
||||||
Download *bool `json:"download"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Controller represents a controller definition (from api.json)
|
|
||||||
type Controller struct {
|
|
||||||
GET *Method `json:"GET"`
|
|
||||||
POST *Method `json:"POST"`
|
|
||||||
PUT *Method `json:"PUT"`
|
|
||||||
DELETE *Method `json:"DELETE"`
|
|
||||||
|
|
||||||
Children map[string]*Controller `json:"/"`
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"git.xdrm.io/go/aicra/driver"
|
|
||||||
)
|
|
||||||
|
|
||||||
// InferFromFolder fills the 'Map' by browsing recursively the
|
|
||||||
// 'Folder' field
|
|
||||||
func (b *builder) InferFromFolder(_root string, _driver driver.Driver) {
|
|
||||||
|
|
||||||
// init map
|
|
||||||
if b.Map == nil {
|
|
||||||
b.Map = make(map[string]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. ignore if no Folder
|
|
||||||
if len(b.Folder) < 1 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. If relative Folder, join to root
|
|
||||||
rootpath := filepath.Join(_root, b.Folder)
|
|
||||||
|
|
||||||
// 3. Walk
|
|
||||||
filepath.Walk(rootpath, func(path string, info os.FileInfo, err error) error {
|
|
||||||
|
|
||||||
// ignore dir
|
|
||||||
if err != nil || info.IsDir() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// format path
|
|
||||||
path, err = filepath.Rel(rootpath, path)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// extract universal path from the driver
|
|
||||||
upath := _driver.Path(_root, b.Folder, path)
|
|
||||||
|
|
||||||
// format name
|
|
||||||
name := upath
|
|
||||||
if name == "/" {
|
|
||||||
name = ""
|
|
||||||
}
|
|
||||||
name = fmt.Sprintf("%s", name)
|
|
||||||
|
|
||||||
// add to map
|
|
||||||
b.Map[name] = upath
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
package config
|
|
||||||
|
|
||||||
// Default contains the default values when omitted in json
|
|
||||||
var Default = Schema{
|
|
||||||
Root: ".",
|
|
||||||
Host: "0.0.0.0",
|
|
||||||
Port: 80,
|
|
||||||
DriverName: "",
|
|
||||||
Types: &builder{
|
|
||||||
Default: true,
|
|
||||||
Folder: "type",
|
|
||||||
},
|
|
||||||
Controllers: &builder{
|
|
||||||
Default: false,
|
|
||||||
Folder: "controller",
|
|
||||||
},
|
|
||||||
Middlewares: &builder{
|
|
||||||
Default: false,
|
|
||||||
Folder: "middleware",
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -0,0 +1,118 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.xdrm.io/go/aicra/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
// checkAndFormat checks for errors and missing fields and sets default values for optional fields.
|
||||||
|
func (methodDef *Method) checkAndFormat(servicePath string, httpMethod string) error {
|
||||||
|
|
||||||
|
// 1. fail on missing description
|
||||||
|
if len(methodDef.Description) < 1 {
|
||||||
|
return fmt.Errorf("missing %s.%s description", servicePath, httpMethod)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. stop if no parameter
|
||||||
|
if methodDef.Parameters == nil || len(methodDef.Parameters) < 1 {
|
||||||
|
methodDef.Parameters = make(map[string]*Parameter, 0)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. for each parameter
|
||||||
|
for pName, pData := range methodDef.Parameters {
|
||||||
|
|
||||||
|
// check name
|
||||||
|
if strings.Trim(pName, "_") != pName {
|
||||||
|
return fmt.Errorf("invalid name '%s' must not begin/end with '_'", pName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pData.Rename) < 1 {
|
||||||
|
pData.Rename = pName
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Check for name/rename conflict
|
||||||
|
for paramName, param := range methodDef.Parameters {
|
||||||
|
|
||||||
|
// ignore self
|
||||||
|
if pName == paramName {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Same rename field
|
||||||
|
if pData.Rename == param.Rename {
|
||||||
|
return fmt.Errorf("rename conflict for %s.%s parameter '%s'", servicePath, httpMethod, pData.Rename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Not-renamed field matches a renamed field
|
||||||
|
if pName == param.Rename {
|
||||||
|
return fmt.Errorf("name conflict for %s.%s parameter '%s'", servicePath, httpMethod, pName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Renamed field matches name
|
||||||
|
if pData.Rename == paramName {
|
||||||
|
return fmt.Errorf("name conflict for %s.%s parameter '%s'", servicePath, httpMethod, pName)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Manage invalid type
|
||||||
|
if len(pData.Type) < 1 {
|
||||||
|
return fmt.Errorf("invalid type for %s.%s parameter '%s'", servicePath, httpMethod, pName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. Fail on missing description
|
||||||
|
if len(pData.Description) < 1 {
|
||||||
|
return fmt.Errorf("missing description for %s.%s parameter '%s'", servicePath, httpMethod, pName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8. Fail on missing type
|
||||||
|
if len(pData.Type) < 1 {
|
||||||
|
return fmt.Errorf("missing type for %s.%s parameter '%s'", servicePath, httpMethod, pName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 9. Set optional + type
|
||||||
|
if pData.Type[0] == '?' {
|
||||||
|
pData.Optional = true
|
||||||
|
pData.Type = pData.Type[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
func scopeHasPermission(permission string, scope []string) bool {
|
||||||
|
for _, s := range scope {
|
||||||
|
if permission == s {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
|
@ -1,108 +0,0 @@
|
||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"git.xdrm.io/go/aicra/driver"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Parse extracts a Meta from a json config file (aicra.json)
|
|
||||||
func Parse(_path string) (*Schema, error) {
|
|
||||||
|
|
||||||
/* 1. ppen file */
|
|
||||||
file, err := os.Open(_path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("cannot open file")
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
/* 2. Init receiver dataset */
|
|
||||||
receiver := &Schema{}
|
|
||||||
|
|
||||||
/* 3. Decode json */
|
|
||||||
decoder := json.NewDecoder(file)
|
|
||||||
err = decoder.Decode(receiver)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 4. Error on invalid driver */
|
|
||||||
receiver.DriverName = strings.ToLower(receiver.DriverName)
|
|
||||||
switch receiver.DriverName {
|
|
||||||
case "generic":
|
|
||||||
receiver.Driver = &driver.Generic{}
|
|
||||||
case "plugin":
|
|
||||||
receiver.Driver = &driver.Plugin{}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return nil, errors.New("invalid driver; choose from 'generic', 'plugin'")
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 5. Fail on absolute folders */
|
|
||||||
if len(receiver.Types.Folder) > 0 && filepath.IsAbs(receiver.Types.Folder) {
|
|
||||||
return nil, errors.New("types folder must be relative to root")
|
|
||||||
}
|
|
||||||
if len(receiver.Controllers.Folder) > 0 && filepath.IsAbs(receiver.Controllers.Folder) {
|
|
||||||
return nil, errors.New("controllers folder must be relative to root")
|
|
||||||
}
|
|
||||||
if len(receiver.Middlewares.Folder) > 0 && filepath.IsAbs(receiver.Middlewares.Folder) {
|
|
||||||
return nil, errors.New("middlewares folder must be relative to root")
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 7. Format result (default values, etc) */
|
|
||||||
receiver.setDefaults()
|
|
||||||
|
|
||||||
return receiver, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// setDefaults sets defaults values and checks for missing data
|
|
||||||
func (m *Schema) setDefaults() {
|
|
||||||
|
|
||||||
// 1. extract absolute root folder
|
|
||||||
absroot, err := filepath.Abs(m.Root)
|
|
||||||
if err == nil {
|
|
||||||
m.Root = absroot
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. host
|
|
||||||
if len(m.Host) < 1 {
|
|
||||||
m.Host = Default.Host
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. port
|
|
||||||
if m.Port == 0 {
|
|
||||||
m.Port = Default.Port
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Use default builders if not set
|
|
||||||
if m.Types == nil {
|
|
||||||
m.Types = Default.Types
|
|
||||||
}
|
|
||||||
if m.Controllers == nil {
|
|
||||||
m.Controllers = Default.Controllers
|
|
||||||
}
|
|
||||||
if m.Middlewares == nil {
|
|
||||||
m.Middlewares = Default.Middlewares
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. Use default folders if not set
|
|
||||||
if m.Types.Folder == "" {
|
|
||||||
m.Types.Folder = Default.Types.Folder
|
|
||||||
}
|
|
||||||
if m.Controllers.Folder == "" {
|
|
||||||
m.Controllers.Folder = Default.Controllers.Folder
|
|
||||||
}
|
|
||||||
if m.Middlewares.Folder == "" {
|
|
||||||
m.Middlewares.Folder = Default.Middlewares.Folder
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6. Infer Maps from Folders
|
|
||||||
m.Types.InferFromFolder(m.Root, m.Driver)
|
|
||||||
m.Controllers.InferFromFolder(m.Root, m.Driver)
|
|
||||||
m.Middlewares.InferFromFolder(m.Root, m.Driver)
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parse builds a service from a json reader and checks for most format errors.
|
||||||
|
func Parse(r io.Reader) (*Service, error) {
|
||||||
|
|
||||||
|
receiver := &Service{}
|
||||||
|
|
||||||
|
err := json.NewDecoder(r).Decode(receiver)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = receiver.checkAndFormat("/")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return receiver, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method returns the actual method from the http method.
|
||||||
|
func (svc *Service) Method(httpMethod string) *Method {
|
||||||
|
httpMethod = strings.ToUpper(httpMethod)
|
||||||
|
|
||||||
|
switch httpMethod {
|
||||||
|
case http.MethodGet:
|
||||||
|
return svc.GET
|
||||||
|
case http.MethodPost:
|
||||||
|
return svc.POST
|
||||||
|
case http.MethodPut:
|
||||||
|
return svc.PUT
|
||||||
|
case http.MethodDelete:
|
||||||
|
return svc.DELETE
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Browse the service childtree and returns the farthest matching child. The `path` is a formatted URL split by '/'
|
||||||
|
func (svc *Service) Browse(path []string) (*Service, int) {
|
||||||
|
currentService := svc
|
||||||
|
var depth int
|
||||||
|
|
||||||
|
// for each URI depth
|
||||||
|
for depth = 0; depth < len(path); depth++ {
|
||||||
|
currentPath := path[depth]
|
||||||
|
|
||||||
|
child, exists := currentService.Children[currentPath]
|
||||||
|
if !exists {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
currentService = child
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentService, depth
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkAndFormat checks for errors and missing fields and sets default values for optional fields.
|
||||||
|
func (svc *Service) checkAndFormat(servicePath string) error {
|
||||||
|
|
||||||
|
// 1. check and format every method
|
||||||
|
for _, httpMethod := range availableHTTPMethods {
|
||||||
|
methodDef := svc.Method(httpMethod)
|
||||||
|
if methodDef == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err := methodDef.checkAndFormat(servicePath, httpMethod)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. stop if no child */
|
||||||
|
if svc.Children == nil || len(svc.Children) < 1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. for each controller */
|
||||||
|
for childService, ctl := range svc.Children {
|
||||||
|
|
||||||
|
// 3. invalid name */
|
||||||
|
if strings.ContainsAny(childService, "/-") {
|
||||||
|
return fmt.Errorf("Controller '%s' must not contain any slash '/' nor '-' symbols", childService)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. check recursively */
|
||||||
|
err := ctl.checkAndFormat(childService)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
|
@ -1,50 +1,32 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import "net/http"
|
||||||
"git.xdrm.io/go/aicra/driver"
|
|
||||||
)
|
|
||||||
|
|
||||||
type builder struct {
|
var availableHTTPMethods = []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete}
|
||||||
// Default tells whether or not to ignore the built-in components
|
|
||||||
Default bool `json:"default,ommitempty"`
|
|
||||||
|
|
||||||
// Folder is used to infer the 'Map' object
|
// Service represents a service definition (from api.json)
|
||||||
Folder string `json:"folder,ommitempty"`
|
type Service struct {
|
||||||
|
GET *Method `json:"GET"`
|
||||||
|
POST *Method `json:"POST"`
|
||||||
|
PUT *Method `json:"PUT"`
|
||||||
|
DELETE *Method `json:"DELETE"`
|
||||||
|
|
||||||
// Map defines the association path=>file
|
Children map[string]*Service `json:"/"`
|
||||||
Map map[string]string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Schema represents an AICRA configuration (not the API, the server, drivers, etc)
|
// Parameter represents a parameter definition (from api.json)
|
||||||
type Schema struct {
|
type Parameter struct {
|
||||||
// Root is root of the project structure default is "." (current directory)
|
Description string `json:"info"`
|
||||||
Root string `json:"root,ommitempty"`
|
Type string `json:"type"`
|
||||||
|
Rename string `json:"name,omitempty"`
|
||||||
// Host is the hostname to listen to (default is 0.0.0.0)
|
Optional bool
|
||||||
Host string `json:"host,ommitempty"`
|
Default *interface{} `json:"default"`
|
||||||
// Port is the port to listen to (default is 80)
|
}
|
||||||
Port uint16 `json:"port,ommitempty"`
|
|
||||||
|
// Method represents a method definition (from api.json)
|
||||||
// DriverName is the driver used to load the controllers and middlewares
|
type Method struct {
|
||||||
DriverName string `json:"driver"`
|
Description string `json:"info"`
|
||||||
Driver driver.Driver
|
Permission [][]string `json:"scope"`
|
||||||
|
Parameters map[string]*Parameter `json:"in"`
|
||||||
// Types defines :
|
Download *bool `json:"download"`
|
||||||
// - the type folder
|
|
||||||
// - whether to load the built-in types
|
|
||||||
//
|
|
||||||
// types are omitted if not set (no default)
|
|
||||||
Types *builder `json:"types,ommitempty"`
|
|
||||||
|
|
||||||
// Controllers defines :
|
|
||||||
// - the controller folder
|
|
||||||
//
|
|
||||||
// (default is .build/controller)
|
|
||||||
Controllers *builder `json:"controllers,ommitempty"`
|
|
||||||
|
|
||||||
// Middlewares defines :
|
|
||||||
// - the middleware folder
|
|
||||||
//
|
|
||||||
// (default is .build/middleware)
|
|
||||||
Middlewares *builder `json:"middlewares,ommitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue