Skip to main content

openapiv3_resolve/resolved/
shared.rs

1use super::ResolvedSchema;
2use std::fmt;
3use std::ops::Deref;
4use std::sync::{Arc, Weak};
5
6/// A resolved item, shared by every `$ref` that named it.
7///
8/// Dereferences to the item. It cannot be cloned, and the document it belongs
9/// to hands out borrows only, so a `Shared` value can never outlive its
10/// document: that is what lets a [`NestedSchema`] be dereferenced without a
11/// check, however the schemas refer to each other.
12///
13/// ```compile_fail,E0277
14/// use openapiv3_resolve::{ResolvedSchema, Shared};
15///
16/// fn detach(shared: &Shared<ResolvedSchema>) -> Shared<ResolvedSchema> {
17///     Clone::clone(shared)
18/// }
19/// ```
20pub struct Shared<T>(Arc<T>);
21
22impl<T> Shared<T> {
23    pub(super) fn new(item: Arc<T>) -> Self {
24        Self(item)
25    }
26
27    /// The address of the item, for telling whether two `Shared` values name
28    /// the same component: `std::ptr::eq(Shared::as_ptr(a), Shared::as_ptr(b))`.
29    ///
30    /// An associated function, like [`Arc::as_ptr`], so it cannot shadow a
31    /// method of `T`.
32    pub fn as_ptr(this: &Self) -> *const T {
33        Arc::as_ptr(&this.0)
34    }
35}
36
37impl<T> Deref for Shared<T> {
38    type Target = T;
39
40    fn deref(&self) -> &T {
41        &self.0
42    }
43}
44
45impl<T: fmt::Debug> fmt::Debug for Shared<T> {
46    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47        self.0.fmt(f)
48    }
49}
50
51impl<T: PartialEq> PartialEq for Shared<T> {
52    fn eq(&self, other: &Self) -> bool {
53        self.0 == other.0
54    }
55}
56
57/// A schema nested inside another schema.
58///
59/// [`get`](Self::get) borrows the nested schema. Nearly always that schema is
60/// owned or shared like any other; the exception is a `$ref` that points back
61/// at a schema which contains it (a tree node whose children are nodes, say).
62/// Such an edge is [`recursive`](Self::is_recursive) and holds only a weak
63/// pointer, because a cycle of owning pointers would never be freed. Which
64/// edge of a cycle is the recursive one is decided by document order: it is
65/// the first `$ref`, walking `components` then `paths`, that closes the cycle.
66/// Within one schema its kind (type, properties, `oneOf`, ...) is walked
67/// before its discriminator mapping, so a cycle that could close through
68/// either closes through the kind.
69///
70/// The weak pointer always upgrades: every schema a `$ref` can name lives in
71/// the document's `components`, and this edge can only be reached by
72/// borrowing from that document.
73///
74/// Two recursive edges are equal when they point at the same allocation, so
75/// comparing two independently resolved documents that contain a cycle
76/// reports them as different.
77pub struct NestedSchema(Edge);
78
79enum Edge {
80    Schema(Arc<ResolvedSchema>),
81    Recursive(Weak<ResolvedSchema>),
82}
83
84impl NestedSchema {
85    pub(super) fn schema(schema: Arc<ResolvedSchema>) -> Self {
86        Self(Edge::Schema(schema))
87    }
88
89    pub(super) fn recursive(schema: Weak<ResolvedSchema>) -> Self {
90        Self(Edge::Recursive(schema))
91    }
92
93    /// Another edge to the same target, recursive if this one is.
94    ///
95    /// Not `Clone`, because a public clone would let an edge outlive the
96    /// document it points into; while the document is being built that
97    /// cannot happen.
98    pub(super) fn duplicate(&self) -> Self {
99        Self(match &self.0 {
100            Edge::Schema(schema) => Edge::Schema(Arc::clone(schema)),
101            Edge::Recursive(schema) => Edge::Recursive(Weak::clone(schema)),
102        })
103    }
104
105    /// Borrows the nested schema.
106    pub fn get(&self) -> SchemaGuard<'_> {
107        SchemaGuard(match &self.0 {
108            Edge::Schema(schema) => Guard::Borrowed(schema),
109            // Infallible: the target is owned by the document's `components`,
110            // and `self` can only be reached by borrowing from that document
111            // (`Shared` is not `Clone`, and the document hands out borrows only).
112            #[allow(clippy::expect_used)]
113            Edge::Recursive(schema) => Guard::Upgraded(
114                schema
115                    .upgrade()
116                    .expect("recursive schema edge outlived its document"),
117            ),
118        })
119    }
120
121    /// Whether this edge points back at a schema that contains it.
122    pub fn is_recursive(&self) -> bool {
123        matches!(self.0, Edge::Recursive(_))
124    }
125}
126
127impl fmt::Debug for NestedSchema {
128    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129        match &self.0 {
130            Edge::Schema(schema) => schema.fmt(f),
131            // Printing the target would never terminate.
132            Edge::Recursive(_) => f.write_str("Recursive(..)"),
133        }
134    }
135}
136
137impl PartialEq for NestedSchema {
138    fn eq(&self, other: &Self) -> bool {
139        match (&self.0, &other.0) {
140            (Edge::Schema(left), Edge::Schema(right)) => left == right,
141            // Comparing contents here would never terminate.
142            (Edge::Recursive(left), Edge::Recursive(right)) => Weak::ptr_eq(left, right),
143            (Edge::Schema(_), Edge::Recursive(_)) | (Edge::Recursive(_), Edge::Schema(_)) => false,
144        }
145    }
146}
147
148/// A borrow of a [`NestedSchema`]'s target; dereferences to the schema.
149pub struct SchemaGuard<'a>(Guard<'a>);
150
151enum Guard<'a> {
152    Borrowed(&'a ResolvedSchema),
153    Upgraded(Arc<ResolvedSchema>),
154}
155
156impl SchemaGuard<'_> {
157    /// The address of the schema, comparable with [`Shared::as_ptr`].
158    pub fn as_ptr(this: &Self) -> *const ResolvedSchema {
159        match &this.0 {
160            Guard::Borrowed(schema) => *schema,
161            Guard::Upgraded(schema) => Arc::as_ptr(schema),
162        }
163    }
164}
165
166impl Deref for SchemaGuard<'_> {
167    type Target = ResolvedSchema;
168
169    fn deref(&self) -> &ResolvedSchema {
170        match &self.0 {
171            Guard::Borrowed(schema) => schema,
172            Guard::Upgraded(schema) => schema,
173        }
174    }
175}
176
177impl fmt::Debug for SchemaGuard<'_> {
178    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179        (**self).fmt(f)
180    }
181}