Skip to main content

Custom 🔌

You can extend httpin by adding your custom directives.

Firstly you should understand the concepts of Directive and Directive Executor.

To add your custom directive, you need to:

Steps

1. Create a Directive Executor

by implementing the httpin_core.DirectiveExecutor interface:

type DirectiveCaseFormatter struct {
Transform func(string) string
}

func (f *DirectiveCaseFormatter) Decode(rtm *httpin_core.DirectiveRuntime) error {
if rtm.Value.Type().Elem().Kind() != reflect.String {
return errors.New("not a string")
}

currentValue := rtm.Value.Elem().String()
newValue := f.Transform(currentValue)
rtm.Value.Elem().SetString(newValue)
return nil
}

func (f *DirectiveCaseFormatter) Encode(rtm *httpin_core.DirectiveRuntime) error {
if rtm.Value.Type().Kind() != reflect.String {
return errors.New("not a string")
}

currentValue := rtm.Value.String()
newValue := f.Transform(currentValue)
rtm.Value.SetString(newValue)
return nil
}

2. Register your Directive to httpin

by calling httpin_core.RegisterDirective:

func init() {
httpin_core.RegisterDirective("to_lowercase", DirectiveCaseFormatter{
Transform: strings.ToLower,
})

httpin_core.RegisterDirective("to_uppercase", DirectiveCaseFormatter{
Transform: strings.ToUpper,
})
}

NB: make sure to register your directive executor before you start the server.

3. Use your Directive

type ListUsersInput struct {
Username string `in:"query=username;to_lowercase"`
Gender string `in:"query=gender;to_uppercase"`
}

Runable Example

package main

import (
"errors"
"fmt"
"net/http"
"net/http/httptest"
"reflect"
"strings"

"github.com/ggicci/httpin"
httpin_core "github.com/ggicci/httpin/core"
"github.com/go-chi/chi/v5"
)

type DirectiveCaseFormatter struct {
Transform func(string) string
}

func (f *DirectiveCaseFormatter) Decode(rtm *httpin_core.DirectiveRuntime) error {
if rtm.Value.Type().Elem().Kind() != reflect.String {
return errors.New("not a string")
}

currentValue := rtm.Value.Elem().String()
newValue := f.Transform(currentValue)
rtm.Value.Elem().SetString(newValue)
return nil
}

func (f *DirectiveCaseFormatter) Encode(rtm *httpin_core.DirectiveRuntime) error {
if rtm.Value.Type().Kind() != reflect.String {
return errors.New("not a string")
}

currentValue := rtm.Value.String()
newValue := f.Transform(currentValue)
rtm.Value.SetString(newValue)
return nil
}

func init() {
httpin_core.RegisterDirective("to_lowercase", &DirectiveCaseFormatter{
Transform: strings.ToLower,
})

httpin_core.RegisterDirective("to_uppercase", &DirectiveCaseFormatter{
Transform: strings.ToUpper,
})
}

type ListUsersInput struct {
Username string `in:"query=username;to_lowercase"`
Gender string `in:"query=gender;to_uppercase"`
}

func ListUsersHandler(rw http.ResponseWriter, r *http.Request) {
input := r.Context().Value(httpin.Input).(*ListUsersInput)
fmt.Printf("input: %#v\n", input)
}

func main() {
router := chi.NewRouter()

// Bind input struct with handler.
router.With(
httpin.NewInput(ListUsersInput{}),
).Get("/users", ListUsersHandler)

r, _ := http.NewRequest("GET", "/users?username=Ggicci&gender=male", nil)

rw := httptest.NewRecorder()
router.ServeHTTP(rw, r)
}