libasyncapi parses AsyncAPI 3.0+ documents into strongly typed Go models. It provides a
high-level API for normal application work and a low-level, YAML-aware API for tooling that
needs source nodes, line and column numbers, key ordering, references, or render fidelity.
AsyncAPI 2.x is intentionally unsupported.
- AsyncAPI 3.0+ parsing with high-level and low-level models.
- Lenient partial parsing with accumulated diagnostics.
- Local and remote multi-file reference resolution.
- Ordered maps and access to the original YAML node tree.
- Model rendering and original-document serialization.
- Visitor-based document traversal, including schemas and protocol bindings.
- Breaking and non-breaking change detection through
what-changed. - Typed HTTP, Kafka, WebSocket, AMQP, MQTT, and SQS bindings.
- Inline and composed AsyncAPI 3 bundling through
bundler.
Use github.com/pb33f/libasyncapi/bundler to produce a portable YAML document:
config := &libasyncapi.DocumentConfiguration{
BasePath: "/path/to/specs",
SpecFilePath: "asyncapi.yaml",
AllowFileReferences: true,
ExcludeExtensionRefs: true,
}
bundled, err := bundler.BundleBytes(spec, config)BundleBytesComposed(spec, config, &bundler.BundleCompositionConfig{Delimiter: "__"})
moves imported objects into AsyncAPI component sections. Collisions add the source
file name and, when needed, a numeric suffix. Both modes preserve root-local
references and fields that require Reference Objects. Cycles use local references.
Operation messages continue to reference their channel's messages; operations and
channel servers continue to reference the required root locations. An operations-only
actor can reference channels in an external domain file. When no root destination
exists, the bundler imports the required external channels and servers at the root.
Existing root destinations take precedence; ambiguous targets still return an error.
Inline mode
emits each shared external schema once and uses local references for later uses
to prevent exponential output growth.
The bundler reads the source tree directly and does not require a typed model. It preserves comments, key order, explicit empty values, examples, bindings, and multi-format schema content. YAML aliases and merge keys are expanded. It does not validate the complete AsyncAPI contract or merge traits. Input bytes and configuration are not modified. An error returns no output, including when a reference cannot be loaded.
Remote loading requires AllowRemoteReferences. LocalFS and RemoteURLHandler
can supply custom sources. SpecFilePath identifies the entry document for
references back to it. A nonempty BasePath enables local loading and supplies the
entry document's external reference directory, even when SpecFilePath is in a
different directory. Fragment-only references retain the entry document's identity;
loaded files resolve relative paths from their own locations. Set
ExcludeExtensionRefs to keep $ref values in extensions as data;
example values are always treated as data.
BasePath is a starting directory, not a file access boundary: parent paths and
absolute file references can read outside it, and their contents enter the bundle.
For untrusted input, supply a LocalFS that enforces the required boundary
(including symlink access). Referenced scalar documents are rejected except for
boolean schemas and content inside a Multi Format Schema Object.
Schema resource IDs and anchors are resolved before relocation and removed from
the output so local references use the bundled document's scope. OpenAPI-format
schemas also resolve their references. A Reference Object in the schema field
of a Multi Format Schema Object loads the referenced JSON or YAML value, including
Avro records, unions, and primitive types. The bundler preserves the schema content
without interpreting language-specific imports or nested data as JSON Schema.
Kafka groupId and clientId, and MQTT schema-valued fields, also resolve their
references. Circular YAML aliases, multiple YAML documents, and dynamic or recursive JSON Schema reference
keywords return errors. These features need separate semantics and are not silently
expanded.
The document parser resolves message pointers through channel Reference Objects,
including composed bundles, while retaining the original source nodes. Successful
alias repairs are removed from Document.Errors(); raw index diagnostics remain
available through libopenapi's index accessors. JSON input uses JSON string and
whitespace semantics and retains source line and column positions.
go get github.com/pb33f/libasyncapipackage main
import (
"fmt"
"log"
"github.com/pb33f/libasyncapi"
)
func main() {
spec := []byte(`asyncapi: 3.0.0
info:
title: Orders API
version: 1.0.0
channels:
orders:
address: /orders
`)
doc, err := libasyncapi.NewDocument(spec)
if err != nil {
log.Fatal(err)
}
model := doc.Model()
fmt.Printf("%s uses AsyncAPI %s\n", model.Info.Title, doc.GetVersion())
for name, channel := range model.Channels.FromOldest() {
if channel.Address != nil {
fmt.Printf("channel %s: %s\n", name, *channel.Address)
}
}
}Output:
Orders API uses AsyncAPI 3.0.0
channel orders: /orders
Parsing continues where possible. A document can therefore contain a useful partial model and one or more diagnostics:
doc, err := libasyncapi.NewDocument(spec)
if err != nil {
log.Fatal(err) // The document could not be created at all.
}
if doc.IsPartial() {
for _, parseErr := range doc.Errors() {
log.Printf("AsyncAPI diagnostic: %v", parseErr)
}
}Use Model() for ordinary application logic:
model := doc.Model()
fmt.Println(model.Info.Title)Use GoLow() when source information matters:
lowModel := doc.GoLow()
fmt.Println(lowModel.Info.Value.Title.KeyNode.Line)Every high-level model type exposes GoLow() and GoLowUntyped() for moving back to its
source-aware representation.
External references are disabled by default. Enable only the reference types your application needs:
config := libasyncapi.NewDocumentConfiguration()
config.BasePath = "/path/to/asyncapi"
config.AllowFileReferences = true
config.AllowRemoteReferences = false
doc, err := libasyncapi.NewDocumentWithConfiguration(spec, config)Render() writes the current high-level model. Serialize() writes the original YAML tree:
rendered, err := doc.Render()
original, err := doc.Serialize()The what-changed package produces a hierarchical change model with breaking-change
classification:
import what_changed "github.com/pb33f/libasyncapi/what-changed"
changes := what_changed.CompareDocuments(originalDoc, updatedDoc)
if changes != nil {
fmt.Printf("%d changes, %d breaking\n",
changes.TotalChanges(), changes.TotalBreakingChanges())
}The public Go API is documented at pkg.go.dev/github.com/pb33f/libasyncapi. Full project documentation and a dedicated logo have not been published yet.
Need help, have a question, or want to share what you are building? Join the pb33f Discord.
libasyncapi is released under the MIT License.