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}