use super::resolver::Resolver;
use super::{NestedSchema, ResolvedSchemaKind};
use crate::ResolveError;
use indexmap::IndexMap;
use openapiv3::{Discriminator, ReferenceOr, Schema, SchemaKind};
#[derive(Debug, PartialEq)]
pub struct ResolvedDiscriminator {
pub property_name: String,
pub mapping: IndexMap<String, NestedSchema>,
pub extensions: IndexMap<String, serde_json::Value>,
}
pub(super) fn resolve_discriminator(
cx: &mut Resolver<'_>,
discriminator: &Discriminator,
kind: &SchemaKind,
resolved: &ResolvedSchemaKind,
) -> Result<ResolvedDiscriminator, ResolveError> {
let Discriminator {
property_name,
mapping,
extensions,
} = discriminator;
let alternatives = alternatives(cx, kind, resolved)?;
let mapping = mapping
.iter()
.map(|(value, target)| {
let target = MappingTarget::classify(target);
let edge = match &alternatives {
Some(alternatives) => {
let schema = cx.schema_name(target)?;
alternatives
.iter()
.find(|(name, _)| *name == schema)
.map(|(_, edge)| edge.duplicate())
.ok_or_else(|| ResolveError::DiscriminatorMappingMismatch {
property_name: property_name.clone(),
value: value.clone(),
schema,
})?
}
None => cx.nested_target(target)?,
};
Ok((value.clone(), edge))
})
.collect::<Result<_, _>>()?;
Ok(ResolvedDiscriminator {
property_name: property_name.clone(),
mapping,
extensions: extensions.clone(),
})
}
#[derive(Clone, Copy)]
pub(super) enum MappingTarget<'a> {
Reference(&'a str),
Name(&'a str),
}
impl<'a> MappingTarget<'a> {
fn classify(target: &'a str) -> Self {
if target.contains('#') {
Self::Reference(target)
} else {
Self::Name(target)
}
}
}
fn alternatives<'a>(
cx: &Resolver<'_>,
kind: &'a SchemaKind,
resolved: &'a ResolvedSchemaKind,
) -> Result<Option<Vec<(String, &'a NestedSchema)>>, ResolveError> {
let (Some(entries), Some(edges)) = (entries_of(kind), edges_of(resolved)) else {
return Ok(None);
};
entries
.into_iter()
.zip(edges)
.filter_map(|(entry, edge)| match entry {
ReferenceOr::Item(_) => None,
ReferenceOr::Reference { reference } => Some((reference, edge)),
})
.map(|(reference, edge)| Ok((cx.schema_name(MappingTarget::Reference(reference))?, edge)))
.collect::<Result<_, _>>()
.map(Some)
}
fn entries_of(kind: &SchemaKind) -> Option<Vec<&ReferenceOr<Schema>>> {
match kind {
SchemaKind::OneOf { one_of } => Some(one_of.iter().collect()),
SchemaKind::AnyOf { any_of } => Some(any_of.iter().collect()),
SchemaKind::Any(any) if !(any.one_of.is_empty() && any.any_of.is_empty()) => {
Some(any.one_of.iter().chain(&any.any_of).collect())
}
SchemaKind::Any(_)
| SchemaKind::Type(_)
| SchemaKind::AllOf { .. }
| SchemaKind::Not { .. } => None,
}
}
fn edges_of(resolved: &ResolvedSchemaKind) -> Option<Vec<&NestedSchema>> {
match resolved {
ResolvedSchemaKind::OneOf { one_of } => Some(one_of.iter().collect()),
ResolvedSchemaKind::AnyOf { any_of } => Some(any_of.iter().collect()),
ResolvedSchemaKind::Any(any) if !(any.one_of.is_empty() && any.any_of.is_empty()) => {
Some(any.one_of.iter().chain(&any.any_of).collect())
}
ResolvedSchemaKind::Any(_)
| ResolvedSchemaKind::Type(_)
| ResolvedSchemaKind::AllOf { .. }
| ResolvedSchemaKind::Not { .. } => None,
}
}