Skip to content

Repository files navigation

openapi-aggregator

Aggregate and merge OpenAPI 3.x specifications from multiple sources into a single spec.

Available as both a Rust library and a CLI tool.

Features

  • Multiple source types – local YAML files, local JSON files, and HTTP endpoints (with custom headers)
  • Config-file driven – define sources and merge options in a single YAML config
  • Conflict resolution – choose between error, overwrite, or rename strategies for duplicate paths and component names
  • $ref rewriting – when using the rename strategy, $ref pointers are automatically updated
  • Path prefixing – optionally prefix every path with the source name to guarantee uniqueness
  • Info override – set a custom title, version, and description in the merged output
  • Per-source custom blocks – deep-merge arbitrary OpenAPI blocks/extensions from config (provider-agnostic)

Installation

Quick install (Linux / macOS)

curl -sSfL https://raw.githubusercontent.com/includeamin/openapi-aggregator/main/install.sh | sh

Install a specific version or to a custom directory:

VERSION=v0.1.0 curl -sSfL https://raw.githubusercontent.com/includeamin/openapi-aggregator/main/install.sh | sh
INSTALL_DIR=/usr/local/bin curl -sSfL https://raw.githubusercontent.com/includeamin/openapi-aggregator/main/install.sh | sh

Uninstall:

curl -sSfL https://raw.githubusercontent.com/includeamin/openapi-aggregator/main/install.sh | sh -s -- --uninstall

From source

cargo install --path .

Pre-built binaries

Download from GitHub Releases. Binaries are available for Linux (x86_64, aarch64), macOS (x86_64, aarch64), and Windows (x86_64).

CLI Usage

# Merge using a config file (defaults to openapi-aggregator.yaml)
openapi-aggregator
# Specify a config file and output location
openapi-aggregator -c my-config.yaml -o merged.yaml
# Output as JSON
openapi-aggregator -c my-config.yaml -f json
# Print help
openapi-aggregator --help

Configuration

Create an openapi-aggregator.yaml (see config.example.yaml):

sources:
- name: petstorepath: ./specs/petstore.yamladditional_blocks:
x-custom-root:
enabled: truepaths:
/pets:
get:
x-custom-operation:
rate_limit: 100
- name: userspath: ./specs/users.json
- name: billingurl: https://billing.example.com/openapi.jsonheaders:
Authorization: "Bearer token"output:
format: yaml # yaml | jsonmerge:
conflict_strategy: error # error | overwrite | renameprefix_paths: falseinfo:
title: "My Aggregated API"version: "1.0.0"

Source detection

Sources are detected automatically by their fields:

  • If url is present → HTTP source
  • If path is present → file source (YAML or JSON auto-detected from content)

Additional source blocks

Each source can define additional_blocks as any YAML/JSON object. It is deep-merged into that source document before merge, so you can inject vendor extensions (for example API gateway related x-... blocks) or other custom OpenAPI fragments without adding provider-specific fields.

Library Usage

use openapi_aggregator::{aggregate,Config,Source,MergeConfig};#[tokio::main]asyncfnmain() -> Result<(),Box<dyn std::error::Error>>{let config = Config{sources:vec![Source::File{
name:Some("petstore".into()),
path:"./specs/petstore.yaml".into(),
additional_blocks:None,
tag_prefix:None,},Source::Http{
name:Some("billing".into()),
url:"https://billing.example.com/openapi.json".into(),
headers:[("Authorization".into(),"Bearer token".into())].into_iter().collect(),
additional_blocks:None,
tag_prefix:None,},],output:Default::default(),merge:MergeConfig::default(),};let merged = aggregate(&config).await?;println!("{}", serde_json::to_string_pretty(&merged)?);Ok(())}

Or load directly from a config file:

use openapi_aggregator::aggregate_from_file;use std::path::Path;#[tokio::main]asyncfnmain() -> Result<(),Box<dyn std::error::Error>>{let merged = aggregate_from_file(Path::new("openapi-aggregator.yaml")).await?;println!("{}", serde_yaml::to_string(&merged)?);Ok(())}

Merge Behaviour

StrategyDuplicate pathsDuplicate components
errorFail immediatelyFail immediately
overwriteLast source winsLast source wins
renamePrefix path with /{source_name}Rename to {source_name}_{component}

When prefix_paths: true, all paths are prefixed regardless of conflicts.

When using rename, any $ref pointing to a renamed component is rewritten automatically.

Development

# Run tests
cargo test# Lint
cargo clippy --all-targets -- -D warnings
# Format
cargo fmt

License

MIT

About

A simple library to aggregate openapi spec from different sources

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages