roas-overlay
Rust implementation of the OpenAPI Overlay Specification: parse, validate, and apply Overlay documents to OpenAPI specs.
An Overlay is a sidecar document whose ordered list of actions — each a RFC 9535 JSONPath target plus an update, remove, or v1.1 copy instruction — transforms a target OpenAPI document. Common uses: layering environment-specific changes over a base API, adding vendor extensions without forking, removing internal endpoints from a public bundle.
This crate is a sibling of roas (the typed parser / validator / merger for OpenAPI 2.0–3.2). It operates on serde_json::Value so a single implementation works across every OpenAPI version, and so overlays that produce intermediate states the typed model would reject (drop a required field then add it back) still apply cleanly.
Versions
| Overlay version | Feature flag | Status | Adds over the previous version |
|---|---|---|---|
| 1.0 | v1_0 (default) |
✅ implemented | — |
| 1.1 | v1_1 |
✅ implemented | copy action, info.description |
v1_0 and v1_1 are independent — enable whichever you need. With both enabled, an impl From<v1_0::Overlay> for v1_1::Overlay is available for upconverting an existing v1.0 document.
Quick start
use EnumSet;
use Apply;
use Overlay;
use Validate;
let overlay: Overlay = from_str.unwrap;
overlay.validate.expect;
let mut target: Value = from_str.unwrap;
let report = overlay.apply.unwrap;
assert_eq!;
assert_eq!;
assert!;
YAML overlays work the same way — parse with serde_yaml_ng (or any other YAML crate) into Overlay.
Apply algorithm
For each action in declaration order, against the current working copy of the target:
- Compile the
targetJSONPath. Syntax errors abort the merge withInvalidJsonPath. - Resolve matching nodes via
serde_json_path. - Zero matches → silent no-op (or
ZeroMatcherror underApplyOptions::ErrorOnZeroMatch). - Targets must be objects or arrays for every action (spec §4.4); primitives or
nullraisePrimitiveActionTarget. remove: true→ drop each matched node from its container. Sibling array indices are preserved by processing matches in reverse.- v1.1 only: if
copyis set, the action's source JSONPath is evaluated against the current doc; it must match exactly one node (CopySourceNotFound/CopySourceMultiple). The source value is then used as the merge value, exactly likeupdate. Setting bothupdateandcopyraisesConflictingMergeSources. - Otherwise, the merge value (
update, or thecopysource) is merged into each matched node by kind:- Object target → recursive merge per §4.4.3.1: shared object keys recurse; primitives replace; nested arrays concatenate.
- Array target → depends on the version:
- v1.0 — the merge value is appended as a single new element (spec §3.3: "an entry to append to the array").
- v1.1 — an array merge value is concatenated element-wise; an object or primitive merge value is appended as a single element (spec §3.3).
On any error the target document is left untouched — Overlay::apply operates on a clone and commits only on success.
Options
ApplyOptions (EnumSet):
ErrorOnZeroMatch— fail when an action'stargetselects zero nodes (default: silent no-op).ErrorOnMixedKindMatch— fail when anupdateselects nodes of mixed kind (some objects, some arrays). The v1.1 spec calls this out normatively; this option lets v1.0 callers opt in.
ValidationOptions (EnumSet):
IgnoreEmptyInfoTitle,IgnoreEmptyInfoVersion— allowinfo.title/info.versionto be empty.
Behind the clap feature, both enums implement clap::ValueEnum so downstream CLIs (such as roas-cli) can surface them directly.
Validation
Validate::validate returns every diagnostic it finds rather than failing on the first one. Diagnostics carry a JSONPath-flavor path (e.g. #.actions[3].target).
License
Licensed under either of Apache License, Version 2.0 or MIT license at your option.