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