lagogin

package module
v0.10.0 Latest Latest
Warning

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

Go to latest
Published: May 22, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package lagogin is the official Gin adapter for lagodev.

It mirrors the Laravel-style DX of the `web` package — `(any, error)` handler signature, automatic status mapping, Resource() one-liner, JWT middleware, pagination, validation, and OpenAPI generation — but builds on top of github.com/gin-gonic/gin instead of net/http.

Quick start:

r := gin.Default()
r.Use(lagogin.QueryLog(conn)) // X-DB-Query-Count header

lagogin.Resource(r, "posts", &PostController{Conn: conn})

api := r.Group("/api", lagogin.AuthJWT(authManager))
api.GET("/me", lagogin.H(meHandler))

r.GET("/posts", lagogin.H(func(c *lagogin.Ctx) (any, error) {
    return c.Paginate(orm.Query[Post](conn).OrderBy("id", "desc"))
}))

The handler signature `func(*lagogin.Ctx) (any, error)` removes the repetitive `if err != nil { c.JSON(...) }` boilerplate: orm.ErrNotFound is automatically mapped to 404, other errors to 500, nil to 204, and any returned value is JSON-encoded with 200 (or whatever Status() set).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func APIResource

func APIResource(r IRouter, name string, ctrl ResourceController)

APIResource is an alias for Resource — same behavior, Laravel-flavored name.

func Auth

func Auth() gin.HandlerFunc

Auth is the lightweight cousin of AuthJWT — only checks the header is present, stores the raw token, and lets the handler verify it however it likes. Use AuthJWT when you already have an auth.Manager handy.

func AuthJWT

func AuthJWT(m *auth.Manager) gin.HandlerFunc

AuthJWT verifies an Authorization: Bearer <token> header. Valid claims are stored on the gin.Context as "auth_user_id", "auth_role", "auth_claims" — retrievable via Ctx.UserID(), Ctx.Role(), or c.Get(...).

Invalid or missing tokens abort with 401 + JSON {"error": "..."}.

func CORS

func CORS(allowed ...string) gin.HandlerFunc

CORS returns a CORS middleware. Pass explicit origins to restrict; use "*" or no args for permissive.

func H

func H(h Handler) gin.HandlerFunc

H converts a Handler into a gin.HandlerFunc. orm.ErrNotFound is mapped to 404, validation errors to 422, any other error to 500. A `(value, nil)` return is JSON-encoded with status 200; `(nil, nil)` becomes 204.

func HS

func HS(handlers ...Handler) []gin.HandlerFunc

HS converts a series of handlers into gin.HandlerFunc slice for routes that take a final handler plus middleware. Equivalent to manually calling H on each.

func Instrument

func Instrument(conn *database.Connection) *database.Connection

Instrument enables per-connection query counting for QueryLog. Call once at startup before installing the middleware. The returned connection is the same pointer — Instrument only registers it with the global counter table and replaces conn.Log with a counting wrapper.

The wrapper delegates Info/Warn/Error/SQL/SlowSQL to the original logger, so SQL tracing and slow-query reporting continue to work unchanged.

func ObserveQuery

func ObserveQuery(conn *database.Connection)

ObserveQuery bumps the per-connection counter. Called by Connection observation hooks; tests and custom executors can call it directly.

func OpenAPI

func OpenAPI(info SpecInfo, opts ...Option) map[string]any

OpenAPI returns an OpenAPI 3.0 spec built from the Resource() registry. Each registered resource produces the canonical 5 endpoints plus a JSON schema (id-only by default — apps can pass a Models map to enrich it).

spec := lagogin.OpenAPI(lagogin.SpecInfo{Title: "MyAPI", Version: "1.0"})
r.GET("/openapi.json", lagogin.H(func(c *lagogin.Ctx) (any, error) { return spec, nil }))

To customize per-resource schemas (request/response bodies), pass a Schemas map keyed by resource name (the plural used in Resource()):

lagogin.OpenAPI(info, lagogin.WithSchema("posts", PostSchema()))

func QueryLog

func QueryLog(conn *database.Connection) gin.HandlerFunc

QueryLog returns a middleware that counts SQL queries for the lifetime of each request and writes the total in the X-DB-Query-Count response header. If the count crosses threshold (20 by default), a WARN is logged with the request path — a cheap N+1 detector for dev environments.

Requires the connection to be passed through Instrument() once at startup:

conn = lagogin.Instrument(conn)
r.Use(lagogin.QueryLog(conn))

Use QueryLogN to override the threshold.

func QueryLogN

func QueryLogN(conn *database.Connection, threshold int) gin.HandlerFunc

QueryLogN is QueryLog with a custom N+1 warning threshold.

func RegisterRoute

func RegisterRoute(method, path, resource, op string)

RegisterRoute records a custom route in the lagogin registry so it shows up in OpenAPI() output. Call this when you wire a handler outside of Resource() and still want it to appear in the generated spec.

r.GET("/health", lagogin.H(health))
lagogin.RegisterRoute("GET", "/health", "health", "index")

func RequestTimeout

func RequestTimeout(d time.Duration) gin.HandlerFunc

RequestTimeout aborts the request after d. The handler observes a canceled context.Context; long-running DB calls bail out automatically.

func ResetRoutes

func ResetRoutes()

ResetRoutes clears the route registry. Intended for tests that want a clean slate; production code should never call it.

func Resource

func Resource(r IRouter, name string, ctrl ResourceController)

Resource registers 5 canonical RESTful routes for a controller in one call:

GET    /posts            → ctrl.Index
GET    /posts/:id        → ctrl.Show
POST   /posts            → ctrl.Store
PUT    /posts/:id        → ctrl.Update
PATCH  /posts/:id        → ctrl.Update
DELETE /posts/:id        → ctrl.Destroy

`name` should be the plural resource name (e.g. "posts", "users"). The registered routes are also recorded in the lagogin route registry so OpenAPI() and PrintRoutes() can introspect them later.

func ServeOpenAPI

func ServeOpenAPI(r IRouter, info SpecInfo, opts ...Option)

ServeOpenAPI mounts /openapi.json and a simple /docs HTML page on r. The HTML uses Swagger UI from a public CDN; for offline environments, serve the JSON yourself and host UI assets locally.

func Validate

func Validate(dst any) error

Validate inspects struct tags on dst and runs lightweight validators against each field. Supported tag form:

`validate:"required,min=3,max=200,email,oneof=admin user"`

Supported rules:

required             — disallow zero values (including empty strings)
min=N                — int/float ≥ N, string length ≥ N, slice len ≥ N
max=N                — opposite of min
email                — basic RFC-ish email shape
url                  — must start with http:// or https://
oneof=a b c          — value must equal one of the listed tokens
alpha                — letters only
alphanumeric         — letters and digits only
uuid                 — 8-4-4-4-12 hex pattern

On failure Validate returns *ValidationError; respond() maps it to a 422 Unprocessable Entity with `{"errors": {...}}`.

func WriteOpenAPI

func WriteOpenAPI(path string, info SpecInfo, opts ...Option) error

WriteOpenAPI serializes the spec to a file at path. Convenient for committing a spec alongside the codebase.

Types

type Ctx

type Ctx struct {
	*gin.Context
}

Ctx wraps *gin.Context with helpers that match the lagodev web.Context API. Handlers receive *Ctx and return (any, error). The H wrapper converts the return value into a Gin response automatically.

func (*Ctx) Bind

func (c *Ctx) Bind(dst any) error

Bind decodes the JSON body into dst. On failure responds with 400 and returns the parse error so the handler can `return nil, err`.

func (*Ctx) BindAndValidate

func (c *Ctx) BindAndValidate(dst any) error

BindAndValidate combines Bind() and Validate(). Returns the same *ValidationError on rule failure (mapped to 422 by respond), or the underlying decoder error on malformed JSON (handled as 400 by Bind).

func (*Ctx) Created

func (c *Ctx) Created(v any) any

Created marks the response 201 and returns v so the handler can:

return c.Created(post), nil

func (*Ctx) Ctx

func (c *Ctx) Ctx() context.Context

Ctx returns the request's context.Context.

func (*Ctx) NoContent

func (c *Ctx) NoContent()

NoContent writes a 204 with no body.

func (*Ctx) Param

func (c *Ctx) Param(name string) string

Param returns a URL path parameter (e.g. ":id").

func (*Ctx) ParamInt

func (c *Ctx) ParamInt(name string) int

ParamInt parses a path parameter as int. Returns 0 on parse error.

func (*Ctx) ParamUint

func (c *Ctx) ParamUint(name string) uint64

ParamUint parses a path parameter as uint64. Returns 0 on parse error.

func (*Ctx) Query

func (c *Ctx) Query(name string) string

Query returns a URL query string parameter.

func (*Ctx) QueryDefault

func (c *Ctx) QueryDefault(name, fallback string) string

QueryDefault returns the query parameter, or fallback when empty.

func (*Ctx) QueryInt

func (c *Ctx) QueryInt(name string, fallback int) int

QueryInt parses a query parameter as int with a fallback.

func (*Ctx) Role

func (c *Ctx) Role() string

Role returns the auth role populated by AuthJWT, or "" when unauthenticated.

func (*Ctx) UserID

func (c *Ctx) UserID() uint64

UserID — populated by AuthJWT middleware. Returns 0 when unauthenticated.

type Handler

type Handler func(c *Ctx) (any, error)

Handler is the lagogin-style handler signature: `(any, error)`. The H() wrapper converts it into a `gin.HandlerFunc` with automatic status mapping. Use it like:

r.GET("/users/:id", lagogin.H(func(c *lagogin.Ctx) (any, error) {
    return svc.Get(c.Ctx(), c.ParamUint("id"))
}))

type IRouter

type IRouter interface {
	GET(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes
	POST(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes
	PUT(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes
	PATCH(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes
	DELETE(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes
}

IRouter is the minimum gin router surface lagogin needs. *gin.Engine and *gin.RouterGroup both satisfy it.

type Option

type Option func(*config)

Option configures OpenAPI().

func WithSchema

func WithSchema(name string, schema any) Option

WithSchema attaches a JSON schema for the named resource. Keys may be a resource name ("posts") or a model name ("Post").

type Page

type Page struct {
	Data     any   `json:"data"`
	Total    int64 `json:"total"`
	Page     int   `json:"page"`
	PerPage  int   `json:"per_page"`
	LastPage int   `json:"last_page"`
	From     int   `json:"from"`
	To       int   `json:"to"`
}

Page describes one paginated payload — a Laravel-style envelope so clients can render pagination UI without extra round-trips.

func Paginate

func Paginate[T any](c *Ctx, q *ormBuilder[T]) (Page, error)

Paginate runs a model-aware query through page/per_page query params and returns a Page envelope. The query is cloned-by-reference: callers should not reuse the builder after calling Paginate.

Example:

r.GET("/posts", lagogin.H(func(c *lagogin.Ctx) (any, error) {
    return lagogin.Paginate[Post](c, orm.Query[Post](conn).OrderBy("id", "desc"))
}))

Query params:

page      — 1-indexed page number (default 1)
per_page  — items per page (default 25, max 200)

The function uses orm.Builder[T] generics so the result type is correct at compile time and no reflection is needed beyond what the ORM already does.

type PaginatedQuery

type PaginatedQuery[T any] interface {
	Count(ctx context.Context) (int64, error)
	Limit(n int) *T
	Offset(n int) *T
}

PaginatedQuery is the contract Paginate() expects — both *orm.Builder[T] and *query.Builder satisfy it via a thin adapter.

type ResourceController

type ResourceController interface {
	Index(c *Ctx) (any, error)
	Show(c *Ctx) (any, error)
	Store(c *Ctx) (any, error)
	Update(c *Ctx) (any, error)
	Destroy(c *Ctx) (any, error)
}

ResourceController is the contract for Resource(r, name, ctrl). All 5 methods must be implemented; leave any of them returning (nil, nil) if you don't need the endpoint.

type RouteEntry

type RouteEntry struct {
	Method   string
	Path     string
	Resource string
	Op       string
}

RouteEntry is the public view of an internal routeInfo.

func Routes

func Routes() []RouteEntry

Routes returns a sorted snapshot of every Resource()-registered route. Useful for debug output or test assertions.

type SpecInfo

type SpecInfo struct {
	Title       string `json:"title"`
	Description string `json:"description,omitempty"`
	Version     string `json:"version"`
}

SpecInfo is the OpenAPI 3.0 info object — minimum metadata for a spec.

type ValidationError

type ValidationError struct {
	Message string
	Fields  map[string]string
}

ValidationError is the error type respond() recognizes and maps to 422. The Fields map is keyed by JSON field name (or struct field name when no `json:"..."` tag is present); each value is a human-readable message.

func (*ValidationError) Error

func (e *ValidationError) Error() string

Error implements the error interface.

Jump to

Keyboard shortcuts

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