Skip to content

Commit

Permalink
Add support specifying the map type to use (#708)
Browse files Browse the repository at this point in the history
  • Loading branch information
JosiahBull authored Dec 20, 2024
1 parent 9065106 commit d2e1757
Show file tree
Hide file tree
Showing 18 changed files with 926 additions and 40 deletions.
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

0 comments on commit d2e1757

Please sign in to comment.