implement driver for type checkers | move controller/middleware/checker definition inside the 'driver' package (all uses are exported from this place) | now driver.Driver returns 'Controller', 'Middleware', and 'Checker' in the interface | a lot a junk
This commit is contained in:
parent
37fe30ebc7
commit
d406338777
|
@ -1,14 +1,7 @@
|
|||
package driver
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
e "git.xdrm.io/go/aicra/err"
|
||||
"git.xdrm.io/go/aicra/response"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Generic tells the aicra instance to use the generic driver to load controller/middleware executables
|
||||
|
@ -40,125 +33,19 @@ func (d Generic) Build(_root, _folder, _path string) string {
|
|||
// Compiled returns whether the driver has to be build
|
||||
func (d Generic) Compiled() bool { return false }
|
||||
|
||||
// RunController implements the Driver interface
|
||||
func (d *Generic) RunController(_path []string, _method string) (func(response.Arguments) response.Response, e.Error) {
|
||||
|
||||
/* (1) Build controller path */
|
||||
path := strings.Join(_path, "-")
|
||||
if len(path) == 0 {
|
||||
path = fmt.Sprintf("./controller/ROOT")
|
||||
} else {
|
||||
path = fmt.Sprintf("./controller/%s", path)
|
||||
// LoadController implements the Driver interface
|
||||
func (d *Generic) LoadController(_path string) (Controller, error) {
|
||||
return GenericController(_path), nil
|
||||
}
|
||||
|
||||
/* (2) Format method */
|
||||
method := strings.ToUpper(_method)
|
||||
|
||||
return func(d response.Arguments) response.Response {
|
||||
|
||||
res := response.New()
|
||||
|
||||
/* (1) Prepare stdin data */
|
||||
d["_HTTP_METHOD_"] = method
|
||||
stdin, err := json.Marshal(d)
|
||||
if err != nil {
|
||||
res.Err = e.UncallableController
|
||||
return *res
|
||||
}
|
||||
|
||||
/* (2) Try to load command with <stdin> -> stdout */
|
||||
cmd := exec.Command(path, 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
|
||||
|
||||
}, e.Success
|
||||
}
|
||||
|
||||
// LoadMiddleware returns a new middleware function; it must be a
|
||||
// LoadMiddleware returns a new middleware; it must be a
|
||||
// valid and existing folder/filename file
|
||||
func (d *Generic) LoadMiddleware(_path string) (func(http.Request, *[]string), error) {
|
||||
|
||||
/* (1) Check plugin name */
|
||||
if len(_path) < 1 {
|
||||
return nil, fmt.Errorf("Middleware name must not be empty")
|
||||
func (d *Generic) LoadMiddleware(_path string) (Middleware, error) {
|
||||
return GenericMiddleware(_path), nil
|
||||
}
|
||||
|
||||
/* (2) Create method + error */
|
||||
return func(_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(_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)
|
||||
}
|
||||
|
||||
}, 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
|
||||
}
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
package driver
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
e "git.xdrm.io/go/aicra/err"
|
||||
"git.xdrm.io/go/aicra/response"
|
||||
"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 response.Arguments) response.Response {
|
||||
|
||||
res := response.New()
|
||||
|
||||
/* (1) Prepare stdin data */
|
||||
stdin, err := json.Marshal(d)
|
||||
if err != nil {
|
||||
res.Err = e.UncallableController
|
||||
return *res
|
||||
}
|
||||
|
||||
/* (2) Try to load command with <stdin> -> stdout */
|
||||
cmd := exec.Command(string(path), 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 response.Arguments) response.Response {
|
||||
return path.Get(d)
|
||||
}
|
||||
func (path GenericController) Put(d response.Arguments) response.Response {
|
||||
return path.Get(d)
|
||||
}
|
||||
func (path GenericController) Delete(d response.Arguments) response.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), _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), 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"
|
||||
|
||||
}
|
140
driver/plugin.go
140
driver/plugin.go
|
@ -2,12 +2,8 @@ package driver
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"git.xdrm.io/go/aicra/err"
|
||||
"git.xdrm.io/go/aicra/response"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"plugin"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Plugin tells the aicra instance to use the plugin driver to load controller/middleware executables
|
||||
|
@ -34,104 +30,88 @@ func (d Plugin) Path(_root, _folder, _src string) string {
|
|||
|
||||
// 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 }
|
||||
|
||||
// RunController implements the Driver interface
|
||||
func (d *Plugin) RunController(_path []string, _method string) (func(response.Arguments) response.Response, err.Error) {
|
||||
// LoadController returns a new Controller
|
||||
func (d *Plugin) LoadController(_path string) (Controller, error) {
|
||||
|
||||
/* (1) Build controller path */
|
||||
path := strings.Join(_path, "-")
|
||||
if len(path) == 0 {
|
||||
path = fmt.Sprintf(".build/controller/ROOT.so")
|
||||
} else {
|
||||
path = fmt.Sprintf(".build/controller/%s.so", path)
|
||||
}
|
||||
|
||||
/* (2) Format url */
|
||||
method := strings.ToLower(_method)
|
||||
|
||||
/* (2) Try to load plugin */
|
||||
p, err2 := plugin.Open(path)
|
||||
if err2 != nil {
|
||||
return nil, err.UncallableController
|
||||
}
|
||||
|
||||
/* (3) Try to extract exported field */
|
||||
m, err2 := p.Lookup("Export")
|
||||
if err2 != nil {
|
||||
return nil, err.UncallableController
|
||||
}
|
||||
|
||||
exported, ok := m.(func() Controller)
|
||||
if !ok {
|
||||
return nil, err.UncallableController
|
||||
}
|
||||
|
||||
/* (4) Controller */
|
||||
ctl := exported()
|
||||
|
||||
/* (4) Check signature */
|
||||
switch method {
|
||||
case "get":
|
||||
return ctl.Get, err.Success
|
||||
case "post":
|
||||
return ctl.Post, err.Success
|
||||
case "put":
|
||||
return ctl.Put, err.Success
|
||||
case "delete":
|
||||
return ctl.Delete, err.Success
|
||||
}
|
||||
fmt.Printf("method: %s\n", method)
|
||||
|
||||
return nil, err.UncallableMethod
|
||||
}
|
||||
|
||||
// LoadMiddleware returns a new middleware function; it must be a
|
||||
// valid and existing folder/filename file with the .so extension
|
||||
func (d *Plugin) LoadMiddleware(_path string) (func(http.Request, *[]string), error) {
|
||||
|
||||
// ignore non .so files
|
||||
if !strings.HasSuffix(_path, ".so") {
|
||||
return nil, fmt.Errorf("Invalid name")
|
||||
}
|
||||
|
||||
/* (1) Check plugin name */
|
||||
if len(_path) < 1 {
|
||||
return nil, fmt.Errorf("Middleware name must not be empty")
|
||||
}
|
||||
|
||||
/* (2) Check plugin extension */
|
||||
if !strings.HasSuffix(_path, ".so") {
|
||||
_path = fmt.Sprintf("%s.so", _path)
|
||||
}
|
||||
|
||||
/* (3) Try to load the plugin */
|
||||
/* 1. Try to load plugin */
|
||||
p, err := plugin.Open(_path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
/* (4) Extract exported fields */
|
||||
mw, err := p.Lookup("Export")
|
||||
/* 2. Try to extract exported field */
|
||||
m, err := p.Lookup("Export")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Missing method 'Inspect()'; %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
exported, ok := mw.(func() Middleware)
|
||||
exporter, ok := m.(func() Controller)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Inspect() is malformed")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
/* (5) Return Inspect method */
|
||||
return exported().Inspect, nil
|
||||
/* 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,7 +1,6 @@
|
|||
package driver
|
||||
|
||||
import (
|
||||
"git.xdrm.io/go/aicra/err"
|
||||
"git.xdrm.io/go/aicra/response"
|
||||
"net/http"
|
||||
)
|
||||
|
@ -14,8 +13,15 @@ type Driver interface {
|
|||
Build(string, string, string) string
|
||||
Compiled() bool
|
||||
|
||||
RunController(_path []string, _method string) (func(response.Arguments) response.Response, err.Error)
|
||||
LoadMiddleware(_path string) (func(http.Request, *[]string), error)
|
||||
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
|
||||
|
|
|
@ -2,104 +2,20 @@ package checker
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"plugin"
|
||||
"strings"
|
||||
"git.xdrm.io/go/aicra/driver"
|
||||
)
|
||||
|
||||
var ErrNoMatchingType = errors.New("no matching type")
|
||||
var ErrDoesNotMatch = errors.New("does not match")
|
||||
var ErrEmptyTypeName = errors.New("type name must not be empty")
|
||||
|
||||
// CreateRegistry creates an empty type registry
|
||||
func CreateRegistry(_folder string) *Registry {
|
||||
|
||||
/* (1) Create registry */
|
||||
reg := &Registry{
|
||||
Types: make([]Type, 0),
|
||||
func CreateRegistry() Registry {
|
||||
return make(Registry)
|
||||
}
|
||||
|
||||
/* (2) List types */
|
||||
files, err := ioutil.ReadDir(_folder)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
/* (3) Else try to load each given default */
|
||||
for _, file := range files {
|
||||
|
||||
// ignore non .so files
|
||||
if !strings.HasSuffix(file.Name(), ".so") {
|
||||
continue
|
||||
}
|
||||
|
||||
err := reg.add(file.Name())
|
||||
if err != nil {
|
||||
log.Printf("cannot load plugin '%s'", file.Name())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return reg
|
||||
}
|
||||
|
||||
// add adds a type to the registry; it must be a
|
||||
// valid and existing plugin name with or without the .so extension
|
||||
// it must be located in the relative directory ./types
|
||||
func (reg *Registry) add(pluginName string) error {
|
||||
|
||||
/* (1) Check plugin name */
|
||||
if len(pluginName) < 1 {
|
||||
return ErrEmptyTypeName
|
||||
}
|
||||
|
||||
/* (2) Check if valid plugin name */
|
||||
if strings.ContainsAny(pluginName, "/") {
|
||||
return fmt.Errorf("'%s' can only be a name, not a path", pluginName)
|
||||
}
|
||||
|
||||
/* (3) Check plugin extension */
|
||||
if !strings.HasSuffix(pluginName, ".so") {
|
||||
pluginName = fmt.Sprintf("%s.so", pluginName)
|
||||
}
|
||||
|
||||
/* (4) Try to load the plugin */
|
||||
p, err := plugin.Open(fmt.Sprintf(".build/type/%s", pluginName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
/* (5) Export wanted properties */
|
||||
matcher, err := p.Lookup("Match")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Missing method 'Match()'; %s", err)
|
||||
}
|
||||
|
||||
checker, err := p.Lookup("Check")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Missing method 'Check()'; %s", err)
|
||||
}
|
||||
|
||||
/* (6) Cast Match+Check */
|
||||
matcherCast, ok := matcher.(func(string) bool)
|
||||
if !ok {
|
||||
return fmt.Errorf("Match() is malformed")
|
||||
}
|
||||
|
||||
checkerCast, ok := checker.(func(interface{}) bool)
|
||||
if !ok {
|
||||
return fmt.Errorf("Check() is malformed")
|
||||
}
|
||||
|
||||
/* (7) Add type to registry */
|
||||
reg.Types = append(reg.Types, Type{
|
||||
Match: matcherCast,
|
||||
Check: checkerCast,
|
||||
})
|
||||
|
||||
return nil
|
||||
// Add adds a new checker for a path
|
||||
func (reg Registry) Add(_path string, _element driver.Checker) {
|
||||
reg[_path] = _element
|
||||
}
|
||||
|
||||
// Run finds a type checker from the registry matching the type @typeName
|
||||
|
@ -107,28 +23,23 @@ func (reg *Registry) add(pluginName string) error {
|
|||
// the @typeName name, error is returned by default.
|
||||
func (reg Registry) Run(typeName string, value interface{}) error {
|
||||
|
||||
var T *Type
|
||||
|
||||
/* (1) Iterate to find matching type (take first) */
|
||||
for _, t := range reg.Types {
|
||||
for _, t := range reg {
|
||||
|
||||
// stop if found
|
||||
if t.Match(typeName) {
|
||||
T = &t
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/* (2) Abort if no matching type */
|
||||
if T == nil {
|
||||
return ErrNoMatchingType
|
||||
}
|
||||
|
||||
/* (3) Check */
|
||||
if !T.Check(value) {
|
||||
return ErrDoesNotMatch
|
||||
}
|
||||
|
||||
// check value
|
||||
if t.Check(value) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ErrDoesNotMatch
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return ErrNoMatchingType
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
package checker
|
||||
|
||||
import (
|
||||
"git.xdrm.io/go/aicra/driver"
|
||||
)
|
||||
|
||||
// Matcher returns whether a type 'name' matches a type
|
||||
type Matcher func(name string) bool
|
||||
|
||||
|
@ -8,15 +12,6 @@ type Matcher func(name string) bool
|
|||
// to provide indulgent type check if needed
|
||||
type Checker func(value interface{}) bool
|
||||
|
||||
// Type contains all necessary methods
|
||||
// for a type provided by user/developer
|
||||
type Type struct {
|
||||
Match func(string) bool
|
||||
Check func(interface{}) bool
|
||||
}
|
||||
|
||||
// Registry represents a registry containing all available
|
||||
// Type-s to be used by the framework according to the configuration
|
||||
type Registry struct {
|
||||
Types []Type // registered Type-s
|
||||
}
|
||||
type Registry map[string]driver.Checker
|
||||
|
|
|
@ -7,19 +7,15 @@ import (
|
|||
"path/filepath"
|
||||
)
|
||||
|
||||
// format inits the map if not set
|
||||
func (b *builder) format() {
|
||||
|
||||
if b.Map == nil {
|
||||
b.Map = make(map[string]string)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 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
|
||||
|
@ -46,10 +42,10 @@ func (b *builder) InferFromFolder(_root string, _driver driver.Driver) {
|
|||
|
||||
// format name
|
||||
name := upath
|
||||
if name == "ROOT" {
|
||||
if name == "/" {
|
||||
name = ""
|
||||
}
|
||||
name = fmt.Sprintf("/%s", name)
|
||||
name = fmt.Sprintf("%s", name)
|
||||
|
||||
// add to map
|
||||
b.Map[name] = upath
|
||||
|
|
|
@ -2,22 +2,20 @@ package config
|
|||
|
||||
// Default contains the default values when ommited in json
|
||||
var Default = Schema{
|
||||
Root: ".",
|
||||
Host: "0.0.0.0",
|
||||
Port: 80,
|
||||
DriverName: "",
|
||||
Types: &builder{
|
||||
Default: true,
|
||||
Folder: "",
|
||||
Map: nil,
|
||||
Folder: "type",
|
||||
},
|
||||
Controllers: &builder{
|
||||
Default: false,
|
||||
Folder: "",
|
||||
Map: nil,
|
||||
Folder: "controller",
|
||||
},
|
||||
Middlewares: &builder{
|
||||
Default: false,
|
||||
Folder: "",
|
||||
Map: nil,
|
||||
Folder: "middleware",
|
||||
},
|
||||
}
|
||||
|
|
|
@ -41,12 +41,7 @@ func Parse(_path string) (*Schema, error) {
|
|||
return nil, errors.New("invalid driver; choose from 'generic', 'plugin'")
|
||||
}
|
||||
|
||||
/* 5. Fail if type map is set */
|
||||
if receiver.Types.Map != nil {
|
||||
return nil, errors.New("types must not feature the 'map'")
|
||||
}
|
||||
|
||||
/* 6. Fail on absolute folders */
|
||||
/* 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")
|
||||
}
|
||||
|
@ -83,7 +78,7 @@ func (m *Schema) setDefaults() {
|
|||
m.Port = Default.Port
|
||||
}
|
||||
|
||||
// 4. Empty builders
|
||||
// 4. Use default builders if not set
|
||||
if m.Types == nil {
|
||||
m.Types = Default.Types
|
||||
}
|
||||
|
@ -94,10 +89,16 @@ func (m *Schema) setDefaults() {
|
|||
m.Middlewares = Default.Middlewares
|
||||
}
|
||||
|
||||
// 5. Init map if not set
|
||||
m.Types.format()
|
||||
m.Controllers.format()
|
||||
m.Middlewares.format()
|
||||
// 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)
|
||||
|
|
|
@ -12,13 +12,13 @@ type builder struct {
|
|||
Folder string `json:"folder,ommitempty"`
|
||||
|
||||
// Map defines the association path=>file
|
||||
Map map[string]string `json:"map,ommitempty"`
|
||||
Map map[string]string
|
||||
}
|
||||
|
||||
// Schema represents an AICRA configuration (not the API, the server, drivers, etc)
|
||||
type Schema struct {
|
||||
// Root is root of the project structure
|
||||
Root string `json:"root"`
|
||||
// Root is root of the project structure default is "." (current directory)
|
||||
Root string `json:"root,ommitempty"`
|
||||
|
||||
// Host is the hostname to listen to (default is 0.0.0.0)
|
||||
Host string `json:"host,ommitempty"`
|
||||
|
@ -26,28 +26,24 @@ type Schema struct {
|
|||
Port uint16 `json:"port,ommitempty"`
|
||||
|
||||
// DriverName is the driver used to load the controllers and middlewares
|
||||
// (default is 'plugin')
|
||||
DriverName string `json:"driver"`
|
||||
Driver driver.Driver
|
||||
|
||||
// Types defines :
|
||||
// - the type folder
|
||||
// - each type by 'name => path'
|
||||
// - whether to load the built-in types
|
||||
//
|
||||
// types are ommited if not set (no default)
|
||||
Types *builder `json:"types,ommitempty"`
|
||||
|
||||
// Controllers defines :
|
||||
// - the controller folder (as a single string)
|
||||
// - each controller by 'name => path' (as a map)
|
||||
// - the controller folder
|
||||
//
|
||||
// (default is .build/controller)
|
||||
Controllers *builder `json:"controllers,ommitempty"`
|
||||
|
||||
// Middlewares defines :
|
||||
// - the middleware folder (as a single string)
|
||||
// - each middleware by 'name => path' (as a map)
|
||||
// - the middleware folder
|
||||
//
|
||||
// (default is .build/middleware)
|
||||
Middlewares *builder `json:"middlewares,ommitempty"`
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
package request
|
||||
|
||||
import (
|
||||
"git.xdrm.io/go/aicra/driver"
|
||||
"git.xdrm.io/go/aicra/err"
|
||||
"git.xdrm.io/go/aicra/response"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
@ -27,11 +24,3 @@ func FromHTTP(req *http.Request) (*Request, error) {
|
|||
|
||||
return inst, nil
|
||||
}
|
||||
|
||||
// RunController tries to load a controller from its uri
|
||||
// checks for its given method ('Get', 'Post', 'Put', or 'Delete')
|
||||
func (i *Request) RunController(_method string, _driver driver.Driver) (func(response.Arguments) response.Response, err.Error) {
|
||||
|
||||
return _driver.RunController(i.Path, _method)
|
||||
|
||||
}
|
||||
|
|
|
@ -2,38 +2,17 @@ package middleware
|
|||
|
||||
import (
|
||||
"git.xdrm.io/go/aicra/driver"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"path"
|
||||
)
|
||||
|
||||
// CreateRegistry creates an empty middleware registry
|
||||
func CreateRegistry(_driver driver.Driver, _folder string) *Registry {
|
||||
|
||||
/* (1) Create registry */
|
||||
reg := &Registry{
|
||||
Middlewares: make([]*Wrapper, 0),
|
||||
// CreateRegistry creates an empty registry
|
||||
func CreateRegistry() Registry {
|
||||
return make(Registry)
|
||||
}
|
||||
|
||||
/* (2) List middleware files */
|
||||
files, err := ioutil.ReadDir(_folder)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
/* (3) Else try to load each given default */
|
||||
for _, file := range files {
|
||||
|
||||
mwFunc, err := _driver.LoadMiddleware(path.Join(_folder, file.Name()))
|
||||
if err != nil {
|
||||
log.Printf("cannot load middleware '%s' | %s", file.Name(), err)
|
||||
}
|
||||
reg.Middlewares = append(reg.Middlewares, &Wrapper{Inspect: mwFunc})
|
||||
|
||||
}
|
||||
|
||||
return reg
|
||||
// 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)
|
||||
|
@ -43,8 +22,8 @@ func (reg Registry) Run(req http.Request) []string {
|
|||
scope := make([]string, 0)
|
||||
|
||||
/* (2) Execute each middleware */
|
||||
for _, m := range reg.Middlewares {
|
||||
m.Inspect(req, &scope)
|
||||
for _, mw := range reg {
|
||||
mw.Inspect(req, &scope)
|
||||
}
|
||||
|
||||
return scope
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"git.xdrm.io/go/aicra/driver"
|
||||
)
|
||||
|
||||
// Scope represents a list of scope processed by middlewares
|
||||
|
@ -12,13 +12,7 @@ import (
|
|||
// purposes, the type is always used as its definition ([]string)
|
||||
type Scope []string
|
||||
|
||||
// Wrapper is a struct that stores middleware Inspect() method
|
||||
type Wrapper struct {
|
||||
Inspect func(http.Request, *[]string)
|
||||
}
|
||||
|
||||
// Registry represents a registry containing all registered
|
||||
// middlewares to be processed before routing any request
|
||||
type Registry struct {
|
||||
Middlewares []*Wrapper
|
||||
}
|
||||
// The map is <name> => <middleware>
|
||||
type Registry map[string]driver.Middleware
|
||||
|
|
96
server.go
96
server.go
|
@ -7,8 +7,10 @@ import (
|
|||
"git.xdrm.io/go/aicra/internal/config"
|
||||
apirequest "git.xdrm.io/go/aicra/internal/request"
|
||||
"git.xdrm.io/go/aicra/middleware"
|
||||
"git.xdrm.io/go/aicra/response"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Server represents an AICRA instance featuring:
|
||||
|
@ -17,8 +19,8 @@ import (
|
|||
// * its controllers (api config)
|
||||
type Server struct {
|
||||
controller *api.Controller // controllers
|
||||
checker *checker.Registry // type checker registry
|
||||
middleware *middleware.Registry // middlewares
|
||||
checker checker.Registry // type checker registry
|
||||
middleware middleware.Registry // middlewares
|
||||
schema *config.Schema
|
||||
}
|
||||
|
||||
|
@ -34,32 +36,47 @@ func New(_path string) (*Server, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
/* 2. Default driver : Plugin */
|
||||
_folders := make([]string, 0, 2)
|
||||
_folders = append(_folders, ".build/type")
|
||||
if schema.Driver.Name() == "plugin" { // plugin
|
||||
_folders = append(_folders, ".build/middleware")
|
||||
} else { // generic
|
||||
_folders = append(_folders, "middleware")
|
||||
}
|
||||
|
||||
/* (1) Init instance */
|
||||
/* 2. Init instance */
|
||||
var i = &Server{
|
||||
controller: nil,
|
||||
schema: schema,
|
||||
}
|
||||
|
||||
/* (2) Load configuration */
|
||||
/* 3. Load configuration */
|
||||
i.controller, err = api.Parse(_path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
/* (3) Default type registry */
|
||||
i.checker = checker.CreateRegistry(_folders[0])
|
||||
/* 4. Load type registry */
|
||||
i.checker = checker.CreateRegistry()
|
||||
|
||||
/* (4) Default middleware registry */
|
||||
i.middleware = middleware.CreateRegistry(schema.Driver, _folders[1])
|
||||
// add default types if set
|
||||
|
||||
// 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
|
||||
|
||||
|
@ -109,25 +126,52 @@ func (s *Server) ServeHTTP(res http.ResponseWriter, req *http.Request) {
|
|||
|
||||
/* (5) Load controller
|
||||
---------------------------------------------------------*/
|
||||
controllerImplementation, callErr := apiRequest.RunController(req.Method, s.schema.Driver)
|
||||
if callErr.Code != e.Success.Code {
|
||||
httpError(res, callErr)
|
||||
log.Printf("[err] %s\n", err)
|
||||
// get paths
|
||||
ctlBuildPath := strings.Join(apiRequest.Path, "/")
|
||||
ctlBuildPath = s.schema.Driver.Build(s.schema.Root, s.schema.Controllers.Folder, ctlBuildPath)
|
||||
|
||||
// get controller
|
||||
ctlObject, err := s.schema.Driver.LoadController(ctlBuildPath)
|
||||
httpMethod := strings.ToUpper(req.Method)
|
||||
if err != nil {
|
||||
httpErr := e.UncallableController
|
||||
httpErr.BindArgument(err)
|
||||
httpError(res, httpErr)
|
||||
log.Printf("err( %s )\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
var ctlMethod func(response.Arguments) response.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 Authorization header into controller */
|
||||
/* (1) Give HTTP METHOD */
|
||||
parameters["_HTTP_METHOD_"] = httpMethod
|
||||
|
||||
/* (2) Give Authorization header into controller */
|
||||
parameters["_AUTHORIZATION_"] = req.Header.Get("Authorization")
|
||||
|
||||
/* (2) Give Scope into controller */
|
||||
/* (3) Give Scope into controller */
|
||||
parameters["_SCOPE_"] = scope
|
||||
|
||||
/* (3) Execute */
|
||||
response := controllerImplementation(parameters)
|
||||
/* (4) Execute */
|
||||
response := ctlMethod(parameters)
|
||||
|
||||
/* (4) Extract http headers */
|
||||
/* (5) Extract http headers */
|
||||
for k, v := range response.Dump() {
|
||||
if k == "_REDIRECT_" {
|
||||
if newLocation, ok := v.(string); ok {
|
||||
|
|
Loading…
Reference in New Issue