docs(api): add AX usage examples
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
08cb1385d3
commit
ec945970ee
14 changed files with 262 additions and 0 deletions
20
api.go
20
api.go
|
|
@ -91,11 +91,21 @@ func (e *Engine) Addr() string {
|
|||
}
|
||||
|
||||
// Groups returns a copy of all registered route groups.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// groups := engine.Groups()
|
||||
func (e *Engine) Groups() []RouteGroup {
|
||||
return slices.Clone(e.groups)
|
||||
}
|
||||
|
||||
// GroupsIter returns an iterator over all registered route groups.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// for group := range engine.GroupsIter() {
|
||||
// _ = group
|
||||
// }
|
||||
func (e *Engine) GroupsIter() iter.Seq[RouteGroup] {
|
||||
groups := slices.Clone(e.groups)
|
||||
return slices.Values(groups)
|
||||
|
|
@ -115,6 +125,10 @@ func (e *Engine) Register(group RouteGroup) {
|
|||
|
||||
// Channels returns all WebSocket channel names from registered StreamGroups.
|
||||
// Groups that do not implement StreamGroup are silently skipped.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// channels := engine.Channels()
|
||||
func (e *Engine) Channels() []string {
|
||||
var channels []string
|
||||
for _, g := range e.groups {
|
||||
|
|
@ -126,6 +140,12 @@ func (e *Engine) Channels() []string {
|
|||
}
|
||||
|
||||
// ChannelsIter returns an iterator over WebSocket channel names from registered StreamGroups.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// for channel := range engine.ChannelsIter() {
|
||||
// _ = channel
|
||||
// }
|
||||
func (e *Engine) ChannelsIter() iter.Seq[string] {
|
||||
groups := slices.Clone(e.groups)
|
||||
return func(yield func(string) bool) {
|
||||
|
|
|
|||
24
authentik.go
24
authentik.go
|
|
@ -14,6 +14,10 @@ import (
|
|||
)
|
||||
|
||||
// AuthentikConfig holds settings for the Authentik forward-auth integration.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// cfg := api.AuthentikConfig{Issuer: "https://auth.example.com/", ClientID: "core-api"}
|
||||
type AuthentikConfig struct {
|
||||
// Issuer is the OIDC issuer URL (e.g. https://auth.example.com/application/o/my-app/).
|
||||
Issuer string
|
||||
|
|
@ -32,6 +36,10 @@ type AuthentikConfig struct {
|
|||
|
||||
// AuthentikUser represents an authenticated user extracted from Authentik
|
||||
// forward-auth headers or a validated JWT.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// user := &api.AuthentikUser{Username: "alice", Groups: []string{"admins"}}
|
||||
type AuthentikUser struct {
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
|
|
@ -43,6 +51,10 @@ type AuthentikUser struct {
|
|||
}
|
||||
|
||||
// HasGroup reports whether the user belongs to the named group.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// user.HasGroup("admins")
|
||||
func (u *AuthentikUser) HasGroup(group string) bool {
|
||||
return slices.Contains(u.Groups, group)
|
||||
}
|
||||
|
|
@ -53,6 +65,10 @@ const authentikUserKey = "authentik_user"
|
|||
// GetUser retrieves the AuthentikUser from the Gin context.
|
||||
// Returns nil when no user has been set (unauthenticated request or
|
||||
// middleware not active).
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// user := api.GetUser(c)
|
||||
func GetUser(c *gin.Context) *AuthentikUser {
|
||||
val, exists := c.Get(authentikUserKey)
|
||||
if !exists {
|
||||
|
|
@ -204,6 +220,10 @@ func authentikMiddleware(cfg AuthentikConfig, publicPaths func() []string) gin.H
|
|||
// RequireAuth is Gin middleware that rejects unauthenticated requests.
|
||||
// It checks for a user set by the Authentik middleware and returns 401
|
||||
// when none is present.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// r.GET("/private", api.RequireAuth(), handler)
|
||||
func RequireAuth() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if GetUser(c) == nil {
|
||||
|
|
@ -218,6 +238,10 @@ func RequireAuth() gin.HandlerFunc {
|
|||
// RequireGroup is Gin middleware that rejects requests from users who do
|
||||
// not belong to the specified group. Returns 401 when no user is present
|
||||
// and 403 when the user lacks the required group membership.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// r.GET("/admin", api.RequireGroup("admins"), handler)
|
||||
func RequireGroup(group string) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
user := GetUser(c)
|
||||
|
|
|
|||
36
bridge.go
36
bridge.go
|
|
@ -23,6 +23,10 @@ import (
|
|||
)
|
||||
|
||||
// ToolDescriptor describes a tool that can be exposed as a REST endpoint.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// desc := api.ToolDescriptor{Name: "ping", Description: "Ping the service"}
|
||||
type ToolDescriptor struct {
|
||||
Name string // Tool name, e.g. "file_read" (becomes POST path segment)
|
||||
Description string // Human-readable description
|
||||
|
|
@ -33,6 +37,10 @@ type ToolDescriptor struct {
|
|||
|
||||
// ToolBridge converts tool descriptors into REST endpoints and OpenAPI paths.
|
||||
// It implements both RouteGroup and DescribableGroup.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// bridge := api.NewToolBridge("/mcp")
|
||||
type ToolBridge struct {
|
||||
basePath string
|
||||
name string
|
||||
|
|
@ -75,12 +83,24 @@ func (b *ToolBridge) Add(desc ToolDescriptor, handler gin.HandlerFunc) {
|
|||
}
|
||||
|
||||
// Name returns the bridge identifier.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// name := bridge.Name()
|
||||
func (b *ToolBridge) Name() string { return b.name }
|
||||
|
||||
// BasePath returns the URL prefix for all tool endpoints.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// path := bridge.BasePath()
|
||||
func (b *ToolBridge) BasePath() string { return b.basePath }
|
||||
|
||||
// RegisterRoutes mounts POST /{tool_name} for each registered tool.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// bridge.RegisterRoutes(rg)
|
||||
func (b *ToolBridge) RegisterRoutes(rg *gin.RouterGroup) {
|
||||
for _, t := range b.tools {
|
||||
rg.POST("/"+t.descriptor.Name, t.handler)
|
||||
|
|
@ -88,6 +108,10 @@ func (b *ToolBridge) RegisterRoutes(rg *gin.RouterGroup) {
|
|||
}
|
||||
|
||||
// Describe returns OpenAPI route descriptions for all registered tools.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// descs := bridge.Describe()
|
||||
func (b *ToolBridge) Describe() []RouteDescription {
|
||||
tools := b.snapshotTools()
|
||||
descs := make([]RouteDescription, 0, len(tools))
|
||||
|
|
@ -98,6 +122,12 @@ func (b *ToolBridge) Describe() []RouteDescription {
|
|||
}
|
||||
|
||||
// DescribeIter returns an iterator over OpenAPI route descriptions for all registered tools.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// for rd := range bridge.DescribeIter() {
|
||||
// _ = rd
|
||||
// }
|
||||
func (b *ToolBridge) DescribeIter() iter.Seq[RouteDescription] {
|
||||
tools := b.snapshotTools()
|
||||
return func(yield func(RouteDescription) bool) {
|
||||
|
|
@ -124,6 +154,12 @@ func (b *ToolBridge) Tools() []ToolDescriptor {
|
|||
}
|
||||
|
||||
// ToolsIter returns an iterator over all registered tool descriptors.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// for desc := range bridge.ToolsIter() {
|
||||
// _ = desc
|
||||
// }
|
||||
func (b *ToolBridge) ToolsIter() iter.Seq[ToolDescriptor] {
|
||||
tools := b.snapshotTools()
|
||||
return func(yield func(ToolDescriptor) bool) {
|
||||
|
|
|
|||
|
|
@ -56,6 +56,10 @@ type openAPIParameter struct {
|
|||
}
|
||||
|
||||
// OpenAPIClientOption configures a runtime OpenAPI client.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// client := api.NewOpenAPIClient(api.WithSpec("./openapi.yaml"))
|
||||
type OpenAPIClientOption func(*OpenAPIClient)
|
||||
|
||||
// WithSpec sets the filesystem path to the OpenAPI document.
|
||||
|
|
|
|||
12
graphql.go
12
graphql.go
|
|
@ -23,9 +23,17 @@ type graphqlConfig struct {
|
|||
}
|
||||
|
||||
// GraphQLOption configures a GraphQL endpoint.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// opts := []api.GraphQLOption{api.WithPlayground(), api.WithGraphQLPath("/gql")}
|
||||
type GraphQLOption func(*graphqlConfig)
|
||||
|
||||
// WithPlayground enables the GraphQL Playground UI at {path}/playground.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// api.WithGraphQL(schema, api.WithPlayground())
|
||||
func WithPlayground() GraphQLOption {
|
||||
return func(cfg *graphqlConfig) {
|
||||
cfg.playground = true
|
||||
|
|
@ -34,6 +42,10 @@ func WithPlayground() GraphQLOption {
|
|||
|
||||
// WithGraphQLPath sets a custom URL path for the GraphQL endpoint.
|
||||
// The default path is "/graphql".
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// api.WithGraphQL(schema, api.WithGraphQLPath("/gql"))
|
||||
func WithGraphQLPath(path string) GraphQLOption {
|
||||
return func(cfg *graphqlConfig) {
|
||||
cfg.path = normaliseGraphQLPath(path)
|
||||
|
|
|
|||
16
group.go
16
group.go
|
|
@ -10,6 +10,10 @@ import (
|
|||
|
||||
// RouteGroup registers API routes onto a Gin router group.
|
||||
// Subsystems implement this interface to declare their endpoints.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// var g api.RouteGroup = &myGroup{}
|
||||
type RouteGroup interface {
|
||||
// Name returns a human-readable identifier for the group.
|
||||
Name() string
|
||||
|
|
@ -22,6 +26,10 @@ type RouteGroup interface {
|
|||
}
|
||||
|
||||
// StreamGroup optionally declares WebSocket channels a subsystem publishes to.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// var sg api.StreamGroup = &myStreamGroup{}
|
||||
type StreamGroup interface {
|
||||
// Channels returns the list of channel names this group streams on.
|
||||
Channels() []string
|
||||
|
|
@ -30,6 +38,10 @@ type StreamGroup interface {
|
|||
// DescribableGroup extends RouteGroup with OpenAPI metadata.
|
||||
// RouteGroups that implement this will have their endpoints
|
||||
// included in the generated OpenAPI specification.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// var dg api.DescribableGroup = &myDescribableGroup{}
|
||||
type DescribableGroup interface {
|
||||
RouteGroup
|
||||
// Describe returns endpoint descriptions for OpenAPI generation.
|
||||
|
|
@ -38,6 +50,10 @@ type DescribableGroup interface {
|
|||
|
||||
// DescribableGroupIter extends DescribableGroup with an iterator-based
|
||||
// description source for callers that want to avoid slice allocation.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// var dg api.DescribableGroupIter = &myDescribableGroup{}
|
||||
type DescribableGroupIter interface {
|
||||
DescribableGroup
|
||||
// DescribeIter returns endpoint descriptions for OpenAPI generation.
|
||||
|
|
|
|||
20
i18n.go
20
i18n.go
|
|
@ -22,6 +22,14 @@ const i18nCatalogKey = "i18n.catalog"
|
|||
const i18nDefaultLocaleKey = "i18n.default_locale"
|
||||
|
||||
// I18nConfig configures the internationalisation middleware.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// cfg := api.I18nConfig{
|
||||
// DefaultLocale: "en",
|
||||
// Supported: []string{"en", "fr"},
|
||||
// Messages: map[string]map[string]string{"fr": {"greeting": "Bonjour"}},
|
||||
// }
|
||||
type I18nConfig struct {
|
||||
// DefaultLocale is the fallback locale when the Accept-Language header
|
||||
// is absent or does not match any supported locale. Defaults to "en".
|
||||
|
|
@ -43,6 +51,10 @@ type I18nConfig struct {
|
|||
// with quality weighting support. The detected locale is stored in the Gin
|
||||
// context and can be retrieved by handlers via GetLocale().
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// api.New(api.WithI18n(api.I18nConfig{Supported: []string{"en", "fr"}}))
|
||||
//
|
||||
// If messages are configured, handlers can look up localised strings via
|
||||
// GetMessage(). This is a lightweight bridge — the go-i18n grammar engine
|
||||
// can replace the message map later.
|
||||
|
|
@ -103,6 +115,10 @@ func i18nMiddleware(matcher language.Matcher, cfg I18nConfig) gin.HandlerFunc {
|
|||
|
||||
// GetLocale returns the detected locale for the current request.
|
||||
// Returns "en" if the i18n middleware was not applied.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// locale := api.GetLocale(c)
|
||||
func GetLocale(c *gin.Context) string {
|
||||
if v, ok := c.Get(i18nContextKey); ok {
|
||||
if s, ok := v.(string); ok {
|
||||
|
|
@ -115,6 +131,10 @@ func GetLocale(c *gin.Context) string {
|
|||
// GetMessage looks up a localised message by key for the current request.
|
||||
// Returns the message string and true if found, or empty string and false
|
||||
// if the key does not exist or the i18n middleware was not applied.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// msg, ok := api.GetMessage(c, "greeting")
|
||||
func GetMessage(c *gin.Context, key string) (string, bool) {
|
||||
if v, ok := c.Get(i18nMessagesKey); ok {
|
||||
if msgs, ok := v.(map[string]string); ok {
|
||||
|
|
|
|||
|
|
@ -110,6 +110,10 @@ func requestIDMiddleware() gin.HandlerFunc {
|
|||
|
||||
// GetRequestID returns the request ID assigned by requestIDMiddleware.
|
||||
// Returns an empty string when the middleware was not applied.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// id := api.GetRequestID(c)
|
||||
func GetRequestID(c *gin.Context) string {
|
||||
if v, ok := c.Get(requestIDContextKey); ok {
|
||||
if s, ok := v.(string); ok {
|
||||
|
|
@ -121,6 +125,10 @@ func GetRequestID(c *gin.Context) string {
|
|||
|
||||
// GetRequestDuration returns the elapsed time since requestIDMiddleware started
|
||||
// handling the request. Returns 0 when the middleware was not applied.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// d := api.GetRequestDuration(c)
|
||||
func GetRequestDuration(c *gin.Context) time.Duration {
|
||||
if v, ok := c.Get(requestStartContextKey); ok {
|
||||
if started, ok := v.(time.Time); ok && !started.IsZero() {
|
||||
|
|
@ -133,6 +141,10 @@ func GetRequestDuration(c *gin.Context) time.Duration {
|
|||
// GetRequestMeta returns request metadata collected by requestIDMiddleware.
|
||||
// The returned meta includes the request ID and elapsed duration when
|
||||
// available. It returns nil when neither value is available.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// meta := api.GetRequestMeta(c)
|
||||
func GetRequestMeta(c *gin.Context) *Meta {
|
||||
meta := &Meta{}
|
||||
|
||||
|
|
|
|||
|
|
@ -191,6 +191,10 @@ func (sb *SpecBuilder) Build(groups []RouteGroup) ([]byte, error) {
|
|||
// BuildIter generates the complete OpenAPI 3.1 JSON spec from a route-group
|
||||
// iterator. The iterator is snapshotted before building so the result stays
|
||||
// stable even if the source changes during rendering.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// data, err := (&api.SpecBuilder{Title: "Service"}).BuildIter(api.RegisteredSpecGroupsIter())
|
||||
func (sb *SpecBuilder) BuildIter(groups iter.Seq[RouteGroup]) ([]byte, error) {
|
||||
return sb.Build(collectRouteGroups(groups))
|
||||
}
|
||||
|
|
|
|||
88
options.go
88
options.go
|
|
@ -27,6 +27,10 @@ import (
|
|||
)
|
||||
|
||||
// Option configures an Engine during construction.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// engine, _ := api.New(api.WithAddr(":8080"))
|
||||
type Option func(*Engine)
|
||||
|
||||
// WithAddr sets the listen address for the server.
|
||||
|
|
@ -42,6 +46,10 @@ func WithAddr(addr string) Option {
|
|||
|
||||
// WithBearerAuth adds bearer token authentication middleware.
|
||||
// Requests to /health and the Swagger UI path are exempt.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// api.New(api.WithBearerAuth("secret"))
|
||||
func WithBearerAuth(token string) Option {
|
||||
return func(e *Engine) {
|
||||
e.middlewares = append(e.middlewares, bearerAuthMiddleware(token, func() []string {
|
||||
|
|
@ -56,6 +64,10 @@ func WithBearerAuth(token string) Option {
|
|||
|
||||
// WithRequestID adds middleware that assigns an X-Request-ID to every response.
|
||||
// Client-provided IDs are preserved; otherwise a random hex ID is generated.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// api.New(api.WithRequestID())
|
||||
func WithRequestID() Option {
|
||||
return func(e *Engine) {
|
||||
e.middlewares = append(e.middlewares, requestIDMiddleware())
|
||||
|
|
@ -80,6 +92,10 @@ func WithResponseMeta() Option {
|
|||
// Pass "*" to allow all origins, or supply specific origin URLs.
|
||||
// Standard methods (GET, POST, PUT, PATCH, DELETE, OPTIONS) and common
|
||||
// headers (Authorization, Content-Type, X-Request-ID) are permitted.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// api.New(api.WithCORS("*"))
|
||||
func WithCORS(allowOrigins ...string) Option {
|
||||
return func(e *Engine) {
|
||||
cfg := cors.Config{
|
||||
|
|
@ -100,6 +116,10 @@ func WithCORS(allowOrigins ...string) Option {
|
|||
}
|
||||
|
||||
// WithMiddleware appends arbitrary Gin middleware to the engine.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// api.New(api.WithMiddleware(loggingMiddleware))
|
||||
func WithMiddleware(mw ...gin.HandlerFunc) Option {
|
||||
return func(e *Engine) {
|
||||
e.middlewares = append(e.middlewares, mw...)
|
||||
|
|
@ -109,6 +129,10 @@ func WithMiddleware(mw ...gin.HandlerFunc) Option {
|
|||
// WithStatic serves static files from the given root directory at urlPrefix.
|
||||
// Directory listing is disabled; only individual files are served.
|
||||
// Internally this uses gin-contrib/static as Gin middleware.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// api.New(api.WithStatic("/assets", "./public"))
|
||||
func WithStatic(urlPrefix, root string) Option {
|
||||
return func(e *Engine) {
|
||||
e.middlewares = append(e.middlewares, static.Serve(urlPrefix, static.LocalFile(root, false)))
|
||||
|
|
@ -130,6 +154,10 @@ func WithWSHandler(h http.Handler) Option {
|
|||
|
||||
// WithWSPath sets a custom URL path for the WebSocket endpoint.
|
||||
// The default path is "/ws".
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// api.New(api.WithWSPath("/socket"))
|
||||
func WithWSPath(path string) Option {
|
||||
return func(e *Engine) {
|
||||
e.wsPath = normaliseWSPath(path)
|
||||
|
|
@ -139,6 +167,10 @@ func WithWSPath(path string) Option {
|
|||
// WithAuthentik adds Authentik forward-auth middleware that extracts user
|
||||
// identity from X-authentik-* headers set by a trusted reverse proxy.
|
||||
// The middleware is permissive: unauthenticated requests are allowed through.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// api.New(api.WithAuthentik(api.AuthentikConfig{TrustedProxy: true}))
|
||||
func WithAuthentik(cfg AuthentikConfig) Option {
|
||||
return func(e *Engine) {
|
||||
e.middlewares = append(e.middlewares, authentikMiddleware(cfg, func() []string {
|
||||
|
|
@ -151,6 +183,10 @@ func WithAuthentik(cfg AuthentikConfig) Option {
|
|||
// The middleware appends Deprecation, optional Sunset, optional Link, and
|
||||
// X-API-Warn headers without clobbering any existing header values. Use it to
|
||||
// deprecate an entire route group or API version.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// api.New(api.WithSunset("2026-12-31", "https://api.example.com/v2"))
|
||||
func WithSunset(sunsetDate, replacement string) Option {
|
||||
return func(e *Engine) {
|
||||
e.middlewares = append(e.middlewares, ApiSunset(sunsetDate, replacement))
|
||||
|
|
@ -159,6 +195,10 @@ func WithSunset(sunsetDate, replacement string) Option {
|
|||
|
||||
// WithSwagger enables the Swagger UI at /swagger/ by default.
|
||||
// The title, description, and version populate the OpenAPI info block.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// api.New(api.WithSwagger("Service", "Public API", "1.0.0"))
|
||||
func WithSwagger(title, description, version string) Option {
|
||||
return func(e *Engine) {
|
||||
e.swaggerTitle = title
|
||||
|
|
@ -170,6 +210,10 @@ func WithSwagger(title, description, version string) Option {
|
|||
|
||||
// WithSwaggerPath sets a custom URL path for the Swagger UI.
|
||||
// The default path is "/swagger".
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// api.New(api.WithSwaggerPath("/docs"))
|
||||
func WithSwaggerPath(path string) Option {
|
||||
return func(e *Engine) {
|
||||
e.swaggerPath = normaliseSwaggerPath(path)
|
||||
|
|
@ -296,6 +340,10 @@ func WithSwaggerExternalDocs(description, url string) Option {
|
|||
//
|
||||
// WARNING: pprof exposes sensitive runtime data and should only be
|
||||
// enabled in development or behind authentication in production.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// api.New(api.WithPprof())
|
||||
func WithPprof() Option {
|
||||
return func(e *Engine) {
|
||||
e.pprofEnabled = true
|
||||
|
|
@ -310,6 +358,10 @@ func WithPprof() Option {
|
|||
// WARNING: expvar exposes runtime internals (memory allocation,
|
||||
// goroutine counts, command-line arguments) and should only be
|
||||
// enabled in development or behind authentication in production.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// api.New(api.WithExpvar())
|
||||
func WithExpvar() Option {
|
||||
return func(e *Engine) {
|
||||
e.expvarEnabled = true
|
||||
|
|
@ -321,6 +373,10 @@ func WithExpvar() Option {
|
|||
// X-Content-Type-Options nosniff, and Referrer-Policy strict-origin-when-cross-origin.
|
||||
// SSL redirect is not enabled so the middleware works behind a reverse proxy
|
||||
// that terminates TLS.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// api.New(api.WithSecure())
|
||||
func WithSecure() Option {
|
||||
return func(e *Engine) {
|
||||
e.middlewares = append(e.middlewares, secure.New(secure.Config{
|
||||
|
|
@ -337,6 +393,10 @@ func WithSecure() Option {
|
|||
// WithGzip adds gzip response compression middleware via gin-contrib/gzip.
|
||||
// An optional compression level may be supplied (e.g. gzip.BestSpeed,
|
||||
// gzip.BestCompression). If omitted, gzip.DefaultCompression is used.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// api.New(api.WithGzip())
|
||||
func WithGzip(level ...int) Option {
|
||||
return func(e *Engine) {
|
||||
l := gzip.DefaultCompression
|
||||
|
|
@ -350,6 +410,10 @@ func WithGzip(level ...int) Option {
|
|||
// WithBrotli adds Brotli response compression middleware using andybalholm/brotli.
|
||||
// An optional compression level may be supplied (e.g. BrotliBestSpeed,
|
||||
// BrotliBestCompression). If omitted, BrotliDefaultCompression is used.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// api.New(api.WithBrotli())
|
||||
func WithBrotli(level ...int) Option {
|
||||
return func(e *Engine) {
|
||||
l := BrotliDefaultCompression
|
||||
|
|
@ -363,6 +427,10 @@ func WithBrotli(level ...int) Option {
|
|||
// WithSlog adds structured request logging middleware via gin-contrib/slog.
|
||||
// Each request is logged with method, path, status code, latency, and client IP.
|
||||
// If logger is nil, slog.Default() is used.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// api.New(api.WithSlog(nil))
|
||||
func WithSlog(logger *slog.Logger) Option {
|
||||
return func(e *Engine) {
|
||||
if logger == nil {
|
||||
|
|
@ -384,6 +452,10 @@ func WithSlog(logger *slog.Logger) Option {
|
|||
//
|
||||
// A zero or negative duration effectively disables the timeout (the handler
|
||||
// runs without a deadline) — this is safe and will not panic.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// api.New(api.WithTimeout(5 * time.Second))
|
||||
func WithTimeout(d time.Duration) Option {
|
||||
return func(e *Engine) {
|
||||
if d <= 0 {
|
||||
|
|
@ -456,6 +528,10 @@ func WithRateLimit(limit int) Option {
|
|||
// gin-contrib/sessions using a cookie-based store. The name parameter
|
||||
// sets the session cookie name (e.g. "session") and secret is the key
|
||||
// used for cookie signing and encryption.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// api.New(api.WithSessions("session", []byte("secret")))
|
||||
func WithSessions(name string, secret []byte) Option {
|
||||
return func(e *Engine) {
|
||||
store := cookie.NewStore(secret)
|
||||
|
|
@ -468,6 +544,10 @@ func WithSessions(name string, secret []byte) Option {
|
|||
// holding the desired model and policy rules. The middleware extracts the
|
||||
// subject from HTTP Basic Authentication, evaluates it against the request
|
||||
// method and path, and returns 403 Forbidden when the policy denies access.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// api.New(api.WithAuthz(enforcer))
|
||||
func WithAuthz(enforcer *casbin.Enforcer) Option {
|
||||
return func(e *Engine) {
|
||||
e.middlewares = append(e.middlewares, authz.NewAuthorizer(enforcer))
|
||||
|
|
@ -487,6 +567,10 @@ func WithAuthz(enforcer *casbin.Enforcer) Option {
|
|||
//
|
||||
// Requests with a missing, malformed, or invalid signature are rejected with
|
||||
// 401 Unauthorised or 400 Bad Request.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// api.New(api.WithHTTPSign(secrets))
|
||||
func WithHTTPSign(secrets httpsign.Secrets, opts ...httpsign.Option) Option {
|
||||
return func(e *Engine) {
|
||||
auth := httpsign.NewAuthenticator(secrets, opts...)
|
||||
|
|
@ -512,6 +596,10 @@ func WithSSE(broker *SSEBroker) Option {
|
|||
|
||||
// WithSSEPath sets a custom URL path for the SSE endpoint.
|
||||
// The default path is "/events".
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// api.New(api.WithSSEPath("/stream"))
|
||||
func WithSSEPath(path string) Option {
|
||||
return func(e *Engine) {
|
||||
e.ssePath = normaliseSSEPath(path)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,10 @@ type Response[T any] struct {
|
|||
}
|
||||
|
||||
// Error describes a failed API request.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// err := api.Error{Code: "invalid_input", Message: "Name is required"}
|
||||
type Error struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
|
|
@ -25,6 +29,10 @@ type Error struct {
|
|||
}
|
||||
|
||||
// Meta carries pagination and request metadata.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// meta := api.Meta{RequestID: "req_123", Duration: "12ms"}
|
||||
type Meta struct {
|
||||
RequestID string `json:"request_id,omitempty"`
|
||||
Duration string `json:"duration,omitempty"`
|
||||
|
|
|
|||
|
|
@ -76,6 +76,12 @@ func RegisteredSpecGroups() []RouteGroup {
|
|||
//
|
||||
// The iterator snapshots the current registry contents so callers can range
|
||||
// over it without holding the registry lock.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// for g := range api.RegisteredSpecGroupsIter() {
|
||||
// _ = g
|
||||
// }
|
||||
func RegisteredSpecGroupsIter() iter.Seq[RouteGroup] {
|
||||
specRegistry.mu.RLock()
|
||||
groups := slices.Clone(specRegistry.groups)
|
||||
|
|
|
|||
4
sse.go
4
sse.go
|
|
@ -192,6 +192,10 @@ func (b *SSEBroker) Handler() gin.HandlerFunc {
|
|||
}
|
||||
|
||||
// ClientCount returns the number of currently connected SSE clients.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// n := broker.ClientCount()
|
||||
func (b *SSEBroker) ClientCount() int {
|
||||
b.mu.RLock()
|
||||
defer b.mu.RUnlock()
|
||||
|
|
|
|||
|
|
@ -8,6 +8,10 @@ import "strings"
|
|||
//
|
||||
// It is intentionally small and serialisable so callers can inspect the active HTTP
|
||||
// surface without rebuilding an OpenAPI document.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// cfg := api.TransportConfig{SwaggerPath: "/swagger", WSPath: "/ws"}
|
||||
type TransportConfig struct {
|
||||
SwaggerPath string
|
||||
GraphQLPath string
|
||||
|
|
@ -22,6 +26,10 @@ type TransportConfig struct {
|
|||
//
|
||||
// The result snapshots the Engine state at call time and normalises any configured
|
||||
// URL paths using the same rules as the runtime handlers.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// cfg := engine.TransportConfig()
|
||||
func (e *Engine) TransportConfig() TransportConfig {
|
||||
if e == nil {
|
||||
return TransportConfig{}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue