Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 36 additions & 6 deletions typify-impl/src/type_entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1133,6 +1133,7 @@ impl TypeEntry {
let mut prop_error = Vec::new();
let mut prop_type = Vec::new();
let mut prop_type_scoped = Vec::new();
let mut prop_is_unit = Vec::new();

properties.iter().for_each(|prop| {
prop_doc.push(prop.description.as_ref().map(|d| quote! { #[doc = #d] }));
Expand All @@ -1143,6 +1144,7 @@ impl TypeEntry {
));

let prop_type_entry = type_space.id_to_entry.get(&prop.type_id).unwrap();
prop_is_unit.push(matches!(prop_type_entry.details, TypeEntryDetails::Unit));
prop_type.push(prop_type_entry.type_ident(type_space, &None));
prop_type_scoped
.push(prop_type_entry.type_ident(type_space, &Some("super".to_string())));
Expand Down Expand Up @@ -1264,10 +1266,38 @@ impl TypeEntry {
quote! { value }
};

let prop_default = prop_default.iter().map(|pd| match pd {
PropDefault::None(err_msg) => quote! { Err(#err_msg.to_string()) },
PropDefault::Default(default_fn) => quote! { Ok(#default_fn) },
PropDefault::Custom(custom_fn) => quote! { Ok(super::#custom_fn) },
// The source value is not read when every property is unit-typed.
let from_value_ident = if prop_is_unit.iter().all(|is_unit| *is_unit) {
quote! { _value }
} else {
quote! { value }
};

// For unit-typed properties (produced by null schemas) the value is
// always `()`, so we emit `Ok(())` rather than passing the unit value
// to `Ok(..)`, which would trip clippy's `unit_arg` lint.
let prop_default =
prop_default
.iter()
.zip(&prop_is_unit)
.map(|(pd, is_unit)| match pd {
PropDefault::None(err_msg) => quote! { Err(#err_msg.to_string()) },
PropDefault::Default(_) | PropDefault::Custom(_) if *is_unit => {
quote! { Ok(()) }
}
PropDefault::Default(default_fn) => quote! { Ok(#default_fn) },
PropDefault::Custom(custom_fn) => quote! { Ok(super::#custom_fn) },
});

// Per-property initializers for the `From<super::T>` impl. Reading a
// unit field yields `()`, so we emit `Ok(())` directly to avoid
// passing a unit value to `Ok(..)` (clippy's `unit_arg` lint).
let prop_from = prop_name.iter().zip(&prop_is_unit).map(|(name, is_unit)| {
if *is_unit {
quote! { #name: Ok(()) }
} else {
quote! { #name: Ok(value.#name) }
}
});

output.add_item(
Expand Down Expand Up @@ -1324,10 +1354,10 @@ impl TypeEntry {

// Construct a builder from the item.
impl ::std::convert::From<super::#type_name> for #type_name {
fn from(#value_ident: super::#type_name) -> Self {
fn from(#from_value_ident: super::#type_name) -> Self {
Self {
#(
#prop_name: Ok(value.#prop_name),
#prop_from,
)*
}
}
Expand Down
13 changes: 13 additions & 0 deletions typify/tests/schemas/builder-unit-property.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "UnitProperty",
"type": "object",
"required": ["unit"],
"properties": {
"unit": {
"type": "null",
"default": null
}
},
"additionalProperties": false
}
98 changes: 98 additions & 0 deletions typify/tests/schemas/builder-unit-property.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#![deny(warnings)]
#[doc = r" Error types."]
pub mod error {
#[doc = r" Error from a `TryFrom` or `FromStr` implementation."]
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())
}
}
}
#[doc = "`UnitProperty`"]
#[doc = r""]
#[doc = r" <details><summary>JSON schema</summary>"]
#[doc = r""]
#[doc = r" ```json"]
#[doc = "{"]
#[doc = " \"title\": \"UnitProperty\","]
#[doc = " \"type\": \"object\","]
#[doc = " \"required\": ["]
#[doc = " \"unit\""]
#[doc = " ],"]
#[doc = " \"properties\": {"]
#[doc = " \"unit\": {"]
#[doc = " \"default\": null,"]
#[doc = " \"type\": \"null\""]
#[doc = " }"]
#[doc = " },"]
#[doc = " \"additionalProperties\": false"]
#[doc = "}"]
#[doc = r" ```"]
#[doc = r" </details>"]
#[derive(:: serde :: Deserialize, :: serde :: Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
pub struct UnitProperty {
pub unit: (),
}
impl UnitProperty {
pub fn builder() -> builder::UnitProperty {
Default::default()
}
}
#[doc = r" Types for composing complex structures."]
pub mod builder {
#[derive(Clone, Debug)]
pub struct UnitProperty {
unit: ::std::result::Result<(), ::std::string::String>,
}
impl ::std::default::Default for UnitProperty {
fn default() -> Self {
Self {
unit: Err("no value supplied for unit".to_string()),
}
}
}
impl UnitProperty {
pub fn unit<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<()>,
T::Error: ::std::fmt::Display,
{
self.unit = value
.try_into()
.map_err(|e| format!("error converting supplied value for unit: {e}"));
self
}
}
impl ::std::convert::TryFrom<UnitProperty> for super::UnitProperty {
type Error = super::error::ConversionError;
fn try_from(
value: UnitProperty,
) -> ::std::result::Result<Self, super::error::ConversionError> {
Ok(Self { unit: value.unit? })
}
}
impl ::std::convert::From<super::UnitProperty> for UnitProperty {
fn from(_value: super::UnitProperty) -> Self {
Self { unit: Ok(()) }
}
}
}
fn main() {}