From d4063387777454c35a2ac7e10438866602d7c0b2 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Mon, 1 Oct 2018 19:27:38 +0200 Subject: [PATCH] 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 --- driver/generic.go | 137 +++-------------------------- driver/generic.mockup.go | 166 ++++++++++++++++++++++++++++++++++++ driver/plugin.go | 140 +++++++++++++----------------- driver/types.go | 12 ++- internal/checker/public.go | 123 ++++---------------------- internal/checker/types.go | 15 ++-- internal/config/builder.go | 18 ++-- internal/config/defaults.go | 10 +-- internal/config/parser.go | 23 ++--- internal/config/types.go | 14 ++- internal/request/request.go | 11 --- middleware/public.go | 39 ++------- middleware/types.go | 12 +-- server.go | 98 +++++++++++++++------ 14 files changed, 380 insertions(+), 438 deletions(-) create mode 100644 driver/generic.mockup.go diff --git a/driver/generic.go b/driver/generic.go index c2728ab..8c60687 100644 --- a/driver/generic.go +++ b/driver/generic.go @@ -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) - } - - /* (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 -> 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 +// LoadController implements the Driver interface +func (d *Generic) LoadController(_path string) (Controller, error) { + return GenericController(_path), nil } -// 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") - } - - /* (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 -> 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 - +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 } diff --git a/driver/generic.mockup.go b/driver/generic.mockup.go new file mode 100644 index 0000000..7f86aa4 --- /dev/null +++ b/driver/generic.mockup.go @@ -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 -> 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 -> 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 -> 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 -> 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" + +} diff --git a/driver/plugin.go b/driver/plugin.go index baac40b..114b7ea 100644 --- a/driver/plugin.go +++ b/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 } diff --git a/driver/types.go b/driver/types.go index 593f067..4237fc6 100644 --- a/driver/types.go +++ b/driver/types.go @@ -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 diff --git a/internal/checker/public.go b/internal/checker/public.go index 124e209..4fc1004 100644 --- a/internal/checker/public.go +++ b/internal/checker/public.go @@ -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), - } - - /* (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 +func CreateRegistry() Registry { + return make(Registry) } -// 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 + + // check value + if t.Check(value) { + return nil + } + + return ErrDoesNotMatch + } + } - /* (2) Abort if no matching type */ - if T == nil { - return ErrNoMatchingType - } - - /* (3) Check */ - if !T.Check(value) { - return ErrDoesNotMatch - } - - return nil + return ErrNoMatchingType } diff --git a/internal/checker/types.go b/internal/checker/types.go index 2818180..e506851 100644 --- a/internal/checker/types.go +++ b/internal/checker/types.go @@ -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 diff --git a/internal/config/builder.go b/internal/config/builder.go index ae6ec09..ca11510 100644 --- a/internal/config/builder.go +++ b/internal/config/builder.go @@ -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 diff --git a/internal/config/defaults.go b/internal/config/defaults.go index a2b4913..8ee688c 100644 --- a/internal/config/defaults.go +++ b/internal/config/defaults.go @@ -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", }, } diff --git a/internal/config/parser.go b/internal/config/parser.go index 0478255..12a9acd 100644 --- a/internal/config/parser.go +++ b/internal/config/parser.go @@ -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) diff --git a/internal/config/types.go b/internal/config/types.go index 53df2e7..19818c5 100644 --- a/internal/config/types.go +++ b/internal/config/types.go @@ -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"` diff --git a/internal/request/request.go b/internal/request/request.go index 16f51d5..3bc3e61 100644 --- a/internal/request/request.go +++ b/internal/request/request.go @@ -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) - -} diff --git a/middleware/public.go b/middleware/public.go index fa0e92c..7596573 100644 --- a/middleware/public.go +++ b/middleware/public.go @@ -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 { +// CreateRegistry creates an empty registry +func CreateRegistry() Registry { + return make(Registry) +} - /* (1) Create registry */ - reg := &Registry{ - Middlewares: make([]*Wrapper, 0), - } - - /* (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 diff --git a/middleware/types.go b/middleware/types.go index 4985027..09eb417 100644 --- a/middleware/types.go +++ b/middleware/types.go @@ -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 => +type Registry map[string]driver.Middleware diff --git a/server.go b/server.go index b820885..5817683 100644 --- a/server.go +++ b/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: @@ -16,9 +18,9 @@ import ( // * its middlewares // * its controllers (api config) type Server struct { - controller *api.Controller // controllers - checker *checker.Registry // type checker registry - middleware *middleware.Registry // middlewares + controller *api.Controller // controllers + 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 {