1use aws_smithy_schema::serde::{SerdeError, SerializableStruct, ShapeSerializer};
9use aws_smithy_schema::Schema;
10use aws_smithy_types::date_time::Format as TimestampFormat;
11use aws_smithy_types::primitive::Encoder;
12use aws_smithy_types::{BigDecimal, BigInteger, DateTime, DiscriminatedDocument, Document};
13
14use crate::codec::JsonCodecSettings;
15
16use std::sync::Arc;
17
18fn is_ascii_digits(s: &str) -> bool {
20 !s.is_empty() && s.bytes().all(|b| b.is_ascii_digit())
21}
22
23fn is_json_number(text: &str) -> bool {
43 let rest = text.strip_prefix('-').unwrap_or(text);
44
45 let (mantissa, exponent) = match rest.split_once(['e', 'E']) {
46 Some((mantissa, exponent)) => (mantissa, Some(exponent)),
47 None => (rest, None),
48 };
49
50 let (int_part, frac_part) = match mantissa.split_once('.') {
51 Some((int_part, frac_part)) => (int_part, Some(frac_part)),
52 None => (mantissa, None),
53 };
54
55 let int_ok = match int_part.as_bytes() {
57 [b'0'] => true,
58 [b'1'..=b'9', tail @ ..] => tail.iter().all(u8::is_ascii_digit),
59 _ => false,
60 };
61
62 let frac_ok = match frac_part {
64 None => true,
65 Some(frac) => is_ascii_digits(frac),
66 };
67
68 let exponent_ok = match exponent {
70 None => true,
71 Some(exponent) => is_ascii_digits(exponent.strip_prefix(['+', '-']).unwrap_or(exponent)),
72 };
73
74 int_ok && frac_ok && exponent_ok
75}
76
77fn to_json_number(text: &str) -> Option<String> {
83 let (coefficient, exponent) = match text.split_once(['e', 'E']) {
84 Some((coefficient, exponent)) => (coefficient, Some(exponent)),
85 None => (text, None),
86 };
87 let (negative, unsigned) = match coefficient.strip_prefix('-') {
88 Some(rest) => (true, rest),
89 None => (false, coefficient.strip_prefix('+').unwrap_or(coefficient)),
90 };
91 let (integer, fraction) = unsigned.split_once('.').unwrap_or((unsigned, ""));
92 if (integer.is_empty() && fraction.is_empty())
93 || !integer
94 .bytes()
95 .chain(fraction.bytes())
96 .all(|b| b.is_ascii_digit())
97 {
98 return None;
99 }
100 let integer = integer.trim_start_matches('0');
101 let mut out = String::with_capacity(text.len());
102 if negative {
103 out.push('-');
104 }
105 out.push_str(if integer.is_empty() { "0" } else { integer });
106 if !fraction.is_empty() {
107 out.push('.');
108 out.push_str(fraction);
109 }
110 if let Some(exponent) = exponent {
111 out.push('e');
112 out.push_str(exponent);
113 }
114 is_json_number(&out).then_some(out)
115}
116
117pub struct JsonSerializer {
119 output: String,
120 settings: Arc<JsonCodecSettings>,
121 needs_comma: bool,
123 expecting_map_key: bool,
125 map_depth: usize,
128 in_struct_context: bool,
135}
136
137impl JsonSerializer {
138 pub(crate) fn new(settings: Arc<JsonCodecSettings>) -> Self {
140 Self {
141 output: String::new(),
142 settings,
143 needs_comma: false,
144 expecting_map_key: false,
145 map_depth: 0,
146 in_struct_context: true,
153 }
154 }
155
156 pub fn finish(self) -> Vec<u8> {
158 self.output.into_bytes()
159 }
160
161 fn prefix(&mut self, schema: &Schema<'_>) {
165 if self.needs_comma {
166 self.output.push(',');
167 }
168 if self.in_struct_context {
174 if let Some(key) = self.settings.member_key(schema) {
175 self.output.push_str(key);
176 }
177 }
178 self.needs_comma = true;
179 if self.map_depth > 0 {
183 self.expecting_map_key = true;
184 }
185 }
186
187 fn get_timestamp_format(&self, schema: &Schema<'_>) -> Result<TimestampFormat, SerdeError> {
189 if let Some(ts_trait) = schema.timestamp_format() {
190 return Ok(match ts_trait.format() {
191 aws_smithy_schema::traits::TimestampFormat::EpochSeconds => {
192 TimestampFormat::EpochSeconds
193 }
194 aws_smithy_schema::traits::TimestampFormat::HttpDate => TimestampFormat::HttpDate,
195 aws_smithy_schema::traits::TimestampFormat::DateTime => TimestampFormat::DateTime,
196 other => {
197 return Err(SerdeError::unsupported(format!(
198 "unsupported timestamp format {other:?}"
199 )))
200 }
201 });
202 }
203 Ok(self.settings.default_timestamp_format)
204 }
205
206 fn write_arbitrary_precision(
236 &mut self,
237 schema: &Schema<'_>,
238 text: &str,
239 ) -> Result<(), SerdeError> {
240 if self.settings.use_string_for_arbitrary_precision() {
241 self.prefix(schema);
242 crate::serialize::JsonValueWriter::new(&mut self.output).string(text);
243 return Ok(());
244 }
245 let normalized;
246 let text = if is_json_number(text) {
247 text
248 } else {
249 normalized = self
250 .settings
251 .normalize_arbitrary_precision()
252 .then(|| to_json_number(text))
253 .flatten()
254 .ok_or_else(|| {
255 SerdeError::invalid_input(format!(
256 "arbitrary-precision value {text:?} is not a JSON number, so it cannot be \
257 emitted as a raw JSON number; enable \
258 `JsonCodecSettings::use_string_for_arbitrary_precision` to send it as a \
259 JSON string, or `normalize_arbitrary_precision` to rewrite it"
260 ))
261 })?;
262 &normalized
263 };
264 self.prefix(schema);
265 self.output.push_str(text);
266 Ok(())
267 }
268
269 fn write_json_value(&mut self, doc: &Document) -> Result<(), SerdeError> {
270 use crate::escape::escape_string;
271 use crate::serialize::JsonValueWriter;
272
273 match doc {
295 Document::Null => self.output.push_str("null"),
296 Document::Bool(b) => self.output.push_str(if *b { "true" } else { "false" }),
297 Document::Number(n) => {
298 let writer = JsonValueWriter::new(&mut self.output);
299 writer.number(*n);
300 }
301 Document::String(s) => {
302 self.output.push('"');
303 self.output.push_str(&escape_string(s));
304 self.output.push('"');
305 }
306 Document::Array(items) => {
307 self.output.push('[');
308 let mut first = true;
309 for item in items {
310 if !first {
311 self.output.push(',');
312 }
313 first = false;
314 self.write_json_value(item)?;
315 }
316 self.output.push(']');
317 }
318 Document::Object(entries) => {
319 self.output.push('{');
320 let mut first = true;
321 for (key, value) in entries {
322 if !first {
323 self.output.push(',');
324 }
325 first = false;
326 self.output.push('"');
327 self.output.push_str(&escape_string(key));
328 self.output.push_str("\":");
329 self.write_json_value(value)?;
330 }
331 self.output.push('}');
332 } }
338 Ok(())
339 }
340
341 pub fn write_discriminated_document(
352 &mut self,
353 schema: &Schema<'_>,
354 value: &DiscriminatedDocument,
355 ) -> Result<(), SerdeError> {
356 use crate::escape::escape_string;
357
358 self.prefix(schema);
359 match (value.discriminator(), value.document()) {
360 (Some(id), Document::Object(entries)) => {
361 debug_assert!(
369 id.contains('#'),
370 "discriminator `{id}` must be an absolute shape id (namespace#name) when serialized"
371 );
372 self.output.push('{');
373 self.output.push_str("\"__type\":\"");
374 self.output.push_str(&escape_string(id));
375 self.output.push('"');
376 for (key, val) in entries {
377 self.output.push(',');
378 self.output.push('"');
379 self.output.push_str(&escape_string(key));
380 self.output.push_str("\":");
381 self.write_json_value(val)?;
382 }
383 self.output.push('}');
384 Ok(())
385 }
386 (_, doc) => self.write_json_value(doc),
389 }
390 }
391}
392
393impl aws_smithy_schema::codec::FinishSerializer for JsonSerializer {
394 fn finish(self) -> Vec<u8> {
395 self.output.into_bytes()
396 }
397}
398
399impl ShapeSerializer for JsonSerializer {
400 fn write_struct(
401 &mut self,
402 schema: &Schema<'_>,
403 value: &dyn SerializableStruct,
404 ) -> Result<(), SerdeError> {
405 self.prefix(schema);
406 self.output.push('{');
407 let saved_comma = self.needs_comma;
408 let saved_depth = self.map_depth;
409 let saved_map_key = self.expecting_map_key;
410 let saved_struct_context = self.in_struct_context;
411 self.needs_comma = false;
412 self.map_depth = 0;
415 self.expecting_map_key = false;
416 self.in_struct_context = true;
418 value.serialize_members(self)?;
419 self.output.push('}');
420 self.needs_comma = saved_comma;
421 self.map_depth = saved_depth;
422 self.expecting_map_key = saved_map_key;
423 self.in_struct_context = saved_struct_context;
424 Ok(())
425 }
426
427 fn write_list(
428 &mut self,
429 schema: &Schema<'_>,
430 write_elements: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
431 ) -> Result<(), SerdeError> {
432 self.prefix(schema);
433 self.output.push('[');
434 let saved = self.needs_comma;
435 let saved_depth = self.map_depth;
436 let saved_map_key = self.expecting_map_key;
437 let saved_struct_context = self.in_struct_context;
438 self.needs_comma = false;
439 self.map_depth = 0;
441 self.expecting_map_key = false;
442 self.in_struct_context = false;
445 write_elements(self)?;
446 self.output.push(']');
447 self.needs_comma = saved;
448 self.map_depth = saved_depth;
449 self.expecting_map_key = saved_map_key;
450 self.in_struct_context = saved_struct_context;
451 Ok(())
452 }
453
454 fn write_map(
455 &mut self,
456 schema: &Schema<'_>,
457 write_entries: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
458 ) -> Result<(), SerdeError> {
459 self.prefix(schema);
460 self.output.push('{');
461 let saved_comma = self.needs_comma;
462 let saved_map_key = self.expecting_map_key;
463 let saved_depth = self.map_depth;
464 let saved_struct_context = self.in_struct_context;
465 self.needs_comma = false;
466 self.expecting_map_key = true;
467 self.map_depth += 1;
471 self.in_struct_context = false;
475 write_entries(self)?;
476 self.map_depth = saved_depth;
477 self.output.push('}');
478 self.needs_comma = saved_comma;
479 self.expecting_map_key = saved_map_key;
480 self.in_struct_context = saved_struct_context;
481 Ok(())
482 }
483
484 fn write_boolean(&mut self, schema: &Schema<'_>, value: bool) -> Result<(), SerdeError> {
485 self.prefix(schema);
486 self.output.push_str(if value { "true" } else { "false" });
487 Ok(())
488 }
489
490 fn write_byte(&mut self, schema: &Schema<'_>, value: i8) -> Result<(), SerdeError> {
491 use std::fmt::Write;
492 self.prefix(schema);
493 write!(&mut self.output, "{}", value).map_err(|e| SerdeError::write_failed(e.to_string()))
494 }
495
496 fn write_short(&mut self, schema: &Schema<'_>, value: i16) -> Result<(), SerdeError> {
497 use std::fmt::Write;
498 self.prefix(schema);
499 write!(&mut self.output, "{}", value).map_err(|e| SerdeError::write_failed(e.to_string()))
500 }
501
502 fn write_integer(&mut self, schema: &Schema<'_>, value: i32) -> Result<(), SerdeError> {
503 use std::fmt::Write;
504 self.prefix(schema);
505 write!(&mut self.output, "{}", value).map_err(|e| SerdeError::write_failed(e.to_string()))
506 }
507
508 fn write_long(&mut self, schema: &Schema<'_>, value: i64) -> Result<(), SerdeError> {
509 use std::fmt::Write;
510 self.prefix(schema);
511 write!(&mut self.output, "{}", value).map_err(|e| SerdeError::write_failed(e.to_string()))
512 }
513
514 fn write_float(&mut self, schema: &Schema<'_>, value: f32) -> Result<(), SerdeError> {
515 self.prefix(schema);
516 let mut encoder = Encoder::from(value);
517 if value.is_finite() {
518 self.output.push_str(encoder.encode());
519 } else {
520 self.output.push('"');
521 self.output.push_str(encoder.encode());
522 self.output.push('"');
523 }
524 Ok(())
525 }
526
527 fn write_double(&mut self, schema: &Schema<'_>, value: f64) -> Result<(), SerdeError> {
528 self.prefix(schema);
529 let mut encoder = Encoder::from(value);
530 if value.is_finite() {
531 self.output.push_str(encoder.encode());
532 } else {
533 self.output.push('"');
534 self.output.push_str(encoder.encode());
535 self.output.push('"');
536 }
537 Ok(())
538 }
539
540 fn write_big_integer(
541 &mut self,
542 schema: &Schema<'_>,
543 value: &BigInteger,
544 ) -> Result<(), SerdeError> {
545 self.write_arbitrary_precision(schema, value.as_ref())
546 }
547
548 fn write_big_decimal(
549 &mut self,
550 schema: &Schema<'_>,
551 value: &BigDecimal,
552 ) -> Result<(), SerdeError> {
553 self.write_arbitrary_precision(schema, value.as_ref())
554 }
555
556 fn write_string(&mut self, schema: &Schema<'_>, value: &str) -> Result<(), SerdeError> {
557 use crate::escape::escape_string;
558 if self.expecting_map_key {
559 if self.needs_comma {
561 self.output.push(',');
562 }
563 self.output.push('"');
564 self.output.push_str(&escape_string(value));
565 self.output.push_str("\":");
566 self.needs_comma = false;
568 self.expecting_map_key = false;
569 } else {
570 self.prefix(schema);
571 self.output.push('"');
572 self.output.push_str(&escape_string(value));
573 self.output.push('"');
574 }
575 Ok(())
576 }
577
578 fn write_blob(
579 &mut self,
580 schema: &Schema<'_>,
581 value: aws_smithy_types::Blob,
582 ) -> Result<(), SerdeError> {
583 use aws_smithy_types::base64;
584 self.prefix(schema);
585 let encoded = base64::encode(value.as_ref());
586 self.output.push('"');
587 self.output.push_str(&encoded);
588 self.output.push('"');
589 Ok(())
590 }
591
592 fn write_timestamp(&mut self, schema: &Schema<'_>, value: &DateTime) -> Result<(), SerdeError> {
593 self.prefix(schema);
594 let format = self.get_timestamp_format(schema)?;
595 let formatted = value
596 .fmt(format)
597 .map_err(|e| SerdeError::write_failed(format!("failed to format timestamp: {e}")))?;
598
599 match format {
600 TimestampFormat::EpochSeconds => {
601 self.output.push_str(&formatted);
603 }
604 _ => {
605 self.output.push('"');
607 self.output.push_str(&formatted);
608 self.output.push('"');
609 }
610 }
611 Ok(())
612 }
613
614 fn write_document(&mut self, schema: &Schema<'_>, value: &Document) -> Result<(), SerdeError> {
615 self.prefix(schema);
616 self.write_json_value(value)?;
617 Ok(())
618 }
619
620 fn write_null(&mut self, schema: &Schema<'_>) -> Result<(), SerdeError> {
621 self.prefix(schema);
622 self.output.push_str("null");
623 Ok(())
624 }
625}
626
627#[cfg(test)]
628mod tests {
629 use super::*;
630 use aws_smithy_schema::prelude::*;
631 use aws_smithy_schema::ShapeType;
632
633 #[test]
634 fn test_write_boolean() {
635 let mut ser = JsonSerializer::new(Arc::new(JsonCodecSettings::default()));
636 ser.write_boolean(&BOOLEAN, true).unwrap();
637 let output = ser.finish();
638 assert_eq!(String::from_utf8(output).unwrap(), "true");
639 }
640
641 #[test]
642 fn test_write_string() {
643 let mut ser = JsonSerializer::new(Arc::new(JsonCodecSettings::default()));
644 ser.write_string(&STRING, "hello").unwrap();
645 let output = ser.finish();
646 assert_eq!(String::from_utf8(output).unwrap(), "\"hello\"");
647 }
648
649 #[test]
650 fn test_write_integer() {
651 let mut ser = JsonSerializer::new(Arc::new(JsonCodecSettings::default()));
652 ser.write_integer(&INTEGER, 42).unwrap();
653 let output = ser.finish();
654 assert_eq!(String::from_utf8(output).unwrap(), "42");
655 }
656
657 #[test]
658 fn floats_preserve_decimal_points_and_original_precision() {
659 let list = Schema::new(
660 aws_smithy_schema::shape_id!("test", "Floats"),
661 ShapeType::List,
662 );
663 let mut ser = JsonSerializer::new(Arc::new(JsonCodecSettings::default()));
664 ser.write_list(&list, &|ser| {
665 ser.write_float(&FLOAT, 1.0)?;
666 ser.write_double(&DOUBLE, 1.0)?;
667 ser.write_float(&FLOAT, 0.0)?;
668 ser.write_double(&DOUBLE, 0.0)?;
669 ser.write_float(&FLOAT, -0.0)?;
670 ser.write_double(&DOUBLE, -0.0)?;
671 ser.write_float(&FLOAT, 1.01)?;
672 ser.write_double(&DOUBLE, 1.01)?;
673 ser.write_float(&FLOAT, 3.15)?;
674 ser.write_double(&DOUBLE, 3.15)?;
675 ser.write_float(&FLOAT, f32::INFINITY)?;
676 ser.write_float(&FLOAT, f32::NEG_INFINITY)?;
677 ser.write_float(&FLOAT, f32::NAN)?;
678 ser.write_double(&DOUBLE, f64::INFINITY)?;
679 ser.write_double(&DOUBLE, f64::NEG_INFINITY)?;
680 ser.write_double(&DOUBLE, f64::NAN)?;
681 Ok(())
682 })
683 .unwrap();
684 let output = String::from_utf8(ser.finish()).unwrap();
685 assert_eq!(
686 output,
687 r#"[1.0,1.0,0.0,0.0,-0.0,-0.0,1.01,1.01,3.15,3.15,"Infinity","-Infinity","NaN","Infinity","-Infinity","NaN"]"#
688 );
689 }
690
691 #[test]
692 fn test_write_null() {
693 let mut ser = JsonSerializer::new(Arc::new(JsonCodecSettings::default()));
694 ser.write_null(&STRING).unwrap();
695 let output = ser.finish();
696 assert_eq!(String::from_utf8(output).unwrap(), "null");
697 }
698
699 #[test]
700 fn write_discriminated_document_emits_absolute_type() {
701 let mut obj = std::collections::HashMap::new();
702 obj.insert(
703 "greeting".to_string(),
704 Document::String("hello".to_string()),
705 );
706 let dd = DiscriminatedDocument::new(Document::Object(obj))
707 .with_discriminator("com.example#Greeting");
708 let schema = aws_smithy_schema::Schema::new(
709 aws_smithy_schema::shape_id!("com.example", "Greeting"),
710 ShapeType::Structure,
711 );
712 let mut ser = JsonSerializer::new(Arc::new(JsonCodecSettings::default()));
713 ser.write_discriminated_document(&schema, &dd).unwrap();
714 let out = String::from_utf8(ser.finish()).unwrap();
715 assert_eq!(
716 out,
717 r#"{"__type":"com.example#Greeting","greeting":"hello"}"#
718 );
719 }
720
721 #[cfg(debug_assertions)]
726 #[test]
727 #[should_panic(expected = "absolute shape id")]
728 fn write_discriminated_document_rejects_relative_discriminator() {
729 let mut obj = std::collections::HashMap::new();
730 obj.insert(
731 "greeting".to_string(),
732 Document::String("hello".to_string()),
733 );
734 let mut dd = DiscriminatedDocument::new(Document::Object(obj));
735 dd.set_discriminator_unchecked("Greeting");
736 let schema = aws_smithy_schema::Schema::new(
737 aws_smithy_schema::shape_id!("com.example", "Greeting"),
738 ShapeType::Structure,
739 );
740 let mut ser = JsonSerializer::new(Arc::new(JsonCodecSettings::default()));
741 let _ = ser.write_discriminated_document(&schema, &dd);
742 }
743
744 #[test]
745 fn test_write_list() {
746 let mut ser = JsonSerializer::new(Arc::new(JsonCodecSettings::default()));
747 let list_schema = aws_smithy_schema::Schema::new(
748 aws_smithy_schema::shape_id!("test", "List"),
749 aws_smithy_schema::ShapeType::List,
750 );
751 ser.write_list(&list_schema, &|s: &mut dyn ShapeSerializer| {
752 s.write_integer(&INTEGER, 1)?;
753 s.write_integer(&INTEGER, 2)?;
754 s.write_integer(&INTEGER, 3)?;
755 Ok(())
756 })
757 .unwrap();
758 let output = String::from_utf8(ser.finish()).unwrap();
759 assert_eq!(output, "[1,2,3]");
760 }
761
762 #[test]
763 fn test_write_full_object() {
764 use aws_smithy_schema::serde::SerializableStruct;
765
766 static ACTIVE_MEMBER: Schema<'static> = Schema::new_member(
767 aws_smithy_schema::shape_id!("test", "Struct"),
768 aws_smithy_schema::ShapeType::Boolean,
769 "active",
770 0,
771 );
772 static NAME_MEMBER: Schema<'static> = Schema::new_member(
773 aws_smithy_schema::shape_id!("test", "Struct"),
774 aws_smithy_schema::ShapeType::String,
775 "name",
776 1,
777 );
778 static COUNT_MEMBER: Schema<'static> = Schema::new_member(
779 aws_smithy_schema::shape_id!("test", "Struct"),
780 aws_smithy_schema::ShapeType::Integer,
781 "count",
782 2,
783 );
784 static PRICE_MEMBER: Schema<'static> = Schema::new_member(
785 aws_smithy_schema::shape_id!("test", "Struct"),
786 aws_smithy_schema::ShapeType::Float,
787 "price",
788 3,
789 );
790 static ITEMS_MEMBER: Schema<'static> = Schema::new_member(
791 aws_smithy_schema::shape_id!("test", "Struct"),
792 aws_smithy_schema::ShapeType::List,
793 "items",
794 4,
795 );
796
797 struct TestObject;
798 impl SerializableStruct for TestObject {
799 fn schema(&self) -> &Schema<'_> {
800 static SCHEMA: Schema<'static> = Schema::new_struct(
801 aws_smithy_schema::shape_id!("test", "Struct"),
802 aws_smithy_schema::ShapeType::Structure,
803 &[
804 &ACTIVE_MEMBER,
805 &NAME_MEMBER,
806 &COUNT_MEMBER,
807 &PRICE_MEMBER,
808 &ITEMS_MEMBER,
809 ],
810 );
811 &SCHEMA
812 }
813
814 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
815 s.write_boolean(&ACTIVE_MEMBER, true)?;
816 s.write_string(&NAME_MEMBER, "test")?;
817 s.write_integer(&COUNT_MEMBER, 42)?;
818 s.write_float(&PRICE_MEMBER, 3.15)?;
819 s.write_list(&ITEMS_MEMBER, &|s| {
820 s.write_integer(&INTEGER, 1)?;
821 s.write_integer(&INTEGER, 2)?;
822 Ok(())
823 })?;
824 Ok(())
825 }
826 }
827
828 let struct_schema = aws_smithy_schema::Schema::new(
829 aws_smithy_schema::shape_id!("test", "Struct"),
830 aws_smithy_schema::ShapeType::Structure,
831 );
832 let mut ser = JsonSerializer::new(Arc::new(JsonCodecSettings::default()));
833 ser.write_struct(&struct_schema, &TestObject).unwrap();
834 let output = String::from_utf8(ser.finish()).unwrap();
835 assert_eq!(
836 output,
837 r#"{"active":true,"name":"test","count":42,"price":3.15,"items":[1,2]}"#
838 );
839 }
840
841 #[test]
842 fn test_nested_complex_serialization() {
843 use aws_smithy_schema::serde::SerializableStruct;
844
845 static ID: Schema<'static> = Schema::new_member(
847 aws_smithy_schema::shape_id!("test", "User"),
848 aws_smithy_schema::ShapeType::Long,
849 "id",
850 0,
851 );
852 static NAME: Schema<'static> = Schema::new_member(
853 aws_smithy_schema::shape_id!("test", "User"),
854 aws_smithy_schema::ShapeType::String,
855 "name",
856 1,
857 );
858 static SCORES: Schema<'static> = Schema::new_member(
859 aws_smithy_schema::shape_id!("test", "User"),
860 aws_smithy_schema::ShapeType::List,
861 "scores",
862 2,
863 );
864 static ADDRESS: Schema<'static> = Schema::new_member(
865 aws_smithy_schema::shape_id!("test", "User"),
866 aws_smithy_schema::ShapeType::Structure,
867 "address",
868 3,
869 );
870 static COMPANIES: Schema<'static> = Schema::new_member(
871 aws_smithy_schema::shape_id!("test", "User"),
872 aws_smithy_schema::ShapeType::List,
873 "companies",
874 4,
875 );
876 static TAGS: Schema<'static> = Schema::new_member(
877 aws_smithy_schema::shape_id!("test", "User"),
878 aws_smithy_schema::ShapeType::Map,
879 "tags",
880 5,
881 );
882 static STREET: Schema<'static> = Schema::new_member(
883 aws_smithy_schema::shape_id!("test", "Address"),
884 aws_smithy_schema::ShapeType::String,
885 "street",
886 0,
887 );
888 static CITY: Schema<'static> = Schema::new_member(
889 aws_smithy_schema::shape_id!("test", "Address"),
890 aws_smithy_schema::ShapeType::String,
891 "city",
892 1,
893 );
894 static ZIP: Schema<'static> = Schema::new_member(
895 aws_smithy_schema::shape_id!("test", "Address"),
896 aws_smithy_schema::ShapeType::Integer,
897 "zip",
898 2,
899 );
900 static COMP_NAME: Schema<'static> = Schema::new_member(
901 aws_smithy_schema::shape_id!("test", "Company"),
902 aws_smithy_schema::ShapeType::String,
903 "name",
904 0,
905 );
906 static EMPLOYEES: Schema<'static> = Schema::new_member(
907 aws_smithy_schema::shape_id!("test", "Company"),
908 aws_smithy_schema::ShapeType::List,
909 "employees",
910 1,
911 );
912 static METADATA: Schema<'static> = Schema::new_member(
913 aws_smithy_schema::shape_id!("test", "Company"),
914 aws_smithy_schema::ShapeType::Map,
915 "metadata",
916 2,
917 );
918 static ACTIVE: Schema<'static> = Schema::new_member(
919 aws_smithy_schema::shape_id!("test", "Company"),
920 aws_smithy_schema::ShapeType::Boolean,
921 "active",
922 3,
923 );
924
925 struct AddressStruct;
926 impl SerializableStruct for AddressStruct {
927 fn schema(&self) -> &Schema<'_> {
928 static SCHEMA: Schema<'static> = Schema::new_struct(
929 aws_smithy_schema::shape_id!("test", "Address"),
930 aws_smithy_schema::ShapeType::Structure,
931 &[&STREET, &CITY, &ZIP],
932 );
933 &SCHEMA
934 }
935
936 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
937 s.write_string(&STREET, "123 Main St")?;
938 s.write_string(&CITY, "Seattle")?;
939 s.write_integer(&ZIP, 98101)?;
940 Ok(())
941 }
942 }
943
944 struct CompanyStruct {
945 name: &'static str,
946 employees: &'static [&'static str],
947 metadata: &'static [(&'static str, i32)],
948 active: bool,
949 }
950 impl SerializableStruct for CompanyStruct {
951 fn schema(&self) -> &Schema<'_> {
952 static SCHEMA: Schema<'static> = Schema::new_struct(
953 aws_smithy_schema::shape_id!("test", "Company"),
954 aws_smithy_schema::ShapeType::Structure,
955 &[&COMP_NAME, &EMPLOYEES, &METADATA, &ACTIVE],
956 );
957 &SCHEMA
958 }
959
960 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
961 s.write_string(&COMP_NAME, self.name)?;
962 s.write_list(&EMPLOYEES, &|s| {
963 for e in self.employees {
964 s.write_string(&STRING, e)?;
965 }
966 Ok(())
967 })?;
968 s.write_map(&METADATA, &|s| {
969 for (k, v) in self.metadata {
970 s.write_string(&::aws_smithy_schema::prelude::STRING, k)?;
971 s.write_integer(&::aws_smithy_schema::prelude::INTEGER, *v)?;
972 }
973 Ok(())
974 })?;
975 s.write_boolean(&ACTIVE, self.active)?;
976 Ok(())
977 }
978 }
979
980 struct UserStruct;
981 impl SerializableStruct for UserStruct {
982 fn schema(&self) -> &Schema<'_> {
983 static SCHEMA: Schema<'static> = Schema::new_struct(
984 aws_smithy_schema::shape_id!("test", "User"),
985 aws_smithy_schema::ShapeType::Structure,
986 &[&ID, &NAME, &SCORES, &ADDRESS, &COMPANIES, &TAGS],
987 );
988 &SCHEMA
989 }
990
991 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
992 s.write_long(&ID, 12345)?;
993 s.write_string(&NAME, "John Doe")?;
994 s.write_list(&SCORES, &|s| {
995 s.write_double(&DOUBLE, 95.5)?;
996 s.write_double(&DOUBLE, 87.3)?;
997 s.write_double(&DOUBLE, 92.1)?;
998 Ok(())
999 })?;
1000 s.write_struct(&ADDRESS, &AddressStruct)?;
1001 s.write_list(&COMPANIES, &|s| {
1002 let struct_schema = Schema::new(
1003 aws_smithy_schema::shape_id!("test", "Company"),
1004 aws_smithy_schema::ShapeType::Structure,
1005 );
1006 s.write_struct(
1007 &struct_schema,
1008 &CompanyStruct {
1009 name: "TechCorp",
1010 employees: &["Alice", "Bob"],
1011 metadata: &[("founded", 2010), ("size", 500)],
1012 active: true,
1013 },
1014 )?;
1015 s.write_struct(
1016 &struct_schema,
1017 &CompanyStruct {
1018 name: "StartupInc",
1019 employees: &["Charlie"],
1020 metadata: &[("founded", 2020)],
1021 active: false,
1022 },
1023 )?;
1024 Ok(())
1025 })?;
1026 s.write_map(&TAGS, &|s| {
1027 s.write_string(&::aws_smithy_schema::prelude::STRING, "role")?;
1028 s.write_string(&::aws_smithy_schema::prelude::STRING, "admin")?;
1029 s.write_string(&::aws_smithy_schema::prelude::STRING, "level")?;
1030 s.write_string(&::aws_smithy_schema::prelude::STRING, "senior")?;
1031 Ok(())
1032 })?;
1033 Ok(())
1034 }
1035 }
1036
1037 let struct_schema = Schema::new(
1038 aws_smithy_schema::shape_id!("test", "User"),
1039 aws_smithy_schema::ShapeType::Structure,
1040 );
1041 let mut ser = JsonSerializer::new(Arc::new(JsonCodecSettings::default()));
1042 ser.write_struct(&struct_schema, &UserStruct).unwrap();
1043 let output = ser.finish();
1044 let expected: &[u8] = br#"{"id":12345,"name":"John Doe","scores":[95.5,87.3,92.1],"address":{"street":"123 Main St","city":"Seattle","zip":98101},"companies":[{"name":"TechCorp","employees":["Alice","Bob"],"metadata":{"founded":2010,"size":500},"active":true},{"name":"StartupInc","employees":["Charlie"],"metadata":{"founded":2020},"active":false}],"tags":{"role":"admin","level":"senior"}}"#;
1046 assert_eq!(output, expected);
1047 }
1048
1049 #[test]
1050 fn test_json_name_serialization() {
1051 use aws_smithy_schema::serde::SerializableStruct;
1052
1053 static FOO_MEMBER: Schema<'static> = Schema::new_member(
1054 aws_smithy_schema::shape_id!("test", "MyStruct"),
1055 aws_smithy_schema::ShapeType::String,
1056 "foo",
1057 0,
1058 );
1059 static BAR_MEMBER: Schema<'static> = Schema::new_member(
1061 aws_smithy_schema::shape_id!("test", "MyStruct"),
1062 aws_smithy_schema::ShapeType::Integer,
1063 "bar",
1064 1,
1065 )
1066 .with_json_name("Baz");
1067
1068 struct TestStruct;
1069 impl SerializableStruct for TestStruct {
1070 fn schema(&self) -> &Schema<'_> {
1071 static SCHEMA: Schema<'static> = Schema::new_struct(
1072 aws_smithy_schema::shape_id!("test", "MyStruct"),
1073 aws_smithy_schema::ShapeType::Structure,
1074 &[&FOO_MEMBER, &BAR_MEMBER],
1075 );
1076 &SCHEMA
1077 }
1078
1079 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1080 s.write_string(&FOO_MEMBER, "hello")?;
1081 s.write_integer(&BAR_MEMBER, 42)?;
1082 Ok(())
1083 }
1084 }
1085
1086 let struct_schema = Schema::new(
1088 aws_smithy_schema::shape_id!("test", "MyStruct"),
1089 aws_smithy_schema::ShapeType::Structure,
1090 );
1091 let mut ser = JsonSerializer::new(Arc::new(JsonCodecSettings::default()));
1092 ser.write_struct(&struct_schema, &TestStruct).unwrap();
1093 let output = String::from_utf8(ser.finish()).unwrap();
1094 assert_eq!(output, r#"{"foo":"hello","Baz":42}"#);
1095
1096 let mut ser = JsonSerializer::new(Arc::new(
1098 JsonCodecSettings::builder().use_json_name(false).build(),
1099 ));
1100 ser.write_struct(&struct_schema, &TestStruct).unwrap();
1101 let output = String::from_utf8(ser.finish()).unwrap();
1102 assert_eq!(output, r#"{"foo":"hello","bar":42}"#);
1103 }
1104
1105 #[test]
1111 fn test_json_name_serialization_from_runtime_schema() {
1112 use aws_smithy_schema::serde::SerializableStruct;
1113
1114 let arena: Vec<String> = vec![
1116 String::from("bar"), String::from("RuntimeBaz"), ];
1119
1120 let bar_member: Schema<'_> = Schema::new_member(
1121 aws_smithy_schema::shape_id!("test", "MyStruct"),
1122 aws_smithy_schema::ShapeType::Integer,
1123 &arena[0],
1124 0,
1125 )
1126 .with_json_name(&arena[1]);
1127
1128 let members = [&bar_member];
1130 let struct_schema = Schema::new_struct(
1131 aws_smithy_schema::shape_id!("test", "MyStruct"),
1132 aws_smithy_schema::ShapeType::Structure,
1133 &members,
1134 );
1135
1136 struct RuntimeStruct<'a> {
1137 schema: &'a Schema<'a>,
1138 member: &'a Schema<'a>,
1139 }
1140 impl SerializableStruct for RuntimeStruct<'_> {
1141 fn schema(&self) -> &Schema<'_> {
1142 self.schema
1143 }
1144
1145 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1146 s.write_integer(self.member, 42)
1147 }
1148 }
1149 let value = RuntimeStruct {
1150 schema: &struct_schema,
1151 member: &bar_member,
1152 };
1153 assert!(std::ptr::eq(value.schema(), &struct_schema));
1154
1155 let mut ser = JsonSerializer::new(Arc::new(JsonCodecSettings::default()));
1157 ser.write_struct(&struct_schema, &value).unwrap();
1158 let output = String::from_utf8(ser.finish()).unwrap();
1159 assert_eq!(output, r#"{"RuntimeBaz":42}"#);
1160
1161 let mut ser = JsonSerializer::new(Arc::new(
1163 JsonCodecSettings::builder().use_json_name(false).build(),
1164 ));
1165 ser.write_struct(&struct_schema, &value).unwrap();
1166 let output = String::from_utf8(ser.finish()).unwrap();
1167 assert_eq!(output, r#"{"bar":42}"#);
1168 }
1169
1170 #[test]
1174 fn cached_field_keys_are_escaped() {
1175 use aws_smithy_schema::serde::SerializableStruct;
1176
1177 let names: Vec<String> = vec![
1178 String::from("quote\"d"), String::from("back\\slash"), String::from("line\nbreak\u{1}"), ];
1182 let quoted = Schema::new_member(
1183 aws_smithy_schema::shape_id!("test", "Escapes"),
1184 ShapeType::String,
1185 &names[0],
1186 0,
1187 );
1188 let renamed = Schema::new_member(
1189 aws_smithy_schema::shape_id!("test", "Escapes"),
1190 ShapeType::String,
1191 &names[1],
1192 1,
1193 )
1194 .with_json_name(&names[2]);
1195 let members = ["ed, &renamed];
1196 let struct_schema = Schema::new_struct(
1197 aws_smithy_schema::shape_id!("test", "Escapes"),
1198 ShapeType::Structure,
1199 &members,
1200 );
1201
1202 struct Escapes<'a>(&'a Schema<'a>, &'a Schema<'a>, &'a Schema<'a>);
1203 impl SerializableStruct for Escapes<'_> {
1204 fn schema(&self) -> &Schema<'_> {
1205 self.0
1206 }
1207
1208 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1209 s.write_string(self.1, "a")?;
1210 s.write_string(self.2, "b")
1211 }
1212 }
1213 let value = Escapes(&struct_schema, "ed, &renamed);
1214
1215 for _ in 0..2 {
1216 let mut ser = JsonSerializer::new(Arc::new(JsonCodecSettings::default()));
1217 ser.write_struct(&struct_schema, &value).unwrap();
1218 let output = String::from_utf8(ser.finish()).unwrap();
1219 assert_eq!(output, r#"{"quote\"d":"a","line\nbreak\u0001":"b"}"#);
1220 let parsed: serde_json::Value = serde_json::from_str(&output).unwrap();
1221 assert_eq!(parsed["quote\"d"], "a");
1222 assert_eq!(parsed["line\nbreak\u{1}"], "b");
1223
1224 let mut ser = JsonSerializer::new(Arc::new(
1225 JsonCodecSettings::builder().use_json_name(false).build(),
1226 ));
1227 ser.write_struct(&struct_schema, &value).unwrap();
1228 let output = String::from_utf8(ser.finish()).unwrap();
1229 assert_eq!(output, r#"{"quote\"d":"a","back\\slash":"b"}"#);
1230 }
1231 }
1232
1233 #[test]
1238 fn cached_field_keys_do_not_depend_on_which_codec_was_first() {
1239 use aws_smithy_schema::serde::SerializableStruct;
1240
1241 static RENAMED: Schema<'static> = Schema::new_member(
1242 aws_smithy_schema::shape_id!("test", "FirstCodec"),
1243 ShapeType::String,
1244 "memberName",
1245 0,
1246 )
1247 .with_json_name("wireName");
1248 static STRUCT: Schema<'static> = Schema::new_struct(
1249 aws_smithy_schema::shape_id!("test", "FirstCodec"),
1250 ShapeType::Structure,
1251 &[&RENAMED],
1252 );
1253
1254 struct FirstCodec;
1255 impl SerializableStruct for FirstCodec {
1256 fn schema(&self) -> &Schema<'_> {
1257 &STRUCT
1258 }
1259
1260 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1261 s.write_string(&RENAMED, "v")
1262 }
1263 }
1264
1265 let serialize = |use_json_name: bool| {
1266 let mut ser = JsonSerializer::new(Arc::new(
1267 JsonCodecSettings::builder()
1268 .use_json_name(use_json_name)
1269 .build(),
1270 ));
1271 ser.write_struct(&STRUCT, &FirstCodec).unwrap();
1272 String::from_utf8(ser.finish()).unwrap()
1273 };
1274 assert_eq!(serialize(false), r#"{"memberName":"v"}"#);
1275 assert_eq!(serialize(true), r#"{"wireName":"v"}"#);
1276 assert_eq!(serialize(false), r#"{"memberName":"v"}"#);
1277 }
1278
1279 #[test]
1280 fn struct_inside_map_serializes_member_names_correctly() {
1281 use aws_smithy_schema::serde::{SerializableStruct, ShapeSerializer};
1284
1285 static INNER_NAME: Schema<'static> = Schema::new_member(
1286 aws_smithy_schema::shape_id!("test", "Inner"),
1287 ShapeType::String,
1288 "name",
1289 0,
1290 );
1291 static INNER_MEMBERS: &[&Schema<'_>] = &[&INNER_NAME];
1292 static INNER_SCHEMA: Schema<'static> = Schema::new_struct(
1293 aws_smithy_schema::shape_id!("test", "Inner"),
1294 ShapeType::Structure,
1295 INNER_MEMBERS,
1296 );
1297
1298 struct Inner;
1299 impl SerializableStruct for Inner {
1300 fn schema(&self) -> &Schema<'_> {
1301 &INNER_SCHEMA
1302 }
1303
1304 fn serialize_members(
1305 &self,
1306 ser: &mut dyn ShapeSerializer,
1307 ) -> Result<(), aws_smithy_schema::serde::SerdeError> {
1308 ser.write_string(&INNER_NAME, "Alice")
1309 }
1310 }
1311
1312 static MAP_SCHEMA: Schema<'static> = Schema::new(
1313 aws_smithy_schema::shape_id!("test", "MyMap"),
1314 ShapeType::Map,
1315 );
1316
1317 let mut ser = JsonSerializer::new(Arc::new(JsonCodecSettings::default()));
1318 ser.write_map(&MAP_SCHEMA, &|ser| {
1319 ser.write_string(&aws_smithy_schema::prelude::STRING, "key1")?;
1320 ser.write_struct(&INNER_SCHEMA, &Inner)?;
1321 Ok(())
1322 })
1323 .unwrap();
1324 let output = String::from_utf8(ser.finish()).unwrap();
1325 assert_eq!(output, r#"{"key1":{"name":"Alice"}}"#);
1326 }
1327
1328 #[test]
1337 fn map_value_with_value_member_schema_does_not_leak_member_name() {
1338 static OUTER_VALUE: Schema<'static> = Schema::new_member(
1339 aws_smithy_schema::shape_id!("test", "OuterMap", "value"),
1340 ShapeType::Map,
1341 "value",
1342 1,
1343 );
1344
1345 let outer_map_schema = Schema::new(
1346 aws_smithy_schema::shape_id!("test", "OuterMap"),
1347 ShapeType::Map,
1348 );
1349 let mut ser = JsonSerializer::new(Arc::new(JsonCodecSettings::default()));
1350 ser.write_map(&outer_map_schema, &|s| {
1351 s.write_string(&aws_smithy_schema::prelude::STRING, "outer_k")?;
1352 s.write_map(&OUTER_VALUE, &|s| {
1353 s.write_string(&aws_smithy_schema::prelude::STRING, "inner_k")?;
1354 s.write_string(&aws_smithy_schema::prelude::STRING, "inner_v")?;
1355 Ok(())
1356 })?;
1357 Ok(())
1358 })
1359 .unwrap();
1360 let output = String::from_utf8(ser.finish()).unwrap();
1361 assert_eq!(output, r#"{"outer_k":{"inner_k":"inner_v"}}"#);
1362 }
1363
1364 #[test]
1369 fn list_element_with_member_member_schema_does_not_leak_member_name() {
1370 static OUTER_MEMBER: Schema<'static> = Schema::new_member(
1371 aws_smithy_schema::shape_id!("test", "OuterList", "member"),
1372 ShapeType::Map,
1373 "member",
1374 0,
1375 );
1376
1377 let outer_list_schema = Schema::new(
1378 aws_smithy_schema::shape_id!("test", "OuterList"),
1379 ShapeType::List,
1380 );
1381 let mut ser = JsonSerializer::new(Arc::new(JsonCodecSettings::default()));
1382 ser.write_list(&outer_list_schema, &|s| {
1383 s.write_map(&OUTER_MEMBER, &|s| {
1384 s.write_string(&aws_smithy_schema::prelude::STRING, "k1")?;
1385 s.write_string(&aws_smithy_schema::prelude::STRING, "v1")?;
1386 Ok(())
1387 })?;
1388 Ok(())
1389 })
1390 .unwrap();
1391 let output = String::from_utf8(ser.finish()).unwrap();
1392 assert_eq!(output, r#"[{"k1":"v1"}]"#);
1393 }
1394
1395 #[test]
1398 fn nested_list_with_member_member_schema_does_not_leak_member_name() {
1399 static OUTER_MEMBER: Schema<'static> = Schema::new_member(
1400 aws_smithy_schema::shape_id!("test", "OuterList", "member"),
1401 ShapeType::List,
1402 "member",
1403 0,
1404 );
1405
1406 let outer_list_schema = Schema::new(
1407 aws_smithy_schema::shape_id!("test", "OuterList"),
1408 ShapeType::List,
1409 );
1410 let mut ser = JsonSerializer::new(Arc::new(JsonCodecSettings::default()));
1411 ser.write_list(&outer_list_schema, &|s| {
1412 s.write_list(&OUTER_MEMBER, &|s| {
1413 s.write_integer(&aws_smithy_schema::prelude::INTEGER, 1)?;
1414 s.write_integer(&aws_smithy_schema::prelude::INTEGER, 2)?;
1415 Ok(())
1416 })?;
1417 Ok(())
1418 })
1419 .unwrap();
1420 let output = String::from_utf8(ser.finish()).unwrap();
1421 assert_eq!(output, r#"[[1,2]]"#);
1422 }
1423
1424 #[test]
1430 fn struct_context_restored_after_nested_aggregate_member() {
1431 static A: Schema<'static> = Schema::new_member(
1432 aws_smithy_schema::shape_id!("test", "Outer"),
1433 ShapeType::String,
1434 "a",
1435 0,
1436 );
1437 static B: Schema<'static> = Schema::new_member(
1438 aws_smithy_schema::shape_id!("test", "Outer"),
1439 ShapeType::Map,
1440 "b",
1441 1,
1442 );
1443 static C: Schema<'static> = Schema::new_member(
1444 aws_smithy_schema::shape_id!("test", "Outer"),
1445 ShapeType::String,
1446 "c",
1447 2,
1448 );
1449
1450 struct Outer;
1451 impl SerializableStruct for Outer {
1452 fn schema(&self) -> &Schema<'_> {
1453 static SCHEMA: Schema<'static> = Schema::new_struct(
1454 aws_smithy_schema::shape_id!("test", "Outer"),
1455 aws_smithy_schema::ShapeType::Structure,
1456 &[&A, &B, &C],
1457 );
1458 &SCHEMA
1459 }
1460
1461 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1462 s.write_string(&A, "1")?;
1463 s.write_map(&B, &|s| {
1464 s.write_string(&aws_smithy_schema::prelude::STRING, "k")?;
1465 s.write_string(&aws_smithy_schema::prelude::STRING, "v")?;
1466 Ok(())
1467 })?;
1468 s.write_string(&C, "2")?;
1469 Ok(())
1470 }
1471 }
1472
1473 let outer_schema = Schema::new(
1474 aws_smithy_schema::shape_id!("test", "Outer"),
1475 ShapeType::Structure,
1476 );
1477 let mut ser = JsonSerializer::new(Arc::new(JsonCodecSettings::default()));
1478 ser.write_struct(&outer_schema, &Outer).unwrap();
1479 let output = String::from_utf8(ser.finish()).unwrap();
1480 assert_eq!(output, r#"{"a":"1","b":{"k":"v"},"c":"2"}"#);
1481 }
1482
1483 #[test]
1488 fn map_value_with_json_name_member_schema_does_not_leak_json_name() {
1489 static OUTER_VALUE: Schema<'static> = Schema::new_member(
1490 aws_smithy_schema::shape_id!("test", "OuterMap", "value"),
1491 ShapeType::String,
1492 "value",
1493 1,
1494 )
1495 .with_json_name("CustomName");
1496
1497 let outer_map_schema = Schema::new(
1498 aws_smithy_schema::shape_id!("test", "OuterMap"),
1499 ShapeType::Map,
1500 );
1501 let mut ser = JsonSerializer::new(Arc::new(JsonCodecSettings::default()));
1502 ser.write_map(&outer_map_schema, &|s| {
1503 s.write_string(&aws_smithy_schema::prelude::STRING, "k")?;
1504 s.write_string(&OUTER_VALUE, "v")?;
1505 Ok(())
1506 })
1507 .unwrap();
1508 let output = String::from_utf8(ser.finish()).unwrap();
1509 assert_eq!(output, r#"{"k":"v"}"#);
1510 }
1511
1512 fn settings_with_string_for_arbitrary_precision(value: bool) -> Arc<JsonCodecSettings> {
1515 Arc::new(
1516 JsonCodecSettings::builder()
1517 .use_string_for_arbitrary_precision(value)
1518 .build(),
1519 )
1520 }
1521
1522 #[test]
1523 fn write_big_integer_emits_a_number_by_default() {
1524 use std::str::FromStr;
1528 let bi = BigInteger::from_str("99999999999999999999999").unwrap();
1529 let mut ser = JsonSerializer::new(Arc::new(JsonCodecSettings::default()));
1530 ser.write_big_integer(&aws_smithy_schema::prelude::BIG_INTEGER, &bi)
1531 .unwrap();
1532 let output = String::from_utf8(ser.finish()).unwrap();
1533 assert_eq!(output, "99999999999999999999999");
1534 }
1535
1536 #[test]
1537 fn write_big_integer_emits_a_string_when_setting_enabled() {
1538 use std::str::FromStr;
1539 let bi = BigInteger::from_str("99999999999999999999999").unwrap();
1540 let mut ser = JsonSerializer::new(settings_with_string_for_arbitrary_precision(true));
1541 ser.write_big_integer(&aws_smithy_schema::prelude::BIG_INTEGER, &bi)
1542 .unwrap();
1543 let output = String::from_utf8(ser.finish()).unwrap();
1544 assert_eq!(output, "\"99999999999999999999999\"");
1545 }
1546
1547 #[test]
1548 fn write_big_decimal_emits_a_number_by_default() {
1549 use std::str::FromStr;
1550 let bd = BigDecimal::from_str("1.234e500").unwrap();
1551 let mut ser = JsonSerializer::new(Arc::new(JsonCodecSettings::default()));
1552 ser.write_big_decimal(&aws_smithy_schema::prelude::BIG_DECIMAL, &bd)
1553 .unwrap();
1554 let output = String::from_utf8(ser.finish()).unwrap();
1555 assert!(
1556 !output.starts_with('"'),
1557 "expected a raw JSON number, got {output}"
1558 );
1559 assert_ne!(output, "null");
1560 }
1561
1562 #[test]
1563 fn write_big_decimal_emits_a_string_when_setting_enabled() {
1564 use std::str::FromStr;
1565 let bd = BigDecimal::from_str("0.123456789012345678901234567890").unwrap();
1566 let mut ser = JsonSerializer::new(settings_with_string_for_arbitrary_precision(true));
1567 ser.write_big_decimal(&aws_smithy_schema::prelude::BIG_DECIMAL, &bd)
1568 .unwrap();
1569 let output = String::from_utf8(ser.finish()).unwrap();
1570 assert_eq!(output, "\"0.123456789012345678901234567890\"");
1571 }
1572
1573 fn normalizing_settings() -> Arc<JsonCodecSettings> {
1583 Arc::new(
1584 JsonCodecSettings::builder()
1585 .normalize_arbitrary_precision(true)
1586 .build(),
1587 )
1588 }
1589
1590 #[test]
1591 fn non_json_numbers_are_rejected_on_the_raw_path_by_default() {
1592 use std::str::FromStr;
1593 for text in ["00123", "-01", "00.1", "+1.5", ".5", "1."] {
1594 let bd = BigDecimal::from_str(text).unwrap();
1595 let mut ser = JsonSerializer::new(Arc::new(JsonCodecSettings::default()));
1596 let err = ser
1597 .write_big_decimal(&aws_smithy_schema::prelude::BIG_DECIMAL, &bd)
1598 .expect_err(&format!("expected {text:?} to be rejected by default"));
1599 assert!(
1600 matches!(err, SerdeError::InvalidInput { .. }),
1601 "{text:?}: {err:?}"
1602 );
1603 assert_eq!(String::from_utf8(ser.finish()).unwrap(), "", "for {text:?}");
1605 }
1606 }
1607
1608 #[test]
1609 fn write_big_integer_normalizes_non_json_numbers_on_the_raw_path() {
1610 use std::str::FromStr;
1611 for (text, expected) in [
1612 ("00123", "123"),
1613 ("007", "7"),
1614 ("-01", "-1"),
1615 ("0000", "0"),
1616 ("-0000", "-0"),
1617 ] {
1618 let bi = BigInteger::from_str(text)
1621 .unwrap_or_else(|e| panic!("BigInteger::from_str({text:?}) should succeed: {e}"));
1622 let mut ser = JsonSerializer::new(normalizing_settings());
1623 ser.write_big_integer(&aws_smithy_schema::prelude::BIG_INTEGER, &bi)
1624 .unwrap_or_else(|e| panic!("{text:?} should be normalized: {e:?}"));
1625 assert_eq!(
1626 String::from_utf8(ser.finish()).unwrap(),
1627 expected,
1628 "for {text:?}"
1629 );
1630 }
1631 }
1632
1633 #[test]
1634 fn write_big_decimal_normalizes_non_json_numbers_on_the_raw_path() {
1635 use std::str::FromStr;
1636 for (text, expected) in [
1637 ("00123", "123"),
1638 ("007", "7"),
1639 ("-01", "-1"),
1640 ("00.1", "0.1"),
1641 ("-01.5", "-1.5"),
1642 ("007e2", "7e2"),
1643 ("0000", "0"),
1644 ("00.0", "0.0"),
1645 ("01e-3", "1e-3"),
1646 ("+000987654321", "987654321"),
1647 ("+1.5", "1.5"),
1648 (".5", "0.5"),
1649 ("1.", "1"),
1650 ] {
1651 let bd = BigDecimal::from_str(text)
1652 .unwrap_or_else(|e| panic!("BigDecimal::from_str({text:?}) should succeed: {e}"));
1653 let mut ser = JsonSerializer::new(normalizing_settings());
1654 ser.write_big_decimal(&aws_smithy_schema::prelude::BIG_DECIMAL, &bd)
1655 .unwrap_or_else(|e| panic!("{text:?} should be normalized: {e:?}"));
1656 assert_eq!(
1657 String::from_utf8(ser.finish()).unwrap(),
1658 expected,
1659 "for {text:?}"
1660 );
1661 }
1662 }
1663
1664 #[test]
1665 fn to_json_number_rejects_text_that_is_not_a_number() {
1666 for text in [
1667 "", "-", "+", ".", "abc", "1.2.3", "0x1f", "1e", "e1", "1 2", "NaN", "--1",
1668 ] {
1669 assert_eq!(to_json_number(text), None, "for {text:?}");
1670 }
1671 }
1672
1673 #[test]
1674 fn non_json_numbers_are_accepted_when_emitted_as_strings() {
1675 use std::str::FromStr;
1676 let bi = BigInteger::from_str("00123").unwrap();
1682 let mut ser = JsonSerializer::new(settings_with_string_for_arbitrary_precision(true));
1683 ser.write_big_integer(&aws_smithy_schema::prelude::BIG_INTEGER, &bi)
1684 .unwrap();
1685 assert_eq!(String::from_utf8(ser.finish()).unwrap(), "\"00123\"");
1686
1687 let bd = BigDecimal::from_str("00.1").unwrap();
1688 let mut ser = JsonSerializer::new(settings_with_string_for_arbitrary_precision(true));
1689 ser.write_big_decimal(&aws_smithy_schema::prelude::BIG_DECIMAL, &bd)
1690 .unwrap();
1691 assert_eq!(String::from_utf8(ser.finish()).unwrap(), "\"00.1\"");
1692 }
1693
1694 #[test]
1695 fn write_big_numbers_accept_every_json_number_form() {
1696 use std::str::FromStr;
1697 for text in [
1698 "0",
1699 "-0",
1700 "123",
1701 "-123",
1702 "99999999999999999999999",
1703 "1.5",
1704 "-0.5",
1705 "1e3",
1706 "1E3",
1707 "1e+9",
1708 "1e-9",
1709 "1.23e10",
1710 "1.23E-10",
1711 "0.123456789012345678901234567890",
1712 ] {
1713 let bd = BigDecimal::from_str(text).unwrap();
1714 let mut ser = JsonSerializer::new(Arc::new(JsonCodecSettings::default()));
1715 ser.write_big_decimal(&aws_smithy_schema::prelude::BIG_DECIMAL, &bd)
1716 .unwrap_or_else(|e| panic!("expected {text:?} to serialize: {e}"));
1717 assert_eq!(
1718 String::from_utf8(ser.finish()).unwrap(),
1719 *text,
1720 "bigDecimal {text:?} must be emitted verbatim"
1721 );
1722 }
1723
1724 for text in ["0", "-0", "123", "-123", "99999999999999999999999"] {
1725 let bi = BigInteger::from_str(text).unwrap();
1726 let mut ser = JsonSerializer::new(Arc::new(JsonCodecSettings::default()));
1727 ser.write_big_integer(&aws_smithy_schema::prelude::BIG_INTEGER, &bi)
1728 .unwrap_or_else(|e| panic!("expected {text:?} to serialize: {e}"));
1729 assert_eq!(String::from_utf8(ser.finish()).unwrap(), *text);
1730 }
1731 }
1732
1733 #[test]
1734 fn is_json_number_matches_the_rfc_8259_grammar() {
1735 for good in [
1736 "0",
1737 "-0",
1738 "1",
1739 "-1",
1740 "10",
1741 "1.0",
1742 "-1.25",
1743 "1e1",
1744 "1E1",
1745 "1e+1",
1746 "1e-1",
1747 "0.5e-10",
1748 "1234567890123456789012345678901234567890",
1749 ] {
1750 assert!(is_json_number(good), "expected {good:?} to be accepted");
1751 }
1752 for bad in [
1753 "", "-", "+", "+1", "01", "00", "-01", "1.", ".5", "1..2", "1.2.3", "--1", "1e", "1e+",
1754 "1e-", "e1", "1e2e3", "0x1f", "1 2", "Infinity", "NaN", "1,000",
1755 ] {
1756 assert!(!is_json_number(bad), "expected {bad:?} to be rejected");
1757 }
1758 }
1759
1760 static CD_ENABLED: Schema = Schema::new_member(
1768 aws_smithy_schema::shape_id!("test", "ConnectionDraining$enabled"),
1769 ShapeType::Boolean,
1770 "enabled",
1771 0,
1772 );
1773 static LBA_CD: Schema = Schema::new_member(
1774 aws_smithy_schema::shape_id!("test", "LoadBalancerAttributes$connectionDraining"),
1775 ShapeType::Structure,
1776 "connectionDraining",
1777 0,
1778 );
1779 static LBA_SCHEMA: Schema = Schema::new_struct(
1780 aws_smithy_schema::shape_id!("test", "LoadBalancerAttributes"),
1781 ShapeType::Structure,
1782 &[&LBA_CD],
1783 );
1784
1785 struct ConnectionDraining {
1786 enabled: bool,
1787 }
1788 impl SerializableStruct for ConnectionDraining {
1789 fn schema(&self) -> &Schema<'_> {
1790 static SCHEMA: Schema<'static> = Schema::new_struct(
1791 aws_smithy_schema::shape_id!("test", "ConnectionDraining"),
1792 aws_smithy_schema::ShapeType::Structure,
1793 &[&CD_ENABLED],
1794 );
1795 &SCHEMA
1796 }
1797
1798 fn serialize_members(&self, ser: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1799 let val = &self.enabled;
1801 ser.write_boolean(&CD_ENABLED, *val)
1802 }
1803 }
1804 struct LoadBalancerAttributes {
1805 connection_draining: ConnectionDraining,
1806 }
1807 impl SerializableStruct for LoadBalancerAttributes {
1808 fn schema(&self) -> &Schema<'_> {
1809 &LBA_SCHEMA
1810 }
1811
1812 fn serialize_members(&self, ser: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1813 ser.write_struct(&LBA_CD, &self.connection_draining)
1814 }
1815 }
1816
1817 #[test]
1818 fn required_value_type_false_bool_is_serialized() {
1819 let mut ser = JsonSerializer::new(Arc::new(JsonCodecSettings::default()));
1820 ser.write_struct(
1821 &LBA_SCHEMA,
1822 &LoadBalancerAttributes {
1823 connection_draining: ConnectionDraining { enabled: false },
1824 },
1825 )
1826 .unwrap();
1827 let out = String::from_utf8(ser.finish()).unwrap();
1828 assert_eq!(
1829 out, r#"{"connectionDraining":{"enabled":false}}"#,
1830 "the required-default value-type member must be present with false, not dropped"
1831 );
1832 }
1833
1834 #[test]
1839 fn tagged_union_serializes_as_single_variant_key() {
1840 static EP_ARN: Schema = Schema::new_member(
1841 aws_smithy_schema::shape_id!("test", "Endpoint"),
1842 ShapeType::String,
1843 "arn",
1844 0,
1845 );
1846 static EP_CONFIG: Schema = Schema::new_member(
1847 aws_smithy_schema::shape_id!("test", "Endpoint"),
1848 ShapeType::Structure,
1849 "config",
1850 1,
1851 );
1852 static ENDPOINT_SCHEMA: Schema = Schema::new_struct(
1853 aws_smithy_schema::shape_id!("test", "Endpoint"),
1854 ShapeType::Union,
1855 &[&EP_ARN, &EP_CONFIG],
1856 );
1857 static EC_NAME: Schema = Schema::new_member(
1858 aws_smithy_schema::shape_id!("test", "EndpointConfig"),
1859 ShapeType::String,
1860 "name",
1861 0,
1862 );
1863 static CC_ATTACH: Schema = Schema::new_member(
1864 aws_smithy_schema::shape_id!("test", "CreateConnectionInput"),
1865 ShapeType::Union,
1866 "attachPoint",
1867 0,
1868 );
1869 static CC_REMOTE: Schema = Schema::new_member(
1870 aws_smithy_schema::shape_id!("test", "CreateConnectionInput"),
1871 ShapeType::Union,
1872 "remoteAccount",
1873 1,
1874 );
1875 static INPUT_SCHEMA: Schema = Schema::new_struct(
1876 aws_smithy_schema::shape_id!("test", "CreateConnectionInput"),
1877 ShapeType::Structure,
1878 &[&CC_ATTACH, &CC_REMOTE],
1879 );
1880
1881 struct EndpointConfig {
1882 name: &'static str,
1883 }
1884 impl SerializableStruct for EndpointConfig {
1885 fn schema(&self) -> &Schema<'_> {
1886 static SCHEMA: Schema<'static> = Schema::new_struct(
1887 aws_smithy_schema::shape_id!("test", "EndpointConfig"),
1888 aws_smithy_schema::ShapeType::Structure,
1889 &[&EC_NAME],
1890 );
1891 &SCHEMA
1892 }
1893
1894 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1895 s.write_string(&EC_NAME, self.name)
1896 }
1897 }
1898 enum Endpoint {
1899 Arn(&'static str),
1900 Config(EndpointConfig),
1901 }
1902 impl SerializableStruct for Endpoint {
1903 fn schema(&self) -> &Schema<'_> {
1904 &ENDPOINT_SCHEMA
1905 }
1906
1907 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1908 match self {
1909 Endpoint::Arn(v) => s.write_string(&EP_ARN, v),
1910 Endpoint::Config(v) => s.write_struct(&EP_CONFIG, v),
1911 }
1912 }
1913 }
1914 struct Input {
1915 attach: Endpoint,
1916 remote: Endpoint,
1917 }
1918 impl SerializableStruct for Input {
1919 fn schema(&self) -> &Schema<'_> {
1920 &INPUT_SCHEMA
1921 }
1922
1923 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1924 s.write_struct(&CC_ATTACH, &self.attach)?;
1925 s.write_struct(&CC_REMOTE, &self.remote)
1926 }
1927 }
1928
1929 let mut ser = JsonSerializer::new(Arc::new(JsonCodecSettings::default()));
1931 ser.write_struct(&ENDPOINT_SCHEMA, &Endpoint::Arn("dxcon-abc"))
1932 .unwrap();
1933 assert_eq!(
1934 String::from_utf8(ser.finish()).unwrap(),
1935 r#"{"arn":"dxcon-abc"}"#
1936 );
1937
1938 let mut ser = JsonSerializer::new(Arc::new(JsonCodecSettings::default()));
1940 ser.write_struct(
1941 &INPUT_SCHEMA,
1942 &Input {
1943 attach: Endpoint::Arn("ap"),
1944 remote: Endpoint::Config(EndpointConfig { name: "ra" }),
1945 },
1946 )
1947 .unwrap();
1948 assert_eq!(
1949 String::from_utf8(ser.finish()).unwrap(),
1950 r#"{"attachPoint":{"arn":"ap"},"remoteAccount":{"config":{"name":"ra"}}}"#
1951 );
1952 }
1953}