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.