Ankusa.AsyncApi (ankusa v0.7.0)

Copy Markdown View Source

The AsyncAPI 3.0 document for a running instance: every messaging channel it publishes to, built from what is configured right now.

Served by the admin API at GET /asyncapi.json (application/asyncapi+json). A consumer fetches it to learn which topic, routing key, subject, or channel carries which source, and what the message looks like.

What it is built from

Each source in Ankusa.SourceStore contributes one entry per sink that implements Ankusa.Sink.describe/2: the Kafka, RabbitMQ, NATS, Redis, SQS, and Google Pub/Sub sinks. Ankusa.Sink.Log has no channel to advertise, and Ankusa.Sink.Http describes a destination that carries the provider's raw body (payload: :body), not the envelope this document describes: both are left out. When lifecycle events are on (Ankusa.Lifecycle), their sinks contribute entries too, carrying LifecycleEventV1.

Entries become document parts like so:

  • a server per distinct broker (protocol, host, pathname);
  • a channel per distinct address on a server. Two sources publishing to one Kafka topic share one channel and have one message each, told apart by the record key. A sink whose address a configured function computes per hook has no address to advertise: it gets a channel of its own with a null address, and a description saying so;
  • a message per source in its channel, whose payload is SinkMessageV1 narrowed to that source (and its tenant, when the route resolver fixes it);
  • an operation per channel, send: Ankusa is the publisher.

The document describes the envelope, not the hook: the provider's own body is opaque bytes inside body_base64.

Headers by transport

Beside the JSON body every transport carries the same identity in its own metadata, so a consumer can dedupe or route without decoding the body. A message whose sink carries the ankusa_* set (Ankusa.Sink.Description's ankusa_headers) references SinkMessageHeadersV1; the others have no headers object.

TransportMetadataankusa_tenant_id without a tenant
Kafka, NATSrecord / message headers, Ankusa.Sink.Message.headers/2 (NATS adds Nats-Msg-Id)""
SQS, Google Pub/Submessage attributes, Ankusa.Sink.Message.attributes/2absent (both refuse an empty value)
RabbitMQAMQP message_id and the ankusa_idempotency_key, ankusa_dedupe_key, ankusa_replay_id, ankusa_ordering_key headers; not described by the schemanot sent
Redis pub/subnone: the body is all there isnot sent
HTTPx-ankusa-* request headers (Ankusa.Sink.Http); not in this document, the body is the provider'sx-ankusa-tenant absent

SinkMessageHeadersV1 requires only the names every described transport sends (ankusa_id, ankusa_source_id, ankusa_message_version, ankusa_idempotency_key, content_type); ankusa_tenant_id is optional, so a consumer reads tenant_id from the body when the header is missing or empty.

An instance that sets region (YAML node.region) says so in the document's x-ankusa-region extension; each message's region and instance fields say the same per message.

Nothing in the document is secret. Descriptions carry no userinfo and no credentials (Ankusa.Sink.Description).

Summary

Functions

Build the document for instance from its current configuration.

Functions

document(instance)

@spec document(atom()) :: AsyncApiSpex.Document.t()

Build the document for instance from its current configuration.