1use crate::escape::escape_string;
7use aws_smithy_types::date_time::{DateTimeFormatError, Format};
8use aws_smithy_types::primitive::Encoder;
9use aws_smithy_types::{DateTime, Document, Number};
10use std::borrow::Cow;
11
12pub struct JsonValueWriter<'a> {
13 output: &'a mut String,
14}
15
16impl<'a> JsonValueWriter<'a> {
17 pub fn new(output: &'a mut String) -> Self {
18 JsonValueWriter { output }
19 }
20
21 pub fn null(self) {
23 self.output.push_str("null");
24 }
25
26 pub fn boolean(self, value: bool) {
28 self.output.push_str(match value {
29 true => "true",
30 _ => "false",
31 });
32 }
33
34 pub fn document(self, value: &Document) {
36 match value {
37 Document::Array(values) => {
38 let mut array = self.start_array();
39 for value in values {
40 array.value().document(value);
41 }
42 array.finish();
43 }
44 Document::Bool(value) => self.boolean(*value),
45 Document::Null => self.null(),
46 Document::Number(value) => self.number(*value),
47 Document::Object(values) => {
48 let mut object = self.start_object();
49 for (key, value) in values {
50 object.key(key).document(value);
51 }
52 object.finish();
53 }
54 Document::String(value) => self.string(value),
55 }
56 }
57
58 pub fn string(self, value: &str) {
60 self.output.push('"');
61 self.output.push_str(&escape_string(value));
62 self.output.push('"');
63 }
64
65 pub fn string_unchecked(self, value: &str) {
67 debug_assert!(matches!(escape_string(value), Cow::Borrowed(_)));
69
70 self.output.push('"');
71 self.output.push_str(value);
72 self.output.push('"');
73 }
74
75 pub fn number(self, value: Number) {
77 match value {
78 Number::PosInt(value) => {
79 self.output.push_str(Encoder::from(value).encode());
81 }
82 Number::NegInt(value) => {
83 self.output.push_str(Encoder::from(value).encode());
84 }
85 Number::Float(value) => {
86 let mut encoder: Encoder = value.into();
87 if value.is_infinite() || value.is_nan() {
89 self.string_unchecked(encoder.encode())
90 } else {
91 self.output.push_str(encoder.encode())
92 }
93 }
94 }
95 }
96
97 pub fn date_time(
99 self,
100 date_time: &DateTime,
101 format: Format,
102 ) -> Result<(), DateTimeFormatError> {
103 let formatted = date_time.fmt(format)?;
104 match format {
105 Format::EpochSeconds => self.output.push_str(&formatted),
106 _ => self.string(&formatted),
107 }
108 Ok(())
109 }
110
111 pub fn start_array(self) -> JsonArrayWriter<'a> {
113 JsonArrayWriter::new(self.output)
114 }
115
116 pub fn start_object(self) -> JsonObjectWriter<'a> {
118 JsonObjectWriter::new(self.output)
119 }
120}
121
122pub struct JsonObjectWriter<'a> {
123 json: &'a mut String,
124 started: bool,
125}
126
127impl<'a> JsonObjectWriter<'a> {
128 pub fn new(output: &'a mut String) -> Self {
129 output.push('{');
130 Self {
131 json: output,
132 started: false,
133 }
134 }
135
136 pub fn key(&mut self, key: &str) -> JsonValueWriter<'_> {
138 if self.started {
139 self.json.push(',');
140 }
141 self.started = true;
142
143 self.json.push('"');
144 self.json.push_str(&escape_string(key));
145 self.json.push_str("\":");
146
147 JsonValueWriter::new(self.json)
148 }
149
150 pub fn finish(self) {
152 self.json.push('}');
153 }
154}
155
156pub struct JsonArrayWriter<'a> {
157 json: &'a mut String,
158 started: bool,
159}
160
161impl<'a> JsonArrayWriter<'a> {
162 pub fn new(output: &'a mut String) -> Self {
163 output.push('[');
164 Self {
165 json: output,
166 started: false,
167 }
168 }
169
170 pub fn value(&mut self) -> JsonValueWriter<'_> {
172 self.comma_delimit();
173 JsonValueWriter::new(self.json)
174 }
175
176 pub fn finish(self) {
178 self.json.push(']');
179 }
180
181 fn comma_delimit(&mut self) {
182 if self.started {
183 self.json.push(',');
184 }
185 self.started = true;
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use super::{JsonArrayWriter, JsonObjectWriter};
192 use crate::serialize::JsonValueWriter;
193 use aws_smithy_types::date_time::Format;
194 use aws_smithy_types::{DateTime, Document, Number};
195 use proptest::proptest;
196
197 #[test]
198 fn empty() {
199 let mut output = String::new();
200 JsonObjectWriter::new(&mut output).finish();
201 assert_eq!("{}", &output);
202
203 let mut output = String::new();
204 JsonArrayWriter::new(&mut output).finish();
205 assert_eq!("[]", &output);
206 }
207
208 #[test]
209 fn object_inside_array() {
210 let mut output = String::new();
211 let mut array = JsonArrayWriter::new(&mut output);
212 array.value().start_object().finish();
213 array.value().start_object().finish();
214 array.value().start_object().finish();
215 array.finish();
216 assert_eq!("[{},{},{}]", &output);
217 }
218
219 #[test]
220 fn object_inside_object() {
221 let mut output = String::new();
222 let mut obj_1 = JsonObjectWriter::new(&mut output);
223
224 let mut obj_2 = obj_1.key("nested").start_object();
225 obj_2.key("test").string("test");
226 obj_2.finish();
227
228 obj_1.finish();
229 assert_eq!(r#"{"nested":{"test":"test"}}"#, &output);
230 }
231
232 #[test]
233 fn array_inside_object() {
234 let mut output = String::new();
235 let mut object = JsonObjectWriter::new(&mut output);
236 object.key("foo").start_array().finish();
237 object.key("ba\nr").start_array().finish();
238 object.finish();
239 assert_eq!(r#"{"foo":[],"ba\nr":[]}"#, &output);
240 }
241
242 #[test]
243 fn array_inside_array() {
244 let mut output = String::new();
245
246 let mut arr_1 = JsonArrayWriter::new(&mut output);
247
248 let mut arr_2 = arr_1.value().start_array();
249 arr_2.value().number(Number::PosInt(5));
250 arr_2.finish();
251
252 arr_1.value().start_array().finish();
253 arr_1.finish();
254
255 assert_eq!("[[5],[]]", &output);
256 }
257
258 #[test]
259 fn object() {
260 let mut output = String::new();
261 let mut object = JsonObjectWriter::new(&mut output);
262 object.key("true_val").boolean(true);
263 object.key("false_val").boolean(false);
264 object.key("some_string").string("some\nstring\nvalue");
265 object.key("unchecked_str").string_unchecked("unchecked");
266 object.key("some_number").number(Number::Float(3.5));
267 object.key("some_null").null();
268
269 let mut array = object.key("some_mixed_array").start_array();
270 array.value().string("1");
271 array.value().number(Number::NegInt(-2));
272 array.value().string_unchecked("unchecked");
273 array.value().boolean(true);
274 array.value().boolean(false);
275 array.value().null();
276 array.finish();
277
278 object.finish();
279
280 assert_eq!(
281 r#"{"true_val":true,"false_val":false,"some_string":"some\nstring\nvalue","unchecked_str":"unchecked","some_number":3.5,"some_null":null,"some_mixed_array":["1",-2,"unchecked",true,false,null]}"#,
282 &output
283 );
284 }
285
286 #[test]
287 fn object_date_times() {
288 let mut output = String::new();
289
290 let mut object = JsonObjectWriter::new(&mut output);
291 object
292 .key("epoch_seconds")
293 .date_time(&DateTime::from_secs_f64(5.2), Format::EpochSeconds)
294 .unwrap();
295 object
296 .key("date_time")
297 .date_time(
298 &DateTime::from_str("2021-05-24T15:34:50.123Z", Format::DateTime).unwrap(),
299 Format::DateTime,
300 )
301 .unwrap();
302 object
303 .key("http_date")
304 .date_time(
305 &DateTime::from_str("Wed, 21 Oct 2015 07:28:00 GMT", Format::HttpDate).unwrap(),
306 Format::HttpDate,
307 )
308 .unwrap();
309 object.finish();
310
311 assert_eq!(
312 r#"{"epoch_seconds":5.2,"date_time":"2021-05-24T15:34:50.123Z","http_date":"Wed, 21 Oct 2015 07:28:00 GMT"}"#,
313 &output,
314 )
315 }
316
317 #[test]
318 fn array_date_times() {
319 let mut output = String::new();
320
321 let mut array = JsonArrayWriter::new(&mut output);
322 array
323 .value()
324 .date_time(&DateTime::from_secs_f64(5.2), Format::EpochSeconds)
325 .unwrap();
326 array
327 .value()
328 .date_time(
329 &DateTime::from_str("2021-05-24T15:34:50.123Z", Format::DateTime).unwrap(),
330 Format::DateTime,
331 )
332 .unwrap();
333 array
334 .value()
335 .date_time(
336 &DateTime::from_str("Wed, 21 Oct 2015 07:28:00 GMT", Format::HttpDate).unwrap(),
337 Format::HttpDate,
338 )
339 .unwrap();
340 array.finish();
341
342 assert_eq!(
343 r#"[5.2,"2021-05-24T15:34:50.123Z","Wed, 21 Oct 2015 07:28:00 GMT"]"#,
344 &output,
345 )
346 }
347
348 fn format_document(document: Document) -> String {
349 let mut output = String::new();
350 JsonValueWriter::new(&mut output).document(&document);
351 output
352 }
353
354 #[test]
355 fn document() {
356 assert_eq!("null", format_document(Document::Null));
357 assert_eq!("true", format_document(Document::Bool(true)));
358 assert_eq!("false", format_document(Document::Bool(false)));
359 assert_eq!("5", format_document(Document::Number(Number::PosInt(5))));
360 assert_eq!("\"test\"", format_document(Document::String("test".into())));
361 assert_eq!(
362 "[null,true,\"test\"]",
363 format_document(Document::Array(vec![
364 Document::Null,
365 Document::Bool(true),
366 Document::String("test".into())
367 ]))
368 );
369 assert_eq!(
370 r#"{"test":"foo"}"#,
371 format_document(Document::Object(
372 vec![("test".to_string(), Document::String("foo".into()))]
373 .into_iter()
374 .collect()
375 ))
376 );
377 assert_eq!(
378 r#"{"test1":[{"num":1},{"num":2}]}"#,
379 format_document(Document::Object(
380 vec![(
381 "test1".to_string(),
382 Document::Array(vec![
383 Document::Object(
384 vec![("num".to_string(), Document::Number(Number::PosInt(1))),]
385 .into_iter()
386 .collect()
387 ),
388 Document::Object(
389 vec![("num".to_string(), Document::Number(Number::PosInt(2))),]
390 .into_iter()
391 .collect()
392 ),
393 ])
394 ),]
395 .into_iter()
396 .collect()
397 ))
398 );
399 }
400
401 fn format_test_number(number: Number) -> String {
402 let mut formatted = String::new();
403 JsonValueWriter::new(&mut formatted).number(number);
404 formatted
405 }
406
407 #[test]
408 fn number_formatting() {
409 assert_eq!("1", format_test_number(Number::PosInt(1)));
410 assert_eq!("-1", format_test_number(Number::NegInt(-1)));
411 assert_eq!("1", format_test_number(Number::NegInt(1)));
412 assert_eq!("0.0", format_test_number(Number::Float(0.0)));
413 assert_eq!("10000000000.0", format_test_number(Number::Float(1e10)));
414 assert_eq!("-1.2", format_test_number(Number::Float(-1.2)));
415
416 assert_eq!("\"NaN\"", format_test_number(Number::Float(f64::NAN)));
419 assert_eq!(
420 "\"Infinity\"",
421 format_test_number(Number::Float(f64::INFINITY))
422 );
423 assert_eq!(
424 "\"-Infinity\"",
425 format_test_number(Number::Float(f64::NEG_INFINITY))
426 );
427 }
428
429 proptest! {
430 #[test]
431 fn matches_serde_json_pos_int_format(value: u64) {
432 assert_eq!(
433 serde_json::to_string(&value).unwrap(),
434 format_test_number(Number::PosInt(value)),
435 )
436 }
437
438 #[test]
439 fn matches_serde_json_neg_int_format(value: i64) {
440 assert_eq!(
441 serde_json::to_string(&value).unwrap(),
442 format_test_number(Number::NegInt(value)),
443 )
444 }
445
446 #[test]
447 fn matches_serde_json_float_format(value: f64) {
448 assert_eq!(
449 serde_json::to_string(&value).unwrap(),
450 format_test_number(Number::Float(value)),
451 )
452 }
453 }
454}