tanukirpc

package module
v0.8.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 18, 2026 License: MIT Imports: 22 Imported by: 4

README

tanukirpc

tanukirpc is a practical, fast-developing, type-safe, and easy-to-use RPC/Router library for Go. This library base on go-chi/chi.

Installation

go get -u github.com/mackee/tanukirpc

Usage

This is a simple example of how to use tanukirpc.

package main

import (
	"context"
	"fmt"
	"net/http"

	"github.com/mackee/tanukirpc"
)

type helloRequest struct {
	Name string `urlparam:"name"`
}

type helloResponse struct {
	Message string `json:"message"`
}

func hello(ctx tanukirpc.Context[struct{}], req helloRequest) (*helloResponse, error) {
	return &helloResponse{
		Message: fmt.Sprintf("Hello, %s!", req.Name),
	}, nil
}

func main() {
	r := tanukirpc.NewRouter(struct{}{})
	r.Get("/hello/{name}", tanukirpc.NewHandler(hello))

	if err := r.ListenAndServe(context.Background(), ":8080"); err != nil {
		fmt.Println(err)
	}
}

Features

  • ⭕ Type-safe request/response handler
  • ⭕ URL parameter, Query String, JSON, Form, or custom binding
  • ⭕ Request validation by go-playground/validator
  • ⭕ Custom error handling
  • ⭕ Registry injection
    • for a Dependency Injection
  • ⭕ A development server command that automatically restarts on file changes
    • use tanukiup command
  • ⭕ Generate TypeScript client code
    • use gentypescript command
  • ⭕ defer hooks for cleanup (works correctly with Context Transformation)
  • ⭕ Session management
  • ⭕ Authentication flow
    • ⭕ OpenID Connect
Registry injection and Context Transformation

Registry injection is a unique feature of tanukirpc. You can inject a registry object into the handler function via the Context.

A ContextFactory can generate a Context (and its associated Registry) for each request. For more details, please refer to _example/simple-registry.

You can define a cleanup function for the registry created by the factory. The NewContextHookFactory function accepts an optional closer function (func(ctx Context[Reg]) error). This function is automatically registered using ctx.Defer and executed after the handler finishes, making it suitable for releasing resources associated with the per-request registry.

// Example: Define a closer function when creating the factory
factory := tanukirpc.NewContextHookFactory(
    func(w http.ResponseWriter, req *http.Request) (*MyRegistry, error) {
        // ... create registry ...
        dbConn, err := connectToDB() // Example: Get a DB connection
        if err != nil {
            return nil, err
        }
        registry := &MyRegistry{db: dbConn}
        return registry, nil
    },
    func(ctx tanukirpc.Context[*MyRegistry]) error {
        // This function will be called after the handler
        registry := ctx.Registry()
        return registry.db.Close() // Example: Close the DB connection
    },
)
// Use WithContextFactory option
r := tanukirpc.NewRouter(struct{}{}, tanukirpc.WithContextFactory(factory))

Furthermore, tanukirpc allows composing contexts using RouteWithTransformer. This function takes an existing router (*Router[Reg1]), a Transformer[Reg1, Reg2], a route pattern, and a function to define routes within the transformed context (func(r *Router[Reg2])). It also accepts optional closer functions (func(ctx Context[Reg2]) error) specific to the transformed context.

This enables creating nested routing structures where inner routes operate with a different registry (Reg2) derived from the outer registry (Reg1). The transformer defines how to get Reg2 from Context[Reg1]. Importantly, Defer calls registered in the outer context (Context[Reg1]) are correctly executed even when the request is handled within the inner, transformed context (Context[Reg2]). The closer functions provided to RouteWithTransformer are executed after the inner handlers complete, allowing for resource cleanup specific to the transformed context.

// Example: Using RouteWithTransformer
type OuterRegistry struct { /* ... */ }
type InnerRegistry struct { /* ... derived from OuterRegistry */ }

// Define how to transform OuterRegistry context to InnerRegistry
transformer := tanukirpc.NewTransformer(func(ctx tanukirpc.Context[*OuterRegistry]) (*InnerRegistry, error) {
    // ... logic to create InnerRegistry from ctx.Registry() ...
    innerReg := &InnerRegistry{ /* ... */ }
    // Register a defer function in the *outer* context if needed
    ctx.Defer(func() error { fmt.Println("Outer context defer"); return nil })
    return innerReg, nil
})

// Define a closer for the inner context
innerCloser := func(ctx tanukirpc.Context[*InnerRegistry]) error {
    fmt.Println("Closing inner context resources")
    // ... cleanup for InnerRegistry ...
    return nil
}

outerRouter := tanukirpc.NewRouter(&OuterRegistry{})

// Create nested routes with a transformed context
tanukirpc.RouteWithTransformer(outerRouter, transformer, "/inner", func(innerRouter *tanukirpc.Router[*InnerRegistry]) {
    innerRouter.Get("/data", tanukirpc.NewHandler(func(ctx tanukirpc.Context[*InnerRegistry], req struct{}) (*struct{}, error) {
        // Handler uses InnerRegistry via ctx.Registry()
        ctx.Defer(func() error { fmt.Println("Inner context defer"); return nil }) // Defer in inner context
        fmt.Println("Handling request with inner context")
        return &struct{}{}, nil
    }))
}, innerCloser) // Pass the closer for the inner context
Use case
  • Database connection management (per-request or shared)
  • Logger configuration per route group
  • Authentication/Authorization context layering
  • Resource binding by path parameter. Examples can be found in _example/todo.
Request binding

tanukirpc supports the following request bindings by default:

  • URL parameter (like a /entity/{id} path): use the urlparam struct tag
  • Query String: use the query struct tag
  • JSON (application/json): use the json struct tag
  • Form (application/x-www-form-urlencoded): use the form struct tag
  • Raw Body: use the rawbody struct tag with []byte or io.ReadCloser
    • also support naked []byte or io.ReadCloser

If you want to use other bindings, you can implement the tanukirpc.Codec interface and specify it using the tanukirpc.WithCodec option when initializing the router.

tanukirpc.NewRouter(YourRegistry, tanukirpc.WithCodec(yourCodec))
Request validation

tanukirpc automatically validation by go-playground/validator when contains validate struct tag in request struct.

type YourRequest struct {
    Name string `form:"name" validate:"required"`
}

If you want to use custom validation, you can implement the tanukirpc.Validatable interface in your request struct. tanukirpc will call the Validatable.Validate method after binding the request and before calling the handler function.

Error handling

tanukirpc has a default error handler. If you want to use custom error handling, you can implement the tanukirpc.ErrorHooker interface and use this with the tanukirpc.WithErrorHooker option when initializing the router.

Response with Status Code

If you want to return a response with a specific status code, you can use the tanukirpc.WrapErrorWithStatus.

// this handler returns a 404 status code
func notFoundHandler(ctx tanukirpc.Context[struct{}], struct{}) (*struct{}, error) {
    return nil, tanukirpc.WrapErrorWithStatus(http.StatusNotFound, errors.New("not found"))
}

Also, you can use the tanukirpc.ErrorRedirectTo function. This function returns a response with a 3xx status code and a Location header.

// this handler returns a 301 status code
func redirectHandler(ctx tanukirpc.Context[struct{}], struct{}) (*struct{}, error) {
    return nil, tanukirpc.ErrorRedirectTo(http.StatusMovedPermanently, "/new-location")
}
Middleware

You can use tanukirpc with go-chi/chi/middleware or func (http.Handler) http.Handler style middlewares. gorilla/handlers is also included in this.

If you want to use middleware, you can use *Router.Use or *Router.With.

tanukiup command

The tanukiup command is very useful during development. When you start your server via the tanukiup command, it detects file changes, triggers a build, and restarts the server.

Usage

You can use the tanukiup command as follows:

$ go run github.com/mackee/tanukirpc/cmd/tanukiup -dir ./...
  • The -dir option specifies the directory to be watched. By appending ... to the end, it recursively includes all subdirectories in the watch scope. If you want to exclude certain directories, use the -ignore-dir option. You can specify multiple directories by providing comma-separated values or by using the option multiple times. By default, the server will restart when files with the .go extension are updated.

  • The -addr option allows the tanukiup command to act as a server itself. After building and starting the server application created with tanukirpc, it proxies requests to this process. The application must be started with *tanukirpc.Router.ListenAndServe; otherwise, the -addr option will not function. Only the paths registered with tanukirpc.Router will be proxied to the server application.

  • Additionally, there is an option called -catchall-target that can be used in conjunction with -addr. This option allows you to proxy requests for paths that are not registered with tanukirpc.Router to another server address. This is particularly useful when working with a frontend development server (e.g., webpack, vite).

Additionally, it detects the go:generate lines for the gentypescript command mentioned later, and automatically runs them before restarting.

Client code generation

A web application server using tanukirpc can generate client-side code based on the type information of each endpoint.

gentypescript generates client-side code specifically for TypeScript. By using the generated client implementation, you can send and receive API requests with type safety for each endpoint.

To generate the client code, first call genclient.AnalyzeTarget with the router as an argument to clearly define the target router.

Next, add the following go:generate line:

//go:generate go run github.com/mackee/tanukirpc/cmd/gentypescript -out ./frontend/src/client.ts ./

The -out option specifies the output file name. Additionally, append ./ to specify the package to be analyzed.

When you run go generate ./ in the package containing this file, or when you start the server via the aforementioned tanukiup command, the TypeScript client code will be generated.

For more detailed usage, refer to the _example/todo directory.

Defer hooks

tanukirpc supports defer hooks for cleanup. You can register a function using ctx.Defer to be called after the handler function has been executed. These hooks are executed in LIFO (Last-In, First-Out) order.

Defer supports three timings:

  • DeferDoTimingAfterResponse (default): Executes after the response has been written. Suitable for cleanup tasks like closing connections or logging.
  • DeferDoTimingBeforeCheckError: Executes before checking if the response is an error. This timing is useful for actions that should occur regardless of whether the response is an error, such as logging or cleanup tasks. It will run even if the handler returns an error using tanukirpc.WrapErrorWithStatus, unlike the other timings.
  • DeferDoTimingBeforeResponse: Executes before the response is written. Useful for modifying headers or performing actions just before sending the response.

Deferred functions work correctly even when using RouteWithTransformer. Functions deferred in an outer context will execute after functions deferred in an inner context (respecting LIFO order across context boundaries).

func myHandler(ctx tanukirpc.Context[struct{}], req myRequest) (*myResponse, error) {
    // This will run after the response is sent (default)
    ctx.Defer(func() error {
        fmt.Println("Cleanup after response")
        // Close the database connection, release resources, logging, enqueue job etc...
        return nil
    })

    // This will run just before the response is sent
    ctx.Defer(func() error {
        fmt.Println("Action before response")
        ctx.Response().Header().Set("X-Custom-Header", "value")
        return nil
    }, tanukirpc.DeferDoTimingBeforeResponse)

    fmt.Println("Handler logic executing...")
    return &myResponse{Data: "Success"}, nil
}

fnc myErrorHandler(ctx tanukirpc.Context[struct{}], req myRequest) (*myResponse, error) {
    // This will run before checking if the response is an error
    ctx.Defer(func() error {
        fmt.Println("Cleanup before checking error")
        return nil
    }, tanukirpc.DeferDoTimingBeforeCheckError)

    // Simulate an error
    return nil, tanukirpc.WrapErrorWithStatus(http.StatusInternalServerError, errors.New("something went wrong"))
}
Session Management

tanukirpc provides convenient utilities for session management. You can use the gorilla/sessions package or other session management libraries.

To get started, create a session store and wrap it using tanukirpc/auth/gorilla.NewStore.

import (
    "github.com/gorilla/sessions"
    "github.com/mackee/tanukirpc/sessions/gorilla"
    tsessions "github.com/mackee/tanukirpc/sessions"
)

func newStore(secrets []byte) (tsessions.Store, error) {
    sessionStore := sessions.NewCookieStore(secrets)
    store, err := gorilla.NewStore(sessionStore)
    if err != nil {
        return nil, err
    }
    return store, nil
}

In RegistryFactory, you can create a session using the tanukirpc/sessions.Store.

type RegistryFactory struct {
    Store tsessions.Store
}

type Registry struct {
    sessionAccessor tsessions.Accessor
}

func (r *RegistryFactory) NewRegistry(w http.ResponseWriter, req *http.Request) (*Registry, error) {
	accessor, err := r.Store.GetAccessor(req)
	if err != nil {
		if tsessions.IsInvalidSessionError(err) {
			// Decide how to recover in your application, for example:
			// - expire the broken cookie
			// - return tanukirpc.ErrorRedirectTo(...)
			// - return tanukirpc.WrapErrorWithStatus(...)
		}
		return nil, fmt.Errorf("failed to get session accessor: %w", err)
	}

    return &Registry{
        sessionAccessor: accessor,
    }, nil
}

func (r *Registry) Session() tsessions.Accessor {
    return r.sessionAccessor
}

The Registry type implements the tanukirpc/sessions.RegistryWithAccessor interface.

Authentication Flow

tanukirpc supports the OpenID Connect authentication flow. You can use the tanukirpc/auth/oidc.NewHandlers function to create handlers for this flow, which includes a set of handlers to facilitate user authentication.

Requirements

tanukirpc/auth/oidc.Handlers requires a Registry that implements the tanukirpc/sessions.RegistryWithAccessor interface. For more details, refer to the Session Management section.

Usage
oidcAuth := oidc.NewHandlers(
    oauth2Config, // *golang.org/x/oauth2.Config
    provider,     // *github.com/coreos/go-oidc/v3/oidc.Provider
)
router.Route("/auth", func(router *tanukirpc.Router[*Registry]) {
    router.Get("/redirect", tanukirpc.NewHandler(oidcAuth.Redirect))
    router.Get("/callback", tanukirpc.NewHandler(oidcAuth.Callback))
    router.Get("/logout", tanukirpc.NewHandler(oidcAuth.Logout))
})

License

Copyright (c) 2024- mackee

Licensed under MIT License.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrRequestNotSupportedAtThisCodec  = errors.New("request not supported at this codec")
	ErrRequestContinueDecode           = errors.New("request continue decode")
	ErrResponseNotSupportedAtThisCodec = errors.New("response not supported at this codec")
	DefaultCodecList                   = CodecList{
		NewURLParamCodec(),
		NewQueryCodec(),
		NewFormCodec(),
		NewJSONCodec(),
		NewRawBodyCodec(),
		&nopCodec{},
	}
)

Functions

func ErrorRedirectTo added in v0.4.0

func ErrorRedirectTo(status int, redirect string) error

func NewFormCodec

func NewFormCodec() *codec

NewFormCodec returns a new FormCodec. This codec supports request decoding only. The content type header of the request is application/x-www-form-urlencoded. If you want to use this codec, you need to set the struct field tag like a `form:"name"`.

func NewJSONCodec

func NewJSONCodec() *codec

NewJSONCodec returns a new JSONCodec. This codec supports request and response encoding and decoding. The content type header of the request is application/json and */*, and the content type of the response is application/json.

func NewLogger added in v0.2.0

func NewLogger(logger *slog.Logger, keys []fmt.Stringer) *slog.Logger

NewLogger returns a new logger with the given logger. This logger output with the informwation with request ID. If the given logger is nil, it returns use the default logger. keys is the whitelist of keys that use read from context.Context.

func NewQueryCodec

func NewQueryCodec() *queryCodec

NewQueryCodec returns a new QueryCodec. This codec supports request decoding only. If you want to query parameter that like a /hello?name=world, you can set the struct field tag like a `query:"name"`.

func NewURLParamCodec

func NewURLParamCodec() *urlParamCodec

NewURLParamCodec returns a new URLParamCodec. This codec supports request decoding only. If you want to url parameter that like a /hello/{name}, you can set the struct field tag like a `urlparam:"name"`.

func URLParam

func URLParam[Reg any](ctx Context[Reg], name string) string

func WrapErrorWithStatus

func WrapErrorWithStatus(status int, err error) error

Types

type AccessLogger added in v0.2.0

type AccessLogger interface {
	Log(ctx gocontext.Context, logger *slog.Logger, ww WrapResponseWriter, req *http.Request, err error, t1 time.Time, t2 time.Time) error
}

type Codec

type Codec interface {
	Name() string
	Decode(r *http.Request, v any) error
	Encode(w http.ResponseWriter, r *http.Request, v any) error
}

Codec is a interface for encoding and decoding request and response.

type CodecList

type CodecList []Codec

CodecList is list of Codec. This codec process the request and response in order.

func (CodecList) Decode

func (c CodecList) Decode(r *http.Request, v any) error

func (CodecList) Encode

func (c CodecList) Encode(w http.ResponseWriter, r *http.Request, v any) error

func (CodecList) Name

func (c CodecList) Name() string

type Context

type Context[Reg any] interface {
	gocontext.Context
	Request() *http.Request
	Response() http.ResponseWriter
	Registry() Reg
	Defer(fn DeferFunc, priority ...DeferDoTiming)
	DeferDo(priority DeferDoTiming) error
}

type ContextFactory

type ContextFactory[Reg any] interface {
	Build(w http.ResponseWriter, req *http.Request) (Context[Reg], error)
}

func NewContextHookFactory

func NewContextHookFactory[Reg any](fn func(w http.ResponseWriter, req *http.Request) (Reg, error), closer ...func(ctx Context[Reg]) error) ContextFactory[Reg]

type Decoder

type Decoder interface {
	Decode(v any) error
}

type DecoderFunc

type DecoderFunc func(r io.Reader) Decoder

type DefaultContextFactory

type DefaultContextFactory[Reg any] struct {
	// contains filtered or unexported fields
}

func (*DefaultContextFactory[Reg]) Build

func (d *DefaultContextFactory[Reg]) Build(w http.ResponseWriter, req *http.Request) (Context[Reg], error)

type DeferDoTiming added in v0.2.0

type DeferDoTiming int
const (
	DeferDoTimingBeforeResponse DeferDoTiming = iota
	DeferDoTimingAfterResponse
	DeferDoTimingBeforeCheckError
)

type DeferFunc added in v0.2.0

type DeferFunc func() error

type DeferFuncCallerStack added in v0.2.0

type DeferFuncCallerStack struct {
	PC   uintptr
	File string
	Line int
	Ok   bool
}

func (*DeferFuncCallerStack) String added in v0.2.0

func (d *DeferFuncCallerStack) String() string

type DeferFuncError added in v0.2.0

type DeferFuncError struct {
	Err    error
	Caller *DeferFuncCallerStack
}

func (*DeferFuncError) Error added in v0.2.0

func (d *DeferFuncError) Error() string

func (*DeferFuncError) Unwrap added in v0.2.0

func (d *DeferFuncError) Unwrap() error

type Encoder

type Encoder interface {
	Encode(v any) error
}

type EncoderFunc

type EncoderFunc func(w io.Writer) Encoder

type ErrCodecDecode

type ErrCodecDecode struct {
	// contains filtered or unexported fields
}

func (*ErrCodecDecode) Error

func (e *ErrCodecDecode) Error() string

func (*ErrCodecDecode) Unwrap

func (e *ErrCodecDecode) Unwrap() error

type ErrCodecEncode

type ErrCodecEncode struct {
	// contains filtered or unexported fields
}

func (*ErrCodecEncode) Error

func (e *ErrCodecEncode) Error() string

func (*ErrCodecEncode) Unwrap

func (e *ErrCodecEncode) Unwrap() error

type ErrorBody

type ErrorBody struct {
	Message string `json:"message"`
}

type ErrorHooker

type ErrorHooker interface {
	OnError(w http.ResponseWriter, req *http.Request, logger *slog.Logger, codec Codec, err error)
}

type ErrorMessage

type ErrorMessage struct {
	Error ErrorBody `json:"error"`
}

type ErrorWithRedirect added in v0.4.0

type ErrorWithRedirect interface {
	error
	Status() int
	Redirect() string
}

type ErrorWithStatus

type ErrorWithStatus interface {
	error
	Status() int
}

type Handler

type Handler[Reg any] interface {
	// contains filtered or unexported methods
}

func NewHandler

func NewHandler[Req any, Res any, Reg any](h HandlerFunc[Req, Res, Reg]) Handler[Reg]

type HandlerFunc

type HandlerFunc[Req any, Res any, Reg any] func(Context[Reg], Req) (Res, error)

type ListenAndServeOption added in v0.3.0

type ListenAndServeOption func(*listenAndServeConfig)

func WithDisableTanukiupProxy added in v0.3.0

func WithDisableTanukiupProxy() ListenAndServeOption

func WithNoSetDefaultLogger added in v0.3.1

func WithNoSetDefaultLogger() ListenAndServeOption

func WithShutdownTimeout added in v0.3.0

func WithShutdownTimeout(d time.Duration) ListenAndServeOption

type RawBodyCodec added in v0.4.0

type RawBodyCodec struct{}

RawBodyCodec is a codec that reads the request body as is.

func NewRawBodyCodec added in v0.4.0

func NewRawBodyCodec() *RawBodyCodec

func (*RawBodyCodec) Decode added in v0.4.0

func (r *RawBodyCodec) Decode(req *http.Request, v any) error

func (*RawBodyCodec) Encode added in v0.4.0

func (r *RawBodyCodec) Encode(w http.ResponseWriter, req *http.Request, v any) error

func (*RawBodyCodec) Name added in v0.4.0

func (r *RawBodyCodec) Name() string

type Router

type Router[Reg any] struct {
	// contains filtered or unexported fields
}

func NewRouter

func NewRouter[Reg any](reg Reg, opts ...RouterOption[Reg]) *Router[Reg]

NewRouter creates a new Router.

The registry is used to create a context.

func RouteWithTransformer

func RouteWithTransformer[Reg1 any, Reg2 any](r *Router[Reg1], tr Transformer[Reg1, Reg2], pattern string, fn func(r *Router[Reg2]), closer ...func(Context[Reg2]) error) *Router[Reg1]

func (*Router[Reg]) Connect

func (r *Router[Reg]) Connect(pattern string, h Handler[Reg])

func (*Router[Reg]) Delete

func (r *Router[Reg]) Delete(pattern string, h Handler[Reg])

func (*Router[Reg]) Get

func (r *Router[Reg]) Get(pattern string, h Handler[Reg])

func (*Router[Reg]) Head

func (r *Router[Reg]) Head(pattern string, h Handler[Reg])

func (*Router[Reg]) ListenAndServe added in v0.3.0

func (r *Router[Reg]) ListenAndServe(ctx gocontext.Context, addr string, opts ...ListenAndServeOption) error

ListenAndServe starts the server. If the context is canceled, the server will be shutdown.

func (*Router[Reg]) MethodNotAllowed

func (r *Router[Reg]) MethodNotAllowed(h Handler[Reg])

func (*Router[Reg]) Mount

func (r *Router[Reg]) Mount(pattern string, h http.Handler)

func (*Router[Reg]) NotFound

func (r *Router[Reg]) NotFound(h Handler[Reg])

func (*Router[Reg]) Options

func (r *Router[Reg]) Options(pattern string, h Handler[Reg])

func (*Router[Reg]) Patch

func (r *Router[Reg]) Patch(pattern string, h Handler[Reg])

func (*Router[Reg]) Post

func (r *Router[Reg]) Post(pattern string, h Handler[Reg])

func (*Router[Reg]) Put

func (r *Router[Reg]) Put(pattern string, h Handler[Reg])

func (*Router[Reg]) Route

func (r *Router[Reg]) Route(pattern string, fn func(r *Router[Reg])) *Router[Reg]

func (*Router[Reg]) ServeHTTP

func (r *Router[Reg]) ServeHTTP(w http.ResponseWriter, req *http.Request)

func (*Router[Reg]) Trace

func (r *Router[Reg]) Trace(pattern string, h Handler[Reg])

func (*Router[Reg]) Use

func (r *Router[Reg]) Use(middlewares ...func(http.Handler) http.Handler)

func (*Router[Reg]) With

func (r *Router[Reg]) With(middlewares ...func(http.Handler) http.Handler) *Router[Reg]

type RouterOption

type RouterOption[Reg any] func(*Router[Reg]) *Router[Reg]

func WithAccessLogger added in v0.2.0

func WithAccessLogger[Reg any](al AccessLogger) RouterOption[Reg]

func WithChiRouter

func WithChiRouter[Reg any](cr chi.Router) RouterOption[Reg]

func WithCodec

func WithCodec[Reg any](codec Codec) RouterOption[Reg]

func WithContextFactory

func WithContextFactory[Reg any](cf ContextFactory[Reg]) RouterOption[Reg]

func WithDefaultMiddleware added in v0.2.0

func WithDefaultMiddleware[Reg any](middlewares ...func(http.Handler) http.Handler) RouterOption[Reg]

func WithErrorHooker

func WithErrorHooker[Reg any](eh ErrorHooker) RouterOption[Reg]

func WithLogger added in v0.2.0

func WithLogger[Reg any](logger *slog.Logger) RouterOption[Reg]

type Transformer

type Transformer[Reg1 any, Reg2 any] interface {
	Transform(ctx Context[Reg1]) (Reg2, error)
}

func NewTransformer

func NewTransformer[Reg1 any, Reg2 any](fn func(ctx Context[Reg1]) (Reg2, error)) Transformer[Reg1, Reg2]

type Validatable

type Validatable interface {
	Validate() error
}

type ValidateError

type ValidateError struct {
	// contains filtered or unexported fields
}

func (*ValidateError) Error

func (v *ValidateError) Error() string

func (*ValidateError) Status

func (v *ValidateError) Status() int

func (*ValidateError) Unwrap

func (v *ValidateError) Unwrap() error

type WrapResponseWriter added in v0.2.0

type WrapResponseWriter interface {
	http.ResponseWriter
	Status() int
	BytesWritten() int
}

Directories

Path Synopsis
_example
simple-use command
todo module
auth
oidc
Package oidc provides handlers for OIDC authentication.
Package oidc provides handlers for OIDC authentication.
cmd
gentypescript command
showpaths command
tanukiup command
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL