api/group.go
Snider da1839f730 feat(api): webhooks + sunset headers + WithWebSocket + cmd/api migration
- webhook.go: HMAC-SHA256 WebhookSigner matching PHP WebhookSignature —
  sign/verify, X-Webhook-Signature / X-Webhook-Timestamp headers,
  VerifyRequest middleware helper, 5-minute default tolerance,
  secret generator (RFC §6)
- sunset.go: ApiSunsetWith(date, replacement, opts...) + WithSunsetNoticeURL;
  ApiSunset now emits API-Suggested-Replacement when replacement set;
  RouteDescription.NoticeURL surfaces API-Deprecation-Notice-URL (RFC §8)
- options.go + api.go + transport.go: WithWebSocket(gin.HandlerFunc)
  alongside existing WithWSHandler(http.Handler); gin form wins when
  both supplied (RFC §2.2)
- openapi.go: apiSuggestedReplacement + apiDeprecationNoticeURL as
  reusable header components; NoticeURL on a RouteDescription flips
  operation deprecated flag and emits response header doc
- cmd/api/*.go: migrated from Cobra (cli.NewCommand, StringFlag) to
  new path-based CLI API (c.Command + core.Options.String/Int/Bool);
  replaces the 1,422-line Cobra test suite with _Good/_Bad/_Ugly
  triads on the new surface
- webhook_test.go + sunset_test.go + websocket_test.go: full coverage

Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-14 14:51:04 +01:00

129 lines
4.3 KiB
Go

// SPDX-License-Identifier: EUPL-1.2
package api
import (
"iter"
"github.com/gin-gonic/gin"
)
// 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
// BasePath returns the URL prefix for all routes in this group.
BasePath() string
// RegisterRoutes mounts handlers onto the provided router group.
RegisterRoutes(rg *gin.RouterGroup)
}
// 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
}
// 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.
Describe() []RouteDescription
}
// 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.
DescribeIter() iter.Seq[RouteDescription]
}
// RouteDescription describes a single endpoint for OpenAPI generation.
//
// Example:
//
// rd := api.RouteDescription{
// Method: "POST",
// Path: "/users",
// Summary: "Create a user",
// Description: "Creates a new user account.",
// Tags: []string{"users"},
// StatusCode: 201,
// RequestBody: map[string]any{"type": "object"},
// Response: map[string]any{"type": "object"},
// }
type RouteDescription struct {
Method string // HTTP method: GET, POST, PUT, DELETE, PATCH
Path string // Path relative to BasePath, e.g. "/generate"
Summary string // Short summary
Description string // Long description
Tags []string // OpenAPI tags for grouping
// Hidden omits the route from generated documentation.
Hidden bool
// Deprecated marks the operation as deprecated in OpenAPI.
Deprecated bool
// SunsetDate marks when a deprecated operation will be removed.
// Use YYYY-MM-DD or an RFC 7231 HTTP date string.
SunsetDate string
// Replacement points to the successor endpoint URL, when known.
Replacement string
// NoticeURL points to a detailed deprecation notice or migration guide,
// surfaced as the API-Deprecation-Notice-URL response header per spec §8.
NoticeURL string
// StatusCode is the documented 2xx success status code.
// Zero defaults to 200.
StatusCode int
// Security overrides the default bearerAuth requirement when non-nil.
// Use an empty, non-nil slice to mark the route as public.
Security []map[string][]string
Parameters []ParameterDescription
RequestBody map[string]any // JSON Schema for request body (nil for GET)
RequestExample any // Optional example payload for the request body.
Response map[string]any // JSON Schema for success response data
ResponseExample any // Optional example payload for the success response.
ResponseHeaders map[string]string
}
// ParameterDescription describes an OpenAPI parameter for a route.
//
// Example:
//
// param := api.ParameterDescription{
// Name: "id",
// In: "path",
// Description: "User identifier",
// Required: true,
// Schema: map[string]any{"type": "string"},
// Example: "usr_123",
// }
type ParameterDescription struct {
Name string // Parameter name.
In string // Parameter location: path, query, header, or cookie.
Description string // Human-readable parameter description.
Required bool // Whether the parameter is required.
Deprecated bool // Whether the parameter is deprecated.
Schema map[string]any // JSON Schema for the parameter value.
Example any // Optional example value.
}