Skip to content

Commit dee9c58

Browse files
committed
feat(indexmap): Add support for changing HashMaps to IndexMaps.
1 parent f409d37 commit dee9c58

File tree

12 files changed

+167
-28
lines changed

12 files changed

+167
-28
lines changed

Cargo.lock

Lines changed: 12 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

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

59+
### IndexMap
60+
61+
By default, Typify uses `HashMap` for objects. If you prefer to use `IndexMap`
62+
or some other object, you can specify this by calling `with_map_to_use` on the
63+
`TypeSpaceSettings` object, and providing the full path to the type you want to
64+
use. E.g. `::std::collections::HashMap` or `::indexmap::IndexMap`.
65+
5966
### OneOf
6067

6168
The `oneOf` construct maps to a Rust enum. Typify maps this to the various

typify-impl/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ syn = { version = "2.0.90", features = ["full"] }
2121
thiserror = "2.0.3"
2222
unicode-ident = "1.0.14"
2323

24-
2524
[dev-dependencies]
2625
env_logger = "0.10.2"
2726
expectorate = "1.1.0"

typify-impl/src/convert.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1200,6 +1200,7 @@ impl TypeSpace {
12001200
!= Some(&Schema::Bool(false)) =>
12011201
{
12021202
let type_entry = self.make_map(
1203+
self.settings.map_to_use.clone(),
12031204
type_name.into_option(),
12041205
property_names,
12051206
additional_properties,
@@ -1236,6 +1237,7 @@ impl TypeSpace {
12361237
));
12371238

12381239
let type_entry = self.make_map(
1240+
self.settings.map_to_use.clone(),
12391241
type_name.into_option(),
12401242
&property_names,
12411243
&additional_properties,
@@ -1245,7 +1247,12 @@ impl TypeSpace {
12451247
}
12461248

12471249
None => {
1248-
let type_entry = self.make_map(type_name.into_option(), &None, &None)?;
1250+
let type_entry = self.make_map(
1251+
self.settings.map_to_use.clone(),
1252+
type_name.into_option(),
1253+
&None,
1254+
&None,
1255+
)?;
12491256
Ok((type_entry, metadata))
12501257
}
12511258

typify-impl/src/defaults.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,9 @@ impl TypeEntry {
199199
Err(Error::invalid_value())
200200
}
201201
}
202-
TypeEntryDetails::Map(key_id, value_id) => {
202+
TypeEntryDetails::Map {
203+
key_id, value_id, ..
204+
} => {
203205
if let serde_json::Value::Object(m) = default {
204206
if m.is_empty() {
205207
Ok(DefaultKind::Intrinsic)
@@ -620,7 +622,7 @@ fn all_props<'a>(
620622

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

typify-impl/src/lib.rs

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -238,20 +238,37 @@ pub(crate) enum DefaultImpl {
238238
}
239239

240240
/// Settings that alter type generation.
241-
#[derive(Debug, Default, Clone)]
241+
#[derive(Debug, Clone)]
242242
pub struct TypeSpaceSettings {
243243
type_mod: Option<String>,
244244
extra_derives: Vec<String>,
245245
struct_builder: bool,
246246

247247
unknown_crates: UnknownPolicy,
248248
crates: BTreeMap<String, CrateSpec>,
249+
map_to_use: String,
249250

250251
patch: BTreeMap<String, TypeSpacePatch>,
251252
replace: BTreeMap<String, TypeSpaceReplace>,
252253
convert: Vec<TypeSpaceConversion>,
253254
}
254255

256+
impl Default for TypeSpaceSettings {
257+
fn default() -> Self {
258+
Self {
259+
map_to_use: "::std::collections::HashMap".to_string(),
260+
type_mod: Default::default(),
261+
extra_derives: Default::default(),
262+
struct_builder: Default::default(),
263+
unknown_crates: Default::default(),
264+
crates: Default::default(),
265+
patch: Default::default(),
266+
replace: Default::default(),
267+
convert: Default::default(),
268+
}
269+
}
270+
}
271+
255272
#[derive(Debug, Clone)]
256273
struct CrateSpec {
257274
version: CrateVers,
@@ -454,6 +471,13 @@ impl TypeSpaceSettings {
454471
);
455472
self
456473
}
474+
475+
/// Specify the map implementation to use in generated types. The default is
476+
/// `std::collections::HashMap`.
477+
pub fn with_map_to_use(&mut self, map_to_use: String) -> &mut Self {
478+
self.map_to_use = map_to_use;
479+
self
480+
}
457481
}
458482

459483
impl TypeSpacePatch {
@@ -970,9 +994,9 @@ impl<'a> Type<'a> {
970994
// Compound types
971995
TypeEntryDetails::Option(type_id) => TypeDetails::Option(type_id.clone()),
972996
TypeEntryDetails::Vec(type_id) => TypeDetails::Vec(type_id.clone()),
973-
TypeEntryDetails::Map(key_id, value_id) => {
974-
TypeDetails::Map(key_id.clone(), value_id.clone())
975-
}
997+
TypeEntryDetails::Map {
998+
key_id, value_id, ..
999+
} => TypeDetails::Map(key_id.clone(), value_id.clone()),
9761000
TypeEntryDetails::Set(type_id) => TypeDetails::Set(type_id.clone()),
9771001
TypeEntryDetails::Box(type_id) => TypeDetails::Box(type_id.clone()),
9781002
TypeEntryDetails::Tuple(types) => TypeDetails::Tuple(Box::new(types.iter().cloned())),

typify-impl/src/structs.rs

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ impl TypeSpace {
119119
additional_properties @ Some(_) => {
120120
let sub_type_name = type_name.as_ref().map(|base| format!("{}_extra", base));
121121
let map_type = self.make_map(
122+
self.settings.map_to_use.clone(),
122123
sub_type_name,
123124
&validation.property_names,
124125
additional_properties,
@@ -205,6 +206,7 @@ impl TypeSpace {
205206

206207
pub(crate) fn make_map(
207208
&mut self,
209+
map_to_use: String,
208210
type_name: Option<String>,
209211
property_names: &Option<Box<Schema>>,
210212
additional_properties: &Option<Box<Schema>>,
@@ -237,7 +239,12 @@ impl TypeSpace {
237239
None => self.id_for_schema(Name::Unknown, &Schema::Bool(true))?,
238240
};
239241

240-
Ok(TypeEntryDetails::Map(key_id, value_id).into())
242+
Ok(TypeEntryDetails::Map {
243+
map_to_use,
244+
key_id,
245+
value_id,
246+
}
247+
.into())
241248
}
242249

243250
/// Perform a schema conversion for a type that must be string-like.
@@ -381,7 +388,14 @@ pub(crate) fn generate_serde_attr(
381388
serde_options.push(quote! { skip_serializing_if = "::std::vec::Vec::is_empty" });
382389
DefaultFunction::Default
383390
}
384-
(StructPropertyState::Optional, TypeEntryDetails::Map(key_id, value_id)) => {
391+
(
392+
StructPropertyState::Optional,
393+
TypeEntryDetails::Map {
394+
map_to_use,
395+
key_id,
396+
value_id,
397+
},
398+
) => {
385399
serde_options.push(quote! { default });
386400

387401
let key_ty = type_space
@@ -400,8 +414,10 @@ pub(crate) fn generate_serde_attr(
400414
skip_serializing_if = "::serde_json::Map::is_empty"
401415
});
402416
} else {
417+
// Append ::is_empty to the string.
418+
let map_to_use = format!("{}::is_empty", map_to_use);
403419
serde_options.push(quote! {
404-
skip_serializing_if = "::std::collections::HashMap::is_empty"
420+
skip_serializing_if = #map_to_use
405421
});
406422
}
407423
DefaultFunction::Default
@@ -458,7 +474,7 @@ fn has_default(
458474
// No default specified.
459475
(Some(TypeEntryDetails::Option(_)), None) => StructPropertyState::Optional,
460476
(Some(TypeEntryDetails::Vec(_)), None) => StructPropertyState::Optional,
461-
(Some(TypeEntryDetails::Map(..)), None) => StructPropertyState::Optional,
477+
(Some(TypeEntryDetails::Map { .. }), None) => StructPropertyState::Optional,
462478
(Some(TypeEntryDetails::Unit), None) => StructPropertyState::Optional,
463479
(_, None) => StructPropertyState::Required,
464480

@@ -471,7 +487,9 @@ fn has_default(
471487
StructPropertyState::Optional
472488
}
473489
// Default specified is the same as the implicit default: {}
474-
(Some(TypeEntryDetails::Map(..)), Some(serde_json::Value::Object(m))) if m.is_empty() => {
490+
(Some(TypeEntryDetails::Map { .. }), Some(serde_json::Value::Object(m)))
491+
if m.is_empty() =>
492+
{
475493
StructPropertyState::Optional
476494
}
477495
// Default specified is the same as the implicit default: false

typify-impl/src/type_entry.rs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,11 @@ pub(crate) enum TypeEntryDetails {
146146
Option(TypeId),
147147
Box(TypeId),
148148
Vec(TypeId),
149-
Map(TypeId, TypeId),
149+
Map {
150+
map_to_use: String,
151+
key_id: TypeId,
152+
value_id: TypeId,
153+
},
150154
Set(TypeId),
151155
Array(TypeId, usize),
152156
Tuple(Vec<TypeId>),
@@ -595,7 +599,7 @@ impl TypeEntry {
595599
TypeEntryDetails::Unit
596600
| TypeEntryDetails::Option(_)
597601
| TypeEntryDetails::Vec(_)
598-
| TypeEntryDetails::Map(_, _)
602+
| TypeEntryDetails::Map { .. }
599603
| TypeEntryDetails::Set(_) => {
600604
matches!(impl_name, TypeSpaceImpl::Default)
601605
}
@@ -1618,7 +1622,11 @@ impl TypeEntry {
16181622
quote! { ::std::vec::Vec<#item> }
16191623
}
16201624

1621-
TypeEntryDetails::Map(key_id, value_id) => {
1625+
TypeEntryDetails::Map {
1626+
map_to_use,
1627+
key_id,
1628+
value_id,
1629+
} => {
16221630
let key_ty = type_space
16231631
.id_to_entry
16241632
.get(key_id)
@@ -1635,7 +1643,10 @@ impl TypeEntry {
16351643
} else {
16361644
let key_ident = key_ty.type_ident(type_space, type_mod);
16371645
let value_ident = value_ty.type_ident(type_space, type_mod);
1638-
quote! { ::std::collections::HashMap<#key_ident, #value_ident> }
1646+
1647+
let map_to_use = syn::parse_str::<syn::TypePath>(map_to_use)
1648+
.expect("map type path wasn't valid");
1649+
quote! { #map_to_use<#key_ident, #value_ident> }
16391650
}
16401651
}
16411652

@@ -1746,7 +1757,7 @@ impl TypeEntry {
17461757
| TypeEntryDetails::Struct(_)
17471758
| TypeEntryDetails::Newtype(_)
17481759
| TypeEntryDetails::Vec(_)
1749-
| TypeEntryDetails::Map(..)
1760+
| TypeEntryDetails::Map { .. }
17501761
| TypeEntryDetails::Set(_)
17511762
| TypeEntryDetails::Box(_)
17521763
| TypeEntryDetails::Native(_)
@@ -1815,7 +1826,9 @@ impl TypeEntry {
18151826
TypeEntryDetails::Unit => "()".to_string(),
18161827
TypeEntryDetails::Option(type_id) => format!("option {}", type_id.0),
18171828
TypeEntryDetails::Vec(type_id) => format!("vec {}", type_id.0),
1818-
TypeEntryDetails::Map(key_id, value_id) => {
1829+
TypeEntryDetails::Map {
1830+
key_id, value_id, ..
1831+
} => {
18191832
format!("map {} {}", key_id.0, value_id.0)
18201833
}
18211834
TypeEntryDetails::Set(type_id) => format!("set {}", type_id.0),

typify-impl/src/value.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,9 @@ impl TypeEntry {
9595
.collect::<Option<Vec<_>>>()?;
9696
quote! { vec![#(#values),*] }
9797
}
98-
TypeEntryDetails::Map(key_id, value_id) => {
98+
TypeEntryDetails::Map {
99+
key_id, value_id, ..
100+
} => {
99101
let obj = value.as_object()?;
100102
let key_ty = type_space.id_to_entry.get(key_id).unwrap();
101103
let value_ty = type_space.id_to_entry.get(value_id).unwrap();
@@ -424,7 +426,7 @@ fn value_for_struct_props(
424426
match &type_entry.details {
425427
TypeEntryDetails::Struct(_)
426428
| TypeEntryDetails::Option(_)
427-
| TypeEntryDetails::Map(..) => (),
429+
| TypeEntryDetails::Map { .. } => (),
428430
_ => unreachable!(),
429431
}
430432

typify-test/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ edition = "2021"
77
regress = "0.10.1"
88
serde = "1.0.215"
99
serde_json = "1.0.133"
10+
indexmap = { version = "2.7.0", features = ["serde"]}
1011

1112
[build-dependencies]
1213
ipnetwork = { version = "0.20.0", features = ["schemars"] }
1314
prettyplease = "0.2.25"
1415
schemars = "0.8.21"
1516
serde = "1.0.215"
1617
syn = "2.0.90"
17-
typify = { path = "../typify" }
18+
typify = { path = "../typify"}

0 commit comments

Comments
 (0)