use crate::ident::{field_ident, type_ident, unique_ident, variant_ident};
use crate::model::{
Component, ComponentKind, ConstrainedType, EnumVariant, Field, RangeType, RangeTypeRef, TypeRef,
};
use crate::parse::registry::TypeRegistry;
use crate::parse::validate::{
ValidatedComponent, ValidatedComponentKind, ValidatedDocument, ValidatedField, ValidatedType,
ValidatedTypeKind,
};
use std::collections::{BTreeMap, BTreeSet};
pub(super) struct SchemaLowerer<'a, 'doc> {
document: &'a ValidatedDocument<'doc>,
component_kinds: BTreeMap<String, ComponentKind>,
component_refs: BTreeMap<String, TypeRef>,
}
impl<'a, 'doc> SchemaLowerer<'a, 'doc> {
pub(super) fn new(document: &'a ValidatedDocument<'doc>) -> Self {
Self {
document,
component_kinds: BTreeMap::new(),
component_refs: BTreeMap::new(),
}
}
pub(super) fn parse_components(&mut self, registry: &mut TypeRegistry) -> Vec<Component> {
self.document
.components
.iter()
.map(|component| self.parse_component(component, registry))
.collect()
}
pub(super) fn parse_type_ref_with_hint(
&mut self,
ty: &ValidatedType,
type_name_hint: &str,
registry: &mut TypeRegistry,
) -> TypeRef {
let mut parsed =
self.parse_type_ref_base(&ty.kind, type_name_hint, &ty.description, registry);
if let Some(validation) = &ty.validation {
parsed = registry.constrained_ref(
type_name_hint,
ty.description.clone(),
parsed,
validation.clone(),
);
}
if ty.nullable {
TypeRef::option(parsed)
} else {
parsed
}
}
fn parse_component(
&mut self,
component: &ValidatedComponent,
registry: &mut TypeRegistry,
) -> Component {
let rust_name = type_ident(&component.schema_name);
let kind = self.parse_component_kind(component, registry);
Component {
rust_name,
description: component.description.clone(),
kind,
}
}
fn parse_component_kind(
&mut self,
component: &ValidatedComponent,
registry: &mut TypeRegistry,
) -> ComponentKind {
let rust_name = type_ident(&component.schema_name);
if let Some(kind) = self.component_kinds.get(&rust_name) {
return kind.clone();
}
let kind = match &component.kind {
ValidatedComponentKind::Reference(reference) => {
ComponentKind::Alias(self.component_ref(reference, registry))
}
ValidatedComponentKind::Struct(fields) => ComponentKind::Struct(
self.parse_struct_fields(&component.schema_name, fields, registry),
),
ValidatedComponentKind::Type(ty) => self.parse_component_type(
&component.schema_name,
&component.description,
ty,
registry,
),
};
self.component_kinds.insert(rust_name, kind.clone());
kind
}
fn parse_component_type(
&mut self,
schema_name: &str,
description: &Option<String>,
ty: &ValidatedType,
registry: &mut TypeRegistry,
) -> ComponentKind {
let rust_name = type_ident(schema_name);
match (&ty.kind, ty.nullable, ty.validation.as_ref()) {
(ValidatedTypeKind::Enum(variants), false, None) => {
ComponentKind::Enum(variants.clone())
}
(ValidatedTypeKind::Range(scalar), false, None) => ComponentKind::Range(RangeType {
rust_name,
description: description.clone(),
scalar: *scalar,
}),
(_, false, Some(validation)) => ComponentKind::Nutype(ConstrainedType {
rust_name,
description: description.clone(),
inner: self.parse_type_ref_base(&ty.kind, schema_name, &ty.description, registry),
validation: validation.clone(),
}),
(_, true, Some(validation)) => {
let hint = format!("{schema_name} value");
let base =
self.parse_type_ref_base(&ty.kind, schema_name, &ty.description, registry);
let inner = registry.constrained_ref(
&hint,
ty.description.clone(),
base,
validation.clone(),
);
ComponentKind::Alias(TypeRef::option(inner))
}
(ValidatedTypeKind::Enum(_) | ValidatedTypeKind::Range(_), true, None) => {
let hint = format!("{schema_name} value");
ComponentKind::Alias(self.parse_type_ref_with_hint(ty, &hint, registry))
}
_ => ComponentKind::Alias(self.parse_type_ref_with_hint(ty, schema_name, registry)),
}
}
fn parse_struct_fields(
&mut self,
schema_name: &str,
fields: &[ValidatedField],
registry: &mut TypeRegistry,
) -> Vec<Field> {
let mut used = BTreeSet::new();
let mut parsed = Vec::with_capacity(fields.len());
for field in fields {
parsed.push(Field {
wire_name: field.wire_name.clone(),
rust_name: unique_ident(field_ident(&field.wire_name), &mut used),
description: field.description.clone(),
ty: self.parse_type_ref_with_hint(
&field.ty,
&format!("{schema_name} {}", field.wire_name),
registry,
),
required: field.required,
treat_error_as_none: field.treat_error_as_none,
});
}
parsed
}
fn parse_type_ref_base(
&mut self,
kind: &ValidatedTypeKind,
type_name_hint: &str,
description: &Option<String>,
registry: &mut TypeRegistry,
) -> TypeRef {
match kind {
ValidatedTypeKind::Named(rust_name) => self.component_ref(rust_name, registry),
ValidatedTypeKind::String => TypeRef::String,
ValidatedTypeKind::ParsedString(parse_as) => TypeRef::ParsedString(*parse_as),
ValidatedTypeKind::ParsedInteger(parse_as) => TypeRef::ParsedInteger(*parse_as),
ValidatedTypeKind::Integer(integer_type) => TypeRef::Integer(*integer_type),
ValidatedTypeKind::F32 => TypeRef::F32,
ValidatedTypeKind::F64 => TypeRef::F64,
ValidatedTypeKind::Bool => TypeRef::Bool,
ValidatedTypeKind::Array(item) => TypeRef::Array(Box::new(
self.parse_type_ref_with_hint(item, &format!("{type_name_hint} item"), registry),
)),
ValidatedTypeKind::Enum(variants) => parse_inline_enum_ref(
variants.clone(),
type_name_hint,
description.clone(),
registry,
),
ValidatedTypeKind::Range(scalar) => {
registry.inline_range_ref(type_name_hint, description.clone(), *scalar)
}
}
}
fn component_ref(&mut self, rust_name: &str, registry: &mut TypeRegistry) -> TypeRef {
if let Some(ty) = self.component_refs.get(rust_name) {
return ty.clone();
}
let component = self.validated_component(rust_name);
let kind = self.parse_component_kind(&component, registry);
let ty = match &kind {
ComponentKind::Struct(_) | ComponentKind::Enum(_) => {
TypeRef::Named(rust_name.to_owned())
}
ComponentKind::Range(range_type) => TypeRef::Range(RangeTypeRef {
rust_name: range_type.rust_name.clone(),
scalar: range_type.scalar,
}),
ComponentKind::Alias(alias) => alias.clone(),
ComponentKind::Nutype(constrained_type) => TypeRef::Constrained {
rust_name: constrained_type.rust_name.clone(),
inner: Box::new(constrained_type.inner.clone()),
},
};
self.component_refs.insert(rust_name.to_owned(), ty.clone());
ty
}
fn validated_component(&self, rust_name: &str) -> ValidatedComponent {
self.document
.components
.iter()
.find(|component| type_ident(&component.schema_name) == rust_name)
.cloned()
.unwrap_or_else(|| panic!("component reference {rust_name} validated before lowering"))
}
}
fn parse_inline_enum_ref(
mut variants: Vec<EnumVariant>,
type_name_hint: &str,
description: Option<String>,
registry: &mut TypeRegistry,
) -> TypeRef {
let default_empty_variant = variant_ident("");
variants.retain(|variant| {
!variant.wire_name.is_empty() || variant.rust_name != default_empty_variant
});
if variants.is_empty() {
TypeRef::String
} else {
registry.inline_enum_ref(type_name_hint, description, variants)
}
}