mirror of
https://github.com/ggicci/httpin.git
synced 2024-11-28 08:49:05 +02:00
115 lines
3.5 KiB
Go
115 lines
3.5 KiB
Go
package httpin
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"reflect"
|
|
"strings"
|
|
)
|
|
|
|
var (
|
|
executors map[string]DirectiveExecutor
|
|
)
|
|
|
|
func init() {
|
|
executors = make(map[string]DirectiveExecutor)
|
|
|
|
RegisterDirectiveExecutor("form", DirectiveExecutorFunc(formValueExtractor))
|
|
RegisterDirectiveExecutor("header", DirectiveExecutorFunc(headerValueExtractor))
|
|
RegisterDirectiveExecutor("body", DirectiveExecutorFunc(bodyDecoder))
|
|
RegisterDirectiveExecutor("required", DirectiveExecutorFunc(required))
|
|
}
|
|
|
|
// DirectiveExecutor is the interface implemented by a "directive executor".
|
|
type DirectiveExecutor interface {
|
|
Execute(*DirectiveContext) error
|
|
}
|
|
|
|
// RegisterDirectiveExecutor registers a named executor globally, which
|
|
// implemented the DirectiveExecutor interface. Will panic if the name were
|
|
// taken or nil executor.
|
|
func RegisterDirectiveExecutor(name string, exe DirectiveExecutor) {
|
|
if _, ok := executors[name]; ok {
|
|
panic(fmt.Sprintf("duplicate executor: %q", name))
|
|
}
|
|
ReplaceDirectiveExecutor(name, exe)
|
|
}
|
|
|
|
// ReplaceDirectiveExecutor works like RegisterDirectiveExecutor without panic
|
|
// on duplicate names.
|
|
func ReplaceDirectiveExecutor(name string, exe DirectiveExecutor) {
|
|
if exe == nil {
|
|
panic(fmt.Sprintf("nil executor: %q", name))
|
|
}
|
|
executors[name] = exe
|
|
debug("directive executor replaced: %q\n", name)
|
|
}
|
|
|
|
// DirectiveExecutorFunc is an adpator to allow to use of ordinary functions as
|
|
// httpin.DirectiveExecutor.
|
|
type DirectiveExecutorFunc func(*DirectiveContext) error
|
|
|
|
// Execute calls f(ctx).
|
|
func (f DirectiveExecutorFunc) Execute(ctx *DirectiveContext) error {
|
|
return f(ctx)
|
|
}
|
|
|
|
// DirectiveContext holds essential information about the field being resolved
|
|
// and the active HTTP request. Working as the context in a directive executor.
|
|
type DirectiveContext struct {
|
|
directive
|
|
ValueType reflect.Type
|
|
Value reflect.Value
|
|
Request *http.Request
|
|
Context context.Context
|
|
}
|
|
|
|
// DeliverContextValue binds a value to the specified key in the context. And it
|
|
// will be delivered among the executors in the same field resolver.
|
|
func (c *DirectiveContext) DeliverContextValue(key, value interface{}) {
|
|
c.Context = context.WithValue(c.Context, key, value)
|
|
}
|
|
|
|
// directive defines the profile to locate an httpin.DirectiveExecutor instance
|
|
// and drive it with essential arguments.
|
|
type directive struct {
|
|
Executor string // name of the executor
|
|
Argv []string // argv
|
|
}
|
|
|
|
// buildDirective builds a `directive` by parsing a directive string extracted
|
|
// from the struct tag.
|
|
//
|
|
// Example directives are:
|
|
// "form=page,page_index" -> { Executor: "form", Args: ["page", "page_index"] }
|
|
// "header=x-api-token" -> { Executor: "header", Args: ["x-api-token"] }
|
|
func buildDirective(directiveStr string) (*directive, error) {
|
|
parts := strings.SplitN(directiveStr, "=", 2)
|
|
executor := parts[0]
|
|
var argv []string
|
|
if len(parts) == 2 {
|
|
// Split the remained string by delimiter `,` as argv.
|
|
argv = strings.Split(parts[1], ",")
|
|
}
|
|
|
|
// Ensure that the corresponding executor had been registered.
|
|
dir := &directive{Executor: executor, Argv: argv}
|
|
if dir.getExecutor() == nil {
|
|
return nil, fmt.Errorf("%w: %q", ErrUnregisteredExecutor, dir.Executor)
|
|
}
|
|
|
|
// TODO(ggicci): hook custom validators, e.g. dir.Validate()
|
|
return dir, nil
|
|
}
|
|
|
|
// Execute locates the executor and runs it with the specified context.
|
|
func (d *directive) Execute(ctx *DirectiveContext) error {
|
|
return d.getExecutor().Execute(ctx)
|
|
}
|
|
|
|
// getExecutor locates the executor by its name. It must exist.
|
|
func (d *directive) getExecutor() DirectiveExecutor {
|
|
return executors[d.Executor]
|
|
}
|