Skip to main content

openapiv3_resolve/resolved/
schema.rs

1use super::discriminator::resolve_discriminator;
2use super::resolver::{Resolvable, Resolver};
3use super::{NestedSchema, ResolvedAnySchema, ResolvedDiscriminator, ResolvedType};
4use crate::ResolveError;
5use indexmap::IndexMap;
6use openapiv3::{AdditionalProperties, ExternalDocumentation, Schema, SchemaData, SchemaKind};
7
8/// [`Schema`] with every `$ref` replaced by the schema it named.
9#[derive(Debug, PartialEq)]
10pub struct ResolvedSchema {
11    /// See [`Schema::schema_data`].
12    pub schema_data: ResolvedSchemaData,
13    /// See [`Schema::schema_kind`].
14    pub schema_kind: ResolvedSchemaKind,
15}
16
17/// [`SchemaData`] with the discriminator's mapping targets resolved.
18///
19/// `Default` exists for the placeholder a schema's slot holds while that
20/// schema is still being resolved; it has no meaning of its own.
21#[derive(Debug, Default, PartialEq)]
22pub struct ResolvedSchemaData {
23    /// See [`SchemaData::nullable`].
24    pub nullable: bool,
25    /// See [`SchemaData::read_only`].
26    ///
27    /// Mirrors upstream as-is: the specification forbids a schema being both
28    /// read-only and write-only, which neither `openapiv3` nor this crate
29    /// enforces.
30    pub read_only: bool,
31    /// See [`SchemaData::write_only`], and the note on [`Self::read_only`].
32    pub write_only: bool,
33    /// See [`SchemaData::deprecated`].
34    pub deprecated: bool,
35    /// See [`SchemaData::external_docs`].
36    pub external_docs: Option<ExternalDocumentation>,
37    /// See [`SchemaData::example`].
38    pub example: Option<serde_json::Value>,
39    /// See [`SchemaData::title`].
40    pub title: Option<String>,
41    /// See [`SchemaData::description`].
42    pub description: Option<String>,
43    /// See [`SchemaData::discriminator`].
44    pub discriminator: Option<ResolvedDiscriminator>,
45    /// See [`SchemaData::default`].
46    pub default: Option<serde_json::Value>,
47    /// See [`SchemaData::extensions`].
48    pub extensions: IndexMap<String, serde_json::Value>,
49}
50
51/// [`SchemaKind`] with every `$ref` replaced by the schema it named.
52#[derive(Debug, PartialEq)]
53pub enum ResolvedSchemaKind {
54    /// See [`SchemaKind::Type`].
55    Type(ResolvedType),
56    /// See [`SchemaKind::OneOf`].
57    OneOf {
58        /// The alternatives, exactly one of which must match.
59        one_of: Vec<NestedSchema>,
60    },
61    /// See [`SchemaKind::AllOf`].
62    AllOf {
63        /// The schemas that must all match.
64        all_of: Vec<NestedSchema>,
65    },
66    /// See [`SchemaKind::AnyOf`].
67    AnyOf {
68        /// The alternatives, at least one of which must match.
69        any_of: Vec<NestedSchema>,
70    },
71    /// See [`SchemaKind::Not`].
72    Not {
73        /// The schema that must not match.
74        not: NestedSchema,
75    },
76    /// See [`SchemaKind::Any`].
77    Any(Box<ResolvedAnySchema>),
78}
79
80/// [`AdditionalProperties`] with a `$ref` replaced by the schema it named.
81#[derive(Debug, PartialEq)]
82pub enum ResolvedAdditionalProperties {
83    /// See [`AdditionalProperties::Any`].
84    Any(bool),
85    /// See [`AdditionalProperties::Schema`].
86    Schema(NestedSchema),
87}
88
89impl Resolvable for Schema {
90    type Resolved = ResolvedSchema;
91
92    fn resolve_inline(&self, cx: &mut Resolver<'_>) -> Result<Self::Resolved, ResolveError> {
93        // Kind first, and the order is load-bearing: a discriminator mapping
94        // on a `oneOf`/`anyOf` reuses the alternatives' edges, so those have
95        // to exist already. It also decides which edge of a cycle that runs
96        // through both is the recursive one; see `NestedSchema`.
97        let schema_kind = self.schema_kind.resolve_inline(cx)?;
98        let discriminator = self
99            .schema_data
100            .discriminator
101            .as_ref()
102            .map(|discriminator| {
103                resolve_discriminator(cx, discriminator, &self.schema_kind, &schema_kind)
104            })
105            .transpose()?;
106        Ok(ResolvedSchema {
107            schema_data: resolve_schema_data(&self.schema_data, discriminator),
108            schema_kind,
109        })
110    }
111}
112
113impl Resolvable for SchemaKind {
114    type Resolved = ResolvedSchemaKind;
115
116    fn resolve_inline(&self, cx: &mut Resolver<'_>) -> Result<Self::Resolved, ResolveError> {
117        Ok(match self {
118            Self::Type(kind) => ResolvedSchemaKind::Type(kind.resolve_inline(cx)?),
119            Self::OneOf { one_of } => ResolvedSchemaKind::OneOf {
120                one_of: cx.nested_vec(one_of)?,
121            },
122            Self::AllOf { all_of } => ResolvedSchemaKind::AllOf {
123                all_of: cx.nested_vec(all_of)?,
124            },
125            Self::AnyOf { any_of } => ResolvedSchemaKind::AnyOf {
126                any_of: cx.nested_vec(any_of)?,
127            },
128            Self::Not { not } => ResolvedSchemaKind::Not {
129                not: cx.nested(not)?,
130            },
131            Self::Any(any) => ResolvedSchemaKind::Any(Box::new(any.resolve_inline(cx)?)),
132        })
133    }
134}
135
136impl Resolvable for AdditionalProperties {
137    type Resolved = ResolvedAdditionalProperties;
138
139    fn resolve_inline(&self, cx: &mut Resolver<'_>) -> Result<Self::Resolved, ResolveError> {
140        Ok(match self {
141            Self::Any(allowed) => ResolvedAdditionalProperties::Any(*allowed),
142            Self::Schema(schema) => ResolvedAdditionalProperties::Schema(cx.nested(schema)?),
143        })
144    }
145}
146
147/// Copies `data` over, with its discriminator already resolved.
148fn resolve_schema_data(
149    data: &SchemaData,
150    discriminator: Option<ResolvedDiscriminator>,
151) -> ResolvedSchemaData {
152    // Destructured without `..` so that a field added upstream fails to
153    // compile here instead of being quietly dropped from the mirror. The
154    // original discriminator is bound so it counts as handled, the resolved
155    // one is what goes in.
156    let SchemaData {
157        nullable,
158        read_only,
159        write_only,
160        deprecated,
161        external_docs,
162        example,
163        title,
164        description,
165        discriminator: _,
166        default,
167        extensions,
168    } = data;
169    ResolvedSchemaData {
170        nullable: *nullable,
171        read_only: *read_only,
172        write_only: *write_only,
173        deprecated: *deprecated,
174        external_docs: external_docs.clone(),
175        example: example.clone(),
176        title: title.clone(),
177        description: description.clone(),
178        discriminator,
179        default: default.clone(),
180        extensions: extensions.clone(),
181    }
182}