diff --git a/errors.go b/errors.go index 252f8ee..f3c7145 100644 --- a/errors.go +++ b/errors.go @@ -8,8 +8,17 @@ func (err cerr) Error() string { return string(err) } -// ErrNoServiceForHandler - no service matching this handler -const ErrNoServiceForHandler = cerr("no service found for this handler") +// ErrLateType - cannot add datatype after setting up the definition +const ErrLateType = cerr("types cannot be added after Setup") -// ErrNoHandlerForService - no handler matching this service -const ErrNoHandlerForService = cerr("no handler found for this service") +// ErrNotSetup - not set up yet +const ErrNotSetup = cerr("not set up") + +// ErrAlreadySetup - already set up +const ErrAlreadySetup = cerr("already set up") + +// ErrUnknownService - no service matching this handler +const ErrUnknownService = cerr("unknown service") + +// ErrMissingHandler - missing handler +const ErrMissingHandler = cerr("missing handler") diff --git a/http.go b/http.go index ad8ebe9..0f95ec7 100644 --- a/http.go +++ b/http.go @@ -9,14 +9,14 @@ import ( ) // httpHandler wraps the aicra server to allow handling http requests -type httpHandler Server +type httpHandler Builder // ServeHTTP implements http.Handler and has to be called on each request func (server httpHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) { defer req.Body.Close() // 1. find a matching service in the config - service := server.config.Find(req) + service := server.conf.Find(req) if service == nil { response := api.EmptyResponse().WithError(api.ErrorUnknownService) response.ServeHTTP(res, req) @@ -55,25 +55,15 @@ func (server httpHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) } // 6. find a matching handler - var foundHandler *handler - var found bool - - for _, handler := range server.handlers { - if handler.Method == service.Method && handler.Path == service.Pattern { - foundHandler = handler - found = true + var handler *apiHandler + for _, h := range server.handlers { + if h.Method == service.Method && h.Path == service.Pattern { + handler = h } } // 7. fail if found no handler - if foundHandler == nil { - if found { - r := api.EmptyResponse().WithError(api.ErrorUncallableService) - r.ServeHTTP(res, req) - logError(r) - return - } - + if handler == nil { r := api.EmptyResponse().WithError(api.ErrorUnknownService) r.ServeHTTP(res, req) logError(r) @@ -91,7 +81,7 @@ func (server httpHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) apireq.Param = dataset.Data // 10. execute - returned, apiErr := foundHandler.dynHandler.Handle(dataset.Data) + returned, apiErr := handler.dyn.Handle(dataset.Data) response := api.EmptyResponse().WithError(apiErr) for key, value := range returned { diff --git a/server.go b/server.go index 59a6285..75df07a 100644 --- a/server.go +++ b/server.go @@ -2,61 +2,59 @@ package aicra import ( "fmt" + "io" "net/http" - "os" "git.xdrm.io/go/aicra/datatype" "git.xdrm.io/go/aicra/dynfunc" "git.xdrm.io/go/aicra/internal/config" ) -// Server represents an AICRA instance featuring: type checkers, services -type Server struct { - config *config.Server +// Builder for an aicra server +type Builder struct { + conf *config.Server handlers []*apiHandler } +// represents an server handler type apiHandler struct { - Method string - Path string - dynHandler *dynfunc.Handler + Method string + Path string + dyn *dynfunc.Handler } -// New creates a framework instance from a configuration file -func New(configPath string, dtypes ...datatype.T) (*Server, error) { - var ( - err error - configFile io.ReadCloser - ) - - // 1. init instance - var i = &Server{ - config: nil, - handlers: make([]*handler, 0), +// AddType adds an available datatype to the api definition +func (b *Builder) AddType(t datatype.T) { + if b.conf == nil { + b.conf = &config.Server{} } - - // 2. open config file - configFile, err = os.Open(configPath) - if err != nil { - return nil, err + if b.conf.Services != nil { + panic(ErrLateType) } - defer configFile.Close() - - // 3. load configuration - i.config, err = config.Parse(configFile, dtypes...) - if err != nil { - return nil, err - } - - return i, nil - + b.conf.Types = append(b.conf.Types, t) } -// Handle sets a new handler for an HTTP method to a path -func (s *Server) Handle(method, path string, fn interface{}) error { +// Setup the builder with its api definition +// panics if already setup +func (b *Builder) Setup(r io.Reader) error { + if b.conf == nil { + b.conf = &config.Server{} + } + if b.conf.Services != nil { + panic(ErrAlreadySetup) + } + return b.conf.Parse(r) +} + +// Bind a dynamic handler to a REST service +func (b *Builder) Bind(method, path string, fn interface{}) error { + if b.conf.Services == nil { + return ErrNotSetup + } + // find associated service var service *config.Service - for _, s := range s.config.Services { + for _, s := range b.conf.Services { if method == s.Method && path == s.Pattern { service = s break @@ -67,25 +65,26 @@ func (s *Server) Handle(method, path string, fn interface{}) error { return fmt.Errorf("%s '%s': %w", method, path, ErrUnknownService) } - dynHandler, err := dynfunc.Build(fn, *service) + dyn, err := dynfunc.Build(fn, *service) if err != nil { return fmt.Errorf("%s '%s' handler: %w", method, path, err) } - s.handlers = append(s.handlers, &apiHandler{ - Path: path, - Method: method, - dynHandler: dynHandler, + b.handlers = append(b.handlers, &apiHandler{ + Path: path, + Method: method, + dyn: dyn, }) return nil } -// ToHTTPServer converts the server to a http.Handler -func (s Server) ToHTTPServer() (http.Handler, error) { - for _, service := range s.config.Services { +// Build a fully-featured HTTP server +func (b Builder) Build() (http.Handler, error) { + + for _, service := range b.conf.Services { var hasAssociatedHandler bool - for _, handler := range s.handlers { + for _, handler := range b.handlers { if handler.Method == service.Method && handler.Path == service.Pattern { hasAssociatedHandler = true break @@ -96,5 +95,5 @@ func (s Server) ToHTTPServer() (http.Handler, error) { } } - return httpHandler(s), nil + return httpHandler(b), nil }