openapiv3_resolve/resolved/
discriminator.rs1use super::resolver::Resolver;
2use super::{NestedSchema, ResolvedSchemaKind};
3use crate::ResolveError;
4use indexmap::IndexMap;
5use openapiv3::{Discriminator, ReferenceOr, Schema, SchemaKind};
6
7#[derive(Debug, PartialEq)]
25pub struct ResolvedDiscriminator {
26 pub property_name: String,
28 pub mapping: IndexMap<String, NestedSchema>,
30 pub extensions: IndexMap<String, serde_json::Value>,
32}
33
34pub(super) fn resolve_discriminator(
37 cx: &mut Resolver<'_>,
38 discriminator: &Discriminator,
39 kind: &SchemaKind,
40 resolved: &ResolvedSchemaKind,
41) -> Result<ResolvedDiscriminator, ResolveError> {
42 let Discriminator {
45 property_name,
46 mapping,
47 extensions,
48 } = discriminator;
49 let alternatives = alternatives(cx, kind, resolved)?;
50 let mapping = mapping
51 .iter()
52 .map(|(value, target)| {
53 let target = MappingTarget::classify(target);
54 let edge = match &alternatives {
55 Some(alternatives) => {
56 let schema = cx.schema_name(target)?;
57 alternatives
58 .iter()
59 .find(|(name, _)| *name == schema)
60 .map(|(_, edge)| edge.duplicate())
61 .ok_or_else(|| ResolveError::DiscriminatorMappingMismatch {
62 property_name: property_name.clone(),
63 value: value.clone(),
64 schema,
65 })?
66 }
67 None => cx.nested_target(target)?,
68 };
69 Ok((value.clone(), edge))
70 })
71 .collect::<Result<_, _>>()?;
72 Ok(ResolvedDiscriminator {
73 property_name: property_name.clone(),
74 mapping,
75 extensions: extensions.clone(),
76 })
77}
78
79#[derive(Clone, Copy)]
86pub(super) enum MappingTarget<'a> {
87 Reference(&'a str),
89 Name(&'a str),
91}
92
93impl<'a> MappingTarget<'a> {
94 fn classify(target: &'a str) -> Self {
95 if target.contains('#') {
96 Self::Reference(target)
97 } else {
98 Self::Name(target)
99 }
100 }
101}
102
103fn alternatives<'a>(
107 cx: &Resolver<'_>,
108 kind: &'a SchemaKind,
109 resolved: &'a ResolvedSchemaKind,
110) -> Result<Option<Vec<(String, &'a NestedSchema)>>, ResolveError> {
111 let (Some(entries), Some(edges)) = (entries_of(kind), edges_of(resolved)) else {
112 return Ok(None);
113 };
114 entries
115 .into_iter()
116 .zip(edges)
117 .filter_map(|(entry, edge)| match entry {
119 ReferenceOr::Item(_) => None,
120 ReferenceOr::Reference { reference } => Some((reference, edge)),
121 })
122 .map(|(reference, edge)| Ok((cx.schema_name(MappingTarget::Reference(reference))?, edge)))
123 .collect::<Result<_, _>>()
124 .map(Some)
125}
126
127fn entries_of(kind: &SchemaKind) -> Option<Vec<&ReferenceOr<Schema>>> {
128 match kind {
129 SchemaKind::OneOf { one_of } => Some(one_of.iter().collect()),
130 SchemaKind::AnyOf { any_of } => Some(any_of.iter().collect()),
131 SchemaKind::Any(any) if !(any.one_of.is_empty() && any.any_of.is_empty()) => {
132 Some(any.one_of.iter().chain(&any.any_of).collect())
133 }
134 SchemaKind::Any(_)
135 | SchemaKind::Type(_)
136 | SchemaKind::AllOf { .. }
137 | SchemaKind::Not { .. } => None,
138 }
139}
140
141fn edges_of(resolved: &ResolvedSchemaKind) -> Option<Vec<&NestedSchema>> {
142 match resolved {
143 ResolvedSchemaKind::OneOf { one_of } => Some(one_of.iter().collect()),
144 ResolvedSchemaKind::AnyOf { any_of } => Some(any_of.iter().collect()),
145 ResolvedSchemaKind::Any(any) if !(any.one_of.is_empty() && any.any_of.is_empty()) => {
146 Some(any.one_of.iter().chain(&any.any_of).collect())
147 }
148 ResolvedSchemaKind::Any(_)
149 | ResolvedSchemaKind::Type(_)
150 | ResolvedSchemaKind::AllOf { .. }
151 | ResolvedSchemaKind::Not { .. } => None,
152 }
153}