test dynfunc package; standardize and refactor api #14

Merged
xdrm-brackets merged 19 commits from test/dynamic into 0.3.0 2020-04-04 10:09:20 +00:00
3 changed files with 66 additions and 68 deletions
Showing only changes of commit d69dd2508c - Show all commits

View File

@ -8,8 +8,17 @@ func (err cerr) Error() string {
return string(err) return string(err)
} }
// ErrNoServiceForHandler - no service matching this handler // ErrLateType - cannot add datatype after setting up the definition
const ErrNoServiceForHandler = cerr("no service found for this handler") const ErrLateType = cerr("types cannot be added after Setup")
// ErrNoHandlerForService - no handler matching this service // ErrNotSetup - not set up yet
const ErrNoHandlerForService = cerr("no handler found for this service") 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")

26
http.go
View File

@ -9,14 +9,14 @@ import (
) )
// httpHandler wraps the aicra server to allow handling http requests // 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 // ServeHTTP implements http.Handler and has to be called on each request
func (server httpHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) { func (server httpHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
defer req.Body.Close() defer req.Body.Close()
// 1. find a matching service in the config // 1. find a matching service in the config
service := server.config.Find(req) service := server.conf.Find(req)
if service == nil { if service == nil {
response := api.EmptyResponse().WithError(api.ErrorUnknownService) response := api.EmptyResponse().WithError(api.ErrorUnknownService)
response.ServeHTTP(res, req) response.ServeHTTP(res, req)
@ -55,25 +55,15 @@ func (server httpHandler) ServeHTTP(res http.ResponseWriter, req *http.Request)
} }
// 6. find a matching handler // 6. find a matching handler
var foundHandler *handler var handler *apiHandler
var found bool for _, h := range server.handlers {
if h.Method == service.Method && h.Path == service.Pattern {
for _, handler := range server.handlers { handler = h
if handler.Method == service.Method && handler.Path == service.Pattern {
foundHandler = handler
found = true
} }
} }
// 7. fail if found no handler // 7. fail if found no handler
if foundHandler == nil { if handler == nil {
if found {
r := api.EmptyResponse().WithError(api.ErrorUncallableService)
r.ServeHTTP(res, req)
logError(r)
return
}
r := api.EmptyResponse().WithError(api.ErrorUnknownService) r := api.EmptyResponse().WithError(api.ErrorUnknownService)
r.ServeHTTP(res, req) r.ServeHTTP(res, req)
logError(r) logError(r)
@ -91,7 +81,7 @@ func (server httpHandler) ServeHTTP(res http.ResponseWriter, req *http.Request)
apireq.Param = dataset.Data apireq.Param = dataset.Data
// 10. execute // 10. execute
returned, apiErr := foundHandler.dynHandler.Handle(dataset.Data) returned, apiErr := handler.dyn.Handle(dataset.Data)
response := api.EmptyResponse().WithError(apiErr) response := api.EmptyResponse().WithError(apiErr)
for key, value := range returned { for key, value := range returned {

View File

@ -2,61 +2,59 @@ package aicra
import ( import (
"fmt" "fmt"
"io"
"net/http" "net/http"
"os"
"git.xdrm.io/go/aicra/datatype" "git.xdrm.io/go/aicra/datatype"
"git.xdrm.io/go/aicra/dynfunc" "git.xdrm.io/go/aicra/dynfunc"
"git.xdrm.io/go/aicra/internal/config" "git.xdrm.io/go/aicra/internal/config"
) )
// Server represents an AICRA instance featuring: type checkers, services // Builder for an aicra server
type Server struct { type Builder struct {
config *config.Server conf *config.Server
handlers []*apiHandler handlers []*apiHandler
} }
// represents an server handler
type apiHandler struct { type apiHandler struct {
Method string Method string
Path string Path string
dynHandler *dynfunc.Handler dyn *dynfunc.Handler
} }
// New creates a framework instance from a configuration file // AddType adds an available datatype to the api definition
func New(configPath string, dtypes ...datatype.T) (*Server, error) { func (b *Builder) AddType(t datatype.T) {
var ( if b.conf == nil {
err error b.conf = &config.Server{}
configFile io.ReadCloser }
) if b.conf.Services != nil {
panic(ErrLateType)
// 1. init instance }
var i = &Server{ b.conf.Types = append(b.conf.Types, t)
config: nil,
handlers: make([]*handler, 0),
} }
// 2. open config file // Setup the builder with its api definition
configFile, err = os.Open(configPath) // panics if already setup
if err != nil { func (b *Builder) Setup(r io.Reader) error {
return nil, err if b.conf == nil {
b.conf = &config.Server{}
} }
defer configFile.Close() if b.conf.Services != nil {
panic(ErrAlreadySetup)
// 3. load configuration }
i.config, err = config.Parse(configFile, dtypes...) return b.conf.Parse(r)
if err != nil {
return nil, err
} }
return i, nil // 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
} }
// Handle sets a new handler for an HTTP method to a path
func (s *Server) Handle(method, path string, fn interface{}) error {
// find associated service // find associated service
var service *config.Service var service *config.Service
for _, s := range s.config.Services { for _, s := range b.conf.Services {
if method == s.Method && path == s.Pattern { if method == s.Method && path == s.Pattern {
service = s service = s
break break
@ -67,25 +65,26 @@ func (s *Server) Handle(method, path string, fn interface{}) error {
return fmt.Errorf("%s '%s': %w", method, path, ErrUnknownService) return fmt.Errorf("%s '%s': %w", method, path, ErrUnknownService)
} }
dynHandler, err := dynfunc.Build(fn, *service) dyn, err := dynfunc.Build(fn, *service)
if err != nil { if err != nil {
return fmt.Errorf("%s '%s' handler: %w", method, path, err) return fmt.Errorf("%s '%s' handler: %w", method, path, err)
} }
s.handlers = append(s.handlers, &apiHandler{ b.handlers = append(b.handlers, &apiHandler{
Path: path, Path: path,
Method: method, Method: method,
dynHandler: dynHandler, dyn: dyn,
}) })
return nil return nil
} }
// ToHTTPServer converts the server to a http.Handler // Build a fully-featured HTTP server
func (s Server) ToHTTPServer() (http.Handler, error) { func (b Builder) Build() (http.Handler, error) {
for _, service := range s.config.Services {
for _, service := range b.conf.Services {
var hasAssociatedHandler bool var hasAssociatedHandler bool
for _, handler := range s.handlers { for _, handler := range b.handlers {
if handler.Method == service.Method && handler.Path == service.Pattern { if handler.Method == service.Method && handler.Path == service.Pattern {
hasAssociatedHandler = true hasAssociatedHandler = true
break break
@ -96,5 +95,5 @@ func (s Server) ToHTTPServer() (http.Handler, error) {
} }
} }
return httpHandler(s), nil return httpHandler(b), nil
} }