From b341b4b8606368fa627a19c3bd0e047d42221c9e Mon Sep 17 00:00:00 2001 From: Virgil Date: Wed, 1 Apr 2026 20:17:46 +0000 Subject: [PATCH] docs(api): add AX usage examples Co-Authored-By: Virgil --- client.go | 13 +++++++++++++ codegen.go | 12 ++++++++++++ openapi.go | 9 +++++++++ response.go | 25 +++++++++++++++++++++++++ sse.go | 21 +++++++++++++++++++++ 5 files changed, 80 insertions(+) diff --git a/client.go b/client.go index 0eda237..fcd6779 100644 --- a/client.go +++ b/client.go @@ -20,6 +20,11 @@ import ( // OpenAPIClient is a small runtime client that can call operations by their // OpenAPI operationId. It loads the spec once, resolves the HTTP method and // path for each operation, and performs JSON request/response handling. +// +// Example: +// +// client := api.NewOpenAPIClient(api.WithSpec("./openapi.yaml"), api.WithBaseURL("https://api.example.com")) +// data, err := client.Call("get_health", nil) type OpenAPIClient struct { specPath string baseURL string @@ -78,6 +83,10 @@ func WithHTTPClient(client *http.Client) OpenAPIClientOption { } // NewOpenAPIClient constructs a runtime client for calling OpenAPI operations. +// +// Example: +// +// client := api.NewOpenAPIClient(api.WithSpec("./openapi.yaml")) func NewOpenAPIClient(opts ...OpenAPIClientOption) *OpenAPIClient { c := &OpenAPIClient{ httpClient: http.DefaultClient, @@ -97,6 +106,10 @@ func NewOpenAPIClient(opts ...OpenAPIClientOption) *OpenAPIClient { // include "path", "query", "header", "cookie", and "body" keys to explicitly // control where the values are sent. When no explicit body is provided, // requests with a declared requestBody send the remaining parameters as JSON. +// +// Example: +// +// data, err := client.Call("create_item", map[string]any{"name": "alpha"}) func (c *OpenAPIClient) Call(operationID string, params any) (any, error) { if err := c.load(); err != nil { return nil, err diff --git a/codegen.go b/codegen.go index b8cb12e..a8e9d20 100644 --- a/codegen.go +++ b/codegen.go @@ -32,6 +32,10 @@ var supportedLanguages = map[string]string{ } // SDKGenerator wraps openapi-generator-cli for SDK generation. +// +// Example: +// +// gen := &api.SDKGenerator{SpecPath: "./openapi.yaml", OutputDir: "./sdk", PackageName: "service"} type SDKGenerator struct { // SpecPath is the path to the OpenAPI spec file (JSON or YAML). SpecPath string @@ -45,6 +49,10 @@ type SDKGenerator struct { // Generate creates an SDK for the given language using openapi-generator-cli. // The language must be one of the supported languages returned by SupportedLanguages(). +// +// Example: +// +// err := gen.Generate(context.Background(), "go") func (g *SDKGenerator) Generate(ctx context.Context, language string) error { generator, ok := supportedLanguages[language] if !ok { @@ -94,6 +102,10 @@ func (g *SDKGenerator) Available() bool { // SupportedLanguages returns the list of supported SDK target languages // in sorted order for deterministic output. +// +// Example: +// +// langs := api.SupportedLanguages() func SupportedLanguages() []string { return slices.Sorted(maps.Keys(supportedLanguages)) } diff --git a/openapi.go b/openapi.go index f53769c..22866f3 100644 --- a/openapi.go +++ b/openapi.go @@ -11,6 +11,11 @@ import ( ) // SpecBuilder constructs an OpenAPI 3.1 specification from registered RouteGroups. +// +// Example: +// +// builder := &api.SpecBuilder{Title: "Service", Version: "1.0.0"} +// spec, err := builder.Build(engine.Groups()) type SpecBuilder struct { Title string Description string @@ -21,6 +26,10 @@ type SpecBuilder struct { // Build generates the complete OpenAPI 3.1 JSON spec. // Groups implementing DescribableGroup contribute endpoint documentation. // Other groups are listed as tags only. +// +// Example: +// +// data, err := (&api.SpecBuilder{Title: "Service", Version: "1.0.0"}).Build(engine.Groups()) func (sb *SpecBuilder) Build(groups []RouteGroup) ([]byte, error) { spec := map[string]any{ "openapi": "3.1.0", diff --git a/response.go b/response.go index ff4b1a6..5f98b54 100644 --- a/response.go +++ b/response.go @@ -5,6 +5,11 @@ package api import "github.com/gin-gonic/gin" // Response is the standard envelope for all API responses. +// +// Example: +// +// resp := api.OK(map[string]any{"id": 42}) +// resp.Success // true type Response[T any] struct { Success bool `json:"success"` Data T `json:"data,omitempty"` @@ -29,6 +34,10 @@ type Meta struct { } // OK wraps data in a successful response envelope. +// +// Example: +// +// c.JSON(http.StatusOK, api.OK(map[string]any{"name": "status"})) func OK[T any](data T) Response[T] { return Response[T]{ Success: true, @@ -37,6 +46,10 @@ func OK[T any](data T) Response[T] { } // Fail creates an error response with the given code and message. +// +// Example: +// +// c.JSON(http.StatusBadRequest, api.Fail("invalid_input", "Name is required")) func Fail(code, message string) Response[any] { return Response[any]{ Success: false, @@ -48,6 +61,10 @@ func Fail(code, message string) Response[any] { } // FailWithDetails creates an error response with additional detail payload. +// +// Example: +// +// c.JSON(http.StatusBadRequest, api.FailWithDetails("invalid_input", "Name is required", map[string]any{"field": "name"})) func FailWithDetails(code, message string, details any) Response[any] { return Response[any]{ Success: false, @@ -60,6 +77,10 @@ func FailWithDetails(code, message string, details any) Response[any] { } // Paginated wraps data in a successful response with pagination metadata. +// +// Example: +// +// c.JSON(http.StatusOK, api.Paginated(items, 2, 50, 200)) func Paginated[T any](data T, page, perPage, total int) Response[T] { return Response[T]{ Success: true, @@ -75,6 +96,10 @@ func Paginated[T any](data T, page, perPage, total int) Response[T] { // AttachRequestMeta merges request metadata into an existing response envelope. // Existing pagination metadata is preserved; request_id and duration are added // when available from the Gin context. +// +// Example: +// +// resp = api.AttachRequestMeta(c, resp) func AttachRequestMeta[T any](c *gin.Context, resp Response[T]) Response[T] { meta := GetRequestMeta(c) if meta == nil { diff --git a/sse.go b/sse.go index a1e4d02..dd690a2 100644 --- a/sse.go +++ b/sse.go @@ -15,6 +15,11 @@ import ( // to subscribed clients. Clients connect via a GET endpoint and receive // a streaming text/event-stream response. Each client may optionally // subscribe to a specific channel via the ?channel= query parameter. +// +// Example: +// +// broker := api.NewSSEBroker() +// engine.GET("/events", broker.Handler()) type SSEBroker struct { mu sync.RWMutex wg sync.WaitGroup @@ -37,6 +42,10 @@ type sseEvent struct { } // NewSSEBroker creates a ready-to-use SSE broker. +// +// Example: +// +// broker := api.NewSSEBroker() func NewSSEBroker() *SSEBroker { return &SSEBroker{ clients: make(map[*sseClient]struct{}), @@ -46,6 +55,10 @@ func NewSSEBroker() *SSEBroker { // Publish sends an event to all clients subscribed to the given channel. // Clients subscribed to an empty channel (no ?channel= param) receive // events on every channel. The data value is JSON-encoded before sending. +// +// Example: +// +// broker.Publish("system", "ready", map[string]any{"status": "ok"}) func (b *SSEBroker) Publish(channel, event string, data any) { encoded, err := json.Marshal(data) if err != nil { @@ -81,6 +94,10 @@ func (b *SSEBroker) Publish(channel, event string, data any) { // Handler returns a Gin handler for the SSE endpoint. Clients connect with // a GET request and receive events as text/event-stream. An optional // ?channel= query parameter subscribes the client to a specific channel. +// +// Example: +// +// engine.GET("/events", broker.Handler()) func (b *SSEBroker) Handler() gin.HandlerFunc { return func(c *gin.Context) { channel := c.Query("channel") @@ -154,6 +171,10 @@ func (b *SSEBroker) ClientCount() int { // Drain signals all connected clients to disconnect and waits for their // handler goroutines to exit. Useful for graceful shutdown. +// +// Example: +// +// broker.Drain() func (b *SSEBroker) Drain() { b.mu.Lock() for client := range b.clients {