Documentation
¶
Overview ¶
Package jsonapi is the [JSON:API](https://jsonapi.org/format/) codec: it rewrites a proto-JSON REST response into the {data, meta, links, errors} envelope (TransformSingleResource, TransformCollection, TransformMeta, TransformError).
The transformation is driven by an operation-keyed route table the consumer supplies (typically generated from its API definitions): each REST RPC classifies into a Role, and Config.MatchRoute finds a request's RouteEntry, so a response is transformed by role rather than by re-guessing method/path/data-presence. The HTTP middleware that applies it lives in transport/jsonapi.
Index ¶
- Constants
- func TransformCollection(body map[string]any, cfg ResourceConfig, selfLink string) map[string]any
- func TransformError(statusCode int, body []byte) []byte
- func TransformMeta(body map[string]any) map[string]any
- func TransformSingleResource(body map[string]any, cfg ResourceConfig, selfLink string) map[string]any
- type Config
- type ResourceConfig
- type Role
- type RouteEntry
Constants ¶
const MemberCode = "code"
MemberCode is the error-body member carrying the machine-readable error code, both in a Connect error body and in a JSON:API error object.
Variables ¶
This section is empty.
Functions ¶
func TransformCollection ¶
func TransformCollection( body map[string]any, cfg ResourceConfig, selfLink string, ) map[string]any
TransformCollection converts a Vanguard list response to JSON:API format.
Input: {"users": [...], "pagination": {"nextPageToken":"abc", "totalCount":45, "totalIsEstimate":true}} Output: {"data": [...], "meta": {"totalCount":45, "totalIsEstimate":true}, "links":{"self":"...", "next":"..."}}
func TransformError ¶
TransformError converts a Connect/Vanguard error response body to JSON:API error format.
Input: {"code":5, "message":"user not found"} (Vanguard: numeric google.rpc.Code)
or {"code":"not_found", "message":"user not found"} (this package's own writers)
Output: {"errors":[{"code":"not_found","status":"404","detail":"user not found"}]}
The member carries three fields: "code" (the machine-readable app error code, defaulting to "unknown" when the upstream body is empty, unparseable, or carries an unrecognized code), "status" (the HTTP status as a string) and "detail" (the human-readable message). Only "detail" is optional — it is omitted rather than filled with a placeholder when the upstream body carries no message, so a client can tell "no message was sent" from "this is the message". No "title" is emitted: it was derived from the status code alone and so duplicated "status".
Vanguard renders a transcoded Connect/gRPC error as a google.rpc.Status whose "code" is a NUMBER, so the numeric code is normalized to its canonical connect name (see normalizeErrorCode); without that every REST error would degrade to "unknown", stripping the machine-readable code every caller keys on.
The document always holds exactly one error member; JSON:API models "errors" as an array, and that shape is kept even though nothing here reports more than one failure at a time.
func TransformMeta ¶
TransformMeta converts a non-resource response to a JSON:API meta-only document. Per the JSON:API spec, a valid document must contain at least one of: data, errors, or meta. Action endpoints that return status info (not a resource) use this format.
Input: {"sent": 5, "failed": 0, "failedEmails": []} Output: {"meta": {"sent": 5, "failed": 0, "failedEmails": []}}
func TransformSingleResource ¶
func TransformSingleResource( body map[string]any, cfg ResourceConfig, selfLink string, ) map[string]any
TransformSingleResource converts a Vanguard single-resource response to JSON:API format.
Input: {"user": {"id":"123", "email":"a@b.com", ...}} Output: {"data": {"type":"users", "id":"123", "attributes":{...}}, "links":{"self":"..."}}
Types ¶
type Config ¶
type Config struct {
// BasePath is the URL prefix all routes share (e.g. "/api/v1/demo"). Paths
// outside it skip the route-table scan (and pass through untransformed).
BasePath string
// Routes is the generated operation-keyed route table for the service.
Routes []RouteEntry
}
Config holds the JSON:API middleware configuration: the generated route table plus the service base path used only as a fast-reject prefix.
func (Config) MatchRoute ¶
func (c Config) MatchRoute(method, path string) (RouteEntry, bool)
MatchRoute finds the RouteEntry whose method and path template match a concrete request (method, path). A template segment matches when it is a "{param}" wildcard or byte-equal to the concrete segment. When more than one template could match, a literal-segment match beats a "{param}" wildcard at the same position (so "/connectors/test" resolves to the literal action route, not "/connectors/{id}"). Returns false when no route matches.
type ResourceConfig ¶
type ResourceConfig struct {
// Type is the JSON:API resource type (e.g. "users").
Type string
// SingleKey is the protobuf JSON key for a single resource response
// (e.g. "user" from GetUserResponse).
SingleKey string
// CollectionKey is the protobuf JSON key for a list response
// (e.g. "users" from ListUsersResponse).
CollectionKey string
// PaginationKey is the protobuf JSON key for pagination metadata
// (e.g. "pagination" from ListUsersResponse).
PaginationKey string
}
ResourceConfig describes how a single protobuf resource type maps to JSON:API response structure. It is the transform functions' input, derived from the matched RouteEntry.
type Role ¶
type Role string
Role is the JSON:API operation role a REST route classifies into. The consumer's route table (typically emitted by its code generator) tags each route with one; the middleware switches on it.
const ( // RoleList is a collection read: GET returning data[] + meta. RoleList Role = "LIST" // RoleGetOne is a single-resource read: GET on an instance or a singleton. RoleGetOne Role = "GET_ONE" // RoleCreate is a resource creation: POST returning the created resource. RoleCreate Role = "CREATE" // RoleUpdate is a resource replacement/patch: PUT or PATCH. RoleUpdate Role = "UPDATE" // RoleDelete is a resource deletion: DELETE (204, no body). RoleDelete Role = "DELETE" // RoleAction is a non-CRUD operation: meta-only, never a data resource. RoleAction Role = "ACTION" )
The JSON:API operation roles. Every REST route carries exactly one.
type RouteEntry ¶
type RouteEntry struct {
// Method is the upper-case HTTP verb (GET/POST/PUT/PATCH/DELETE).
Method string
// PathTemplate is the raw google.api.http path template, base path included
// (e.g. "/api/v1/widgets/{widget_id}/parts/{part_id}"). A
// "{param}" segment is a wildcard when matching a concrete request path.
PathTemplate string
// Role is the JSON:API operation role driving the transform.
Role Role
// ResourceType is the JSON:API data.type (e.g. "users"); empty for a
// DELETE/action response that carries no resource shape.
ResourceType string
// SingleKey is the response's single-resource key (e.g. "user"); empty when
// the response carries no single resource.
SingleKey string
// CollectionKey is the response's collection key (e.g. "users"); empty when
// the response carries no collection.
CollectionKey string
// PaginationKey is the response's pagination-metadata key; carried for fidelity.
PaginationKey string
// RPC is the fully-qualified method name (for diagnostics); unused at runtime.
RPC string
// RequestEnvelope is true when the inbound body is JSON:API-enveloped
// ({data:{type,attributes}}) and must be validated + unwrapped — CREATE and
// UPDATE only. (Ordered last to keep the string fields packed.)
RequestEnvelope bool
}
RouteEntry is one operation-keyed row of the route table: an HTTP binding (method + path template) tagged with its JSON:API Role and the response resource keys. It is the single contract the middleware consumes; a consumer's route table (hand-written or generated) is a slice of this type.
func (RouteEntry) Resource ¶
func (e RouteEntry) Resource() ResourceConfig
Resource projects a RouteEntry onto the resource-key shape the transform functions consume, keeping them decoupled from the route table.