diff --git a/err/defaults.go b/err/defaults.go new file mode 100644 index 0000000..9ce8dc6 --- /dev/null +++ b/err/defaults.go @@ -0,0 +1,34 @@ +package err + +var ( + /* Base */ + Success = Error{0, "all right", nil} + Failure = Error{1, "it failed", nil} + Unknown = Error{-1, "", nil} + + NoMatchFound = Error{2, "no resource found", nil} + AlreadyExists = Error{3, "resource already exists", nil} + + Config = Error{4, "configuration error", nil} + + /* I/O */ + Upload = Error{100, "upload failed", nil} + Download = Error{101, "download failed", nil} + MissingDownloadHeaders = Error{102, "download headers are missing", nil} + MissingDownloadBody = Error{103, "download body is missing", nil} + + /* Controllers */ + UnknownController = Error{200, "unknown controller", nil} + UnknownMethod = Error{201, "unknown method", nil} + UncallableController = Error{202, "uncallable controller", nil} + UncallableMethod = Error{203, "uncallable method", nil} + + /* Permissions */ + Permission = Error{300, "permission error", nil} + Token = Error{301, "token error", nil} + + /* Check */ + MissingParam = Error{400, "missing parameter", nil} + InvalidParam = Error{401, "invalid parameter", nil} + InvalidDefaultParam = Error{402, "invalid default param", nil} +) diff --git a/err/interface.go b/err/interface.go new file mode 100644 index 0000000..6c4dfef --- /dev/null +++ b/err/interface.go @@ -0,0 +1,52 @@ +package err + +import ( + "encoding/json" + "fmt" +) + +type Error struct { + Code int + Reason string + Arguments []interface{} +} + +// BindArgument adds an argument to the error +// to be displayed back to API caller +func (e *Error) BindArgument(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) + +} + +// Implements json.Marshaler +func (e Error) MarshalJSON() ([]byte, error) { + + var json_arguments string + + /* (1) Marshal 'Arguments' if set */ + if e.Arguments != nil && len(e.Arguments) > 0 { + arg_representation, err := json.Marshal(e.Arguments) + if err == nil { + json_arguments = fmt.Sprintf(",\"arguments\":%s", arg_representation) + } + + } + + /* (2) Render JSON manually */ + return []byte(fmt.Sprintf("{\"error\":%d,\"reason\":\"%s\"%s}", e.Code, e.Reason, json_arguments)), nil + +} diff --git a/errors.go b/errors.go deleted file mode 100644 index 34a4e06..0000000 --- a/errors.go +++ /dev/null @@ -1,85 +0,0 @@ -package gfw - -import ( - "encoding/json" - "fmt" -) - -type Err struct { - Code int - Reason string - Arguments []interface{} -} - -var ( - /* Base */ - ErrSuccess = Err{0, "all right", nil} - ErrFailure = Err{1, "it failed", nil} - ErrUnknown = Err{-1, "", nil} - - ErrNoMatchFound = Err{2, "no resource found", nil} - ErrAlreadyExists = Err{3, "resource already exists", nil} - - ErrConfig = Err{4, "configuration error", nil} - - /* I/O */ - ErrUpload = Err{100, "upload failed", nil} - ErrDownload = Err{101, "download failed", nil} - ErrMissingDownloadHeaders = Err{102, "download headers are missing", nil} - ErrMissingDownloadBody = Err{103, "download body is missing", nil} - - /* Controllers */ - ErrUnknownController = Err{200, "unknown controller", nil} - ErrUnknownMethod = Err{201, "unknown method", nil} - ErrUncallableController = Err{202, "uncallable controller", nil} - ErrUncallableMethod = Err{203, "uncallable method", nil} - - /* Permissions */ - ErrPermission = Err{300, "permission error", nil} - ErrToken = Err{301, "token error", nil} - - /* Check */ - ErrMissingParam = Err{400, "missing parameter", nil} - ErrInvalidParam = Err{401, "invalid parameter", nil} - ErrInvalidDefaultParam = Err{402, "invalid default param", nil} -) - -// BindArgument adds an argument to the error -// to be displayed back to API caller -func (e *Err) BindArgument(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 Err) Error() string { - - return fmt.Sprintf("[%d] %s", e.Code, e.Reason) - -} - -// Implements json.Marshaler -func (e Err) MarshalJSON() ([]byte, error) { - - var json_arguments string - - /* (1) Marshal 'Arguments' if set */ - if e.Arguments != nil && len(e.Arguments) > 0 { - arg_representation, err := json.Marshal(e.Arguments) - if err == nil { - json_arguments = fmt.Sprintf(",\"arguments\":%s", arg_representation) - } - - } - - /* (2) Render JSON manually */ - return []byte(fmt.Sprintf("{\"error\":%d,\"reason\":\"%s\"%s}", e.Code, e.Reason, json_arguments)), nil - -} diff --git a/loader.go b/loader.go index e674100..ada36ae 100644 --- a/loader.go +++ b/loader.go @@ -3,6 +3,7 @@ package gfw import ( "git.xdrm.io/xdrm-brackets/gfw/checker" "git.xdrm.io/xdrm-brackets/gfw/config" + "git.xdrm.io/xdrm-brackets/gfw/err" ) // Init initilises a new framework instance @@ -18,7 +19,7 @@ func Init(path string, typeChecker *checker.TypeRegistry) (*Server, error) { inst := &Server{ config: nil, Params: make(map[string]interface{}), - err: ErrSuccess, + err: err.Success, } /* (2) Load configuration */ diff --git a/request_builder.go b/request_builder.go index 1ec6a9b..bff6b5d 100644 --- a/request_builder.go +++ b/request_builder.go @@ -3,8 +3,10 @@ package gfw import ( "encoding/json" "fmt" + "git.xdrm.io/xdrm-brackets/gfw/err" "log" "net/http" + "plugin" "reflect" "strings" "time" @@ -189,3 +191,40 @@ func parseHttpData(data interface{}) interface{} { return dvalue } + +// loadController tries to load a controller from its uri +// checks for its given method ('Get', 'Post', 'Put', or 'Delete') +func (i *Request) loadController(method string) (func(map[string]interface{}) (map[string]interface{}, err.Error), error) { + + /* (1) Build controller path */ + path := fmt.Sprintf("%si.so", i.ControllerUri) + + /* (2) Format url */ + tmp := []byte(strings.ToLower(method)) + tmp[0] = tmp[0] - ('a' - 'A') + method = string(tmp) + + fmt.Printf("method is '%s'\n", method) + return nil, nil + + /* (2) Try to load plugin */ + p, err2 := plugin.Open(path) + if err2 != nil { + return nil, err2 + } + + /* (3) Try to extract method */ + m, err2 := p.Lookup(method) + if err2 != nil { + return nil, err2 + } + + /* (4) Check signature */ + callable, validSignature := m.(func(map[string]interface{}) (map[string]interface{}, err.Error)) + if !validSignature { + return nil, fmt.Errorf("Invalid signature for method %s", method) + } + + return callable, nil + +} diff --git a/request_data.go b/request_data.go index 60ab0be..67ca941 100644 --- a/request_data.go +++ b/request_data.go @@ -9,15 +9,15 @@ import ( "strings" ) -// buildRequestDataFromRequest builds a 'RequestData' +// buildRequestDataFromRequest builds a 'requestData' // from an http request -func buildRequestDataFromRequest(req *http.Request) *RequestData { +func buildRequestDataFromRequest(req *http.Request) *requestData { - i := &RequestData{ - Url: make([]*RequestParameter, 0), - Get: make(map[string]*RequestParameter), - Form: make(map[string]*RequestParameter), - Set: make(map[string]*RequestParameter), + i := &requestData{ + Url: make([]*requestParameter, 0), + Get: make(map[string]*requestParameter), + Form: make(map[string]*requestParameter), + Set: make(map[string]*requestParameter), } // GET (query) data @@ -37,7 +37,7 @@ func buildRequestDataFromRequest(req *http.Request) *RequestData { // bindUrl stores URL data and fills 'Set' // with creating pointers inside 'Url' -func (i *RequestData) fillUrl(data []string) { +func (i *requestData) fillUrl(data []string) { for index, value := range data { @@ -45,7 +45,7 @@ func (i *RequestData) fillUrl(data []string) { setindex := fmt.Sprintf("URL#%d", index) // store value in 'Set' - i.Set[setindex] = &RequestParameter{ + i.Set[setindex] = &requestParameter{ Parsed: false, Value: value, } @@ -58,7 +58,7 @@ func (i *RequestData) fillUrl(data []string) { } // fetchGet stores data from the QUERY (in url parameters) -func (i *RequestData) fetchGet(req *http.Request) { +func (i *requestData) fetchGet(req *http.Request) { for name, value := range req.URL.Query() { @@ -72,7 +72,7 @@ func (i *RequestData) fetchGet(req *http.Request) { setindex := fmt.Sprintf("GET@%s", name) // store value in 'Set' - i.Set[setindex] = &RequestParameter{ + i.Set[setindex] = &requestParameter{ Parsed: false, Value: value, } @@ -89,7 +89,7 @@ func (i *RequestData) fetchGet(req *http.Request) { // - parse 'form-data' if not supported (not POST requests) // - parse 'x-www-form-urlencoded' // - parse 'application/json' -func (i *RequestData) fetchForm(req *http.Request) { +func (i *requestData) fetchForm(req *http.Request) { contentType := req.Header.Get("Content-Type") @@ -116,7 +116,7 @@ func (i *RequestData) fetchForm(req *http.Request) { // parseJsonForm parses JSON from the request body inside 'Form' // and 'Set' -func (i *RequestData) parseJsonForm(req *http.Request) { +func (i *requestData) parseJsonForm(req *http.Request) { parsed := make(map[string]interface{}, 0) @@ -137,7 +137,7 @@ func (i *RequestData) parseJsonForm(req *http.Request) { } // store value in 'Set' - i.Set[name] = &RequestParameter{ + i.Set[name] = &requestParameter{ Parsed: true, Value: value, } @@ -151,7 +151,7 @@ func (i *RequestData) parseJsonForm(req *http.Request) { // parseUrlencodedForm parses urlencoded from the request body inside 'Form' // and 'Set' -func (i *RequestData) parseUrlencodedForm(req *http.Request) { +func (i *requestData) parseUrlencodedForm(req *http.Request) { // use http.Request interface req.ParseForm() @@ -165,7 +165,7 @@ func (i *RequestData) parseUrlencodedForm(req *http.Request) { } // store value in 'Set' - i.Set[name] = &RequestParameter{ + i.Set[name] = &requestParameter{ Parsed: false, Value: value, } @@ -178,7 +178,7 @@ func (i *RequestData) parseUrlencodedForm(req *http.Request) { // parseMultipartForm parses multi-part from the request body inside 'Form' // and 'Set' -func (i *RequestData) parseMultipartForm(req *http.Request) { +func (i *requestData) parseMultipartForm(req *http.Request) { /* (1) Create reader */ mpr := multipart.CreateReader(req) @@ -196,7 +196,7 @@ func (i *RequestData) parseMultipartForm(req *http.Request) { } // store value in 'Set' - i.Set[name] = &RequestParameter{ + i.Set[name] = &requestParameter{ Parsed: false, File: component.File, Value: component.Data, diff --git a/router.go b/router.go index 7560171..3e03769 100644 --- a/router.go +++ b/router.go @@ -3,6 +3,7 @@ package gfw import ( "fmt" "git.xdrm.io/xdrm-brackets/gfw/config" + "git.xdrm.io/xdrm-brackets/gfw/err" "log" "net/http" "strings" @@ -13,65 +14,30 @@ func (s *Server) route(res http.ResponseWriter, req *http.Request) { /* (1) Build request ---------------------------------------------------------*/ /* (1) Try to build request */ - request, err := buildRequest(req) - if err != nil { + request, err2 := buildRequest(req) + if err2 != nil { log.Fatal(req) } /* (2) Find a controller ---------------------------------------------------------*/ - /* (1) Init browsing cursors */ - ctl := s.config - uriIndex := 0 - - /* (2) Browse while there is uri parts */ - for uriIndex < len(request.Uri) { - uri := request.Uri[uriIndex] - - child, hasKey := ctl.Children[uri] - - // stop if no matchind child - if !hasKey { - break - } - - request.ControllerUri = append(request.ControllerUri, uri) - ctl = child - uriIndex++ - - } - - /* (3) Extract & store URI params */ - request.Data.fillUrl(request.Uri[uriIndex:]) + controller := s.findController(request) /* (3) Check method ---------------------------------------------------------*/ - /* (1) Unavailable method */ - if !config.IsMethodAvailable(req.Method) { + method := s.getMethod(controller, req.Method) - Json, _ := ErrUnknownMethod.MarshalJSON() + if method == nil { + Json, _ := err.UnknownMethod.MarshalJSON() res.Header().Add("Content-Type", "application/json") res.Write(Json) - log.Printf("[err] %s\n", ErrUnknownMethod.Reason) - return - - } - - /* (2) Extract method cursor */ - var method = ctl.Method(req.Method) - - /* (3) Unmanaged HTTP method */ - if method == nil { // unknown method - Json, _ := ErrUnknownMethod.MarshalJSON() - res.Header().Add("Content-Type", "application/json") - res.Write(Json) - log.Printf("[err] %s\n", ErrUnknownMethod.Reason) + log.Printf("[err] %s\n", err.UnknownMethod.Reason) return } /* (4) Check parameters ---------------------------------------------------------*/ - var paramError Err = ErrSuccess + var paramError err.Error = err.Success parameters := make(map[string]interface{}) for name, param := range method.Parameters { @@ -80,14 +46,14 @@ func (s *Server) route(res http.ResponseWriter, req *http.Request) { /* (2) Required & missing */ if !isset && !*param.Optional { - paramError = ErrMissingParam + paramError = err.MissingParam paramError.BindArgument(name) break } /* (3) Optional & missing: set default value */ if !isset { - p = &RequestParameter{ + p = &requestParameter{ Parsed: true, File: param.Type == "FILE", Value: nil, @@ -105,7 +71,7 @@ func (s *Server) route(res http.ResponseWriter, req *http.Request) { /* (4) Fail on unexpected multipart file */ waitFile, gotFile := param.Type == "FILE", p.File if gotFile && !waitFile || !gotFile && waitFile { - paramError = ErrInvalidParam + paramError = err.InvalidParam paramError.BindArgument(name) paramError.BindArgument("FILE") break @@ -120,7 +86,7 @@ func (s *Server) route(res http.ResponseWriter, req *http.Request) { /* (6) Check type */ if s.Checker.Run(param.Type, p.Value) != nil { - paramError = ErrInvalidParam + paramError = err.InvalidParam paramError.BindArgument(name) paramError.BindArgument(param.Type) paramError.BindArgument(p.Value) @@ -131,10 +97,9 @@ func (s *Server) route(res http.ResponseWriter, req *http.Request) { parameters[name] = p.Value } - fmt.Printf("\n") // Fail if argument check failed - if paramError.Code != ErrSuccess.Code { + if paramError.Code != err.Success.Code { Json, _ := paramError.MarshalJSON() res.Header().Add("Content-Type", "application/json") res.Write(Json) @@ -142,9 +107,69 @@ func (s *Server) route(res http.ResponseWriter, req *http.Request) { return } + /* (5) Load controller + ---------------------------------------------------------*/ + callable, err := request.loadController(req.Method) + if err != nil { + log.Printf("[err] %s\n", err) + return + } fmt.Printf("OK\nplugin: '%si.so'\n", strings.Join(request.ControllerUri, "/")) for name, value := range parameters { fmt.Printf(" $%s = %v\n", name, value) } + + /* (6) Execute and get response + ---------------------------------------------------------*/ + out, _ := callable(parameters) + fmt.Printf("-- OUT --\n") + for name, value := range out { + fmt.Printf(" $%s = %v\n", name, value) + } return } + +func (s *Server) findController(req *Request) *config.Controller { + /* (1) Init browsing cursors */ + ctl := s.config + uriIndex := 0 + + /* (2) Browse while there is uri parts */ + for uriIndex < len(req.Uri) { + uri := req.Uri[uriIndex] + + child, hasKey := ctl.Children[uri] + + // stop if no matchind child + if !hasKey { + break + } + + req.ControllerUri = append(req.ControllerUri, uri) + ctl = child + uriIndex++ + + } + + /* (3) Extract & store URI params */ + req.Data.fillUrl(req.Uri[uriIndex:]) + + /* (4) Return controller */ + return ctl + +} + +func (s *Server) getMethod(controller *config.Controller, method string) *config.Method { + + /* (1) Unavailable method */ + if !config.IsMethodAvailable(method) { + return nil + } + + /* (2) Extract method cursor */ + var foundMethod = controller.Method(method) + + /* (3) Return method | nil on error */ + return foundMethod + +} diff --git a/types.go b/types.go index 3bcc677..a48aaf2 100644 --- a/types.go +++ b/types.go @@ -3,13 +3,14 @@ package gfw import ( "git.xdrm.io/xdrm-brackets/gfw/checker" "git.xdrm.io/xdrm-brackets/gfw/config" + "git.xdrm.io/xdrm-brackets/gfw/err" ) type Server struct { config *config.Controller Params map[string]interface{} Checker *checker.TypeRegistry // type check - err Err + err err.Error } type Request struct { @@ -21,21 +22,21 @@ type Request struct { ControllerUri []string // contains all data from URL, GET, and FORM - Data *RequestData + Data *requestData } -type RequestData struct { +type requestData struct { // ordered values from the URI // catches all after the controller path // // points to Request.Data - Url []*RequestParameter + Url []*requestParameter // uri parameters following the QUERY format // // points to Request.Data - Get map[string]*RequestParameter + Get map[string]*requestParameter // form data depending on the Content-Type: // 'application/json' => key-value pair is parsed as json into the map @@ -43,18 +44,18 @@ type RequestData struct { // 'multipart/form-data' => parse form-data format // // points to Request.Data - Form map[string]*RequestParameter + Form map[string]*requestParameter // contains URL+GET+FORM data with prefixes: // - FORM: no prefix // - URL: 'URL#' followed by the index in Uri // - GET: 'GET@' followed by the key in GET - Set map[string]*RequestParameter + Set map[string]*requestParameter } -// RequestParameter represents an http request parameter +// requestParameter represents an http request parameter // that can be of type URL, GET, or FORM (multipart, json, urlencoded) -type RequestParameter struct { +type requestParameter struct { // whether the value has been json-parsed // for optimisation purpose, parameters are only parsed // if they are required by the current controller