docs(api): add AX usage examples

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-01 20:17:46 +00:00
parent e2935ce79e
commit b341b4b860
5 changed files with 80 additions and 0 deletions

View file

@ -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

View file

@ -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))
}

View file

@ -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",

View file

@ -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 {

21
sse.go
View file

@ -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=<name> 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 {