pub mod build;
pub(crate) mod cycles;
mod default;
pub mod error;
pub(crate) mod output;
pub(crate) mod serde_attrs;
pub mod settings;
pub(crate) mod trait_resolution;
pub(crate) mod value_tokens;
pub mod view;
extern crate self as typespace;
use std::collections::{BTreeMap, BTreeSet, btree_map::Entry};
use proc_macro2::TokenStream;
use quote::{ToTokens, format_ident, quote};
use crate::build::{
Enum, JsonValue, Native, NewtypeStruct, Struct, StructProperty, StructPropertySerde,
StructPropertyState, TupleStruct, Type, TypeAlias, TypeCommonBuilt, UnitStruct, VariantDetails,
all_named_types,
};
use crate::default::{SharedDefaultFn, shared_default_fn};
use crate::error::Error;
use crate::output::Outputspace;
use crate::serde_attrs::{SerdeAttrs, SerdeDerives};
use crate::settings::{OptionalNullable, Settings, Std};
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Deserialize, strum::EnumIter,
)]
#[serde(rename_all = "kebab-case")]
#[non_exhaustive]
pub enum TypespaceTrait {
Deserialize,
Serialize,
Clone,
Copy,
Debug,
Display,
FromStr,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Default,
JsonSchema,
}
impl TypespaceTrait {
pub(crate) fn render(&self, settings: &Settings) -> proc_macro2::TokenStream {
if settings.std == Std::FullyQualified {
match self {
TypespaceTrait::Clone => quote! { Clone },
TypespaceTrait::Copy => quote! { Copy },
TypespaceTrait::Debug => quote! { Debug },
TypespaceTrait::Serialize => quote! { ::serde::Serialize },
TypespaceTrait::Deserialize => quote! { ::serde::Deserialize },
TypespaceTrait::JsonSchema => quote! { schemars::JsonSchema },
TypespaceTrait::Ord => quote! { Ord },
TypespaceTrait::PartialOrd => quote! { PartialOrd },
TypespaceTrait::Eq => quote! { Eq },
TypespaceTrait::PartialEq => quote! { PartialEq },
TypespaceTrait::Hash => quote! { Hash },
TypespaceTrait::Display => quote! { ::std::fmt::Display },
TypespaceTrait::FromStr => quote! { ::std::str::FromStr },
TypespaceTrait::Default => quote! { Default },
}
} else {
match self {
TypespaceTrait::Clone => quote! { Clone },
TypespaceTrait::Copy => quote! { Copy },
TypespaceTrait::Debug => quote! { Debug },
TypespaceTrait::Serialize => quote! { ::serde::Serialize },
TypespaceTrait::Deserialize => quote! { ::serde::Deserialize },
TypespaceTrait::JsonSchema => quote! { ::schemars::JsonSchema },
TypespaceTrait::Ord => quote! { Ord },
TypespaceTrait::PartialOrd => quote! { PartialOrd },
TypespaceTrait::Eq => quote! { Eq },
TypespaceTrait::PartialEq => quote! { PartialEq },
TypespaceTrait::Hash => quote! { Hash },
TypespaceTrait::Display => quote! { Display },
TypespaceTrait::FromStr => quote! { FromStr },
TypespaceTrait::Default => quote! { Default },
}
}
}
}
impl std::fmt::Display for TypespaceTrait {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let name = match self {
TypespaceTrait::Clone => "Clone",
TypespaceTrait::Copy => "Copy",
TypespaceTrait::Debug => "Debug",
TypespaceTrait::Serialize => "Serialize",
TypespaceTrait::Deserialize => "Deserialize",
TypespaceTrait::JsonSchema => "JsonSchema",
TypespaceTrait::Display => "Display",
TypespaceTrait::FromStr => "FromStr",
TypespaceTrait::Eq => "Eq",
TypespaceTrait::PartialEq => "PartialEq",
TypespaceTrait::Ord => "Ord",
TypespaceTrait::PartialOrd => "PartialOrd",
TypespaceTrait::Hash => "Hash",
TypespaceTrait::Default => "Default",
};
f.write_str(name)
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, serde::Deserialize)]
pub struct TypespaceTraitSet(BTreeSet<TypespaceTrait>);
impl FromIterator<TypespaceTrait> for TypespaceTraitSet {
fn from_iter<T: IntoIterator<Item = TypespaceTrait>>(iter: T) -> Self {
Self(iter.into_iter().collect())
}
}
impl IntoIterator for TypespaceTraitSet {
type Item = TypespaceTrait;
type IntoIter = std::collections::btree_set::IntoIter<TypespaceTrait>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl TypespaceTraitSet {
pub fn empty() -> Self {
Self(Default::default())
}
pub fn contains(&self, tt: &TypespaceTrait) -> bool {
self.0.contains(tt)
}
pub fn add(&mut self, tt: TypespaceTrait) {
self.0.insert(tt);
}
pub fn remove(&mut self, tt: TypespaceTrait) -> bool {
self.0.remove(&tt)
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = &TypespaceTrait> {
self.0.iter()
}
pub fn difference<'a>(
&'a self,
other: &'a Self,
) -> impl Iterator<Item = &'a TypespaceTrait> + 'a {
self.0.difference(&other.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum TraitProvision {
Never,
Always,
IfParameters,
Unknown,
}
pub struct TypespaceBuilder<Id> {
types: BTreeMap<Id, Type<Id>>,
settings: Settings,
}
impl<Id> Default for TypespaceBuilder<Id> {
fn default() -> Self {
Self::new(Settings::typical())
}
}
impl<Id> TypespaceBuilder<Id> {
pub fn new(settings: Settings) -> Self {
Self {
types: Default::default(),
settings,
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct Obligation<Id> {
pub(crate) required: TypespaceTrait,
pub(crate) path: Vec<error::PathStep<Id>>,
pub(crate) target: Id,
}
#[derive(Debug)]
pub(crate) struct DefaultChecks<Id> {
pub(crate) whole_type: BTreeMap<Id, Vec<Obligation<Id>>>,
pub(crate) deserialized: BTreeMap<Id, Id>,
}
impl<Id> Default for DefaultChecks<Id> {
fn default() -> Self {
Self {
whole_type: BTreeMap::new(),
deserialized: BTreeMap::new(),
}
}
}
impl<Id: Clone + Ord + std::fmt::Debug + std::fmt::Display> TypespaceBuilder<Id> {
pub fn insert(&mut self, id: Id, typ: Type<Id>) -> Result<(), Error<Id>> {
typ.validate_built()?;
match self.types.entry(id) {
Entry::Vacant(e) => {
e.insert(typ);
Ok(())
}
Entry::Occupied(e) => {
Err(Error::DuplicateTypeId {
type_id: e.key().clone(),
})
}
}
}
pub fn contains_type(&self, id: &Id) -> bool {
self.types.contains_key(id)
}
pub fn ident(&self, id: &Id) -> TokenStream {
self.renderer().render_ident(id)
}
pub fn ident_in(&self, id: &Id, scope: &str) -> TokenStream {
self.renderer().render_ident_with_scope(id, Some(scope))
}
pub fn parameter_ident(
&self,
id: &Id,
scope: Option<&str>,
lifetime: Option<&str>,
) -> TokenStream {
self.renderer().render_parameter_ident(id, scope, lifetime)
}
fn renderer(&self) -> TypespaceRenderer<'_, Id> {
TypespaceRenderer::new(&self.types, &self.settings)
}
fn check_derives(&self) -> Result<(), Error<Id>> {
for derive in &self.settings.extra_derives {
if let Err(err) = syn::parse_str::<syn::Path>(derive) {
return Err(Error::InvalidDerive {
derive: derive.clone(),
message: err.to_string(),
});
}
}
Ok(())
}
fn check_containers(&self) -> Result<(), Error<Id>> {
let custom_optional = match &self.settings.optional_nullable {
OptionalNullable::CustomType(container) => Some(("optional-nullable", container, 1)),
_ => None,
};
[
("map", &self.settings.map_type, 2),
("set", &self.settings.set_type, 1),
("vec", &self.settings.vec_type, 1),
]
.into_iter()
.chain(custom_optional)
.try_for_each(|(position, container, parameters)| {
if let Some(trait_) = container.provisions().find_map(|(trait_, provision)| {
matches!(provision, TraitProvision::Unknown).then_some(trait_)
}) {
return Err(Error::ContainerProvisionUnknown {
position,
path: settings::path_text(container.path()),
trait_,
});
}
match container.obligations().len() {
declared if declared == parameters => Ok(()),
declared => Err(Error::ContainerParameterCount {
position,
path: settings::path_text(container.path()),
declared,
parameters,
}),
}
})
}
fn check_optional_nullable_default(&self) -> Result<(), Error<Id>> {
let OptionalNullable::CustomType(container) = &self.settings.optional_nullable else {
return Ok(());
};
let provision = container.provision(TypespaceTrait::Default);
if provision == TraitProvision::Always {
return Ok(());
}
Err(Error::OptionalNullableWrapperDefault {
path: crate::settings::path_text(container.path()),
provision,
})
}
fn check_references(&self) -> Result<(), Error<Id>> {
for (type_id, typ) in &self.types {
for child_id in typ.children() {
if !self.types.contains_key(&child_id) {
return Err(Error::UnknownTypeId {
type_id: type_id.clone(),
child_id,
});
}
}
}
Ok(())
}
fn check_type_names(&self) -> Result<(), Error<Id>> {
let mut names = BTreeMap::<&str, &Id>::new();
for (type_id, typ) in &self.types {
if let Some(common) = typ.common() {
let name = common.built_name();
if let Some(first) = names.insert(name, type_id) {
return Err(Error::DuplicateTypeName {
name: name.to_string(),
first: first.clone(),
second: type_id.clone(),
});
}
}
}
Ok(())
}
fn check_type_defaults(&self) -> Result<DefaultChecks<Id>, Error<Id>> {
let mut checks = DefaultChecks::default();
for (type_id, typ) in &self.types {
if let Some(common) = typ.common()
&& let Some(JsonValue(default)) = &common.default
{
let obligations = self.check_default(default, type_id)?;
if !obligations.is_empty() {
checks.whole_type.insert(type_id.clone(), obligations);
}
}
let mut natives = BTreeSet::new();
match typ {
Type::Struct(struct_info) => {
natives.extend(struct_info.check_field_defaults(self)?)
}
Type::Enum(enum_info) => natives.extend(enum_info.check_field_defaults(self)?),
_ => (),
}
for native in natives {
checks
.deserialized
.entry(native)
.or_insert_with(|| type_id.clone());
}
}
Ok(checks)
}
fn check_type_structure(&self) -> Result<(), Error<Id>> {
for typ in self.types.values() {
let Type::Struct(struct_info) = typ else {
continue;
};
if !struct_info.deny_unknown_fields {
continue;
}
if let Some(prop) = struct_info
.properties
.iter()
.find(|prop| matches!(prop.json_name, StructPropertySerde::Flatten))
{
return Err(Error::FlattenWithDenyUnknownFields {
type_name: struct_info.common.built_name().to_string(),
property: prop.rust_name.clone(),
});
}
}
Ok(())
}
fn check_never_positions(&self) -> Result<(), Error<Id>> {
match self
.types
.iter()
.find_map(|(type_id, typ)| self.never_position(type_id, typ))
{
Some(err) => Err(err),
None => Ok(()),
}
}
fn never_position(&self, type_id: &Id, typ: &Type<Id>) -> Option<Error<Id>> {
let is_never = |id: &Id| matches!(self.types.get(id), Some(Type::Never));
let value_position = |position: &'static str, name: String| Error::NeverInValuePosition {
position,
name,
type_id: type_id.clone(),
};
let transparent_wrapper = |wrapper: &'static str| Error::NeverInTransparentWrapper {
wrapper,
type_id: type_id.clone(),
};
let never_property = |properties: &[StructProperty<Id>]| {
properties
.iter()
.find(|prop| {
!matches!(prop.state, StructPropertyState::Optional) && is_never(&prop.type_id)
})
.map(|prop| prop.rust_name.clone())
};
let never_component = |components: &[Id]| {
components
.iter()
.position(is_never)
.map(|index| index.to_string())
};
match typ {
Type::Struct(Struct { properties, .. }) => {
never_property(properties).map(|name| value_position("property", name))
}
Type::Enum(Enum { variants, .. }) => variants.iter().find_map(|variant| {
let variant_name = &variant.rust_name;
match &variant.details {
VariantDetails::Unit => None,
VariantDetails::Item(id) => is_never(id)
.then(|| value_position("variant payload", variant_name.clone())),
VariantDetails::Tuple(components) => never_component(components).map(|index| {
value_position(
"variant payload component",
format!("{variant_name}.{index}"),
)
}),
VariantDetails::Struct(properties) => never_property(properties).map(|name| {
value_position("variant property", format!("{variant_name}.{name}"))
}),
}
}),
Type::TupleStruct(TupleStruct { fields, rest, .. }) => never_component(fields)
.or_else(|| {
rest.as_ref()
.filter(|id| is_never(id))
.map(|_| fields.len().to_string())
})
.map(|index| value_position("tuple struct field", index)),
Type::Tuple(components) => {
never_component(components).map(|index| value_position("tuple component", index))
}
Type::Array(id, length) => (*length > 0 && is_never(id))
.then(|| value_position("array element", "item".to_string())),
Type::Box(inner) => is_never(inner).then(|| transparent_wrapper("Box")),
Type::TypeAlias(TypeAlias { target, .. }) => {
is_never(target).then(|| transparent_wrapper("type alias"))
}
Type::NewtypeStruct(NewtypeStruct { inner, .. }) => {
is_never(inner).then(|| transparent_wrapper("newtype struct"))
}
Type::Option(_) | Type::Vec(_) | Type::Set(_) | Type::Map(_, _) => None,
Type::Native(_) => None,
Type::UnitStruct(_)
| Type::Unit
| Type::Boolean
| Type::Integer(_)
| Type::Float(_)
| Type::String
| Type::JsonValue
| Type::Never => None,
}
}
pub fn finalize<F>(self, make_box_id: F) -> Result<Typespace<Id>, Error<Id>>
where
F: FnMut(&Id) -> Id,
{
self.check_derives()?;
self.check_containers()?;
self.check_optional_nullable_default()?;
self.check_references()?;
self.check_type_names()?;
self.check_never_positions()?;
self.check_type_structure()?;
let default_checks = self.check_type_defaults()?;
let Self {
mut types,
settings,
} = self;
build_commons(&mut types);
cycles::break_cycles(&mut types, make_box_id);
cycles::check_anonymous_cycles(&types)?;
trait_resolution::resolve_from_string_irrefutable(&mut types);
trait_resolution::resolve_traits(&mut types, &settings, &default_checks)?;
Ok(Typespace { types, settings })
}
}
pub fn no_cycles<Id>(_: &Id) -> Id {
panic!("unexpected cycle in typespace")
}
pub struct Typespace<Id> {
pub(crate) types: BTreeMap<Id, Type<Id>>,
pub settings: Settings,
}
impl<Id: Clone + Ord + std::fmt::Debug + std::fmt::Display> Typespace<Id> {
pub fn get_type(&self, id: &Id) -> view::Type<'_, Id> {
let (id, typ) = self.types.get_key_value(id).expect("invalid type id");
view::Type {
typespace: self,
id,
typ,
}
}
pub fn iter_types(&self) -> impl Iterator<Item = view::Type<'_, Id>> {
self.types.iter().map(|(id, typ)| view::Type {
typespace: self,
id,
typ,
})
}
pub fn to_codespace(&self) -> codespace::Codespace {
TypespaceRenderer::new(&self.types, &self.settings).render()
}
}
pub(crate) struct TypespaceRenderer<'a, Id> {
pub(crate) types: &'a BTreeMap<Id, Type<Id>>,
pub(crate) settings: &'a Settings,
}
impl<'a, Id: Clone + Ord + std::fmt::Debug + std::fmt::Display> TypespaceRenderer<'a, Id> {
pub(crate) fn new(types: &'a BTreeMap<Id, Type<Id>>, settings: &'a Settings) -> Self {
Self { types, settings }
}
fn render(&self) -> codespace::Codespace {
let mut out = Outputspace::default();
for (id, typ) in self.types {
match typ {
Type::Struct(s) => {
let name = s.common.built_name().to_string();
let tokens = s.render(id, self, &mut out);
out.cs().add_item(name, tokens);
}
Type::Enum(e) => {
let name = e.common.built_name().to_string();
let tokens = e.render(id, self, &mut out);
out.cs().add_item(name, tokens);
}
Type::UnitStruct(u) => {
let name = u.common.built_name().to_string();
out.cs().add_item(name, u.render(self));
}
Type::TupleStruct(t) => {
let name = t.common.built_name().to_string();
out.cs().add_item(name, t.render(id, self));
}
Type::NewtypeStruct(n) => {
let name = n.common.built_name().to_string();
let tokens = n.render(id, self, &mut out);
out.cs().add_item(name, tokens);
}
Type::TypeAlias(a) => {
let name = a.common.built_name().to_string();
out.cs().add_item(name, a.render(self));
}
_ => {}
}
}
if out.cs().get_root_mod().has_mod("builder") {
out.cs()
.get_root_mod()
.get_mod("builder")
.add_docs(" Types for composing complex structures.");
}
out.into_codespace()
}
pub(crate) fn add_error_mod(&self, out: &mut Outputspace) {
if !out.cs().get_root_mod().has_mod("error") {
let mut error_mod = codespace::Mod::default();
error_mod.add_docs(" Error types.");
error_mod.add_item(
"",
quote! {
pub struct ConversionError(::std::borrow::Cow<'static, str>);
impl ::std::error::Error for ConversionError {}
impl ::std::fmt::Display for ConversionError {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>)
-> Result<(), ::std::fmt::Error>
{
::std::fmt::Display::fmt(&self.0, f)
}
}
impl ::std::fmt::Debug for ConversionError {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>)
-> Result<(), ::std::fmt::Error>
{
::std::fmt::Debug::fmt(&self.0, f)
}
}
impl From<&'static str> for ConversionError {
fn from(value: &'static str) -> Self {
Self(value.into())
}
}
impl From<String> for ConversionError {
fn from(value: String) -> Self {
Self(value.into())
}
}
},
);
let _ = out.cs().get_root_mod().replace_mod("error", error_mod);
}
}
pub(crate) fn render_ident(&self, id: &Id) -> TokenStream {
self.render_ident_impl(id, None, false)
}
pub(crate) fn render_ident_with_scope(&self, id: &Id, scope: Option<&str>) -> TokenStream {
self.render_ident_impl(id, scope, false)
}
pub(crate) fn render_raw_type(&self, id: &Id) -> TokenStream {
self.render_ident_impl(id, None, true)
}
pub(crate) fn has_builder(&self, id: &Id) -> bool {
self.settings.struct_builder
&& matches!(
self.types.get(id).expect("invalid type id"),
Type::Struct(_)
)
}
pub(crate) fn render_builder_ident(&self, id: &Id, scope: Option<&str>) -> Option<TokenStream> {
self.has_builder(id).then(|| {
let name_ident = format_ident!(
"{}",
self.types
.get(id)
.expect("invalid type id")
.name()
.expect("a struct has a name")
);
let scope_ident = scope.map(|scope| {
let scope_ident = format_ident!("{scope}");
quote! { #scope_ident:: }
});
quote! { #scope_ident builder::#name_ident }
})
}
pub(crate) fn render_parameter_ident(
&self,
id: &Id,
scope: Option<&str>,
lifetime: Option<&str>,
) -> TokenStream {
let lifetime_tok = lifetime
.map(|name| syn::Lifetime::new(&format!("'{name}"), proc_macro2::Span::call_site()));
match self.types.get(id).expect("invalid type id") {
Type::Enum(type_enum) if type_enum.every_variant_is_unit() => {
self.render_ident_with_scope(id, scope)
}
Type::Enum(_)
| Type::Struct(_)
| Type::UnitStruct(_)
| Type::TupleStruct(_)
| Type::NewtypeStruct(_)
| Type::TypeAlias(_)
| Type::Native(_)
| Type::Box(_)
| Type::Vec(_)
| Type::Map(..)
| Type::Set(_)
| Type::Array(..)
| Type::JsonValue => {
let ident = self.render_ident_with_scope(id, scope);
quote! { & #lifetime_tok #ident }
}
Type::String => quote! { & #lifetime_tok str },
Type::Option(inner_id) => {
let inner = self.render_parameter_ident(inner_id, scope, lifetime);
match self.types.get(inner_id).expect("invalid type id") {
Type::Option(_) => inner,
_ => {
let option_type = match &self.settings.std {
Std::FullyQualified => quote! { ::std::option::Option },
Std::Unqualified => quote! { Option },
};
quote! { #option_type<#inner> }
}
}
}
Type::Tuple(inner_ids) => {
let inner = inner_ids
.iter()
.map(|inner_id| self.render_parameter_ident(inner_id, scope, lifetime))
.collect::<Vec<_>>();
if inner.len() == 1 {
quote! { ( #( #inner, )* ) }
} else {
quote! { ( #( #inner ),* ) }
}
}
Type::Unit | Type::Boolean | Type::Integer(_) | Type::Float(_) | Type::Never => {
self.render_ident_with_scope(id, scope)
}
}
}
pub(crate) fn render_std_string(&self) -> TokenStream {
match &self.settings.std {
Std::FullyQualified => quote! { ::std::string::String },
Std::Unqualified => quote! { String },
}
}
pub(crate) fn render_derives(
&self,
traits: &TypespaceTraitSet,
extra_derives: &[String],
comparison_exempt: bool,
) -> Option<TokenStream> {
[TypespaceTrait::Display, TypespaceTrait::FromStr]
.into_iter()
.for_each(|manual_trait| {
if traits.contains(&manual_trait) {
panic!(
"trying to derive {manual_trait} which requires a \
manual implementation; this is a bug",
)
}
});
const WITHHELD_TRAITS: [TypespaceTrait; 6] = [
TypespaceTrait::Copy,
TypespaceTrait::Eq,
TypespaceTrait::PartialEq,
TypespaceTrait::Ord,
TypespaceTrait::PartialOrd,
TypespaceTrait::Hash,
];
let derives = traits
.iter()
.filter(|tt| {
comparison_exempt || !self.settings.typify_compat || !WITHHELD_TRAITS.contains(*tt)
})
.map(|tt| tt.render(self.settings))
.chain(
self.settings
.extra_derives
.iter()
.chain(extra_derives.iter())
.map(|derive| {
syn::parse_str::<syn::Path>(derive)
.expect("invalid derive path")
.to_token_stream()
}),
)
.map(|tokens| (tokens.to_string(), tokens))
.collect::<BTreeMap<_, _>>();
(!derives.is_empty()).then(|| {
let derives = derives.values();
quote! {
#[derive( #( #derives ),* )]
}
})
}
pub(crate) fn render_attrs<'b>(
&'b self,
extra_attrs: &'b [String],
) -> impl Iterator<Item = TokenStream> + 'b {
self.settings
.extra_attrs
.iter()
.chain(extra_attrs.iter())
.map(|attr| attr.parse().unwrap())
}
pub(crate) fn render_ident_impl(
&self,
id: &Id,
scope: Option<&str>,
base_type: bool,
) -> TokenStream {
let ty = self.types.get(id).unwrap();
match ty {
Type::Enum(Enum { common, .. })
| Type::Struct(Struct { common, .. })
| Type::UnitStruct(UnitStruct { common, .. })
| Type::TupleStruct(TupleStruct { common, .. })
| Type::NewtypeStruct(NewtypeStruct { common, .. })
| Type::TypeAlias(TypeAlias { common, .. }) => {
let name = common.built_name();
let name_ident = format_ident!("{name}");
if let Some(scope) = scope {
let scope_ident = format_ident!("{scope}");
quote! { #scope_ident::#name_ident }
} else {
name_ident.into_token_stream()
}
}
Type::Native(Native {
container,
parameters,
}) => {
let path = container.path();
let parameters = (!base_type && !parameters.is_empty()).then(|| {
let parameter_idents = parameters
.iter()
.map(|param_id| self.render_ident_with_scope(param_id, scope));
quote! {
< #( #parameter_idents ),* >
}
});
quote! {
#path #parameters
}
}
Type::Array(schema_ref, n) => {
let inner_ident = self.render_ident_with_scope(schema_ref, scope);
quote! {
[#inner_ident; #n]
}
}
Type::Tuple(schema_refs) => {
let inner_idents = schema_refs
.iter()
.map(|id| self.render_ident_with_scope(id, scope));
quote! {
( #( #inner_idents ),* )
}
}
Type::Option(option_id) => {
let option_type = match &self.settings.std {
Std::FullyQualified => quote! { ::std::option::Option },
Std::Unqualified => quote! { Option },
};
if base_type {
option_type
} else {
let option_ident = self.render_ident_with_scope(option_id, scope);
quote! {
#option_type<#option_ident>
}
}
}
Type::Box(boxed_id) => {
let box_type = match &self.settings.std {
Std::FullyQualified => quote! { ::std::boxed::Box },
Std::Unqualified => quote! { Box },
};
if base_type {
box_type
} else {
let boxed_ident = self.render_ident_with_scope(boxed_id, scope);
quote! {
#box_type<#boxed_ident>
}
}
}
Type::Set(inner_id) => {
let set_type = self.settings.set_type.rendered_path(&self.settings.std);
if base_type {
quote! { #set_type }
} else {
let inner_ident = self.render_ident_with_scope(inner_id, scope);
quote! {
#set_type<#inner_ident>
}
}
}
Type::Vec(inner_id) => {
let vec_type = self.settings.vec_type.rendered_path(&self.settings.std);
if base_type {
quote! { #vec_type }
} else {
let inner_ident = self.render_ident_with_scope(inner_id, scope);
quote! {
#vec_type<#inner_ident>
}
}
}
Type::Map(key_id, value_id) => {
let key_ty = self.types.get(key_id).unwrap();
let value_ty = self.types.get(value_id).unwrap();
let map_type =
if matches!(key_ty, Type::String) && matches!(value_ty, Type::JsonValue) {
quote! { ::serde_json::Map }
} else {
let path = self.settings.map_type.rendered_path(&self.settings.std);
quote! { #path }
};
if base_type {
map_type
} else {
let key_ident = self.render_ident_with_scope(key_id, scope);
let value_ident = self.render_ident_with_scope(value_id, scope);
quote! {
#map_type<#key_ident, #value_ident>
}
}
}
Type::Boolean => quote! { bool },
Type::Integer(name) | Type::Float(name) => syn::parse_str::<syn::TypePath>(name)
.unwrap()
.to_token_stream(),
Type::String => match &self.settings.std {
Std::FullyQualified => quote! { ::std::string::String },
Std::Unqualified => quote! { String },
},
Type::JsonValue => quote! { ::serde_json::Value },
Type::Never => quote! { ::json_serde::Never },
Type::Unit => quote! { () },
}
}
pub(crate) fn render_struct_property(
&self,
StructProperty {
rust_name,
json_name,
state,
description,
type_id,
}: &StructProperty<Id>,
serde_derives: SerdeDerives,
vis_pub: bool,
context: &str,
out: &mut Outputspace,
) -> RenderedStructProperty {
let description = description.as_ref().map(|text| {
quote! {
#[doc = #text]
}
});
let mut serde_options = serde_derives.attrs();
match json_name {
StructPropertySerde::None => {}
StructPropertySerde::Rename(s) => {
serde_options.push(quote! {
rename = #s
});
}
StructPropertySerde::Flatten => {
serde_options.push(quote! {
flatten
});
}
};
let default = match state {
StructPropertyState::Required => DefaultConstructor::None,
StructPropertyState::Optional | StructPropertyState::Default => {
DefaultConstructor::Default
}
StructPropertyState::DefaultValue(JsonValue(value)) => {
let fn_path = self.default_fn(context, rust_name, type_id, value, out);
serde_options.push(quote! { default = #fn_path });
let call = format!("{fn_path}()")
.parse::<TokenStream>()
.expect("a function path followed by () lexes as tokens");
DefaultConstructor::Generated(call)
}
};
let ty = self.types.get(type_id).unwrap();
enum TypeOfInterest<Id> {
Option(Id),
Never,
Other,
}
let type_of_interest = match ty {
Type::Option(id) => TypeOfInterest::Option(id),
Type::Never => TypeOfInterest::Never,
_ => TypeOfInterest::Other,
};
let ty_ident = self.render_ident(type_id);
let ty_ident_scoped = self.render_ident_with_scope(type_id, Some("super"));
let std_opt_type = match &self.settings.std {
Std::FullyQualified => quote! { ::std::option::Option },
Std::Unqualified => quote! { Option },
};
let std_opt_type_str = std_opt_type.clone().token_print();
let std_opt_is_none = format!("{std_opt_type_str}::is_none");
let (prop_ty_ident, prop_ty_ident_scoped) = match (state, type_of_interest) {
(StructPropertyState::Required, TypeOfInterest::Other) => (ty_ident, ty_ident_scoped),
(StructPropertyState::Required, TypeOfInterest::Option(_)) => {
let opt_deserialize = format!("{std_opt_type_str}::deserialize");
serde_options.push(quote! { deserialize_with = #opt_deserialize });
(ty_ident, ty_ident_scoped)
}
(StructPropertyState::Optional, TypeOfInterest::Other) => {
serde_options.push(quote! { default });
serde_options.push(quote! {
deserialize_with = "::json_serde::deserialize_some"
});
serde_options.push(quote! { skip_serializing_if = #std_opt_is_none });
(
quote! { #std_opt_type<#ty_ident> },
quote! {#std_opt_type<#ty_ident_scoped>},
)
}
(StructPropertyState::Optional, TypeOfInterest::Option(inner_id)) => {
match &self.settings.optional_nullable {
OptionalNullable::ConflateAsAbsent => {
serde_options.push(quote! {
skip_serializing_if = #std_opt_is_none
});
(ty_ident, ty_ident_scoped)
}
OptionalNullable::ConflateAsNull => {
(ty_ident, ty_ident_scoped)
}
OptionalNullable::DoubleOption => {
serde_options.push(quote! { default });
serde_options.push(quote! {
deserialize_with = "::json_serde::deserialize_some"
});
serde_options.push(quote! {
skip_serializing_if = #std_opt_is_none
});
(
quote! { #std_opt_type<#ty_ident> },
quote! { #std_opt_type<#ty_ident_scoped> },
)
}
OptionalNullable::CustomType(container) => {
let custom_type_path = container.rendered_path(&self.settings.std);
serde_options.push(quote! { default });
let is_absent = "::json_serde::OptionalNullable::is_absent";
serde_options.push(quote! {
skip_serializing_if = #is_absent
});
let inner_ident = self.render_ident(inner_id);
let inner_ident_scoped =
self.render_ident_with_scope(inner_id, Some("super"));
(
quote! { #custom_type_path<#inner_ident> },
quote! { #custom_type_path<#inner_ident_scoped> },
)
}
}
}
(StructPropertyState::Default, TypeOfInterest::Option(_) | TypeOfInterest::Other) => {
serde_options.push(quote! { default });
self.render_struct_property_add_skip(
&mut serde_options,
type_id,
ty,
std_opt_is_none,
);
(ty_ident, ty_ident_scoped)
}
(
StructPropertyState::DefaultValue(_),
TypeOfInterest::Option(_) | TypeOfInterest::Other,
) => {
(ty_ident, ty_ident_scoped)
}
(StructPropertyState::Optional, TypeOfInterest::Never) => {
serde_options.push(quote! { default });
serde_options.push(quote! {
skip_serializing_if = "::json_serde::always"
});
(
quote! { ::json_serde::Absent },
quote! { ::json_serde::Absent },
)
}
(
StructPropertyState::Required
| StructPropertyState::Default
| StructPropertyState::DefaultValue(_),
TypeOfInterest::Never,
) => unreachable!("finalization rejects a Never property that requires a value"),
};
let rust_name_ident = format_ident!("{rust_name}");
RenderedStructProperty {
description,
serde: serde_options,
vis_pub,
rust_name_ident,
prop_ty_ident,
prop_ty_ident_scoped,
default,
}
}
fn default_fn(
&self,
context: &str,
rust_name: &str,
type_id: &Id,
value: &serde_json::Value,
out: &mut Outputspace,
) -> String {
match shared_default_fn(self.types, type_id, value) {
Some(SharedDefaultFn { helper, path }) => {
out.add_default_helper(helper);
path
}
None => {
let fn_name_str = heck::AsSnakeCase(format!("{context}_{rust_name}")).to_string();
let fn_name_ident = format_ident!("{}", fn_name_str);
let ty_for_fn = self.render_ident_with_scope(type_id, Some("super"));
let body = self.generate_default(value, type_id);
out.cs().get_root_mod().get_mod("defaults").add_item(
context,
quote! {
pub(super) fn #fn_name_ident() -> #ty_for_fn {
#body
}
},
);
format!("defaults::{fn_name_str}")
}
}
}
fn render_struct_property_add_skip(
&self,
serde_options: &mut SerdeAttrs,
ty_id: &Id,
ty: &Type<Id>,
std_opt_is_none: String,
) {
match ty {
all_named_types!(_) => {}
Type::Native(_) => {}
Type::Option(_) => {
serde_options.push(quote! { skip_serializing_if = #std_opt_is_none });
}
Type::Box(boxed_id) => {
let boxed_ty = self.types.get(boxed_id).unwrap();
self.render_struct_property_add_skip(
serde_options,
boxed_id,
boxed_ty,
std_opt_is_none,
);
}
Type::String if self.settings.typify_compat => {}
Type::Vec(_) | Type::Map(_, _) | Type::Set(_) | Type::String => {
let ty_raw_ident = self.render_raw_type(ty_id);
let is_empty = format!("{}::is_empty", ty_raw_ident.token_print());
serde_options.push(quote! { skip_serializing_if = #is_empty });
}
Type::Array(_, _) | Type::Tuple(_) => {}
Type::Unit => {
}
Type::Boolean => {
}
Type::Integer(_) | Type::Float(_) => {}
Type::JsonValue => panic!("Default value for JsonValue is not supported"),
Type::Never => unreachable!("Never properties add no skip attribute"),
}
}
fn array_bounds<'b>(&'b self, id: &'b Id) -> Option<(usize, Option<usize>)> {
array_bounds(self.types, id)
}
}
pub(crate) fn array_bounds<'a, Id: Ord>(
types: &'a BTreeMap<Id, Type<Id>>,
id: &'a Id,
) -> Option<(usize, Option<usize>)> {
array_bounds_seen(types, id, &mut BTreeSet::new())
}
fn array_bounds_seen<'a, Id: Ord>(
types: &'a BTreeMap<Id, Type<Id>>,
id: &'a Id,
seen: &mut BTreeSet<&'a Id>,
) -> Option<(usize, Option<usize>)> {
let mut id = id;
loop {
if !seen.insert(id) {
return None;
}
let ty = types.get(id)?;
match ty {
Type::Enum(_) | Type::UnitStruct(_) => {
return None;
}
Type::Struct(_) => {
return None;
}
Type::TupleStruct(tuple_struct) => {
let fixed = tuple_struct.fields.len();
return match tuple_struct.rest.as_ref() {
None => Some((fixed, Some(fixed))),
Some(rest_id) => {
let (rest_min, rest_max) = array_bounds_seen(types, rest_id, seen)?;
Some((fixed + rest_min, rest_max.map(|max| fixed + max)))
}
};
}
Type::NewtypeStruct(NewtypeStruct {
inner: inner_id, ..
})
| Type::TypeAlias(TypeAlias {
target: inner_id, ..
})
| Type::Box(inner_id) => {
id = inner_id;
}
Type::Native(_) => {
return Some((0, None));
}
Type::Vec(_) | Type::Set(_) => {
return Some((0, None));
}
Type::Array(_, size) => {
return Some((*size, Some(*size)));
}
Type::Option(_) => {
return None;
}
Type::Tuple(items) => {
return Some((items.len(), Some(items.len())));
}
Type::Map(_, _)
| Type::Unit
| Type::Boolean
| Type::Integer(_)
| Type::Float(_)
| Type::String
| Type::JsonValue
| Type::Never => {
return None;
}
}
}
}
pub(crate) enum DefaultConstructor {
None,
Default,
Generated(TokenStream),
}
pub(crate) struct RenderedStructProperty {
pub description: Option<TokenStream>,
pub serde: SerdeAttrs,
pub vis_pub: bool,
pub rust_name_ident: syn::Ident,
pub prop_ty_ident: TokenStream,
pub prop_ty_ident_scoped: TokenStream,
pub default: DefaultConstructor,
}
impl ToTokens for RenderedStructProperty {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self {
description,
serde,
vis_pub,
rust_name_ident,
prop_ty_ident,
prop_ty_ident_scoped: _,
default: _,
} = self;
let vis_pub = vis_pub.then(|| quote! { pub });
tokens.extend(quote! {
#description
#serde
#vis_pub #rust_name_ident: #prop_ty_ident
});
}
}
fn build_commons<Id: Clone>(types: &mut BTreeMap<Id, Type<Id>>) {
for typ in types.values_mut() {
if let Some(common) = typ.common_mut() {
common.built = Some(TypeCommonBuilt {
traits: TypespaceTraitSet::empty(),
from_string_irrefutable: false,
});
}
}
}
trait TokenPrint {
fn token_print(self) -> String;
}
impl TokenPrint for proc_macro2::TokenStream {
fn token_print(self) -> String {
self.into_iter()
.map(|tt| tt.to_string())
.collect::<String>()
}
}
pub(crate) fn has_trait<Id>(
types: &BTreeMap<Id, Type<Id>>,
settings: &Settings,
id: &Id,
trait_: TypespaceTrait,
seen: &mut BTreeSet<Id>,
) -> bool
where
Id: Clone + Ord,
{
if !seen.insert(id.clone()) {
return false;
}
let typ = types.get(id).unwrap();
let answer = match typ {
build::Type::Enum(e) => e
.common
.built
.as_ref()
.is_some_and(|b| b.traits.contains(&trait_)),
build::Type::Struct(s) => s
.common
.built
.as_ref()
.is_some_and(|b| b.traits.contains(&trait_)),
build::Type::NewtypeStruct(n) => n
.common
.built
.as_ref()
.is_some_and(|b| b.traits.contains(&trait_)),
build::Type::UnitStruct(u) => u
.common
.built
.as_ref()
.is_some_and(|b| b.traits.contains(&trait_)),
build::Type::TupleStruct(t) => t
.common
.built
.as_ref()
.is_some_and(|b| b.traits.contains(&trait_)),
build::Type::TypeAlias(a) => has_trait(types, settings, &a.target, trait_, seen),
typ => crate::trait_resolution::unnamed_provides(typ, trait_, settings, &mut |child_id| {
has_trait(types, settings, child_id, trait_, seen)
}),
};
seen.remove(id);
answer
}