libasyncapi

package module
v0.0.4 Latest Latest
Warning

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

Go to latest
Published: Oct 14, 2026 License: MIT Imports: 15 Imported by: 0

README

libasyncapi - high-performance AsyncAPI tools for Go

Go Reference License

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.


Features

  • 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.

Bundling

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. Inline mode emits each shared external schema once and uses local references for later uses to prevent exponential output growth. Ambiguous external targets return an error instead of adding active channels or servers.

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, as it does for the parser. 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.

Install

go get github.com/pb33f/libasyncapi

Quick start

package 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

Partial documents and diagnostics

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)
	}
}

High-level and low-level models

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.

Multi-file documents

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)

Rendering and serialization

Render() writes the current high-level model. Serialize() writes the original YAML tree:

rendered, err := doc.Render()
original, err := doc.Serialize()

Compare documents

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())
}

Documentation and support

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.

License

libasyncapi is released under the MIT License.

Documentation

Overview

Package libasyncapi is a library containing tools for reading and manipulating AsyncAPI 3+ specifications into strongly typed documents. These documents have two APIs, a high level (porcelain) and a low level (plumbing).

Every single type has a 'GoLow()' method that drops down from the high API to the low API. Once in the low API, the entire original document data is available, including all comments, line and column numbers for keys and values.

Supported Versions

This library supports AsyncAPI 3.0 and later. AsyncAPI 2.x specifications are not supported and will return ErrAsyncAPI2NotSupported.

Basic Usage

spec, err := os.ReadFile("asyncapi.yaml")
if err != nil {
    log.Fatal(err)
}

doc, err := libasyncapi.NewDocument(spec)
if err != nil {
    log.Fatal(err)
}

model := doc.Model()
fmt.Printf("API: %s\n", model.Info.Title)

Error Handling

The library uses lenient parsing - it accumulates errors and continues parsing where possible. Use doc.IsPartial() to check if errors occurred, and doc.Errors() to retrieve them. The model may still be usable even with errors.

Multi-File Specifications

For specifications split across multiple files, use NewDocumentWithConfiguration and set the BasePath to enable file reference resolution:

config := libasyncapi.NewDocumentConfiguration()
config.BasePath = "/path/to/spec/directory"
config.AllowFileReferences = true

doc, err := libasyncapi.NewDocumentWithConfiguration(spec, config)

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrAsyncAPI2NotSupported = errors.New("AsyncAPI 2.x is not supported; use AsyncAPI 3.0 or later")

ErrAsyncAPI2NotSupported is returned when attempting to parse an AsyncAPI 2.x specification. This library only supports AsyncAPI 3.0 and later.

View Source
var ErrInvalidAsyncAPIVersion = errors.New("asyncapi version is not a valid semver string")

ErrInvalidAsyncAPIVersion is returned when the asyncapi version field is not a valid semver string.

View Source
var ErrInvalidYAML = errors.New("specification is not valid YAML or JSON")

ErrInvalidYAML is returned when the specification is not valid YAML or JSON.

View Source
var ErrNoAsyncAPIVersion = errors.New("asyncapi version field is required")

ErrNoAsyncAPIVersion is returned when the asyncapi version field is missing from the specification.

Functions

func DefaultSchemaFormat

func DefaultSchemaFormat(asyncapiVersion string) string

DefaultSchemaFormat returns the default schema format for a given AsyncAPI version. Per the spec, when schemaFormat is omitted, it defaults to the AsyncAPI schema format with the version matching the document's asyncapi field.

func DetectAsyncAPIVersion

func DetectAsyncAPIVersion(spec []byte) (string, error)

DetectAsyncAPIVersion extracts and validates the asyncapi version from a specification. It returns the version string and any error encountered. If the specification is AsyncAPI 2.x, it returns ErrAsyncAPI2NotSupported.

func ExtractVersionFromNode

func ExtractVersionFromNode(rootNode *yaml.Node) (string, error)

ExtractVersionFromNode extracts the asyncapi version from an already-parsed YAML node. This avoids double-parsing when the document has already been unmarshaled.

func IsAsyncAPI3

func IsAsyncAPI3(version string) bool

IsAsyncAPI3 checks if the given version string represents AsyncAPI 3.x.

Types

type CircularReferenceError

type CircularReferenceError struct {
	Path  string
	Chain []string
}

CircularReferenceError represents a circular reference detected in the specification.

func NewCircularReferenceError

func NewCircularReferenceError(path string, chain []string) *CircularReferenceError

NewCircularReferenceError creates a new CircularReferenceError.

func (*CircularReferenceError) Error

func (e *CircularReferenceError) Error() string

Error implements the error interface.

type Document

type Document interface {
	// GetVersion will return the exact version of the AsyncAPI specification set for the document.
	GetVersion() string

	// GetSpecInfo will return the SpecInfo instance that contains all specification information.
	GetSpecInfo() *SpecInfo

	// Model returns the high-level AsyncAPI model.
	// Returns nil if parsing completely failed.
	Model() *highasync.AsyncAPI

	// GoLow returns the low-level AsyncAPI model.
	// This provides access to line/column numbers and raw YAML nodes.
	GoLow() *lowasync.AsyncAPI

	// Index returns the reference index for this document.
	Index() *index.SpecIndex

	// Rolodex returns the file management system for multi-file specs.
	Rolodex() *index.Rolodex

	// RootNode returns the original YAML tree (not normalized).
	// This provides raw access for tooling that needs the original structure.
	RootNode() *yaml.Node

	// Serialize marshals the original YAML root node back to bytes.
	// This preserves the original structure and formatting.
	// For model-based rendering, use Render() instead.
	Serialize() ([]byte, error)

	// Render will return a YAML representation of the high-level model.
	Render() ([]byte, error)

	// Errors returns all errors accumulated during parsing.
	// The document may still contain a partial model even if errors occurred.
	Errors() []error

	// IsPartial returns true if errors occurred during parsing.
	// When true, the model may be incomplete but still usable.
	IsPartial() bool
}

Document represents a parsed AsyncAPI document that can be rendered into a model or serialized.

func NewDocument

func NewDocument(spec []byte) (Document, error)

NewDocument will create a new AsyncAPI instance from an AsyncAPI specification []byte array. If anything goes wrong when parsing, reading or processing the specification, there will be no document returned, instead an error will be returned that explains what failed.

This function will NOT automatically follow (meaning load) any file or remote references. If you need to load external references, use NewDocumentWithConfiguration instead.

Example
package 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

func NewDocumentWithConfiguration

func NewDocumentWithConfiguration(spec []byte, config *DocumentConfiguration) (Document, error)

NewDocumentWithConfiguration is the same as NewDocument, except it accepts a configuration that allows control over file and remote reference resolution.

type DocumentConfiguration

type DocumentConfiguration struct {
	// SpecFilePath identifies the entry file, allowing references back to it.
	// Relative paths are resolved against BasePath.
	SpecFilePath string

	// ExcludeExtensionRefs leaves $ref values inside x-* extensions untouched.
	ExcludeExtensionRefs bool

	// BasePath is the base path for resolving relative file references.
	// If empty, the current working directory is used.
	BasePath string

	// BaseURL is the base URL for resolving relative URL references.
	BaseURL *url.URL

	// AllowFileReferences enables resolution of file:// references.
	// Defaults to false.
	AllowFileReferences bool

	// AllowRemoteReferences enables resolution of http:// and https:// references.
	// Defaults to false.
	AllowRemoteReferences bool

	// RemoteURLHandler is a custom function for fetching remote references.
	// If nil, the default http client is used.
	RemoteURLHandler func(url string) (*http.Response, error)

	// LocalFS is a custom filesystem for resolving local file references.
	// If nil, the OS filesystem is used.
	LocalFS fs.FS

	// SkipCircularReferenceCheck disables circular reference detection.
	// This can improve performance but may cause infinite loops if circular
	// references exist in the specification.
	SkipCircularReferenceCheck bool

	// ExtractRefsSequentially processes references one at a time instead of
	// in parallel. This is slower but can be useful for debugging.
	ExtractRefsSequentially bool

	// Logger is an optional structured logger for diagnostic messages.
	Logger *slog.Logger
}

DocumentConfiguration holds configuration options for parsing AsyncAPI documents.

func NewDocumentConfiguration

func NewDocumentConfiguration() *DocumentConfiguration

NewDocumentConfiguration creates a new DocumentConfiguration with default values.

func (*DocumentConfiguration) ToLibOpenAPIConfig

func (c *DocumentConfiguration) ToLibOpenAPIConfig() *datamodel.DocumentConfiguration

ToLibOpenAPIConfig converts this configuration to libopenapi's configuration format.

type ParseError

type ParseError struct {
	Message string
	Line    int
	Column  int
	Path    string
}

ParseError represents an error that occurred during parsing with location information.

func NewParseError

func NewParseError(message string, line, column int, path string) *ParseError

NewParseError creates a new ParseError with the given message and location.

func (*ParseError) Error

func (e *ParseError) Error() string

Error implements the error interface.

type ReferenceError

type ReferenceError struct {
	Reference string
	Message   string
	Line      int
	Column    int
}

ReferenceError represents an error resolving a $ref reference.

func NewReferenceError

func NewReferenceError(ref, message string, line, column int) *ReferenceError

NewReferenceError creates a new ReferenceError.

func (*ReferenceError) Error

func (e *ReferenceError) Error() string

Error implements the error interface.

type SpecInfo

type SpecInfo struct {
	Version       string
	VersionParsed *semver.Version
	SpecType      string
}

SpecInfo contains parsed version information from an AsyncAPI specification.

func ParseAsyncAPIVersion

func ParseAsyncAPIVersion(version string) (*SpecInfo, error)

ParseAsyncAPIVersion parses a version string and returns SpecInfo. It validates that the version is a valid semver and is AsyncAPI 3.x.

type ValidationError

type ValidationError struct {
	Message string
	Path    string
}

ValidationError represents a semantic validation error in the specification.

func NewValidationError

func NewValidationError(message, path string) *ValidationError

NewValidationError creates a new ValidationError.

func (*ValidationError) Error

func (e *ValidationError) Error() string

Error implements the error interface.

Directories

Path Synopsis
Package bundler combines AsyncAPI 3 documents and their referenced files.
Package bundler combines AsyncAPI 3 documents and their referenced files.
datamodel
internal
schemaformat
Package schemaformat identifies schema languages whose references use JSON Schema semantics.
Package schemaformat identifies schema languages whose references use JSON Schema semantics.
yamlparse
Package yamlparse reads YAML and JSON into source nodes.
Package yamlparse reads YAML and JSON into source nodes.
Package visitor provides a visitor pattern for traversing AsyncAPI 3.0 documents.
Package visitor provides a visitor pattern for traversing AsyncAPI 3.0 documents.
Package what_changed determines what changed between two AsyncAPI documents.
Package what_changed determines what changed between two AsyncAPI documents.
model
Package model contains all the comparison logic and change types used to determine what changed between two AsyncAPI documents.
Package model contains all the comparison logic and change types used to determine what changed between two AsyncAPI documents.
reports
Package reports provides summarized, flattened views over the hierarchical change trees produced by the what-changed model.
Package reports provides summarized, flattened views over the hierarchical change trees produced by the what-changed model.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL