1#![doc = include_str!("../README.md")]
2#![forbid(unsafe_code)]
3#![deny(missing_docs)]
4#![deny(
5 clippy::unwrap_used,
6 clippy::expect_used,
7 clippy::indexing_slicing,
8 clippy::panic
9)]
10
11pub use indexmap;
12pub use openapiv3;
13
14mod component;
15mod error;
16mod reference;
17mod resolved;
18
19pub use component::Component;
20pub use error::ResolveError;
21pub use reference::Section;
22pub use resolved::*;
23
24use openapiv3::{OpenAPI, ReferenceOr};
25use reference::ComponentRef;
26use std::borrow::Cow;
27
28pub const MAX_REFERENCE_HOPS: usize = 100;
33
34pub trait Resolve {
36 fn resolve_ref<'a, T: Component>(&'a self, reference: &str) -> Result<&'a T, ResolveError>;
41}
42
43pub trait ResolveWithOpenAPI<T> {
45 fn resolve<'a>(&'a self, openapi: &'a OpenAPI) -> Result<&'a T, ResolveError>;
52}
53
54pub trait ResolveOptionalWithOpenAPI<T> {
59 fn resolve_optional<'a>(&'a self, openapi: &'a OpenAPI) -> Result<Option<&'a T>, ResolveError>;
62}
63
64impl Resolve for OpenAPI {
65 fn resolve_ref<'a, T: Component>(&'a self, reference: &str) -> Result<&'a T, ResolveError> {
66 walk(self, reference).map(|(_, item)| item)
67 }
68}
69
70impl<T: Component> ResolveWithOpenAPI<T> for ReferenceOr<T> {
71 fn resolve<'a>(&'a self, openapi: &'a OpenAPI) -> Result<&'a T, ResolveError> {
72 match self {
73 ReferenceOr::Item(item) => Ok(item),
74 ReferenceOr::Reference { reference } => openapi.resolve_ref(reference),
75 }
76 }
77}
78
79impl<T: Component> ResolveWithOpenAPI<T> for ReferenceOr<Box<T>> {
80 fn resolve<'a>(&'a self, openapi: &'a OpenAPI) -> Result<&'a T, ResolveError> {
81 match self {
82 ReferenceOr::Item(item) => Ok(item),
83 ReferenceOr::Reference { reference } => openapi.resolve_ref(reference),
84 }
85 }
86}
87
88impl<T, R> ResolveOptionalWithOpenAPI<T> for Option<R>
89where
90 R: ResolveWithOpenAPI<T>,
91{
92 fn resolve_optional<'a>(&'a self, openapi: &'a OpenAPI) -> Result<Option<&'a T>, ResolveError> {
93 match self {
94 Some(value) => value.resolve(openapi).map(Some),
95 None => Ok(None),
96 }
97 }
98}
99
100pub(crate) fn walk<'a, 'p, T: Component>(
107 openapi: &'a OpenAPI,
108 reference: &'p str,
109) -> Result<(Cow<'p, str>, &'a T), ResolveError>
110where
111 'a: 'p,
112{
113 let mut pointer: &'p str = reference;
116 let mut hops: usize = 0;
117
118 loop {
119 let (parsed, entry) = lookup::<T>(openapi, pointer)?;
120 match entry {
121 ReferenceOr::Item(item) => return Ok((parsed.name, item)),
122 ReferenceOr::Reference { reference: next } => {
123 hops += 1;
124 if hops > MAX_REFERENCE_HOPS {
125 return Err(ResolveError::ReferenceChainTooLong {
126 reference: reference.to_owned(),
127 last: next.clone(),
128 max_hops: MAX_REFERENCE_HOPS,
129 });
130 }
131 pointer = next.as_str();
132 }
133 }
134 }
135}
136
137fn lookup<'a, 'p, T: Component>(
139 openapi: &'a OpenAPI,
140 pointer: &'p str,
141) -> Result<(ComponentRef<'p>, &'a ReferenceOr<T>), ResolveError> {
142 let parsed = ComponentRef::parse(pointer)?;
143
144 if parsed.section != T::SECTION {
145 return Err(ResolveError::SectionMismatch {
146 expected: T::SECTION,
147 found: parsed.section,
148 });
149 }
150
151 let entry = lookup_named::<T>(openapi, &parsed.name)?;
152 Ok((parsed, entry))
153}
154
155pub(crate) fn lookup_named<'a, T: Component>(
161 openapi: &'a OpenAPI,
162 name: &str,
163) -> Result<&'a ReferenceOr<T>, ResolveError> {
164 let section = T::section(openapi).ok_or(ResolveError::ComponentsMissing {
165 section: T::SECTION,
166 })?;
167 section.get(name).ok_or_else(|| ResolveError::NotFound {
168 section: T::SECTION,
169 name: name.to_owned(),
170 })
171}