moved 'err' into package

This commit is contained in:
Adrien Marquès 2018-06-01 09:09:26 +02:00
parent 84d14dfd50
commit f0727cb9ca
8 changed files with 229 additions and 162 deletions

34
err/defaults.go Normal file
View File

@ -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}
)

52
err/interface.go Normal file
View File

@ -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
}

View File

@ -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
}

View File

@ -3,6 +3,7 @@ package gfw
import ( import (
"git.xdrm.io/xdrm-brackets/gfw/checker" "git.xdrm.io/xdrm-brackets/gfw/checker"
"git.xdrm.io/xdrm-brackets/gfw/config" "git.xdrm.io/xdrm-brackets/gfw/config"
"git.xdrm.io/xdrm-brackets/gfw/err"
) )
// Init initilises a new framework instance // Init initilises a new framework instance
@ -18,7 +19,7 @@ func Init(path string, typeChecker *checker.TypeRegistry) (*Server, error) {
inst := &Server{ inst := &Server{
config: nil, config: nil,
Params: make(map[string]interface{}), Params: make(map[string]interface{}),
err: ErrSuccess, err: err.Success,
} }
/* (2) Load configuration */ /* (2) Load configuration */

View File

@ -3,8 +3,10 @@ package gfw
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"git.xdrm.io/xdrm-brackets/gfw/err"
"log" "log"
"net/http" "net/http"
"plugin"
"reflect" "reflect"
"strings" "strings"
"time" "time"
@ -189,3 +191,40 @@ func parseHttpData(data interface{}) interface{} {
return dvalue 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
}

View File

@ -9,15 +9,15 @@ import (
"strings" "strings"
) )
// buildRequestDataFromRequest builds a 'RequestData' // buildRequestDataFromRequest builds a 'requestData'
// from an http request // from an http request
func buildRequestDataFromRequest(req *http.Request) *RequestData { func buildRequestDataFromRequest(req *http.Request) *requestData {
i := &RequestData{ i := &requestData{
Url: make([]*RequestParameter, 0), Url: make([]*requestParameter, 0),
Get: make(map[string]*RequestParameter), Get: make(map[string]*requestParameter),
Form: make(map[string]*RequestParameter), Form: make(map[string]*requestParameter),
Set: make(map[string]*RequestParameter), Set: make(map[string]*requestParameter),
} }
// GET (query) data // GET (query) data
@ -37,7 +37,7 @@ func buildRequestDataFromRequest(req *http.Request) *RequestData {
// bindUrl stores URL data and fills 'Set' // bindUrl stores URL data and fills 'Set'
// with creating pointers inside 'Url' // with creating pointers inside 'Url'
func (i *RequestData) fillUrl(data []string) { func (i *requestData) fillUrl(data []string) {
for index, value := range data { for index, value := range data {
@ -45,7 +45,7 @@ func (i *RequestData) fillUrl(data []string) {
setindex := fmt.Sprintf("URL#%d", index) setindex := fmt.Sprintf("URL#%d", index)
// store value in 'Set' // store value in 'Set'
i.Set[setindex] = &RequestParameter{ i.Set[setindex] = &requestParameter{
Parsed: false, Parsed: false,
Value: value, Value: value,
} }
@ -58,7 +58,7 @@ func (i *RequestData) fillUrl(data []string) {
} }
// fetchGet stores data from the QUERY (in url parameters) // 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() { for name, value := range req.URL.Query() {
@ -72,7 +72,7 @@ func (i *RequestData) fetchGet(req *http.Request) {
setindex := fmt.Sprintf("GET@%s", name) setindex := fmt.Sprintf("GET@%s", name)
// store value in 'Set' // store value in 'Set'
i.Set[setindex] = &RequestParameter{ i.Set[setindex] = &requestParameter{
Parsed: false, Parsed: false,
Value: value, Value: value,
} }
@ -89,7 +89,7 @@ func (i *RequestData) fetchGet(req *http.Request) {
// - parse 'form-data' if not supported (not POST requests) // - parse 'form-data' if not supported (not POST requests)
// - parse 'x-www-form-urlencoded' // - parse 'x-www-form-urlencoded'
// - parse 'application/json' // - parse 'application/json'
func (i *RequestData) fetchForm(req *http.Request) { func (i *requestData) fetchForm(req *http.Request) {
contentType := req.Header.Get("Content-Type") 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' // parseJsonForm parses JSON from the request body inside 'Form'
// and 'Set' // and 'Set'
func (i *RequestData) parseJsonForm(req *http.Request) { func (i *requestData) parseJsonForm(req *http.Request) {
parsed := make(map[string]interface{}, 0) parsed := make(map[string]interface{}, 0)
@ -137,7 +137,7 @@ func (i *RequestData) parseJsonForm(req *http.Request) {
} }
// store value in 'Set' // store value in 'Set'
i.Set[name] = &RequestParameter{ i.Set[name] = &requestParameter{
Parsed: true, Parsed: true,
Value: value, Value: value,
} }
@ -151,7 +151,7 @@ func (i *RequestData) parseJsonForm(req *http.Request) {
// parseUrlencodedForm parses urlencoded from the request body inside 'Form' // parseUrlencodedForm parses urlencoded from the request body inside 'Form'
// and 'Set' // and 'Set'
func (i *RequestData) parseUrlencodedForm(req *http.Request) { func (i *requestData) parseUrlencodedForm(req *http.Request) {
// use http.Request interface // use http.Request interface
req.ParseForm() req.ParseForm()
@ -165,7 +165,7 @@ func (i *RequestData) parseUrlencodedForm(req *http.Request) {
} }
// store value in 'Set' // store value in 'Set'
i.Set[name] = &RequestParameter{ i.Set[name] = &requestParameter{
Parsed: false, Parsed: false,
Value: value, Value: value,
} }
@ -178,7 +178,7 @@ func (i *RequestData) parseUrlencodedForm(req *http.Request) {
// parseMultipartForm parses multi-part from the request body inside 'Form' // parseMultipartForm parses multi-part from the request body inside 'Form'
// and 'Set' // and 'Set'
func (i *RequestData) parseMultipartForm(req *http.Request) { func (i *requestData) parseMultipartForm(req *http.Request) {
/* (1) Create reader */ /* (1) Create reader */
mpr := multipart.CreateReader(req) mpr := multipart.CreateReader(req)
@ -196,7 +196,7 @@ func (i *RequestData) parseMultipartForm(req *http.Request) {
} }
// store value in 'Set' // store value in 'Set'
i.Set[name] = &RequestParameter{ i.Set[name] = &requestParameter{
Parsed: false, Parsed: false,
File: component.File, File: component.File,
Value: component.Data, Value: component.Data,

123
router.go
View File

@ -3,6 +3,7 @@ package gfw
import ( import (
"fmt" "fmt"
"git.xdrm.io/xdrm-brackets/gfw/config" "git.xdrm.io/xdrm-brackets/gfw/config"
"git.xdrm.io/xdrm-brackets/gfw/err"
"log" "log"
"net/http" "net/http"
"strings" "strings"
@ -13,65 +14,30 @@ func (s *Server) route(res http.ResponseWriter, req *http.Request) {
/* (1) Build request /* (1) Build request
---------------------------------------------------------*/ ---------------------------------------------------------*/
/* (1) Try to build request */ /* (1) Try to build request */
request, err := buildRequest(req) request, err2 := buildRequest(req)
if err != nil { if err2 != nil {
log.Fatal(req) log.Fatal(req)
} }
/* (2) Find a controller /* (2) Find a controller
---------------------------------------------------------*/ ---------------------------------------------------------*/
/* (1) Init browsing cursors */ controller := s.findController(request)
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:])
/* (3) Check method /* (3) Check method
---------------------------------------------------------*/ ---------------------------------------------------------*/
/* (1) Unavailable method */ method := s.getMethod(controller, req.Method)
if !config.IsMethodAvailable(req.Method) {
Json, _ := ErrUnknownMethod.MarshalJSON() if method == nil {
Json, _ := err.UnknownMethod.MarshalJSON()
res.Header().Add("Content-Type", "application/json") res.Header().Add("Content-Type", "application/json")
res.Write(Json) res.Write(Json)
log.Printf("[err] %s\n", ErrUnknownMethod.Reason) log.Printf("[err] %s\n", err.UnknownMethod.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)
return return
} }
/* (4) Check parameters /* (4) Check parameters
---------------------------------------------------------*/ ---------------------------------------------------------*/
var paramError Err = ErrSuccess var paramError err.Error = err.Success
parameters := make(map[string]interface{}) parameters := make(map[string]interface{})
for name, param := range method.Parameters { for name, param := range method.Parameters {
@ -80,14 +46,14 @@ func (s *Server) route(res http.ResponseWriter, req *http.Request) {
/* (2) Required & missing */ /* (2) Required & missing */
if !isset && !*param.Optional { if !isset && !*param.Optional {
paramError = ErrMissingParam paramError = err.MissingParam
paramError.BindArgument(name) paramError.BindArgument(name)
break break
} }
/* (3) Optional & missing: set default value */ /* (3) Optional & missing: set default value */
if !isset { if !isset {
p = &RequestParameter{ p = &requestParameter{
Parsed: true, Parsed: true,
File: param.Type == "FILE", File: param.Type == "FILE",
Value: nil, Value: nil,
@ -105,7 +71,7 @@ func (s *Server) route(res http.ResponseWriter, req *http.Request) {
/* (4) Fail on unexpected multipart file */ /* (4) Fail on unexpected multipart file */
waitFile, gotFile := param.Type == "FILE", p.File waitFile, gotFile := param.Type == "FILE", p.File
if gotFile && !waitFile || !gotFile && waitFile { if gotFile && !waitFile || !gotFile && waitFile {
paramError = ErrInvalidParam paramError = err.InvalidParam
paramError.BindArgument(name) paramError.BindArgument(name)
paramError.BindArgument("FILE") paramError.BindArgument("FILE")
break break
@ -120,7 +86,7 @@ func (s *Server) route(res http.ResponseWriter, req *http.Request) {
/* (6) Check type */ /* (6) Check type */
if s.Checker.Run(param.Type, p.Value) != nil { if s.Checker.Run(param.Type, p.Value) != nil {
paramError = ErrInvalidParam paramError = err.InvalidParam
paramError.BindArgument(name) paramError.BindArgument(name)
paramError.BindArgument(param.Type) paramError.BindArgument(param.Type)
paramError.BindArgument(p.Value) paramError.BindArgument(p.Value)
@ -131,10 +97,9 @@ func (s *Server) route(res http.ResponseWriter, req *http.Request) {
parameters[name] = p.Value parameters[name] = p.Value
} }
fmt.Printf("\n")
// Fail if argument check failed // Fail if argument check failed
if paramError.Code != ErrSuccess.Code { if paramError.Code != err.Success.Code {
Json, _ := paramError.MarshalJSON() Json, _ := paramError.MarshalJSON()
res.Header().Add("Content-Type", "application/json") res.Header().Add("Content-Type", "application/json")
res.Write(Json) res.Write(Json)
@ -142,9 +107,69 @@ func (s *Server) route(res http.ResponseWriter, req *http.Request) {
return 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, "/")) fmt.Printf("OK\nplugin: '%si.so'\n", strings.Join(request.ControllerUri, "/"))
for name, value := range parameters { for name, value := range parameters {
fmt.Printf(" $%s = %v\n", name, value) 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 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
}

View File

@ -3,13 +3,14 @@ package gfw
import ( import (
"git.xdrm.io/xdrm-brackets/gfw/checker" "git.xdrm.io/xdrm-brackets/gfw/checker"
"git.xdrm.io/xdrm-brackets/gfw/config" "git.xdrm.io/xdrm-brackets/gfw/config"
"git.xdrm.io/xdrm-brackets/gfw/err"
) )
type Server struct { type Server struct {
config *config.Controller config *config.Controller
Params map[string]interface{} Params map[string]interface{}
Checker *checker.TypeRegistry // type check Checker *checker.TypeRegistry // type check
err Err err err.Error
} }
type Request struct { type Request struct {
@ -21,21 +22,21 @@ type Request struct {
ControllerUri []string ControllerUri []string
// contains all data from URL, GET, and FORM // contains all data from URL, GET, and FORM
Data *RequestData Data *requestData
} }
type RequestData struct { type requestData struct {
// ordered values from the URI // ordered values from the URI
// catches all after the controller path // catches all after the controller path
// //
// points to Request.Data // points to Request.Data
Url []*RequestParameter Url []*requestParameter
// uri parameters following the QUERY format // uri parameters following the QUERY format
// //
// points to Request.Data // points to Request.Data
Get map[string]*RequestParameter Get map[string]*requestParameter
// form data depending on the Content-Type: // form data depending on the Content-Type:
// 'application/json' => key-value pair is parsed as json into the map // '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 // 'multipart/form-data' => parse form-data format
// //
// points to Request.Data // points to Request.Data
Form map[string]*RequestParameter Form map[string]*requestParameter
// contains URL+GET+FORM data with prefixes: // contains URL+GET+FORM data with prefixes:
// - FORM: no prefix // - FORM: no prefix
// - URL: 'URL#' followed by the index in Uri // - URL: 'URL#' followed by the index in Uri
// - GET: 'GET@' followed by the key in GET // - 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) // 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 // whether the value has been json-parsed
// for optimisation purpose, parameters are only parsed // for optimisation purpose, parameters are only parsed
// if they are required by the current controller // if they are required by the current controller