restruct
Guide for using the Restruct package for struct-based routing and rendering.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Guide for using the Restruct package for struct-based routing and rendering.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
| name | Restruct |
| description | Guide for using the Restruct package for struct-based routing and rendering. |
Restruct is a Go package that provides struct-based routing, automatic parameter extraction, and a view engine with template support. It relies on reflection to map struct methods to HTTP routes and assumes a specific project structure.
fs.FS.Restruct maps struct methods to HTTP routes using naming conventions and struct tags.
Use the route tag on struct fields to define the base path for a nested service.
route:"users" -> Maps the struct to /usersroute:"-" -> Ignores the field (won't be registered as a service).route:"" -> Uses the field name (kebab-cased) as the path.Method names are automatically converted to route paths:
CamelCase -> camel-caseUnderscore_Separated -> / separator (e.g., Hello_World -> hello/world)Index -> Root of the service (/)Any -> Catch-all wildcard /{any*}Suffix_Any -> Wildcard after path (e.g., Files_Any -> files/{any*})_0, _1, etc. -> Path parameters /{0}, /{1} (e.g., Item_0 -> item/{0})Examples:
func (s *Svc) CreateUser() -> .../create-userfunc (s *Svc) Hello_World() -> .../hello/worldfunc (s *Svc) Item_0() -> .../item/{0}func (s *Svc) Any() -> .../{any*}func (s *Svc) Link_Any() -> .../link/{any*}Router interface)Implement the Routes() []rs.Route method on your struct to explicitly define routes, HTTP methods, and per-route middleware.
Handler accepts a string (method name) or a func (used directly):
func (u *User) Routes() []rs.Route {
return []rs.Route{
{Handler: u.CreateUser, Path: ".", Methods: []string{http.MethodPost}},
{Handler: "ReadUser", Path: "{id}", Methods: []string{http.MethodGet}},
{Handler: "UpdateUser", Path: "{id}", Methods: []string{http.MethodPut}},
{Handler: "DeleteUser", Path: "{id}", Methods: []string{http.MethodDelete}},
}
}
Handler: "MethodName" — Resolves to the struct method by name.Handler: u.MethodName or Handler: myFunc — Uses the func directly (same signature rules as regular handlers).Path: "." maps to the service root (e.g., POST /users instead of POST /users/create-user).Path: "{id}" adds a parameter segment.Path uses the default naming convention for the handler method name.Methods allows all HTTP methods.Middlewares on a Route applies only to that specific route.Handlers are methods on your service structs. Restruct supports dependency injection for handler arguments.
Common arguments injected automatically:
*http.Requesthttp.ResponseWritercontext.ContextRequestReader)Return Values:
http.ResponseWriter).error: Returns an error response (handled by ResponseWriter).any / interface{}: Serialized to JSON by the DefaultWriter.*rs.Render: Triggers HTML template rendering at a specific path.*rs.Response: Manual control over status, content-type, headers, and body.*rs.Json: JSON response with a custom status code.(int, any, error): Status code, response body, and error.(any, error): Response body and error.To bind request data (JSON body, query params, form data) to a struct, add the struct pointer or value as an argument to your handler.
type CreateRequest struct {
Name string `json:"name"`
}
func (s *Service) Create(ctx context.Context, req *CreateRequest) any {
// req is populated automatically via RequestReader
return req
}
Inline struct arguments are also supported:
func (c *Calculator) Add(req struct {
A int `json:"a"`
B int `json:"b"`
}) int {
return req.A + req.B
}
Access path parameters via context:
rs.Params(r)["id"] // from *http.Request
rs.Vars(ctx)["id"] // from context.Context
rs.Vars(ctx)["0"] // for auto-generated numeric params (_0, _1, etc.)
rs.Vars(ctx)["any"] // for wildcard catch-all routes
Store and retrieve arbitrary values from the request context (e.g., in middleware):
// Using *http.Request (returns new *http.Request)
r = rs.SetValue(r, "userID", int64(1))
userID := rs.GetValue(r, "userID").(int64)
vals := rs.GetValues(r) // map[string]interface{}
// Using context.Context directly
ctx = rs.SetVal(ctx, "key", "value")
val := rs.GetVal(ctx, "key")
vals := rs.GetVals(ctx) // map[string]interface{}
The rs.View struct implements ResponseWriter and handles template rendering and static file serving.
Implement the Writer interface on your service to associate a View:
func (s *Server) Writer() rs.ResponseWriter {
f, _ := fs.Sub(publicFS, "public")
return &rs.View{
FS: f, // fs.FS (embed.FS, os.DirFS, etc.)
Funcs: template.FuncMap{...}, // Custom template functions
Skips: regexp.MustCompile("^layout"), // Skip files matching regex from routing
Layouts: []string{"layout/*.html"}, // Glob patterns for layout templates
Error: "error.html", // Template for error pages
Data: func(r *http.Request) map[string]any { ... }, // Global template data
Writer: &rs.DefaultWriter{}, // Fallback writer for non-view responses
}
}
| Field | Type | Description |
|---|---|---|
FS | fs.FS | Source file system (required) |
Funcs | template.FuncMap | Custom template functions |
Skips | *regexp.Regexp | Regex to skip files from being routed |
Layouts | []string | Glob patterns for layout/partial templates |
Error | string | Error template path (rendered on errors if Any route matched) |
Data | func(*http.Request) map[string]any | Callback for default template data |
Writer | ResponseWriter | Fallback writer for non-template responses |
Templates receive a map[string]any with:
Request: The current *http.Request{{.id}}, {{.profile}})Data callbackmap[string]any, otherwise available as {{.Data}}If FS implements fs.ReadDirFS, View automatically registers routes for .html and .tmpl files:
index.html -> /about.html -> /aboutblog/post.html -> /blog/postReturn *rs.Render from a handler to render a specific template, regardless of the URL:
func (s *Service) Dashboard(ctx context.Context) (*rs.Render, error) {
return &rs.Render{
Path: "dashboard/main.html",
Data: map[string]interface{}{
"Title": "Dashboard",
},
}, nil
}
The RequestReader interface controls how request data is bound to handler arguments.
type RequestReader interface {
Read(*http.Request, []reflect.Type) ([]reflect.Value, error)
}
The DefaultReader binds JSON body, URL-encoded forms, and multipart forms. Customize the Bind function to add validation:
h.Reader = &rs.DefaultReader{Bind: func(r *http.Request, out interface{}, methods ...string) error {
if err := rs.Bind(r, out, methods...); err != nil {
return err
}
return validate.Struct(out)
}}
rs.Bind(r, out, methods...) — Main bind: dispatches to JSON, form, or query based on content type.rs.BindJson(r, out) — Bind JSON body.rs.BindQuery(r, out) — Bind query string params (uses query struct tag).rs.BindForm(r, out) — Bind form/multipart data (uses form struct tag).The ResponseWriter interface controls how handler return values are sent to the client.
type ResponseWriter interface {
Write(http.ResponseWriter, *http.Request, []reflect.Type, []reflect.Value)
}
Handles JSON output with error mapping. Configurable via:
ErrorHandler func(error) any — Custom error formatting. Return *rs.Response for full control.Errors map[error]Error — Map known errors to custom HTTP statuses/messages.EscapeJsonHtml bool — Whether to escape HTML in JSON output.rs.Response: Full control over status, headers, content-type, and body bytes.rs.Json: JSON response with a custom status code: rs.Json{Status: 201, Content: obj}.rs.Error: Error with status, message, data, and wrapped error.Middleware is the standard func(http.Handler) http.Handler signature.
h.Use(middleware) in Init.Middlewares() []rs.Middleware on the service struct.Middlewares in the Route struct returned from Routes().rs.Recovery — Panic recovery middleware; logs stack trace and returns 500 error.Implement Init(*rs.Handler) to configure the handler after creation:
func (s *Server) Init(h *rs.Handler) {
h.Writer = &rs.DefaultWriter{ErrorHandler: myErrorHandler}
h.Reader = &rs.DefaultReader{Bind: myBind}
h.Use(loggingMiddleware)
}
rs.Handle(pattern, svc) — Register a service on http.DefaultServeMux.rs.NewHandler(svc) — Create a Handler without registering on a mux.h.WithPrefix(prefix) — Set a URL prefix for the handler.h.AddService(path, svc) — Add a sub-service at runtime.h.Routes() — List all registered routes (useful for debugging/docs).h.Use(middleware...) — Add global middleware.rs.MaxBodySize — Maximum request body size for BindJson (default: 10MB).package main
import (
"context"
"net/http"
rs "github.com/altlimit/restruct"
)
type API struct {
User User `route:"users"`
}
type User struct{}
func (u *User) Routes() []rs.Route {
return []rs.Route{
{Handler: "Get", Path: "{id}", Methods: []string{"GET"}},
}
}
func (u *User) Get(ctx context.Context) any {
id := rs.Vars(ctx)["id"]
return map[string]string{"id": id}
}
func main() {
rs.Handle("/", &API{})
http.ListenAndServe(":8080", nil)
}
structtagUtility package for struct tag parsing with caching. Used internally for query and form tag binding.
structtag.GetFieldsByTag(obj, tagName) — Returns []*StructField for all fields with the given tag.structtag.NewStructField(index, tag) — Parses a comma-separated key=value tag string.