jsonapi

package
v0.0.34 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 31, 2026 License: MIT Imports: 4 Imported by: 0

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

View Source
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

func TransformError(statusCode int, body []byte) []byte

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

func TransformMeta(body map[string]any) map[string]any

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL