Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
16 changes: 12 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ applied. Non-required properties with types that already have a default value
(such as a `Vec<T>`) simply get the `#[serde(default)]` attribute (so you won't
see e.g. `Option<Vec<T>>`).

### IndexMap

By default, Typify uses `HashMap` for objects. If you prefer to use `IndexMap`
or some other object, you can specify this by calling `with_map_to_use` on the
`TypeSpaceSettings` object, and providing the full path to the type you want to
use. E.g. `::std::collections::HashMap` or `::indexmap::IndexMap`.

### OneOf

The `oneOf` construct maps to a Rust enum. Typify maps this to the various
Expand Down
1 change: 0 additions & 1 deletion typify-impl/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ syn = { version = "2.0.90", features = ["full"] }
thiserror = "2.0.3"
unicode-ident = "1.0.14"


[dev-dependencies]
env_logger = "0.10.2"
expectorate = "1.1.0"
Expand Down
9 changes: 8 additions & 1 deletion typify-impl/src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1200,6 +1200,7 @@ impl TypeSpace {
!= Some(&Schema::Bool(false)) =>
{
let type_entry = self.make_map(
self.settings.map_type.clone(),
type_name.into_option(),
property_names,
additional_properties,
Expand Down Expand Up @@ -1236,6 +1237,7 @@ impl TypeSpace {
));

let type_entry = self.make_map(
self.settings.map_type.clone(),
type_name.into_option(),
&property_names,
&additional_properties,
Expand All @@ -1245,7 +1247,12 @@ impl TypeSpace {
}

None => {
let type_entry = self.make_map(type_name.into_option(), &None, &None)?;
let type_entry = self.make_map(
self.settings.map_type.clone(),
type_name.into_option(),
&None,
&None,
)?;
Ok((type_entry, metadata))
}

Expand Down
6 changes: 4 additions & 2 deletions typify-impl/src/defaults.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,9 @@ impl TypeEntry {
Err(Error::invalid_value())
}
}
TypeEntryDetails::Map(key_id, value_id) => {
TypeEntryDetails::Map {
key_id, value_id, ..
} => {
if let serde_json::Value::Object(m) = default {
if m.is_empty() {
Ok(DefaultKind::Intrinsic)
Expand Down Expand Up @@ -620,7 +622,7 @@ fn all_props<'a>(

// TODO Rather than an option, this should probably be something
// that lets us say "explicit name" or "type to validate against"
TypeEntryDetails::Map(_, value_id) => return vec![(None, value_id, false)],
TypeEntryDetails::Map { value_id, .. } => return vec![(None, value_id, false)],
_ => unreachable!(),
};

Expand Down
43 changes: 39 additions & 4 deletions typify-impl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,20 +238,37 @@ pub(crate) enum DefaultImpl {
}

/// Settings that alter type generation.
#[derive(Debug, Default, Clone)]
#[derive(Debug, Clone)]
pub struct TypeSpaceSettings {
type_mod: Option<String>,
extra_derives: Vec<String>,
struct_builder: bool,

unknown_crates: UnknownPolicy,
crates: BTreeMap<String, CrateSpec>,
map_type: String,

patch: BTreeMap<String, TypeSpacePatch>,
replace: BTreeMap<String, TypeSpaceReplace>,
convert: Vec<TypeSpaceConversion>,
}

impl Default for TypeSpaceSettings {
fn default() -> Self {
Self {
map_type: "::std::collections::HashMap".to_string(),
type_mod: Default::default(),
extra_derives: Default::default(),
struct_builder: Default::default(),
unknown_crates: Default::default(),
crates: Default::default(),
patch: Default::default(),
replace: Default::default(),
convert: Default::default(),
}
}
}

#[derive(Debug, Clone)]
struct CrateSpec {
version: CrateVers,
Expand Down Expand Up @@ -454,6 +471,24 @@ impl TypeSpaceSettings {
);
self
}

/// Specify the map-like type to be used in generated code.
///
/// ## Requirements
///
/// - The type must have an `is_empty` method that returns a boolean.
/// - The type must have two generic parameters, `K` and `V`.
///
/// ## Examples
///
/// - `::std::collections::HashMap`
/// - `::std::collections::BTreeMap`
/// - `::indexmap::IndexMap`
///
pub fn with_map_type(&mut self, map_type: String) -> &mut Self {
self.map_type = map_type;
self
}
}

impl TypeSpacePatch {
Expand Down Expand Up @@ -970,9 +1005,9 @@ impl<'a> Type<'a> {
// Compound types
TypeEntryDetails::Option(type_id) => TypeDetails::Option(type_id.clone()),
TypeEntryDetails::Vec(type_id) => TypeDetails::Vec(type_id.clone()),
TypeEntryDetails::Map(key_id, value_id) => {
TypeDetails::Map(key_id.clone(), value_id.clone())
}
TypeEntryDetails::Map {
key_id, value_id, ..
} => TypeDetails::Map(key_id.clone(), value_id.clone()),
TypeEntryDetails::Set(type_id) => TypeDetails::Set(type_id.clone()),
TypeEntryDetails::Box(type_id) => TypeDetails::Box(type_id.clone()),
TypeEntryDetails::Tuple(types) => TypeDetails::Tuple(Box::new(types.iter().cloned())),
Expand Down
28 changes: 23 additions & 5 deletions typify-impl/src/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ impl TypeSpace {
additional_properties @ Some(_) => {
let sub_type_name = type_name.as_ref().map(|base| format!("{}_extra", base));
let map_type = self.make_map(
self.settings.map_type.clone(),
sub_type_name,
&validation.property_names,
additional_properties,
Expand Down Expand Up @@ -205,6 +206,7 @@ impl TypeSpace {

pub(crate) fn make_map(
&mut self,
map_to_use: String,
type_name: Option<String>,
property_names: &Option<Box<Schema>>,
additional_properties: &Option<Box<Schema>>,
Expand Down Expand Up @@ -237,7 +239,12 @@ impl TypeSpace {
None => self.id_for_schema(Name::Unknown, &Schema::Bool(true))?,
};

Ok(TypeEntryDetails::Map(key_id, value_id).into())
Ok(TypeEntryDetails::Map {
map_to_use,
key_id,
value_id,
}
.into())
}

/// Perform a schema conversion for a type that must be string-like.
Expand Down Expand Up @@ -381,7 +388,14 @@ pub(crate) fn generate_serde_attr(
serde_options.push(quote! { skip_serializing_if = "::std::vec::Vec::is_empty" });
DefaultFunction::Default
}
(StructPropertyState::Optional, TypeEntryDetails::Map(key_id, value_id)) => {
(
StructPropertyState::Optional,
TypeEntryDetails::Map {
map_to_use,
key_id,
value_id,
},
) => {
serde_options.push(quote! { default });

let key_ty = type_space
Expand All @@ -400,8 +414,10 @@ pub(crate) fn generate_serde_attr(
skip_serializing_if = "::serde_json::Map::is_empty"
});
} else {
// Append ::is_empty to the string.
let map_to_use = format!("{}::is_empty", map_to_use);
serde_options.push(quote! {
skip_serializing_if = "::std::collections::HashMap::is_empty"
skip_serializing_if = #map_to_use
});
}
DefaultFunction::Default
Expand Down Expand Up @@ -458,7 +474,7 @@ fn has_default(
// No default specified.
(Some(TypeEntryDetails::Option(_)), None) => StructPropertyState::Optional,
(Some(TypeEntryDetails::Vec(_)), None) => StructPropertyState::Optional,
(Some(TypeEntryDetails::Map(..)), None) => StructPropertyState::Optional,
(Some(TypeEntryDetails::Map { .. }), None) => StructPropertyState::Optional,
(Some(TypeEntryDetails::Unit), None) => StructPropertyState::Optional,
(_, None) => StructPropertyState::Required,

Expand All @@ -471,7 +487,9 @@ fn has_default(
StructPropertyState::Optional
}
// Default specified is the same as the implicit default: {}
(Some(TypeEntryDetails::Map(..)), Some(serde_json::Value::Object(m))) if m.is_empty() => {
(Some(TypeEntryDetails::Map { .. }), Some(serde_json::Value::Object(m)))
if m.is_empty() =>
{
StructPropertyState::Optional
}
// Default specified is the same as the implicit default: false
Expand Down
25 changes: 19 additions & 6 deletions typify-impl/src/type_entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,11 @@ pub(crate) enum TypeEntryDetails {
Option(TypeId),
Box(TypeId),
Vec(TypeId),
Map(TypeId, TypeId),
Map {
map_to_use: String,
key_id: TypeId,
value_id: TypeId,
},
Set(TypeId),
Array(TypeId, usize),
Tuple(Vec<TypeId>),
Expand Down Expand Up @@ -595,7 +599,7 @@ impl TypeEntry {
TypeEntryDetails::Unit
| TypeEntryDetails::Option(_)
| TypeEntryDetails::Vec(_)
| TypeEntryDetails::Map(_, _)
| TypeEntryDetails::Map { .. }
| TypeEntryDetails::Set(_) => {
matches!(impl_name, TypeSpaceImpl::Default)
}
Expand Down Expand Up @@ -1618,7 +1622,11 @@ impl TypeEntry {
quote! { ::std::vec::Vec<#item> }
}

TypeEntryDetails::Map(key_id, value_id) => {
TypeEntryDetails::Map {
map_to_use,
key_id,
value_id,
} => {
let key_ty = type_space
.id_to_entry
.get(key_id)
Expand All @@ -1635,7 +1643,10 @@ impl TypeEntry {
} else {
let key_ident = key_ty.type_ident(type_space, type_mod);
let value_ident = value_ty.type_ident(type_space, type_mod);
quote! { ::std::collections::HashMap<#key_ident, #value_ident> }

let map_to_use = syn::parse_str::<syn::TypePath>(map_to_use)
.expect("map type path wasn't valid");
quote! { #map_to_use<#key_ident, #value_ident> }
}
}

Expand Down Expand Up @@ -1746,7 +1757,7 @@ impl TypeEntry {
| TypeEntryDetails::Struct(_)
| TypeEntryDetails::Newtype(_)
| TypeEntryDetails::Vec(_)
| TypeEntryDetails::Map(..)
| TypeEntryDetails::Map { .. }
| TypeEntryDetails::Set(_)
| TypeEntryDetails::Box(_)
| TypeEntryDetails::Native(_)
Expand Down Expand Up @@ -1815,7 +1826,9 @@ impl TypeEntry {
TypeEntryDetails::Unit => "()".to_string(),
TypeEntryDetails::Option(type_id) => format!("option {}", type_id.0),
TypeEntryDetails::Vec(type_id) => format!("vec {}", type_id.0),
TypeEntryDetails::Map(key_id, value_id) => {
TypeEntryDetails::Map {
key_id, value_id, ..
} => {
format!("map {} {}", key_id.0, value_id.0)
}
TypeEntryDetails::Set(type_id) => format!("set {}", type_id.0),
Expand Down
6 changes: 4 additions & 2 deletions typify-impl/src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@ impl TypeEntry {
.collect::<Option<Vec<_>>>()?;
quote! { vec![#(#values),*] }
}
TypeEntryDetails::Map(key_id, value_id) => {
TypeEntryDetails::Map {
key_id, value_id, ..
} => {
let obj = value.as_object()?;
let key_ty = type_space.id_to_entry.get(key_id).unwrap();
let value_ty = type_space.id_to_entry.get(value_id).unwrap();
Expand Down Expand Up @@ -424,7 +426,7 @@ fn value_for_struct_props(
match &type_entry.details {
TypeEntryDetails::Struct(_)
| TypeEntryDetails::Option(_)
| TypeEntryDetails::Map(..) => (),
| TypeEntryDetails::Map { .. } => (),
_ => unreachable!(),
}

Expand Down
3 changes: 2 additions & 1 deletion typify-test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ edition = "2021"
regress = "0.10.1"
serde = "1.0.215"
serde_json = "1.0.133"
indexmap = { version = "2.7.0", features = ["serde"]}

[build-dependencies]
ipnetwork = { version = "0.20.0", features = ["schemars"] }
prettyplease = "0.2.25"
schemars = "0.8.21"
serde = "1.0.215"
syn = "2.0.90"
typify = { path = "../typify" }
typify = { path = "../typify"}
Loading
Loading