Help us shape the future of Go! Take the 2026 Go Developer Survey

jsonapi

package
v0.0.7 Latest Latest
Warning

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

Go to latest
Published: Oct 14, 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