1use std::fmt::Write;
2
3use ordered_hash_map::OrderedHashMap;
4use serde::Serialize;
5use serde_json::Value;
6use thiserror::Error;
7
8pub type Result<T> = std::result::Result<T, Error>;
9
10#[derive(Debug, Error)]
11pub enum Error {
12 #[error("Failed to de/serialize value to json")]
13 JsonSerialization(#[from] serde_json::Error),
14 #[error("Failed to format args")]
15 Format(#[from] std::fmt::Error),
16 #[error("Came across triple nested array. Not supported.")]
17 TripleNestedArray,
18 #[error("Came across Value::Object after flatten_map. This shouldn't happen")]
19 ObjectReached,
20}
21
22#[derive(Clone, Copy)]
23pub struct Options<'a> {
24 pub tab: &'a str,
25 pub skip_empty_string: bool,
26 pub skip_empty_object: bool,
27 pub inline_array: bool,
28 pub max_inline_array_length: usize,
29}
30
31impl<'a> Default for Options<'a> {
32 fn default() -> Self {
33 Self {
34 tab: "\t",
35 skip_empty_string: false,
36 skip_empty_object: false,
37 inline_array: false,
38 max_inline_array_length: 50,
39 }
40 }
41}
42
43impl<'a> Options<'a> {
44 pub fn tab(mut self, tab: &'a str) -> Self {
46 self.tab = tab;
47 self
48 }
49
50 pub fn skip_empty_string(mut self, skip_empty_string: bool) -> Self {
52 self.skip_empty_string = skip_empty_string;
53 self
54 }
55
56 pub fn skip_empty_object(mut self, skip_empty_object: bool) -> Self {
58 self.skip_empty_object = skip_empty_object;
59 self
60 }
61
62 pub fn inline_array(mut self, inline_array: bool) -> Self {
64 self.inline_array = inline_array;
65 self
66 }
67
68 pub fn max_inline_array_length(mut self, max_inline_array_length: usize) -> Self {
69 self.max_inline_array_length = max_inline_array_length;
70 self
71 }
72}
73
74pub fn to_string<T: Serialize>(value: &T, options: Options<'_>) -> Result<String> {
75 let Options {
76 tab,
77 skip_empty_string,
78 skip_empty_object,
79 inline_array,
80 max_inline_array_length,
81 } = options;
82 let map = serde_json::from_str(&serde_json::to_string(value).map_err(Error::JsonSerialization)?)
83 .map_err(Error::JsonSerialization)?;
84 let mut res = String::new();
85 for (i, (key, val)) in flatten_map(map, skip_empty_object).into_iter().enumerate() {
86 match &val {
87 Value::Null => {}
88
89 Value::Bool(_) | Value::Number(_) => {
90 if i != 0 {
91 res.push('\n');
92 }
93 res
94 .write_fmt(format_args!("{key} = {val}"))
95 .map_err(Error::Format)?;
96 }
97
98 Value::String(val) => {
99 if skip_empty_string && val.is_empty() {
100 continue;
101 }
102 if i != 0 {
103 res.push('\n');
104 }
105 if val.contains('\n') {
106 res
107 .write_fmt(format_args!("{key} = \"\"\"\n{val}\"\"\""))
108 .map_err(Error::Format)?;
109 } else {
110 res
111 .write_fmt(format_args!("{key} = \"{}\"", val.replace('"', "\\\"")))
112 .map_err(Error::Format)?;
113 }
114 }
115
116 Value::Array(vals) => {
117 if vals.is_empty() {
118 if i != 0 {
119 res.push('\n');
120 }
121 res
122 .write_fmt(format_args!("{key} = []"))
123 .map_err(Error::Format)?;
124 continue;
125 }
126 let mut strs = Vec::<String>::with_capacity(vals.capacity());
127 for val in vals {
128 match val {
129 Value::Null => {}
130 Value::Bool(_) | Value::Number(_) => strs.push(val.to_string()),
131 Value::String(string) => {
132 if skip_empty_string && string.is_empty() {
133 continue;
134 }
135 strs.push(format!("\"{}\"", string.replace('"', "\\\"")))
136 }
137 Value::Object(map) => strs.push(to_array_object_string(&map, options)?),
138 Value::Array(vals) => {
139 let mut out = Vec::new();
140 for val in vals {
141 match val {
142 Value::Null => {}
143 Value::Bool(_) | Value::Number(_) => out.push(val.to_string()),
144 Value::String(string) => out.push(format!("\"{}\"", string.replace('"', "\\\""))),
145 Value::Object(map) => out.push(to_array_object_string(&map, options)?),
146 Value::Array(_) => return Err(Error::TripleNestedArray),
147 }
148 }
149 strs.push(format!("[{}]", out.join(", ")));
150 }
151 }
152 }
153 let total_length = strs.iter().fold(0, |total, curr| total + curr.len());
154 let inline_array = inline_array || total_length <= max_inline_array_length;
155 let join = if inline_array {
156 String::from(", ")
157 } else {
158 format!(",\n{tab}")
159 };
160 let val = strs.join(&join);
161 if i != 0 {
162 res.push('\n');
163 }
164 if inline_array {
165 res
166 .write_fmt(format_args!("{key} = [{val}]"))
167 .map_err(Error::Format)?;
168 } else {
169 res
170 .write_fmt(format_args!("{key} = [\n{tab}{val}\n]"))
171 .map_err(Error::Format)?;
172 }
173 }
174
175 Value::Object(obj) if !skip_empty_object && obj.is_empty() => {
177 if i != 0 {
178 res.push('\n');
179 }
180 res
182 .write_fmt(format_args!("{key} = {{}}"))
183 .map_err(Error::Format)?;
184 }
185
186 Value::Object(_) => return Err(Error::ObjectReached),
188 }
189 }
190 Ok(res)
191}
192
193fn flatten_map(
194 map: OrderedHashMap<String, Value>,
195 skip_empty_object: bool,
196) -> OrderedHashMap<String, Value> {
197 let mut target = OrderedHashMap::new();
198 flatten_map_rec(&mut target, None, map, skip_empty_object);
199 target
200}
201
202fn flatten_map_rec(
203 target: &mut OrderedHashMap<String, Value>,
204 parent_field: Option<String>,
205 source: OrderedHashMap<String, Value>,
206 skip_empty_object: bool,
207) {
208 if !skip_empty_object && source.is_empty() {
209 if let Some(parent_field) = &parent_field {
210 target.insert(
211 parent_field.to_string(),
212 Value::Object(serde_json::Map::new()),
213 );
214 return;
215 }
216 }
217 for (field, val) in source {
218 let parent_field = if let Some(parent_field) = &parent_field {
219 let mut parent_field = parent_field.clone();
220 parent_field.push('.');
221 parent_field.push_str(&field);
222 parent_field
223 } else {
224 field
225 };
226 if let Value::Object(source) = val {
227 flatten_map_rec(
228 target,
229 Some(parent_field),
230 source.into_iter().collect(),
231 skip_empty_object,
232 )
233 } else {
234 target.insert(parent_field, val);
235 }
236 }
237}
238
239fn to_array_object_string(
247 map: &serde_json::Map<String, Value>,
248 options: Options<'_>,
249) -> Result<String> {
250 let mut res = String::new();
251 for token in to_string(&map, options.inline_array(true))?
252 .split(" = ")
253 .map(str::trim)
254 {
255 match token.rsplit_once('\n') {
257 Some((val, next_key)) => {
258 res.push_str(" = ");
259 res.push_str(val.trim());
260 res.push_str(", ");
261 res.push_str(next_key.trim());
262 }
263 None => {
264 if res.is_empty() {
265 res.push_str(token);
267 } else {
268 res.push_str(" = ");
270 res.push_str(token);
271 }
272 }
273 }
274 }
275 Ok(format!("{{ {res} }}"))
276}