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