Response envelope
Envelope<T> is Conta’s standardized API response envelope: a response payload plus optional pagination, tracing, and HATEOAS metadata.
Envelope<T> is the contract: getters only, what a client reads.
MutableEnvelope<T, S> is how a server builds one, adding the setters and the fluent with* chain.
The concrete shapes are SingleEnvelope<T> (a single result), SliceEnvelope<E> (one page of a list) and ListEnvelope<E> (an unpaged collection), flat Lombok @Data classes implementing MutableEnvelope.
They live in the no.conta.http package — java.util + Lombok only, with no Jackson or Micronaut dependency and no serialization annotations.
cmdUuid is a UUID; meta and _related are Map<String, Object>; _links is Map<String, URI>.
Shape
Every field except content is optional.
Absent fields are omitted from the serialized JSON.
Reading an envelope back assumes FAIL_ON_UNKNOWN_PROPERTIES = false, the conta and Micronaut default.
The envelope keys come from default, as-declared property naming.
A global Jackson naming strategy (e.g. SNAKE_CASE) would rename them and emit a non-conforming envelope — keep default naming for envelope serialization.
|
{
"tenantId": "_org123",
"cmdUuid": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
"content": [ ],
"pageNumber": 0,
"size": 10,
"nextCursor": "…",
"meta": { },
"_related": { },
"_links": { "self": "https://..." },
"kind": "slice"
}
content is the payload; everything else is optional metadata, and the example lists every key, not a combination that occurs.
cmdUuid appears only on command-triggered responses, pageNumber/size only on paginated ones (pageNumber is zero-based), nextCursor only on a cursored slice the client can continue from.
meta and _related are free-form per-endpoint maps; _links maps rel to a URI.
Use the Meta and Links builders (Meta.of(…), Links.self(…) / Links.of(…)) to construct these maps.
kind names the envelope’s shape, single, slice or list; conta-http sets it on every envelope it builds, as information for the reader.
Building an envelope
A single result wraps its payload with Envelopes.single:
return Envelopes.single(widget);
Optional metadata is chained on with the with* methods, each returning the same envelope type:
return Envelopes.single(widget)
.withTenantId(tenantId)
.withMeta(Meta.of("calculatedAt", Instant.now()))
.withLinks(Links.self("https://api.conta.no/widgets/" + widget.id()));
For paginated responses, Envelopes.slice(…) maps a Micronaut Page or Slice into a SliceEnvelope, pre-filling content, pageNumber, and size — see Paging.
A cursored read builds its slice with Envelopes.cursored(content, size, nextCursor); a null cursor is the end — see Paging.
An unpaged collection, small and bounded, wraps with Envelopes.list:
return Envelopes.list(registries);
A ListEnvelope carries no paging keys; kind is list.
Use a slice for anything that may grow past one page.
An endpoint with nothing to return answers Envelopes.empty(), a single with content: {}, since content is never null:
return Envelopes.empty().withCmdUuid(cmdUuid);
The meta drawer
meta is the per-endpoint extension point for metadata that doesn’t belong in content and isn’t a standard envelope field.
The rule that keeps it from sprawling: data that is part of the answer goes in content; data about the answer that the client must act on goes in meta; anything that applies to every response should become a new top-level envelope field.