diff --git a/api/error.go b/api/error.go index af72dd1..2abcc01 100644 --- a/api/error.go +++ b/api/error.go @@ -13,6 +13,14 @@ type Error struct { Arguments []interface{} `json:"arguments"` } +// NewError returns a new error from a base error with errorarguments. +func NewError(baseError Error, arguments ...interface{}) Error { + for _, arg := range arguments { + baseError.Put(arg) + } + return baseError +} + // Put adds an argument to the error // to be displayed back to API caller func (e *Error) Put(arg interface{}) { diff --git a/api/response.go b/api/response.go index a6ea101..2ea4e6f 100644 --- a/api/response.go +++ b/api/response.go @@ -16,14 +16,21 @@ type Response struct { Err Error } -// NewResponse creates an empty response -func NewResponse() *Response { - return &Response{ +// NewResponse creates an empty response. An optional error can be passed as its first argument. +func NewResponse(errors ...Error) *Response { + res := &Response{ Status: http.StatusOK, Data: make(ResponseData), Err: ErrorFailure(), Headers: make(http.Header), } + + // optional error + if len(errors) == 1 { + res.Err = errors[0] + } + + return res } // SetData adds/overrides a new response field @@ -51,3 +58,17 @@ func (i *Response) MarshalJSON() ([]byte, error) { return json.Marshal(fmt) } + +// Write writes to an HTTP response. +func (i *Response) Write(w http.ResponseWriter) error { + w.WriteHeader(i.Status) + w.Header().Add("Content-Type", "application/json") + + fmt, err := json.Marshal(i) + if err != nil { + return err + } + w.Write(fmt) + + return nil +} diff --git a/internal/multipart/reader.go b/internal/multipart/reader.go index 3db12cd..f62bc59 100644 --- a/internal/multipart/reader.go +++ b/internal/multipart/reader.go @@ -6,19 +6,18 @@ import ( "io" ) -// NewReader craetes a new reader -func NewReader(_src io.Reader, _boundary string) (*Reader, error) { - +// NewReader creates a new reader from a reader and a boundary. +func NewReader(r io.Reader, boundary string) (*Reader, error) { reader := &Reader{ reader: nil, - boundary: fmt.Sprintf("--%s", _boundary), + boundary: fmt.Sprintf("--%s", boundary), Data: make(map[string]*Component), } // 1. Create reader - dst, ok := _src.(*bufio.Reader) + dst, ok := r.(*bufio.Reader) if !ok { - dst = bufio.NewReader(_src) + dst = bufio.NewReader(r) } reader.reader = dst diff --git a/internal/multipart/types.go b/internal/multipart/types.go index 78c291d..2419a28 100644 --- a/internal/multipart/types.go +++ b/internal/multipart/types.go @@ -2,17 +2,24 @@ package multipart import ( "bufio" - "errors" ) +// ConstError is a wrapper to set constant errors +type ConstError string + +// Error implements error +func (err ConstError) Error() string { + return string(err) +} + // ErrMissingDataName is set when a multipart variable/file has no name="..." -var ErrMissingDataName = errors.New("data has no name") +var ErrMissingDataName = ConstError("data has no name") // ErrDataNameConflict is set when a multipart variable/file name is already used -var ErrDataNameConflict = errors.New("data name conflict") +var ErrDataNameConflict = ConstError("data name conflict") // ErrNoHeader is set when a multipart variable/file has no (valid) header -var ErrNoHeader = errors.New("data has no header") +var ErrNoHeader = ConstError("data has no header") // Component represents a multipart variable/file type Component struct { diff --git a/server.go b/server.go index d9205d8..eadbb99 100644 --- a/server.go +++ b/server.go @@ -51,7 +51,6 @@ func New(configPath string) (*Server, error) { // ServeHTTP implements http.Handler and has to be called on each request func (s *Server) ServeHTTP(res http.ResponseWriter, req *http.Request) { - defer req.Body.Close() // 1. build API request from HTTP request @@ -73,7 +72,9 @@ func (s *Server) ServeHTTP(res http.ResponseWriter, req *http.Request) { // 3. check if matching methodDef exists in config */ var methodDef = serviceDef.Method(req.Method) if methodDef == nil { - httpError(res, api.ErrorUnknownMethod()) + apiResponse := api.NewResponse(api.ErrorUnknownMethod()) + apiResponse.Write(res) + logError(apiResponse) return } @@ -86,7 +87,9 @@ func (s *Server) ServeHTTP(res http.ResponseWriter, req *http.Request) { // Fail if argument check failed if paramError.Code != api.ErrorSuccess().Code { - httpError(res, paramError) + apiResponse := api.NewResponse(paramError) + apiResponse.Write(res) + logError(apiResponse) return } @@ -109,16 +112,17 @@ func (s *Server) ServeHTTP(res http.ResponseWriter, req *http.Request) { // fail if found no handler if serviceHandler == nil { if serviceFound { - apiError := api.ErrorUncallableMethod() - apiError.Put(servicePath) - apiError.Put(req.Method) - httpError(res, apiError) + apiError := api.NewError(api.ErrorUncallableMethod(), servicePath, req.Method) + apiResponse := api.NewResponse(apiError) + apiResponse.Write(res) + logError(apiResponse) return } - apiError := api.ErrorUncallableService() - apiError.Put(servicePath) - httpError(res, apiError) + apiError := api.NewError(api.ErrorUncallableService(), servicePath) + apiResponse := api.NewResponse(apiError) + apiResponse.Write(res) + logError(apiResponse) return } @@ -138,8 +142,8 @@ func (s *Server) ServeHTTP(res http.ResponseWriter, req *http.Request) { } } - // 3. build JSON apiResponse - httpPrint(res, apiResponse) + // 3. write to response + apiResponse.Write(res) return } diff --git a/util.go b/util.go index 305304d..7a94034 100644 --- a/util.go +++ b/util.go @@ -1,9 +1,7 @@ package aicra import ( - "encoding/json" "log" - "net/http" "git.xdrm.io/go/aicra/api" "git.xdrm.io/go/aicra/internal/config" @@ -21,17 +19,17 @@ func (s *Server) extractParameters(store *reqdata.Store, methodParam map[string] // for each param of the config for name, param := range methodParam { - /* (1) Extract value */ + // 1. extract value p, isset := store.Set[name] - /* (2) Required & missing */ + // 2. fail if required & missing if !isset && !param.Optional { apiError = api.ErrorMissingParam() apiError.Put(name) return nil, apiError } - /* (3) Optional & missing: set default value */ + // 3. optional & missing: set default value if !isset { p = &reqdata.Parameter{ Parsed: true, @@ -47,12 +45,12 @@ func (s *Server) extractParameters(store *reqdata.Store, methodParam map[string] continue } - /* (4) Parse parameter if not file */ + // 4. parse parameter if not file if !p.File { p.Parse() } - /* (5) Fail on unexpected multipart file */ + // 5. fail on unexpected multipart file waitFile, gotFile := param.Type == "FILE", p.File if gotFile && !waitFile || !gotFile && waitFile { apiError = api.ErrorInvalidParam() @@ -61,13 +59,13 @@ func (s *Server) extractParameters(store *reqdata.Store, methodParam map[string] return nil, apiError } - /* (6) Do not check if file */ + // 6. do not check if file if gotFile { parameters[param.Rename] = p.Value continue } - /* (7) Check type */ + // 7. check type if s.Checkers.Run(param.Type, p.Value) != nil { apiError = api.ErrorInvalidParam() @@ -85,20 +83,7 @@ func (s *Server) extractParameters(store *reqdata.Store, methodParam map[string] return parameters, apiError } -// Prints an HTTP response -func httpPrint(r http.ResponseWriter, res *api.Response) { - r.WriteHeader(res.Status) - - // write this json - jsonResponse, _ := json.Marshal(res) - r.Header().Add("Content-Type", "application/json") - r.Write(jsonResponse) -} - // Prints an error as HTTP response -func httpError(r http.ResponseWriter, e api.Error) { - JSON, _ := json.Marshal(e) - r.Header().Add("Content-Type", "application/json") - r.Write(JSON) - log.Printf("[http.fail] %s\n", e.Reason) +func logError(res *api.Response) { + log.Printf("[http.fail] %v\n", res.Err) }