Documentation
¶
Overview ¶
Package api is the REST layer: it mounts every operation registered in kernel.APIRegistry under /api/v1 (with scope enforcement, panic recovery and a JSON default content type), serves the generated OpenAPI 3.1 document at /api/v1/openapi.json and the Scalar reference at /api/docs, and provides the core operations (/site, /me, /health) plus helpers (pagination, decoding, error envelopes) for other modules' handlers.
Index ¶
- Constants
- Variables
- func Decode(w http.ResponseWriter, r *http.Request, v any) error
- func Fail(w http.ResponseWriter, r *http.Request, err error)
- func NotFound(w http.ResponseWriter)
- func Op(method, path, summary string, tags, scopes []string, req, resp any, ...) kernel.APIOperation
- func Pagination(r *http.Request) (page, perPage int)
- func ParseValidation(err error) (status int, fields map[string]string)
- func Sort(r *http.Request, allowed []string) (by, dir string)
- func WriteError(w http.ResponseWriter, status int, code, msg string, fields map[string]string)
- func WriteItem(w http.ResponseWriter, v any)
- func WriteList(w http.ResponseWriter, items any, total, page, perPage int)
- type DecodeError
- type FieldErrorer
- type Health
- type InformationValue
- type Me
- type MeKey
- type MeUser
- type Module
- type PublicSiteIdentity
- type SiteInfo
- type SiteInformation
- type SiteLanguages
- type SiteLogo
- type SiteLogos
- type SiteRuntime
- type SiteStructure
Constants ¶
const ( CodeBadRequest = "bad_request" CodeInvalidJSON = "invalid_json" CodeEmptyBody = "empty_body" CodeForbidden = "forbidden" CodeNotFound = "not_found" CodeMethodNotAllowed = "method_not_allowed" CodePayloadTooLarge = "payload_too_large" CodeValidation = "validation_failed" CodeRateLimited = "rate_limited" CodeInternal = "internal_error" )
Error codes used in the error envelope.
const ( DefaultPerPage = 20 MaxPerPage = 100 )
Pagination defaults.
const ContractVersion = "1"
Variables ¶
var CoreVersion = "dev"
CoreVersion is the binary build version; Version remains the API family.
var PageParams = []kernel.APIParam{
{Name: "page", In: "query", Type: "integer", Description: "Page number, from 1 (default 1)."},
{Name: "per_page", In: "query", Type: "integer", Description: "Items per page (default 20, max 100)."},
{Name: "sort", In: "query", Type: "string", Description: "Sort field; prefix with - for descending."},
{Name: "order", In: "query", Type: "string", Description: "asc or desc."},
}
PageParams documents ?page, ?per_page, ?sort and ?order for list operations: append it to APIOperation.Params.
var Version = "0.1.0"
Version is the API version reported in the OpenAPI document. It is a var so main can set it from its build version (or -ldflags "-X github.com/zadstack/gorun/internal/api.Version=…").
Functions ¶
func Decode ¶
Decode reads a single JSON value from the request body into v: at most 1 MiB (w lets net/http close the connection on oversize bodies), unknown fields rejected. Errors are *DecodeError (400 empty_body / invalid_json with field hints, or 413); pass them to Fail or ParseValidation.
func Fail ¶
func Fail(w http.ResponseWriter, r *http.Request, err error)
Fail writes the envelope that fits err: decode errors 400/413, validation 422 with fields, known not-found sentinels (or errors with a NotFound() bool method returning true) 404, anything else 500 with a generic message (the error is logged, never sent).
func Op ¶
func Op(method, path, summary string, tags, scopes []string, req, resp any, h http.HandlerFunc) kernel.APIOperation
Op builds a kernel.APIOperation. path may be given with or without the /v1 prefix ("/content/{id}" and "/v1/content/{id}" are equivalent; chi patterns, including {id:regex}, are allowed). req and resp are example values whose types become the OpenAPI request body and the envelope's data schema (nil for none; a slice for list operations). Every scope in scopes is enforced with auth.Can before h runs. Add query parameters to the returned value's Params (see PageParams).
func Pagination ¶
Pagination reads ?page and ?per_page: page defaults to 1, per_page to 20 and is capped at 100; invalid values fall back to the defaults.
func ParseValidation ¶
ParseValidation classifies err for an HTTP response: a *DecodeError gives its status (400/413) and hints; a FieldErrorer, or any error in the chain that is a struct (pointer) with a string-to-string map field named Fields (content.ValidationError, auth.ValidationError), gives 422 and the fields. Other errors give (0, nil).
func Sort ¶
Sort reads ?sort and ?order against allowed (the first entry is the default). Without a valid ?sort it returns allowed[0] descending (newest first); an explicit ?sort is ascending unless ?order=desc or the field is prefixed with "-". Unknown fields and orders are ignored, so the result is safe to interpolate into ORDER BY.
func WriteError ¶
WriteError writes the error envelope; empty fields are omitted.
func WriteItem ¶
func WriteItem(w http.ResponseWriter, v any)
WriteItem writes a 200 envelope around v.
Types ¶
type DecodeError ¶
type DecodeError struct {
Status int // 400 or 413
Code string // invalid_json or payload_too_large
Message string
Fields map[string]string // field → hint, when known
}
DecodeError is returned by Decode for a body that cannot be bound.
func (*DecodeError) Error ¶
func (e *DecodeError) Error() string
type FieldErrorer ¶
FieldErrorer is implemented by validation errors that carry per-field messages (field → message or i18n key). ParseValidation and Fail map them to 422 validation_failed.
type Health ¶
type Health struct {
Status string `json:"status" enum:"ok"`
Database string `json:"database" enum:"ok"`
}
Health is GET /health (the data /readyz checks).
type InformationValue ¶ added in v0.14.0
type InformationValue struct {
Value *string `json:"value"`
Source string `json:"source"`
Available bool `json:"available"`
Reason string `json:"reason,omitempty"`
}
InformationValue distinguishes unavailable information from a known value and describes its source without exposing settings contents or filesystem paths.
type Me ¶
type Me struct {
Kind string `json:"kind" enum:"user,api_key"`
User MeUser `json:"user"`
APIKey *MeKey `json:"api_key" doc:"Set when authenticated with an API key"`
Scopes []string `json:"scopes" doc:"Effective permissions (role ∩ key scopes)"`
}
Me is GET /me: who is calling and what they may do.
type MeUser ¶
type MeUser struct {
ID string `json:"id"`
Email string `json:"email"`
Name string `json:"name"`
Role string `json:"role"`
Locale string `json:"locale"`
}
MeUser is the acting user in GET /me.
type Module ¶
type Module struct{}
Module is the api kernel.Module.
func (*Module) Migrations ¶
Migrations implements kernel.Module (the api module has no tables).
func (*Module) Register ¶
Register adds the core operations and installs the routes.
Mounting is lazy: modules after api in BootOrder (admin, commerce, wpmigrate, mcp, ext) register operations after this Register returns, so one catch-all handler on /api/v1/* builds the operation router from d.API.Operations() on the first request (sync.Once) and never rebuilds it; operations added after serving starts are ignored. The OpenAPI document is built from the same snapshot, also once.
type PublicSiteIdentity ¶ added in v0.14.0
type PublicSiteIdentity struct {
Title InformationValue `json:"title"`
Tagline InformationValue `json:"tagline"`
BaseURL InformationValue `json:"base_url"`
}
type SiteInfo ¶
type SiteInfo struct {
Title string `json:"title"`
Tagline string `json:"tagline"`
BaseURL string `json:"base_url"`
Locale string `json:"locale" doc:"Default site locale (site.locale)"`
Dir string `json:"dir" enum:"rtl,ltr"`
}
SiteInfo is the site identity block of GET /site.
type SiteInformation ¶ added in v0.14.0
type SiteInformation struct {
Version InformationValue `json:"version" doc:"Build of this running API instance, not an accepted or desired update version"`
Identity PublicSiteIdentity `json:"identity"`
Logos SiteLogos `json:"logos"`
Languages SiteLanguages `json:"languages"`
Appearance *theme.AppearanceReport `json:"appearance"`
AppearanceReason string `json:"appearance_reason,omitempty"`
Limitations []string `json:"limitations"`
}
SiteInformation is additive; the authenticated /site structure contract is unchanged.
type SiteLanguages ¶ added in v0.14.0
type SiteLanguages struct {
Default InformationValue `json:"default"`
Supported []string `json:"supported" doc:"Locales accepted by the running i18n router; not a claim that every page is translated"`
Source string `json:"source"`
Direction string `json:"direction"`
}
type SiteLogo ¶ added in v0.15.0
type SiteLogo struct {
InformationValue
URL InformationValue `` /* 147-byte string literal not displayed */
Path *string `json:"path" doc:"Site-served public path (/media/…) when the storage key is known; null otherwise"`
Rendered *string `json:"rendered" doc:"URL of the variant themes render in the header (thumbnail); null when the file has no variants"`
MediaID *string `json:"media_id"`
Mime *string `json:"mime"`
Variants map[string]string `json:"variants" doc:"Variant name → URL for the resolved file; empty when none"`
Fallback string `json:"fallback,omitempty" doc:"Which lower-precedence source supplied the value (site.logo, theme manifest)"`
}
SiteLogo resolves one logo variant. Value is the media URL templates use for the original file; Path is the site-served /media/ path for the same file, which stays public and unsigned even when the storage URL is remote or signed; Rendered is the variant themes actually print in headers (the thumbnail). Each falls back explicitly, never silently.
type SiteLogos ¶ added in v0.14.0
type SiteLogos struct {
Primary SiteLogo `json:"primary" doc:"site.logo → theme manifest logos.primary"`
Dark SiteLogo `json:"dark" doc:"site.logo_dark → site.logo → theme manifest logos.dark"`
Light SiteLogo `json:"light" doc:"site.logo_light → site.logo → theme manifest logos.light"`
Icon SiteLogo `json:"icon" doc:"site.icon → theme manifest logos.icon"`
}
SiteLogos lists the logo variants by precedence: the site setting for the variant, then site.logo (for dark/light), then the theme manifest.
type SiteRuntime ¶ added in v0.14.0
type SiteStructure ¶
type SiteStructure struct {
Site SiteInfo `json:"site"`
Types []content.Type `json:"types"`
Taxonomies []taxonomy.Taxonomy `json:"taxonomies"`
Menus []menus.Menu `json:"menus"`
Theme *theme.Info `json:"theme" doc:"Active theme (null when no theme module is installed)"`
Templates []theme.TemplateFile `json:"templates"`
}
SiteStructure is GET /site: everything an agent or headless client needs to understand the site. Lists of absent modules are empty, never null.