aicra/handler.go

152 lines
3.3 KiB
Go

package aicra
import (
"fmt"
"net/http"
"strings"
"git.xdrm.io/go/aicra/api"
"git.xdrm.io/go/aicra/internal/config"
"git.xdrm.io/go/aicra/internal/reqdata"
)
// Handler wraps the builder to handle requests
type Handler Builder
// ServeHTTP implements http.Handler and wraps it in middlewares (adapters)
func (s Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var h = http.HandlerFunc(s.resolve)
for _, adapter := range s.adapters {
h = adapter(h)
}
h(w, r)
}
func (s Handler) resolve(w http.ResponseWriter, r *http.Request) {
// 1. find a matching service from config
var service = s.conf.Find(r)
if service == nil {
handleError(api.ErrUnknownService, w, r)
return
}
// 2. extract request data
var input, err = extractInput(service, *r)
if err != nil {
handleError(api.ErrMissingParam, w, r)
return
}
// 3. find a matching handler
var handler *apiHandler
for _, h := range s.handlers {
if h.Method == service.Method && h.Path == service.Pattern {
handler = h
}
}
// 4. fail on no matching handler
if handler == nil {
handleError(api.ErrUncallableService, w, r)
return
}
// replace format '[a]' in scope where 'a' is an existing input's name
scope := make([][]string, len(service.Scope))
for a, list := range service.Scope {
scope[a] = make([]string, len(list))
for b, perm := range list {
scope[a][b] = perm
for name, value := range input.Data {
var (
token = fmt.Sprintf("[%s]", name)
replacement = ""
)
if value != nil {
replacement = fmt.Sprintf("[%v]", value)
}
scope[a][b] = strings.ReplaceAll(scope[a][b], token, replacement)
}
}
}
var auth = api.Auth{
Required: scope,
Active: []string{},
}
// 5. run auth-aware middlewares
var h = api.AuthHandlerFunc(func(a api.Auth, w http.ResponseWriter, r *http.Request) {
if !a.Granted() {
handleError(api.ErrPermission, w, r)
return
}
s.handle(input, handler, service, w, r)
})
for _, adapter := range s.authAdapters {
h = adapter(h)
}
h(auth, w, r)
}
func (s *Handler) handle(input *reqdata.T, handler *apiHandler, service *config.Service, w http.ResponseWriter, r *http.Request) {
// 5. pass execution to the handler
ctx := api.Ctx{Res: w, Req: r}
var outData, outErr = handler.dyn.Handle(ctx, input.Data)
// 6. build res from returned data
var res = api.EmptyResponse().WithError(outErr)
for key, value := range outData {
// find original name from 'rename' field
for name, param := range service.Output {
if param.Rename == key {
res.SetData(name, value)
}
}
}
// 7. apply headers
w.Header().Set("Content-Type", "application/json; charset=utf-8")
for key, values := range res.Headers {
for _, value := range values {
w.Header().Add(key, value)
}
}
res.ServeHTTP(w, r)
}
func handleError(err api.Err, w http.ResponseWriter, r *http.Request) {
var response = api.EmptyResponse().WithError(err)
response.ServeHTTP(w, r)
}
func extractInput(service *config.Service, req http.Request) (*reqdata.T, error) {
var dataset = reqdata.New(service)
// URI data
var err = dataset.GetURI(req)
if err != nil {
return nil, err
}
// query data
err = dataset.GetQuery(req)
if err != nil {
return nil, err
}
// form/json data
err = dataset.GetForm(req)
if err != nil {
return nil, err
}
return dataset, nil
}