2020-04-04 09:50:01 +00:00
|
|
|
package aicra
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"net/http"
|
|
|
|
|
2021-06-20 19:29:46 +00:00
|
|
|
"github.com/xdrm-io/aicra/datatype"
|
|
|
|
"github.com/xdrm-io/aicra/internal/config"
|
|
|
|
"github.com/xdrm-io/aicra/internal/dynfunc"
|
2020-04-04 09:50:01 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Builder for an aicra server
|
|
|
|
type Builder struct {
|
2021-06-20 00:14:31 +00:00
|
|
|
// the server configuration defining available services
|
|
|
|
conf *config.Server
|
|
|
|
// user-defined handlers bound to services from the configuration
|
|
|
|
handlers []*apiHandler
|
|
|
|
// http middlewares wrapping the entire http connection (e.g. logger)
|
|
|
|
middlewares []func(http.Handler) http.Handler
|
|
|
|
// custom middlewares only wrapping the service handler of a request
|
|
|
|
// they will benefit from the request's context that contains service-specific
|
|
|
|
// information (e.g. required permisisons from the configuration)
|
|
|
|
ctxMiddlewares []func(http.Handler) http.Handler
|
2020-04-04 09:50:01 +00:00
|
|
|
}
|
|
|
|
|
2021-03-28 16:50:04 +00:00
|
|
|
// represents an api handler (method-pattern combination)
|
2020-04-04 09:50:01 +00:00
|
|
|
type apiHandler struct {
|
|
|
|
Method string
|
|
|
|
Path string
|
|
|
|
dyn *dynfunc.Handler
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddType adds an available datatype to the api definition
|
2021-04-19 16:46:18 +00:00
|
|
|
func (b *Builder) AddType(t datatype.T) error {
|
2020-04-04 09:50:01 +00:00
|
|
|
if b.conf == nil {
|
|
|
|
b.conf = &config.Server{}
|
|
|
|
}
|
|
|
|
if b.conf.Services != nil {
|
2021-04-19 16:46:18 +00:00
|
|
|
return errLateType
|
2020-04-04 09:50:01 +00:00
|
|
|
}
|
2021-04-18 14:49:46 +00:00
|
|
|
if b.conf.Types == nil {
|
|
|
|
b.conf.Types = make([]datatype.T, 0)
|
|
|
|
}
|
2020-04-04 09:50:01 +00:00
|
|
|
b.conf.Types = append(b.conf.Types, t)
|
2021-04-19 16:46:18 +00:00
|
|
|
return nil
|
2020-04-04 09:50:01 +00:00
|
|
|
}
|
|
|
|
|
2021-06-20 00:14:31 +00:00
|
|
|
// With adds an http middleware on top of the http connection
|
|
|
|
//
|
|
|
|
// Authentication management can only be done with the WithContext() methods as
|
|
|
|
// the service associated with the request has not been found at this stage.
|
|
|
|
// This stage is perfect for logging or generic request management.
|
|
|
|
func (b *Builder) With(mw func(http.Handler) http.Handler) {
|
2021-04-18 14:49:46 +00:00
|
|
|
if b.conf == nil {
|
|
|
|
b.conf = &config.Server{}
|
|
|
|
}
|
2021-06-20 00:14:31 +00:00
|
|
|
if b.middlewares == nil {
|
|
|
|
b.middlewares = make([]func(http.Handler) http.Handler, 0)
|
2021-04-18 14:49:46 +00:00
|
|
|
}
|
2021-06-20 00:14:31 +00:00
|
|
|
b.middlewares = append(b.middlewares, mw)
|
2021-04-18 14:49:46 +00:00
|
|
|
}
|
|
|
|
|
2021-06-20 00:14:31 +00:00
|
|
|
// WithContext adds an http middleware with the fully loaded context
|
|
|
|
//
|
|
|
|
// Logging or generic request management should be done with the With() method as
|
|
|
|
// it wraps the full http connection. Middlewares added through this method only
|
|
|
|
// wrap the user-defined service handler. The context.Context is filled with useful
|
|
|
|
// data that can be access with api.GetRequest(), api.GetResponseWriter(),
|
|
|
|
// api.GetAuth(), etc methods.
|
|
|
|
func (b *Builder) WithContext(mw func(http.Handler) http.Handler) {
|
2021-05-18 07:36:33 +00:00
|
|
|
if b.conf == nil {
|
|
|
|
b.conf = &config.Server{}
|
|
|
|
}
|
2021-06-20 00:14:31 +00:00
|
|
|
if b.ctxMiddlewares == nil {
|
|
|
|
b.ctxMiddlewares = make([]func(http.Handler) http.Handler, 0)
|
2021-05-18 07:36:33 +00:00
|
|
|
}
|
2021-06-20 00:14:31 +00:00
|
|
|
b.ctxMiddlewares = append(b.ctxMiddlewares, mw)
|
2021-05-18 07:36:33 +00:00
|
|
|
}
|
|
|
|
|
2021-03-28 16:50:04 +00:00
|
|
|
// Setup the builder with its api definition file
|
2020-04-04 09:50:01 +00:00
|
|
|
// 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 {
|
2020-04-04 10:40:21 +00:00
|
|
|
panic(errAlreadySetup)
|
2020-04-04 09:50:01 +00:00
|
|
|
}
|
|
|
|
return b.conf.Parse(r)
|
|
|
|
}
|
|
|
|
|
2021-03-28 16:50:04 +00:00
|
|
|
// Bind a dynamic handler to a REST service (method and pattern)
|
2020-04-04 09:50:01 +00:00
|
|
|
func (b *Builder) Bind(method, path string, fn interface{}) error {
|
|
|
|
if b.conf.Services == nil {
|
2020-04-04 10:40:21 +00:00
|
|
|
return errNotSetup
|
2020-04-04 09:50:01 +00:00
|
|
|
}
|
|
|
|
|
2021-03-28 16:50:04 +00:00
|
|
|
// find associated service from config
|
2020-04-04 09:50:01 +00:00
|
|
|
var service *config.Service
|
|
|
|
for _, s := range b.conf.Services {
|
|
|
|
if method == s.Method && path == s.Pattern {
|
|
|
|
service = s
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if service == nil {
|
2020-04-04 10:40:21 +00:00
|
|
|
return fmt.Errorf("%s '%s': %w", method, path, errUnknownService)
|
2020-04-04 09:50:01 +00:00
|
|
|
}
|
|
|
|
|
2021-03-28 16:50:04 +00:00
|
|
|
var dyn, err = dynfunc.Build(fn, *service)
|
2020-04-04 09:50:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("%s '%s' handler: %w", method, path, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
b.handlers = append(b.handlers, &apiHandler{
|
|
|
|
Path: path,
|
|
|
|
Method: method,
|
|
|
|
dyn: dyn,
|
|
|
|
})
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-03-28 16:50:04 +00:00
|
|
|
// Get is equivalent to Bind(http.MethodGet)
|
|
|
|
func (b *Builder) Get(path string, fn interface{}) error {
|
|
|
|
return b.Bind(http.MethodGet, path, fn)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Post is equivalent to Bind(http.MethodPost)
|
|
|
|
func (b *Builder) Post(path string, fn interface{}) error {
|
|
|
|
return b.Bind(http.MethodPost, path, fn)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Put is equivalent to Bind(http.MethodPut)
|
|
|
|
func (b *Builder) Put(path string, fn interface{}) error {
|
|
|
|
return b.Bind(http.MethodPut, path, fn)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete is equivalent to Bind(http.MethodDelete)
|
|
|
|
func (b *Builder) Delete(path string, fn interface{}) error {
|
|
|
|
return b.Bind(http.MethodDelete, path, fn)
|
|
|
|
}
|
|
|
|
|
2020-04-04 09:50:01 +00:00
|
|
|
// Build a fully-featured HTTP server
|
|
|
|
func (b Builder) Build() (http.Handler, error) {
|
|
|
|
|
|
|
|
for _, service := range b.conf.Services {
|
2021-03-28 16:50:04 +00:00
|
|
|
var isHandled bool
|
2020-04-04 09:50:01 +00:00
|
|
|
for _, handler := range b.handlers {
|
|
|
|
if handler.Method == service.Method && handler.Path == service.Pattern {
|
2021-03-28 16:50:04 +00:00
|
|
|
isHandled = true
|
2020-04-04 09:50:01 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2021-03-28 16:50:04 +00:00
|
|
|
if !isHandled {
|
2020-04-04 10:40:21 +00:00
|
|
|
return nil, fmt.Errorf("%s '%s': %w", service.Method, service.Pattern, errMissingHandler)
|
2020-04-04 09:50:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-28 17:03:16 +00:00
|
|
|
return Handler(b), nil
|
2020-04-04 09:50:01 +00:00
|
|
|
}
|