Skip to main content

openapi_deref/
lib.rs

1//! Lightweight OpenAPI / JSON Schema `$ref` resolver.
2//!
3//! Recursively expands all `$ref` pointers in a [`serde_json::Value`],
4//! producing a self-contained document with no unresolved references.
5//!
6//! # Features
7//!
8//! - Resolves internal `$ref` via JSON Pointer ([RFC 6901])
9//! - Cycle detection with a visited-path set
10//! - Typed error model: fatal [`ResolveError`] vs non-fatal [`RefError`]
11//! - [`resolve_strict`] for zero-tolerance mode
12//! - Zero dependencies beyond [`serde_json`] and [`thiserror`]
13//!
14//! [RFC 6901]: https://datatracker.ietf.org/doc/html/rfc6901
15//!
16//! # API levels
17//!
18//! | Function | Returns | Use when |
19//! |---|---|---|
20//! | [`resolve_strict`] | `Result<Value, StrictResolveError>` | Any unresolved ref is unacceptable |
21//! | [`resolve`] | `Result<ResolvedDoc, ResolveError>` | You need to inspect partial results or diagnostics |
22//! | [`resolve_with_root`] | `Result<ResolvedDoc, ResolveError>` | The ref lookup root differs from the target value |
23//!
24//! # Quick start
25//!
26//! ```
27//! use serde_json::json;
28//!
29//! let value = openapi_deref::resolve_strict(&json!({
30//!     "components": { "schemas": { "Id": { "type": "integer" } } },
31//!     "field": { "$ref": "#/components/schemas/Id" }
32//! })).unwrap();
33//!
34//! assert_eq!(value["field"]["type"], "integer");
35//! ```
36//!
37//! # Detailed usage
38//!
39//! ```
40//! use serde_json::json;
41//! use openapi_deref::resolve;
42//!
43//! let spec = json!({
44//!     "components": {
45//!         "schemas": {
46//!             "User": {
47//!                 "type": "object",
48//!                 "properties": { "name": { "type": "string" } }
49//!             }
50//!         }
51//!     },
52//!     "paths": {
53//!         "/users": {
54//!             "get": {
55//!                 "responses": {
56//!                     "200": {
57//!                         "content": {
58//!                             "application/json": {
59//!                                 "schema": { "$ref": "#/components/schemas/User" }
60//!                             }
61//!                         }
62//!                     }
63//!                 }
64//!             }
65//!         }
66//!     }
67//! });
68//!
69//! let doc = resolve(&spec).unwrap();
70//!
71//! // Inspect diagnostics without importing RefError
72//! assert!(doc.is_complete());
73//! assert_eq!(doc.cycles().count(), 0);
74//! assert_eq!(doc.missing_refs().count(), 0);
75//! assert_eq!(doc.external_refs().count(), 0);
76//! ```
77//!
78//! # Error handling
79//!
80//! ```
81//! use serde_json::json;
82//! use openapi_deref::{resolve_strict, StrictResolveError};
83//!
84//! let spec = json!({ "ref": { "$ref": "#/missing" } });
85//!
86//! match resolve_strict(&spec) {
87//!     Ok(value) => { /* fully resolved */ }
88//!     Err(StrictResolveError::Fatal(e)) => {
89//!         eprintln!("fatal: {e}");
90//!     }
91//!     Err(StrictResolveError::Partial(e)) => {
92//!         eprintln!("{e}"); // lists unresolved refs
93//!         let _partial = &e.value; // still usable
94//!     }
95//!     Err(_) => { /* future variants */ }
96//! }
97//! ```
98
99#![warn(missing_docs)]
100
101mod error;
102mod resolved;
103mod resolver;
104
105pub use error::{PartialResolveError, RefError, ResolveError, StrictResolveError};
106pub use resolved::ResolvedDoc;
107pub use resolver::{resolve, resolve_strict, resolve_with_root};