json_to_rust/
lib.rs

1use indexmap::IndexSet;
2use std::io::{BufReader, BufWriter, Read, Write};
3
4mod infer;
5mod util;
6pub use util::Wrapper;
7
8mod generate;
9use generate::{Print, Program};
10
11pub fn generate<R, W>(opts: Options, read: &mut R, write: &mut W) -> anyhow::Result<()>
12where
13    R: Read + ?Sized,
14    W: Write + ?Sized,
15{
16    let mut reader = BufReader::new(read);
17    let mut buf = String::new();
18    reader.read_to_string(&mut buf)?;
19
20    let program = Program::generate(json::parse(&buf)?, &buf, &opts);
21
22    let mut writer = BufWriter::new(write);
23    program.print(&mut writer, &opts)?;
24
25    Ok(())
26}
27
28#[derive(Debug)]
29pub struct Options {
30    pub json_name: Option<String>,
31    pub root_name: String,
32
33    pub make_unit_test: bool,
34    pub make_main: bool,
35
36    pub collapse_option_vec: bool,
37
38    pub tuple_max: Option<usize>,
39
40    pub default_derives: String,
41    pub field_naming: CasingScheme,
42    pub struct_naming: CasingScheme,
43
44    pub vec_wrapper: Wrapper,
45    pub map_wrapper: Wrapper,
46}
47
48#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash)]
49pub enum CasingScheme {
50    Snake,
51    Pascal,
52    Constant,
53    Camel,
54    Identity,
55}
56
57impl CasingScheme {
58    fn convert(self, input: &str) -> String {
59        use inflections::Inflect as _;
60        match self {
61            Self::Snake => input.to_snake_case(),
62            Self::Pascal => input.to_pascal_case(),
63            Self::Constant => input.to_constant_case(),
64            Self::Camel => input.to_camel_case(),
65            Self::Identity => input.to_string(),
66        }
67    }
68}
69
70pub fn no_derives() -> String {
71    custom::<&str, _>(std::iter::empty())
72}
73
74pub fn all_std_derives() -> String {
75    custom(&[
76        "Clone",
77        "Debug",
78        "PartialEq",
79        "PartialOrd",
80        "Eq",
81        "Ord",
82        "Hash",
83    ])
84}
85
86pub fn custom<S, L>(list: L) -> String
87where
88    S: ToString,
89    L: IntoIterator<Item = S>,
90{
91    const DEFAULT: [&str; 2] = ["Serialize", "Deserialize"];
92
93    list.into_iter()
94        .flat_map(|s| {
95            s.to_string()
96                .split(',') // split if the user provided a comma-separated list
97                .filter(|c| !c.starts_with(char::is_numeric)) // invalid trait name (TODO: make this more rigid)
98                .map(ToString::to_string)
99                .collect::<IndexSet<_>>() // de-dupe
100        })
101        .filter(|s| !DEFAULT.contains(&&**s)) // remove defaults if they are in the middle
102        .chain(DEFAULT.iter().map(ToString::to_string)) // append defaults to the end
103        .collect::<IndexSet<_>>() // de-dupe
104        .into_iter()
105        .fold(String::new(), |mut a, c| {
106            if !a.is_empty() {
107                a.push_str(", ");
108            }
109            a.push_str(&c);
110            a
111        })
112}