#![doc = include_str!("../README.md")]
#![forbid(unsafe_code)]
#![deny(missing_docs)]
#![deny(
clippy::unwrap_used,
clippy::expect_used,
clippy::indexing_slicing,
clippy::panic
)]
pub use indexmap;
pub use openapiv3;
mod component;
mod error;
mod reference;
mod resolved;
pub use component::Component;
pub use error::ResolveError;
pub use reference::Section;
pub use resolved::*;
use openapiv3::{OpenAPI, ReferenceOr};
use reference::ComponentRef;
use std::borrow::Cow;
pub const MAX_REFERENCE_HOPS: usize = 100;
pub trait Resolve {
fn resolve_ref<'a, T: Component>(&'a self, reference: &str) -> Result<&'a T, ResolveError>;
}
pub trait ResolveWithOpenAPI<T> {
fn resolve<'a>(&'a self, openapi: &'a OpenAPI) -> Result<&'a T, ResolveError>;
}
pub trait ResolveOptionalWithOpenAPI<T> {
fn resolve_optional<'a>(&'a self, openapi: &'a OpenAPI) -> Result<Option<&'a T>, ResolveError>;
}
impl Resolve for OpenAPI {
fn resolve_ref<'a, T: Component>(&'a self, reference: &str) -> Result<&'a T, ResolveError> {
walk(self, reference).map(|(_, item)| item)
}
}
impl<T: Component> ResolveWithOpenAPI<T> for ReferenceOr<T> {
fn resolve<'a>(&'a self, openapi: &'a OpenAPI) -> Result<&'a T, ResolveError> {
match self {
ReferenceOr::Item(item) => Ok(item),
ReferenceOr::Reference { reference } => openapi.resolve_ref(reference),
}
}
}
impl<T: Component> ResolveWithOpenAPI<T> for ReferenceOr<Box<T>> {
fn resolve<'a>(&'a self, openapi: &'a OpenAPI) -> Result<&'a T, ResolveError> {
match self {
ReferenceOr::Item(item) => Ok(item),
ReferenceOr::Reference { reference } => openapi.resolve_ref(reference),
}
}
}
impl<T, R> ResolveOptionalWithOpenAPI<T> for Option<R>
where
R: ResolveWithOpenAPI<T>,
{
fn resolve_optional<'a>(&'a self, openapi: &'a OpenAPI) -> Result<Option<&'a T>, ResolveError> {
match self {
Some(value) => value.resolve(openapi).map(Some),
None => Ok(None),
}
}
}
pub(crate) fn walk<'a, 'p, T: Component>(
openapi: &'a OpenAPI,
reference: &'p str,
) -> Result<(Cow<'p, str>, &'a T), ResolveError>
where
'a: 'p,
{
let mut pointer: &'p str = reference;
let mut hops: usize = 0;
loop {
let (parsed, entry) = lookup::<T>(openapi, pointer)?;
match entry {
ReferenceOr::Item(item) => return Ok((parsed.name, item)),
ReferenceOr::Reference { reference: next } => {
hops += 1;
if hops > MAX_REFERENCE_HOPS {
return Err(ResolveError::ReferenceChainTooLong {
reference: reference.to_owned(),
last: next.clone(),
max_hops: MAX_REFERENCE_HOPS,
});
}
pointer = next.as_str();
}
}
}
}
fn lookup<'a, 'p, T: Component>(
openapi: &'a OpenAPI,
pointer: &'p str,
) -> Result<(ComponentRef<'p>, &'a ReferenceOr<T>), ResolveError> {
let parsed = ComponentRef::parse(pointer)?;
if parsed.section != T::SECTION {
return Err(ResolveError::SectionMismatch {
expected: T::SECTION,
found: parsed.section,
});
}
let entry = lookup_named::<T>(openapi, &parsed.name)?;
Ok((parsed, entry))
}
pub(crate) fn lookup_named<'a, T: Component>(
openapi: &'a OpenAPI,
name: &str,
) -> Result<&'a ReferenceOr<T>, ResolveError> {
let section = T::section(openapi).ok_or(ResolveError::ComponentsMissing {
section: T::SECTION,
})?;
section.get(name).ok_or_else(|| ResolveError::NotFound {
section: T::SECTION,
name: name.to_owned(),
})
}