Skip to content

Commit

Permalink
refactor: move map_to_use into global namespace.
Browse files Browse the repository at this point in the history
Other: improve documentation & test cases.
  • Loading branch information
JosiahBull committed Dec 6, 2024
1 parent bb7dba6 commit c08f252
Show file tree
Hide file tree
Showing 11 changed files with 51 additions and 71 deletions.
2 changes: 0 additions & 2 deletions Cargo.lock

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

13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,17 @@ 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
#### Alternate Map types

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
By default, Typify uses `std::collections::HashMap` as described above.

If you prefer to use `std::collections::BTreeMap` or map type from a crate such
as `indexmap::IndexMap`, you can specify this by calling `with_map_type` on the
`TypeSpaceSettings` object, and providing the full path to the type you want to
use. E.g. `::std::collections::HashMap` or `::indexmap::IndexMap`.
use. E.g. `::std::collections::BTreeMap` or `::indexmap::IndexMap`.

See the documentation for `TypeSpaceSettings::with_map_type` for the
requirements for a map type.

### OneOf

Expand Down
9 changes: 1 addition & 8 deletions typify-impl/src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1200,7 +1200,6 @@ 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 @@ -1237,7 +1236,6 @@ impl TypeSpace {
));

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

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

Expand Down
6 changes: 2 additions & 4 deletions typify-impl/src/defaults.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,7 @@ 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 @@ -622,7 +620,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
20 changes: 12 additions & 8 deletions typify-impl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -476,14 +476,18 @@ impl TypeSpaceSettings {
///
/// ## Requirements
///
/// - The type must have an `is_empty` method that returns a boolean.
/// - The type must have two generic parameters, `K` and `V`.
/// - Have an `is_empty` method that returns a boolean.
/// - Have two generic parameters, `K` and `V`.
/// - Have a [`std::fmt::Debug`] impl.
/// - Have a [`serde::Serialize``] impl.
/// - Have a [`serde::Deserialize``] impl.
/// - Have a [`Clone`] impl.
///
/// ## Examples
///
/// - `::std::collections::HashMap`
/// - `::std::collections::BTreeMap`
/// - `::indexmap::IndexMap`
/// - [`::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;
Expand Down Expand Up @@ -1005,9 +1009,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
24 changes: 5 additions & 19 deletions typify-impl/src/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@ 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 @@ -206,7 +205,6 @@ 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 @@ -239,12 +237,7 @@ impl TypeSpace {
None => self.id_for_schema(Name::Unknown, &Schema::Bool(true))?,
};

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

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

let map_to_use = &type_space.settings.map_type;
let key_ty = type_space
.id_to_entry
.get(key_id)
Expand All @@ -414,10 +401,9 @@ 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);
let is_empty = format!("{}::is_empty", map_to_use);
serde_options.push(quote! {
skip_serializing_if = #map_to_use
skip_serializing_if = #is_empty
});
}
DefaultFunction::Default
Expand Down
17 changes: 4 additions & 13 deletions typify-impl/src/type_entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,7 @@ pub(crate) enum TypeEntryDetails {
Option(TypeId),
Box(TypeId),
Vec(TypeId),
Map {
map_to_use: String,
key_id: TypeId,
value_id: TypeId,
},
Map(TypeId, TypeId),
Set(TypeId),
Array(TypeId, usize),
Tuple(Vec<TypeId>),
Expand Down Expand Up @@ -1622,11 +1618,8 @@ impl TypeEntry {
quote! { ::std::vec::Vec<#item> }
}

TypeEntryDetails::Map {
map_to_use,
key_id,
value_id,
} => {
TypeEntryDetails::Map(key_id, value_id) => {
let map_to_use = &type_space.settings.map_type;
let key_ty = type_space
.id_to_entry
.get(key_id)
Expand Down Expand Up @@ -1826,9 +1819,7 @@ 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
4 changes: 1 addition & 3 deletions typify-impl/src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,7 @@ 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
1 change: 0 additions & 1 deletion typify-test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ 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"] }
Expand Down
8 changes: 4 additions & 4 deletions typify-test/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ struct WithSet {
#[allow(dead_code)]
#[derive(JsonSchema)]
struct WithMap {
map: HashMap<String, TestStruct>,
map: HashMap<String, String>,
}

struct LoginName;
Expand Down Expand Up @@ -131,9 +131,9 @@ fn main() {
out_file.push("codegen_hashmap.rs");
fs::write(out_file, contents).unwrap();

// Generate with IndexMap
// Generate with a custom map type to validate requirements.
let mut settings = TypeSpaceSettings::default();
settings.with_map_type("::indexmap::IndexMap".to_string());
settings.with_map_type("CustomMap".to_string());
let mut type_space = TypeSpace::new(&settings);

WithMap::add(&mut type_space);
Expand All @@ -142,7 +142,7 @@ fn main() {
prettyplease::unparse(&syn::parse2::<syn::File>(type_space.to_stream()).unwrap());

let mut out_file = Path::new(&env::var("OUT_DIR").unwrap()).to_path_buf();
out_file.push("codegen_indexmap.rs");
out_file.push("codegen_custommap.rs");
fs::write(out_file, contents).unwrap();
}

Expand Down
18 changes: 13 additions & 5 deletions typify-test/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,24 @@ mod hashmap {
}
}

mod indexmap {
use indexmap::IndexMap;
mod custom_map {
#[allow(private_interfaces)]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct CustomMap<K, V> {
key: K,
value: V,
}

include!(concat!(env!("OUT_DIR"), "/codegen_indexmap.rs"));
include!(concat!(env!("OUT_DIR"), "/codegen_custommap.rs"));

#[test]
fn test_with_map() {
// Validate that a map is represented as an IndexMap when requested.
// Validate that a map is represented as an CustomMap when requested.
let _ = WithMap {
map: IndexMap::new(),
map: CustomMap {
key: String::new(),
value: String::new(),
},
};
}
}

0 comments on commit c08f252

Please sign in to comment.