Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(indexmap): Add support for changing HashMaps to IndexMaps. #708

Merged
merged 9 commits into from
Dec 20, 2024
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,22 @@ 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>>`).

#### Alternate Map types

By default, Typify uses `std::collections::HashMap` as described above.

If you prefer to use `std::collections::BTreeMap` or a 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::BTreeMap` or `::indexmap::IndexMap`.

Note that for a custom map type to work you must have `T` defined to generate
a struct as described in [Objects](#objects). If `T` is not defined, typify
will generate code using a `serde_json::Map<String, serde_json::Value>` instead.

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

### OneOf

The `oneOf` construct maps to a Rust enum. Typify maps this to the various
Expand Down
33 changes: 33 additions & 0 deletions cargo-typify/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ pub struct CliArgs {
#[arg(long = "crate")]
crates: Vec<CrateSpec>,

/// Specify the map like type to use.
#[arg(long = "map-type")]
map_type: Option<String>,

/// Specify the policy unknown crates found in schemas with the
/// x-rust-type extension.
#[arg(
Expand Down Expand Up @@ -151,6 +155,10 @@ pub fn convert(args: &CliArgs) -> Result<String> {
settings.with_crate(name, version.clone(), rename.as_ref());
}

if let Some(map_type) = &args.map_type {
settings.with_map_type(map_type.clone());
}

if let Some(unknown_crates) = &args.unknown_crates {
let unknown_crates = match unknown_crates.as_str() {
"generate" => UnknownPolicy::Generate,
Expand Down Expand Up @@ -192,6 +200,7 @@ mod tests {
output: Some(PathBuf::from("-")),
no_builder: false,
crates: vec![],
map_type: None,
unknown_crates: Default::default(),
};

Expand All @@ -207,6 +216,7 @@ mod tests {
output: Some(PathBuf::from("some_file.rs")),
no_builder: false,
crates: vec![],
map_type: None,
unknown_crates: Default::default(),
};

Expand All @@ -222,12 +232,32 @@ mod tests {
output: None,
no_builder: false,
crates: vec![],
map_type: None,
unknown_crates: Default::default(),
};

assert_eq!(args.output_path(), Some(PathBuf::from("input.rs")));
}

#[test]
fn test_use_btree_map() {
let args = CliArgs {
input: PathBuf::from("input.json"),
builder: false,
additional_derives: vec![],
output: None,
no_builder: false,
crates: vec![],
map_type: Some("::std::collections::BTreeMap".to_string()),
unknown_crates: Default::default(),
};

assert_eq!(
args.map_type,
Some("::std::collections::BTreeMap".to_string())
);
}

#[test]
fn test_builder_as_default_style() {
let args = CliArgs {
Expand All @@ -237,6 +267,7 @@ mod tests {
output: None,
no_builder: false,
crates: vec![],
map_type: None,
unknown_crates: Default::default(),
};

Expand All @@ -252,6 +283,7 @@ mod tests {
output: None,
no_builder: true,
crates: vec![],
map_type: None,
unknown_crates: Default::default(),
};

Expand All @@ -267,6 +299,7 @@ mod tests {
output: None,
no_builder: false,
crates: vec![],
map_type: None,
unknown_crates: Default::default(),
};

Expand Down
26 changes: 26 additions & 0 deletions cargo-typify/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,29 @@ fn test_help() {
assert!(output.status.success());
assert_contents("tests/outputs/help.txt", &actual);
}

#[test]
fn test_btree_map() {
use assert_cmd::Command;

let input = concat!(env!("CARGO_MANIFEST_DIR"), "/../example.json");

let temp = TempDir::new("cargo-typify").unwrap();
let output_file = temp.path().join("output.rs");

let mut cmd = Command::cargo_bin("cargo-typify").unwrap();
cmd.args([
"typify",
input,
"--map-type",
"::std::collections::BTreeMap",
"--output",
output_file.to_str().unwrap(),
])
.assert()
.success();

let actual = std::fs::read_to_string(output_file).unwrap();

assert_contents("tests/outputs/custom_btree_map.rs", &actual);
}
19 changes: 12 additions & 7 deletions cargo-typify/tests/outputs/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,22 @@ pub mod error {
#[doc = r""]
#[doc = r" ```json"]
#[doc = "{"]
#[doc = " \"type\": \"object\""]
#[doc = " \"type\": \"object\","]
#[doc = " \"additionalProperties\": {"]
#[doc = " \"type\": \"string\""]
#[doc = " }"]
#[doc = "}"]
#[doc = r" ```"]
#[doc = r" </details>"]
#[derive(:: serde :: Deserialize, :: serde :: Serialize, Clone, Debug)]
pub struct Fruit(pub ::serde_json::Map<::std::string::String, ::serde_json::Value>);
pub struct Fruit(pub ::std::collections::HashMap<::std::string::String, ::std::string::String>);
impl ::std::ops::Deref for Fruit {
type Target = ::serde_json::Map<::std::string::String, ::serde_json::Value>;
fn deref(&self) -> &::serde_json::Map<::std::string::String, ::serde_json::Value> {
type Target = ::std::collections::HashMap<::std::string::String, ::std::string::String>;
fn deref(&self) -> &::std::collections::HashMap<::std::string::String, ::std::string::String> {
&self.0
}
}
impl From<Fruit> for ::serde_json::Map<::std::string::String, ::serde_json::Value> {
impl From<Fruit> for ::std::collections::HashMap<::std::string::String, ::std::string::String> {
fn from(value: Fruit) -> Self {
value.0
}
Expand All @@ -57,8 +60,10 @@ impl From<&Fruit> for Fruit {
value.clone()
}
}
impl From<::serde_json::Map<::std::string::String, ::serde_json::Value>> for Fruit {
fn from(value: ::serde_json::Map<::std::string::String, ::serde_json::Value>) -> Self {
impl From<::std::collections::HashMap<::std::string::String, ::std::string::String>> for Fruit {
fn from(
value: ::std::collections::HashMap<::std::string::String, ::std::string::String>,
) -> Self {
Self(value)
}
}
Expand Down
Loading
Loading