roas-overlay 0.2.0

Rust implementation of the OpenAPI Overlay Specification v1.0 / v1.1 — parse, validate, and apply
Documentation

roas-overlay

Rust implementation of the OpenAPI Overlay Specification: parse, validate, and apply Overlay documents to OpenAPI specs.

crates.io

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::EnumSet;
use roas_overlay::apply::Apply;
use roas_overlay::v1_0::Overlay;
use roas_overlay::validation::Validate;

let overlay: Overlay = serde_json::from_str(r#"{
    "overlay": "1.0.0",
    "info": { "title": "Example", "version": "1.0.0" },
    "actions": [
        { "target": "$.info", "update": { "description": "Patched." } },
        { "target": "$.paths['/internal/metrics']", "remove": true }
    ]
}"#).unwrap();

overlay.validate(EnumSet::empty()).expect("overlay is well-formed");

let mut target: serde_json::Value = serde_json::from_str(r#"{
    "openapi": "3.1.0",
    "info": { "title": "API", "version": "1.0.0" },
    "paths": { "/internal/metrics": { "get": {} } }
}"#).unwrap();

let report = overlay.apply(&mut target, EnumSet::empty()).unwrap();
assert_eq!(report.actions.len(), 2);
assert_eq!(target["info"]["description"], "Patched.");
assert!(target["paths"].as_object().unwrap().is_empty());

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:

  1. Compile the target JSONPath. Syntax errors abort the merge with InvalidJsonPath.
  2. Resolve matching nodes via serde_json_path.
  3. Zero matches → silent no-op (or ZeroMatch error under ApplyOptions::ErrorOnZeroMatch).
  4. Targets must be objects or arrays for every action (spec §4.4); primitives or null raise PrimitiveActionTarget.
  5. remove: true → drop each matched node from its container. Sibling array indices are preserved by processing matches in reverse.
  6. v1.1 only: if copy is 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 like update. Setting both update and copy raises ConflictingMergeSources.
  7. Otherwise, the merge value (update, or the copy source) 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 untouchedOverlay::apply operates on a clone and commits only on success.

Options

ApplyOptions (EnumSet):

  • ErrorOnZeroMatch — fail when an action's target selects zero nodes (default: silent no-op).
  • ErrorOnMixedKindMatch — fail when an update selects 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 — allow info.title / info.version to 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.