1use aws_smithy_schema::codec::FinishSerializer;
7use aws_smithy_schema::extension::SchemaExtensionKey;
8use aws_smithy_schema::serde::{SerdeError, SerializableStruct, ShapeSerializer};
9use aws_smithy_schema::Schema;
10use aws_smithy_types::{BigDecimal, BigInteger, Blob, DateTime, Document};
11use std::fmt::Write;
12use std::ops::Deref;
13use std::sync::Arc;
14use urlencoding::encode;
15
16#[derive(Clone, Copy)]
20enum Segment<'a> {
21 Index(usize),
22 Entry(usize, &'a str),
23}
24
25impl std::fmt::Display for Segment<'_> {
26 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27 match self {
28 Segment::Index(i) => write!(f, "{i}"),
29 Segment::Entry(i, name) => write!(f, "{i}.{name}"),
30 }
31 }
32}
33
34#[derive(Clone, Debug)]
39enum Name {
40 Static(&'static str),
41 Shared(Arc<str>),
42}
43
44impl Deref for Name {
45 type Target = str;
46
47 fn deref(&self) -> &str {
48 match self {
49 Name::Static(name) => name,
50 Name::Shared(name) => name,
51 }
52 }
53}
54
55#[derive(Debug)]
57enum ElementName {
58 Inherit,
62 Default,
64 Named(Name),
65}
66
67impl ElementName {
68 fn of(element: Option<&Schema<'_>>) -> Self {
69 match element {
70 None => ElementName::Inherit,
71 Some(element) => match wire_name(element) {
72 Some(name) => ElementName::Named(name),
73 None => ElementName::Default,
74 },
75 }
76 }
77
78 fn resolve(&self, inherited: Option<Name>, default: &'static str) -> Name {
79 match self {
80 ElementName::Named(name) => name.clone(),
81 ElementName::Default => Name::Static(default),
82 ElementName::Inherit => inherited.unwrap_or(Name::Static(default)),
83 }
84 }
85}
86
87fn wire_name(schema: &Schema<'_>) -> Option<Name> {
89 schema
90 .xml_name()
91 .map(|name| name.value())
92 .or(schema.member_name())
93 .map(|name| Name::Shared(name.into()))
94}
95
96#[derive(Debug)]
99struct CollectionNames {
100 element: ElementName,
102 key: ElementName,
104 nested_element: Option<Name>,
110 nested_key: Option<Name>,
111}
112
113static COLLECTION_NAMES: SchemaExtensionKey<CollectionNames> = SchemaExtensionKey::new(|schema| {
114 let element = schema.member();
115 CollectionNames {
116 element: ElementName::of(element),
117 key: ElementName::of(schema.key()),
118 nested_element: element.and_then(|e| e.member()).and_then(wire_name),
119 nested_key: element.and_then(|e| e.key()).and_then(wire_name),
120 }
121});
122
123enum CollectionContext {
124 List {
125 index: usize,
126 inherited_member_name: Option<Name>,
128 inherited_key_name: Option<Name>,
130 },
131 Map {
132 index: usize,
133 expecting_key: bool,
134 key_name: Name,
135 value_name: Name,
136 inherited_member_name: Option<Name>,
138 inherited_key_name: Option<Name>,
140 },
141}
142
143fn next_collection_segment(context_stack: &mut [CollectionContext]) -> Option<Segment<'_>> {
150 let ctx = context_stack.last_mut()?;
151 Some(match ctx {
152 CollectionContext::List { index, .. } => {
153 let seg = Segment::Index(*index);
154 *index += 1;
155 seg
156 }
157 CollectionContext::Map {
158 index,
159 expecting_key,
160 key_name,
161 value_name,
162 ..
163 } => {
164 if *expecting_key {
165 *expecting_key = false;
166 Segment::Entry(*index, key_name)
167 } else {
168 let seg = Segment::Entry(*index, value_name);
169 *expecting_key = true;
170 *index += 1;
171 seg
172 }
173 }
174 })
175}
176
177fn push_prefix_segment(prefix: &mut String, prefix_lengths: &mut Vec<usize>, segment: Segment<'_>) {
180 let prev_len = prefix.len();
181 if !prefix.is_empty() {
182 prefix.push('.');
183 }
184 let _ = write!(prefix, "{segment}");
186 prefix_lengths.push(prev_len);
187}
188
189pub struct QueryShapeSerializer {
195 output: String,
196 prefix: String,
197 prefix_lengths: Vec<usize>,
198 context_stack: Vec<CollectionContext>,
199}
200
201impl QueryShapeSerializer {
202 pub fn new(action: &str, version: &str) -> Self {
203 let mut output = String::with_capacity(256);
204 let _ = write!(
206 output,
207 "Action={}&Version={}",
208 encode(action),
209 encode(version)
210 );
211 Self {
212 output,
213 prefix: String::with_capacity(64),
214 prefix_lengths: Vec::with_capacity(8),
215 context_stack: Vec::with_capacity(4),
216 }
217 }
218
219 fn wire_name<'a>(&self, schema: &'a Schema<'_>) -> &'a str {
220 schema
221 .xml_name()
222 .map(|t| t.value())
223 .or(schema.member_name())
224 .unwrap_or("")
225 }
226
227 fn inherited_child_names(&self) -> (Option<Name>, Option<Name>) {
239 match self.context_stack.last() {
240 Some(CollectionContext::List {
241 inherited_member_name,
242 inherited_key_name,
243 ..
244 })
245 | Some(CollectionContext::Map {
246 expecting_key: false,
247 inherited_member_name,
248 inherited_key_name,
249 ..
250 }) => (inherited_member_name.clone(), inherited_key_name.clone()),
251 _ => (None, None),
252 }
253 }
254
255 fn push_prefix(&mut self, segment: &str) {
256 let prev_len = self.prefix.len();
257 if !self.prefix.is_empty() {
258 self.prefix.push('.');
259 }
260 self.prefix.push_str(segment);
261 self.prefix_lengths.push(prev_len);
262 }
263
264 fn pop_prefix(&mut self) {
265 let prev_len = self.prefix_lengths.pop().expect("prefix stack underflow");
266 self.prefix.truncate(prev_len);
267 }
268
269 fn write_scalar(&mut self, schema: &Schema<'_>, value: &str) -> Result<(), SerdeError> {
273 let segment = if schema.member_name().is_none() {
274 next_collection_segment(&mut self.context_stack)
275 } else {
276 None
277 };
278 self.output.push('&');
279 match segment {
281 Some(seg) => {
282 let _ = write!(self.output, "{}.{}", self.prefix, seg);
283 }
284 None => {
285 let name = self.wire_name(schema);
286 debug_assert!(
292 !name.is_empty(),
293 "awsQuery scalar has no wire name (would emit an empty param name)"
294 );
295 if self.prefix.is_empty() {
296 self.output.push_str(name);
297 } else {
298 let _ = write!(self.output, "{}.{}", self.prefix, name);
299 }
300 }
301 }
302 self.output.push('=');
303 self.output.push_str(&encode(value));
304 Ok(())
305 }
306}
307
308impl FinishSerializer for QueryShapeSerializer {
309 fn finish(self) -> Vec<u8> {
310 self.output.into_bytes()
311 }
312}
313
314impl ShapeSerializer for QueryShapeSerializer {
315 fn write_struct(
316 &mut self,
317 schema: &Schema<'_>,
318 value: &dyn SerializableStruct,
319 ) -> Result<(), SerdeError> {
320 let is_member = schema.member_name().is_some();
321 let pushed_index = if is_member {
322 self.push_prefix(self.wire_name(schema));
323 false
324 } else if let Some(seg) = next_collection_segment(&mut self.context_stack) {
325 push_prefix_segment(&mut self.prefix, &mut self.prefix_lengths, seg);
326 true
327 } else {
328 false
329 };
330 value.serialize_members(self)?;
331 if is_member || pushed_index {
332 self.pop_prefix();
333 }
334 Ok(())
335 }
336
337 fn write_list(
338 &mut self,
339 schema: &Schema<'_>,
340 write_elements: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
341 ) -> Result<(), SerdeError> {
342 let (inherited_member, _inherited_key) = self.inherited_child_names();
347 let flat = schema.xml_flattened();
348 let is_member = schema.member_name().is_some();
349 let pushed_index = if is_member {
350 self.push_prefix(self.wire_name(schema));
351 false
352 } else if let Some(seg) = next_collection_segment(&mut self.context_stack) {
353 push_prefix_segment(&mut self.prefix, &mut self.prefix_lengths, seg);
354 true
355 } else {
356 false
357 };
358 let names = schema.extension(&COLLECTION_NAMES);
359 if !flat {
360 let member_name = names.element.resolve(inherited_member, "member");
363 self.push_prefix(&member_name);
364 }
365 self.context_stack.push(CollectionContext::List {
367 index: 1,
368 inherited_member_name: names.nested_element.clone(),
369 inherited_key_name: names.nested_key.clone(),
370 });
371 let output_len_before = self.output.len();
372 write_elements(self)?;
373 let wrote_elements = self.output.len() > output_len_before;
374 self.context_stack.pop();
375 if !flat {
376 self.pop_prefix();
377 }
378 if !wrote_elements && is_member {
379 self.output.push('&');
380 self.output.push_str(&self.prefix);
381 self.output.push('=');
382 }
383 if is_member || pushed_index {
384 self.pop_prefix();
385 }
386 Ok(())
387 }
388
389 fn write_map(
390 &mut self,
391 schema: &Schema<'_>,
392 write_entries: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
393 ) -> Result<(), SerdeError> {
394 let (inherited_member, inherited_key) = self.inherited_child_names();
398 let flat = schema.xml_flattened();
399 let is_member = schema.member_name().is_some();
400 let pushed_index = if is_member {
401 self.push_prefix(self.wire_name(schema));
402 false
403 } else if let Some(seg) = next_collection_segment(&mut self.context_stack) {
404 push_prefix_segment(&mut self.prefix, &mut self.prefix_lengths, seg);
405 true
406 } else {
407 false
408 };
409 if !flat {
410 self.push_prefix("entry");
411 }
412 let names = schema.extension(&COLLECTION_NAMES);
415 self.context_stack.push(CollectionContext::Map {
417 index: 1,
418 expecting_key: true,
419 key_name: names.key.resolve(inherited_key, "key"),
420 value_name: names.element.resolve(inherited_member, "value"),
421 inherited_member_name: names.nested_element.clone(),
422 inherited_key_name: names.nested_key.clone(),
423 });
424 write_entries(self)?;
425 self.context_stack.pop();
426 if !flat {
427 self.pop_prefix();
428 }
429 if is_member || pushed_index {
430 self.pop_prefix();
431 }
432 Ok(())
433 }
434
435 fn write_string(&mut self, schema: &Schema<'_>, value: &str) -> Result<(), SerdeError> {
436 self.write_scalar(schema, value)
437 }
438
439 fn write_boolean(&mut self, schema: &Schema<'_>, value: bool) -> Result<(), SerdeError> {
440 self.write_scalar(schema, if value { "true" } else { "false" })
441 }
442
443 fn write_byte(&mut self, schema: &Schema<'_>, value: i8) -> Result<(), SerdeError> {
444 self.write_scalar(schema, &value.to_string())
445 }
446
447 fn write_short(&mut self, schema: &Schema<'_>, value: i16) -> Result<(), SerdeError> {
448 self.write_scalar(schema, &value.to_string())
449 }
450
451 fn write_integer(&mut self, schema: &Schema<'_>, value: i32) -> Result<(), SerdeError> {
452 self.write_scalar(schema, &value.to_string())
453 }
454
455 fn write_long(&mut self, schema: &Schema<'_>, value: i64) -> Result<(), SerdeError> {
456 self.write_scalar(schema, &value.to_string())
457 }
458
459 fn write_float(&mut self, schema: &Schema<'_>, value: f32) -> Result<(), SerdeError> {
460 let s = if value.is_nan() {
461 "NaN".to_string()
462 } else if value == f32::INFINITY {
463 "Infinity".to_string()
464 } else if value == f32::NEG_INFINITY {
465 "-Infinity".to_string()
466 } else {
467 aws_smithy_types::primitive::Encoder::from(value)
468 .encode()
469 .to_owned()
470 };
471 self.write_scalar(schema, &s)
472 }
473
474 fn write_double(&mut self, schema: &Schema<'_>, value: f64) -> Result<(), SerdeError> {
475 let s = if value.is_nan() {
476 "NaN".to_string()
477 } else if value == f64::INFINITY {
478 "Infinity".to_string()
479 } else if value == f64::NEG_INFINITY {
480 "-Infinity".to_string()
481 } else {
482 aws_smithy_types::primitive::Encoder::from(value)
483 .encode()
484 .to_owned()
485 };
486 self.write_scalar(schema, &s)
487 }
488
489 fn write_big_integer(
490 &mut self,
491 schema: &Schema<'_>,
492 value: &BigInteger,
493 ) -> Result<(), SerdeError> {
494 self.write_scalar(schema, value.as_ref())
495 }
496
497 fn write_big_decimal(
498 &mut self,
499 schema: &Schema<'_>,
500 value: &BigDecimal,
501 ) -> Result<(), SerdeError> {
502 self.write_scalar(schema, value.as_ref())
503 }
504
505 fn write_blob(&mut self, schema: &Schema<'_>, value: Blob) -> Result<(), SerdeError> {
506 self.write_scalar(schema, &aws_smithy_types::base64::encode(value.as_ref()))
507 }
508
509 fn write_timestamp(&mut self, schema: &Schema<'_>, value: &DateTime) -> Result<(), SerdeError> {
510 let format = if let Some(ts_trait) = schema.timestamp_format() {
511 match ts_trait.format() {
512 aws_smithy_schema::traits::TimestampFormat::EpochSeconds => {
513 aws_smithy_types::date_time::Format::EpochSeconds
514 }
515 aws_smithy_schema::traits::TimestampFormat::HttpDate => {
516 aws_smithy_types::date_time::Format::HttpDate
517 }
518 aws_smithy_schema::traits::TimestampFormat::DateTime => {
519 aws_smithy_types::date_time::Format::DateTime
520 }
521 other => {
522 return Err(SerdeError::unsupported(format!(
523 "unsupported timestamp format {other:?}"
524 )))
525 }
526 }
527 } else {
528 aws_smithy_types::date_time::Format::DateTime
529 };
530 let formatted = value
531 .fmt(format)
532 .map_err(|e| SerdeError::custom(format!("timestamp format error: {e}")))?;
533 self.write_scalar(schema, &formatted)
534 }
535
536 fn write_document(&mut self, _: &Schema<'_>, _: &Document) -> Result<(), SerdeError> {
537 Err(SerdeError::unsupported(
538 "documents not supported in awsQuery",
539 ))
540 }
541
542 fn write_null(&mut self, _: &Schema<'_>) -> Result<(), SerdeError> {
543 Ok(())
547 }
548}
549
550#[cfg(test)]
551mod tests {
552 use super::*;
553 use aws_smithy_schema::{shape_id, ShapeType};
554
555 static NAME_MEMBER: Schema<'static> =
556 Schema::new_member(shape_id!("test", "Input"), ShapeType::String, "Name", 0);
557 static AGE_MEMBER: Schema<'static> =
558 Schema::new_member(shape_id!("test", "Input"), ShapeType::Integer, "Age", 1);
559 static INPUT_SCHEMA: Schema<'static> = Schema::new_struct(
560 shape_id!("test", "Input"),
561 ShapeType::Structure,
562 &[&NAME_MEMBER, &AGE_MEMBER],
563 );
564
565 struct SimpleInput;
566 impl SerializableStruct for SimpleInput {
567 fn schema(&self) -> &Schema<'_> {
568 &INPUT_SCHEMA
569 }
570
571 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
572 s.write_string(&NAME_MEMBER, "Alice")?;
573 s.write_integer(&AGE_MEMBER, 30)
574 }
575 }
576
577 #[test]
578 fn simple_struct() {
579 let mut ser = QueryShapeSerializer::new("GetUser", "2012-11-05");
580 ser.write_struct(&INPUT_SCHEMA, &SimpleInput).unwrap();
581 let output = String::from_utf8(ser.finish()).unwrap();
582 assert_eq!(
583 output,
584 "Action=GetUser&Version=2012-11-05&Name=Alice&Age=30"
585 );
586 }
587
588 #[test]
589 fn boolean_values() {
590 static M: Schema<'static> =
591 Schema::new_member(shape_id!("test", "I"), ShapeType::Boolean, "Verbose", 0);
592 let mut ser = QueryShapeSerializer::new("Op", "1.0");
593 ser.write_boolean(&M, true).unwrap();
594 assert_eq!(
595 String::from_utf8(ser.finish()).unwrap(),
596 "Action=Op&Version=1.0&Verbose=true"
597 );
598 }
599
600 #[test]
601 fn string_encoding() {
602 static M: Schema<'static> =
603 Schema::new_member(shape_id!("test", "I"), ShapeType::String, "Message", 0);
604 let mut ser = QueryShapeSerializer::new("Op", "1.0");
605 ser.write_string(&M, "hello world&foo=bar").unwrap();
606 assert_eq!(
607 String::from_utf8(ser.finish()).unwrap(),
608 "Action=Op&Version=1.0&Message=hello%20world%26foo%3Dbar"
609 );
610 }
611
612 #[test]
613 fn nested_struct() {
614 static INNER_FIELD: Schema<'static> =
615 Schema::new_member(shape_id!("test", "Inner"), ShapeType::String, "Value", 0);
616 static OUTER_MEMBER: Schema<'static> = Schema::new_member(
617 shape_id!("test", "Outer"),
618 ShapeType::Structure,
619 "Config",
620 0,
621 );
622
623 struct Inner;
624 impl SerializableStruct for Inner {
625 fn schema(&self) -> &Schema<'_> {
626 static SCHEMA: Schema<'static> = Schema::new_struct(
627 aws_smithy_schema::shape_id!("test", "Inner"),
628 aws_smithy_schema::ShapeType::Structure,
629 &[&INNER_FIELD],
630 );
631 &SCHEMA
632 }
633
634 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
635 s.write_string(&INNER_FIELD, "hello")
636 }
637 }
638
639 let mut ser = QueryShapeSerializer::new("Op", "1.0");
640 ser.write_struct(&OUTER_MEMBER, &Inner).unwrap();
641 assert_eq!(
642 String::from_utf8(ser.finish()).unwrap(),
643 "Action=Op&Version=1.0&Config.Value=hello"
644 );
645 }
646
647 #[test]
648 fn list_non_flat() {
649 static M: Schema<'static> =
650 Schema::new_member(shape_id!("test", "I"), ShapeType::List, "Items", 0);
651 let mut ser = QueryShapeSerializer::new("Op", "1.0");
652 ser.write_list(&M, &|s| {
653 s.write_string(&aws_smithy_schema::prelude::STRING, "foo")?;
654 s.write_string(&aws_smithy_schema::prelude::STRING, "bar")
655 })
656 .unwrap();
657 assert_eq!(
658 String::from_utf8(ser.finish()).unwrap(),
659 "Action=Op&Version=1.0&Items.member.1=foo&Items.member.2=bar"
660 );
661 }
662
663 #[test]
664 fn list_flat() {
665 static M: Schema<'static> =
666 Schema::new_member(shape_id!("test", "I"), ShapeType::List, "Items", 0)
667 .with_xml_flattened();
668 let mut ser = QueryShapeSerializer::new("Op", "1.0");
669 ser.write_list(&M, &|s| {
670 s.write_string(&aws_smithy_schema::prelude::STRING, "A")?;
671 s.write_string(&aws_smithy_schema::prelude::STRING, "B")
672 })
673 .unwrap();
674 assert_eq!(
675 String::from_utf8(ser.finish()).unwrap(),
676 "Action=Op&Version=1.0&Items.1=A&Items.2=B"
677 );
678 }
679
680 #[test]
681 fn list_of_integers() {
682 static M: Schema<'static> =
683 Schema::new_member(shape_id!("test", "I"), ShapeType::List, "Ids", 0);
684 let mut ser = QueryShapeSerializer::new("Op", "1.0");
685 ser.write_list(&M, &|s| {
686 s.write_integer(&aws_smithy_schema::prelude::INTEGER, 10)?;
687 s.write_integer(&aws_smithy_schema::prelude::INTEGER, 20)?;
688 s.write_integer(&aws_smithy_schema::prelude::INTEGER, 30)
689 })
690 .unwrap();
691 assert_eq!(
692 String::from_utf8(ser.finish()).unwrap(),
693 "Action=Op&Version=1.0&Ids.member.1=10&Ids.member.2=20&Ids.member.3=30"
694 );
695 }
696
697 #[test]
698 fn map_non_flat() {
699 static M: Schema<'static> =
700 Schema::new_member(shape_id!("test", "I"), ShapeType::Map, "Tags", 0);
701 let mut ser = QueryShapeSerializer::new("Op", "1.0");
702 ser.write_map(&M, &|s| {
703 s.write_string(&aws_smithy_schema::prelude::STRING, "color")?;
704 s.write_string(&aws_smithy_schema::prelude::STRING, "red")?;
705 s.write_string(&aws_smithy_schema::prelude::STRING, "size")?;
706 s.write_string(&aws_smithy_schema::prelude::STRING, "large")
707 })
708 .unwrap();
709 assert_eq!(
710 String::from_utf8(ser.finish()).unwrap(),
711 "Action=Op&Version=1.0\
712 &Tags.entry.1.key=color&Tags.entry.1.value=red\
713 &Tags.entry.2.key=size&Tags.entry.2.value=large"
714 );
715 }
716
717 #[test]
718 fn list_non_flat_uses_renamed_member_name() {
719 static ITEM: Schema<'static> = Schema::new_member(
724 shape_id!("test", "L$member"),
725 ShapeType::String,
726 "member",
727 0,
728 )
729 .with_xml_name("Item");
730 static M: Schema<'static> =
731 Schema::new_member(shape_id!("test", "I"), ShapeType::List, "Items", 0)
732 .with_list_member(&ITEM);
733 let mut ser = QueryShapeSerializer::new("Op", "1.0");
734 ser.write_list(&M, &|s| {
735 s.write_string(&aws_smithy_schema::prelude::STRING, "foo")?;
736 s.write_string(&aws_smithy_schema::prelude::STRING, "bar")
737 })
738 .unwrap();
739 assert_eq!(
740 String::from_utf8(ser.finish()).unwrap(),
741 "Action=Op&Version=1.0&Items.Item.1=foo&Items.Item.2=bar"
742 );
743 }
744
745 #[test]
746 fn map_non_flat_uses_renamed_key_value_names() {
747 static KEY: Schema<'static> =
750 Schema::new_member(shape_id!("test", "M$key"), ShapeType::String, "key", 0)
751 .with_xml_name("Attribute");
752 static VALUE: Schema<'static> =
753 Schema::new_member(shape_id!("test", "M$value"), ShapeType::String, "value", 1)
754 .with_xml_name("Setting");
755 static M: Schema<'static> =
756 Schema::new_member(shape_id!("test", "I"), ShapeType::Map, "Tags", 0)
757 .with_map_members(&KEY, &VALUE);
758 let mut ser = QueryShapeSerializer::new("Op", "1.0");
759 ser.write_map(&M, &|s| {
760 s.write_string(&aws_smithy_schema::prelude::STRING, "color")?;
761 s.write_string(&aws_smithy_schema::prelude::STRING, "red")
762 })
763 .unwrap();
764 assert_eq!(
765 String::from_utf8(ser.finish()).unwrap(),
766 "Action=Op&Version=1.0&Tags.entry.1.Attribute=color&Tags.entry.1.Setting=red"
767 );
768 }
769
770 #[test]
771 fn map_flat() {
772 static M: Schema<'static> =
773 Schema::new_member(shape_id!("test", "I"), ShapeType::Map, "Tags", 0)
774 .with_xml_flattened();
775 let mut ser = QueryShapeSerializer::new("Op", "1.0");
776 ser.write_map(&M, &|s| {
777 s.write_string(&aws_smithy_schema::prelude::STRING, "k1")?;
778 s.write_string(&aws_smithy_schema::prelude::STRING, "v1")
779 })
780 .unwrap();
781 assert_eq!(
782 String::from_utf8(ser.finish()).unwrap(),
783 "Action=Op&Version=1.0&Tags.1.key=k1&Tags.1.value=v1"
784 );
785 }
786}
787
788#[cfg(test)]
789mod cross_validation {
790 use super::*;
791 use crate::QueryWriter;
792 use aws_smithy_schema::{shape_id, ShapeType};
793
794 #[test]
795 fn matches_query_writer_simple_params() {
796 let mut expected = String::new();
797 let mut writer = QueryWriter::new(&mut expected, "SomeAction", "1.0");
798 writer.prefix("Name").string("Alice");
799 writer
800 .prefix("Age")
801 .number(aws_smithy_types::Number::PosInt(30));
802 writer.finish();
803
804 static NAME: Schema<'static> =
805 Schema::new_member(shape_id!("test", "I"), ShapeType::String, "Name", 0);
806 static AGE: Schema<'static> =
807 Schema::new_member(shape_id!("test", "I"), ShapeType::Integer, "Age", 1);
808 static SCHEMA: Schema<'static> =
809 Schema::new_struct(shape_id!("test", "I"), ShapeType::Structure, &[&NAME, &AGE]);
810 struct Input;
811 impl SerializableStruct for Input {
812 fn schema(&self) -> &Schema<'_> {
813 &SCHEMA
814 }
815
816 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
817 s.write_string(&NAME, "Alice")?;
818 s.write_integer(&AGE, 30)
819 }
820 }
821
822 let mut ser = QueryShapeSerializer::new("SomeAction", "1.0");
823 ser.write_struct(&SCHEMA, &Input).unwrap();
824 assert_eq!(String::from_utf8(ser.finish()).unwrap(), expected);
825 }
826
827 #[test]
828 fn matches_query_writer_list() {
829 let mut expected = String::new();
830 let mut writer = QueryWriter::new(&mut expected, "Op", "1.0");
831 let mut list = writer.prefix("ListArg").start_list(false, None);
832 list.entry().string("foo");
833 list.entry().string("bar");
834 list.entry().string("baz");
835 list.finish();
836 writer.finish();
837
838 static M: Schema<'static> =
839 Schema::new_member(shape_id!("test", "I"), ShapeType::List, "ListArg", 0);
840 let mut ser = QueryShapeSerializer::new("Op", "1.0");
841 ser.write_list(&M, &|s| {
842 s.write_string(&aws_smithy_schema::prelude::STRING, "foo")?;
843 s.write_string(&aws_smithy_schema::prelude::STRING, "bar")?;
844 s.write_string(&aws_smithy_schema::prelude::STRING, "baz")
845 })
846 .unwrap();
847 assert_eq!(String::from_utf8(ser.finish()).unwrap(), expected);
848 }
849
850 #[test]
851 fn matches_query_writer_flat_list() {
852 let mut expected = String::new();
853 let mut writer = QueryWriter::new(&mut expected, "Op", "1.0");
854 let mut list = writer.prefix("FlatList").start_list(true, None);
855 list.entry().string("A");
856 list.entry().string("B");
857 list.finish();
858 writer.finish();
859
860 static M: Schema<'static> =
861 Schema::new_member(shape_id!("test", "I"), ShapeType::List, "FlatList", 0)
862 .with_xml_flattened();
863 let mut ser = QueryShapeSerializer::new("Op", "1.0");
864 ser.write_list(&M, &|s| {
865 s.write_string(&aws_smithy_schema::prelude::STRING, "A")?;
866 s.write_string(&aws_smithy_schema::prelude::STRING, "B")
867 })
868 .unwrap();
869 assert_eq!(String::from_utf8(ser.finish()).unwrap(), expected);
870 }
871
872 #[test]
873 fn matches_query_writer_map() {
874 let mut expected = String::new();
875 let mut writer = QueryWriter::new(&mut expected, "Op", "1.0");
876 let mut map = writer.prefix("MapArg").start_map(false, "key", "value");
877 map.entry("bar").string("Bar");
878 map.entry("foo").string("Foo");
879 map.finish();
880 writer.finish();
881
882 static M: Schema<'static> =
883 Schema::new_member(shape_id!("test", "I"), ShapeType::Map, "MapArg", 0);
884 let mut ser = QueryShapeSerializer::new("Op", "1.0");
885 ser.write_map(&M, &|s| {
886 s.write_string(&aws_smithy_schema::prelude::STRING, "bar")?;
887 s.write_string(&aws_smithy_schema::prelude::STRING, "Bar")?;
888 s.write_string(&aws_smithy_schema::prelude::STRING, "foo")?;
889 s.write_string(&aws_smithy_schema::prelude::STRING, "Foo")
890 })
891 .unwrap();
892 assert_eq!(String::from_utf8(ser.finish()).unwrap(), expected);
893 }
894
895 #[test]
896 fn matches_query_writer_nested_prefix() {
897 let mut expected = String::new();
898 let mut writer = QueryWriter::new(&mut expected, "Op", "1.0");
899 writer.prefix("first").prefix("second").string("val");
900 writer.finish();
901
902 static SECOND: Schema<'static> =
903 Schema::new_member(shape_id!("test", "Inner"), ShapeType::String, "second", 0);
904 static FIRST: Schema<'static> =
905 Schema::new_member(shape_id!("test", "Outer"), ShapeType::Structure, "first", 0);
906
907 struct Inner;
908 impl SerializableStruct for Inner {
909 fn schema(&self) -> &Schema<'_> {
910 static SCHEMA: Schema<'static> = Schema::new_struct(
911 aws_smithy_schema::shape_id!("test", "Inner"),
912 aws_smithy_schema::ShapeType::Structure,
913 &[&SECOND],
914 );
915 &SCHEMA
916 }
917
918 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
919 s.write_string(&SECOND, "val")
920 }
921 }
922
923 let mut ser = QueryShapeSerializer::new("Op", "1.0");
924 ser.write_struct(&FIRST, &Inner).unwrap();
925 assert_eq!(String::from_utf8(ser.finish()).unwrap(), expected);
926 }
927
928 #[test]
929 fn nested_renamed_inner_list_member_matches_legacy() {
930 static INNER_MEMBER: Schema<'static> = Schema::new_member(
937 shape_id!("test", "L$member"),
938 ShapeType::String,
939 "member",
940 0,
941 )
942 .with_xml_name("item");
943 static VALUE_LIST: Schema<'static> =
945 Schema::new_member(shape_id!("test", "M$value"), ShapeType::List, "value", 1)
946 .with_list_member(&INNER_MEMBER);
947 static KEY: Schema<'static> =
948 Schema::new_member(shape_id!("test", "M$key"), ShapeType::String, "key", 0);
949 static OUTER_MAP: Schema<'static> =
950 Schema::new_member(shape_id!("test", "I"), ShapeType::Map, "MapOfLists", 0)
951 .with_map_members(&KEY, &VALUE_LIST);
952
953 let mut ser = QueryShapeSerializer::new("Op", "1.0");
954 ser.write_map(&OUTER_MAP, &|s| {
955 s.write_string(&aws_smithy_schema::prelude::STRING, "bar")?;
956 s.write_list(&aws_smithy_schema::prelude::DOCUMENT, &|inner| {
958 inner.write_string(&aws_smithy_schema::prelude::STRING, "C")?;
959 inner.write_string(&aws_smithy_schema::prelude::STRING, "D")
960 })
961 })
962 .unwrap();
963 let schema_out = String::from_utf8(ser.finish()).unwrap();
964
965 let mut expected = String::new();
966 let mut writer = QueryWriter::new(&mut expected, "Op", "1.0");
967 let mut map = writer.prefix("MapOfLists").start_map(false, "key", "value");
968 {
969 let mut list = map.entry("bar").start_list(false, Some("item"));
970 list.entry().string("C");
971 list.entry().string("D");
972 list.finish();
973 }
974 map.finish();
975 writer.finish();
976
977 assert_eq!(schema_out, expected);
978 assert_eq!(
979 schema_out,
980 "Action=Op&Version=1.0&MapOfLists.entry.1.key=bar\
981 &MapOfLists.entry.1.value.item.1=C\
982 &MapOfLists.entry.1.value.item.2=D"
983 );
984 }
985
986 #[test]
987 fn nested_renamed_inner_map_members_matches_legacy() {
988 static INNER_KEY: Schema<'static> =
993 Schema::new_member(shape_id!("test", "IM$key"), ShapeType::String, "key", 0)
994 .with_xml_name("K");
995 static INNER_VALUE: Schema<'static> =
996 Schema::new_member(shape_id!("test", "IM$value"), ShapeType::String, "value", 1)
997 .with_xml_name("V");
998 static VALUE_MAP: Schema<'static> =
1000 Schema::new_member(shape_id!("test", "M$value"), ShapeType::Map, "value", 1)
1001 .with_map_members(&INNER_KEY, &INNER_VALUE);
1002 static OUTER_KEY: Schema<'static> =
1003 Schema::new_member(shape_id!("test", "M$key"), ShapeType::String, "key", 0);
1004 static OUTER_MAP: Schema<'static> =
1005 Schema::new_member(shape_id!("test", "I"), ShapeType::Map, "MapOfMaps", 0)
1006 .with_map_members(&OUTER_KEY, &VALUE_MAP);
1007
1008 let mut ser = QueryShapeSerializer::new("Op", "1.0");
1009 ser.write_map(&OUTER_MAP, &|s| {
1010 s.write_string(&aws_smithy_schema::prelude::STRING, "outer")?;
1011 s.write_map(&aws_smithy_schema::prelude::DOCUMENT, &|inner| {
1012 inner.write_string(&aws_smithy_schema::prelude::STRING, "ik")?;
1013 inner.write_string(&aws_smithy_schema::prelude::STRING, "iv")
1014 })
1015 })
1016 .unwrap();
1017 let schema_out = String::from_utf8(ser.finish()).unwrap();
1018
1019 assert_eq!(
1020 schema_out,
1021 "Action=Op&Version=1.0&MapOfMaps.entry.1.key=outer\
1022 &MapOfMaps.entry.1.value.entry.1.K=ik\
1023 &MapOfMaps.entry.1.value.entry.1.V=iv"
1024 );
1025 }
1026}
1027
1028#[cfg(test)]
1029mod edge_cases {
1030 use super::*;
1031 use crate::QueryWriter;
1032 use aws_smithy_schema::{shape_id, ShapeType};
1033
1034 #[test]
1035 fn empty_string_value() {
1036 let mut expected = String::new();
1037 let mut writer = QueryWriter::new(&mut expected, "Op", "1.0");
1038 writer.prefix("Empty").string("");
1039 writer.finish();
1040
1041 static M: Schema<'static> =
1042 Schema::new_member(shape_id!("test", "I"), ShapeType::String, "Empty", 0);
1043 let mut ser = QueryShapeSerializer::new("Op", "1.0");
1044 ser.write_string(&M, "").unwrap();
1045 assert_eq!(String::from_utf8(ser.finish()).unwrap(), expected);
1046 }
1047
1048 #[test]
1049 fn empty_non_flat_list_emits_bare_param() {
1050 let mut expected = String::new();
1052 let mut writer = QueryWriter::new(&mut expected, "Op", "1.0");
1053 writer.prefix("myList").start_list(false, None).finish();
1054 writer.finish();
1055
1056 static M: Schema<'static> =
1057 Schema::new_member(shape_id!("test", "I"), ShapeType::List, "myList", 0);
1058 let mut ser = QueryShapeSerializer::new("Op", "1.0");
1059 ser.write_list(&M, &|_| Ok(())).unwrap();
1060 assert_eq!(String::from_utf8(ser.finish()).unwrap(), expected);
1061 assert_eq!(expected, "Action=Op&Version=1.0&myList=");
1062 }
1063
1064 #[test]
1065 fn empty_flat_list_emits_bare_param() {
1066 let mut expected = String::new();
1067 let mut writer = QueryWriter::new(&mut expected, "Op", "1.0");
1068 writer.prefix("myList").start_list(true, None).finish();
1069 writer.finish();
1070
1071 static M: Schema<'static> =
1072 Schema::new_member(shape_id!("test", "I"), ShapeType::List, "myList", 0)
1073 .with_xml_flattened();
1074 let mut ser = QueryShapeSerializer::new("Op", "1.0");
1075 ser.write_list(&M, &|_| Ok(())).unwrap();
1076 assert_eq!(String::from_utf8(ser.finish()).unwrap(), expected);
1077 assert_eq!(expected, "Action=Op&Version=1.0&myList=");
1078 }
1079
1080 #[test]
1081 fn empty_list_does_not_alias_single_empty_string_element() {
1082 static EMPTY: Schema<'static> =
1086 Schema::new_member(shape_id!("test", "I"), ShapeType::List, "myList", 0);
1087 let mut empty_ser = QueryShapeSerializer::new("Op", "1.0");
1088 empty_ser.write_list(&EMPTY, &|_| Ok(())).unwrap();
1089 let empty_out = String::from_utf8(empty_ser.finish()).unwrap();
1090
1091 static ONE: Schema<'static> =
1092 Schema::new_member(shape_id!("test", "I"), ShapeType::List, "myList", 0);
1093 let mut one_ser = QueryShapeSerializer::new("Op", "1.0");
1094 one_ser
1095 .write_list(&ONE, &|s| {
1096 s.write_string(&aws_smithy_schema::prelude::STRING, "")
1097 })
1098 .unwrap();
1099 let one_out = String::from_utf8(one_ser.finish()).unwrap();
1100
1101 assert_eq!(empty_out, "Action=Op&Version=1.0&myList=");
1102 assert_eq!(one_out, "Action=Op&Version=1.0&myList.member.1=");
1103 assert_ne!(empty_out, one_out);
1104 }
1105
1106 #[test]
1107 fn sparse_null_list_element_is_dropped_and_index_reflects_emitted_only() {
1108 static M: Schema<'static> =
1111 Schema::new_member(shape_id!("test", "I"), ShapeType::List, "Items", 0);
1112 let mut ser = QueryShapeSerializer::new("Op", "1.0");
1113 ser.write_list(&M, &|s| {
1114 s.write_string(&aws_smithy_schema::prelude::STRING, "a")?;
1115 s.write_null(&aws_smithy_schema::prelude::STRING)?;
1116 s.write_string(&aws_smithy_schema::prelude::STRING, "b")
1117 })
1118 .unwrap();
1119 assert_eq!(
1120 String::from_utf8(ser.finish()).unwrap(),
1121 "Action=Op&Version=1.0&Items.member.1=a&Items.member.2=b"
1122 );
1123 }
1124
1125 #[test]
1126 fn special_characters() {
1127 let mut expected = String::new();
1128 let mut writer = QueryWriter::new(&mut expected, "Op", "1.0");
1129 writer.prefix("Val").string("a=b&c<d>e\"f");
1130 writer.finish();
1131
1132 static M: Schema<'static> =
1133 Schema::new_member(shape_id!("test", "I"), ShapeType::String, "Val", 0);
1134 let mut ser = QueryShapeSerializer::new("Op", "1.0");
1135 ser.write_string(&M, "a=b&c<d>e\"f").unwrap();
1136 assert_eq!(String::from_utf8(ser.finish()).unwrap(), expected);
1137 }
1138
1139 #[test]
1140 fn unicode_value() {
1141 let mut expected = String::new();
1142 let mut writer = QueryWriter::new(&mut expected, "Op", "1.0");
1143 writer.prefix("Name").string("日本語");
1144 writer.finish();
1145
1146 static M: Schema<'static> =
1147 Schema::new_member(shape_id!("test", "I"), ShapeType::String, "Name", 0);
1148 let mut ser = QueryShapeSerializer::new("Op", "1.0");
1149 ser.write_string(&M, "日本語").unwrap();
1150 assert_eq!(String::from_utf8(ser.finish()).unwrap(), expected);
1151 }
1152
1153 #[test]
1154 fn timestamp_epoch_seconds() {
1155 let mut expected = String::new();
1156 let mut writer = QueryWriter::new(&mut expected, "Op", "1.0");
1157 writer
1158 .prefix("Time")
1159 .date_time(
1160 &aws_smithy_types::DateTime::from_secs(1700000000),
1161 aws_smithy_types::date_time::Format::DateTime,
1162 )
1163 .unwrap();
1164 writer.finish();
1165
1166 static M: Schema<'static> =
1167 Schema::new_member(shape_id!("test", "I"), ShapeType::Timestamp, "Time", 0);
1168 let mut ser = QueryShapeSerializer::new("Op", "1.0");
1169 ser.write_timestamp(&M, &aws_smithy_types::DateTime::from_secs(1700000000))
1170 .unwrap();
1171 assert_eq!(String::from_utf8(ser.finish()).unwrap(), expected);
1172 }
1173
1174 #[test]
1175 fn deeply_nested_struct() {
1176 let mut expected = String::new();
1177 let mut writer = QueryWriter::new(&mut expected, "Op", "1.0");
1178 writer.prefix("A").prefix("B").prefix("C").string("deep");
1179 writer.finish();
1180
1181 static C_FIELD: Schema<'static> =
1182 Schema::new_member(shape_id!("t", "C"), ShapeType::String, "C", 0);
1183 static B_MEMBER: Schema<'static> =
1184 Schema::new_member(shape_id!("t", "B"), ShapeType::Structure, "B", 0);
1185 static A_MEMBER: Schema<'static> =
1186 Schema::new_member(shape_id!("t", "A"), ShapeType::Structure, "A", 0);
1187
1188 struct CStruct;
1189 impl SerializableStruct for CStruct {
1190 fn schema(&self) -> &Schema<'_> {
1191 static SCHEMA: Schema<'static> = Schema::new_struct(
1192 aws_smithy_schema::shape_id!("t", "C"),
1193 aws_smithy_schema::ShapeType::Structure,
1194 &[&C_FIELD],
1195 );
1196 &SCHEMA
1197 }
1198
1199 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1200 s.write_string(&C_FIELD, "deep")
1201 }
1202 }
1203 struct BStruct;
1204 impl SerializableStruct for BStruct {
1205 fn schema(&self) -> &Schema<'_> {
1206 static SCHEMA: Schema<'static> = Schema::new_struct(
1207 aws_smithy_schema::shape_id!("t", "B"),
1208 aws_smithy_schema::ShapeType::Structure,
1209 &[&B_MEMBER],
1210 );
1211 &SCHEMA
1212 }
1213
1214 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1215 s.write_struct(&B_MEMBER, &CStruct)
1216 }
1217 }
1218
1219 let mut ser = QueryShapeSerializer::new("Op", "1.0");
1220 ser.write_struct(&A_MEMBER, &BStruct).unwrap();
1221 assert_eq!(String::from_utf8(ser.finish()).unwrap(), expected);
1222 }
1223
1224 #[test]
1225 fn list_inside_struct() {
1226 let mut expected = String::new();
1227 let mut writer = QueryWriter::new(&mut expected, "Op", "1.0");
1228 let mut outer = writer.prefix("Outer");
1229 let mut list = outer.prefix("Items").start_list(false, None);
1230 list.entry().string("x");
1231 list.entry().string("y");
1232 list.finish();
1233 writer.finish();
1234
1235 static ITEMS: Schema<'static> =
1236 Schema::new_member(shape_id!("t", "S"), ShapeType::List, "Items", 0);
1237 static OUTER: Schema<'static> =
1238 Schema::new_member(shape_id!("t", "T"), ShapeType::Structure, "Outer", 0);
1239
1240 struct Inner;
1241 impl SerializableStruct for Inner {
1242 fn schema(&self) -> &Schema<'_> {
1243 static SCHEMA: Schema<'static> = Schema::new_struct(
1244 aws_smithy_schema::shape_id!("t", "T"),
1245 aws_smithy_schema::ShapeType::Structure,
1246 &[&ITEMS],
1247 );
1248 &SCHEMA
1249 }
1250
1251 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1252 s.write_list(&ITEMS, &|s| {
1253 s.write_string(&aws_smithy_schema::prelude::STRING, "x")?;
1254 s.write_string(&aws_smithy_schema::prelude::STRING, "y")
1255 })
1256 }
1257 }
1258
1259 let mut ser = QueryShapeSerializer::new("Op", "1.0");
1260 ser.write_struct(&OUTER, &Inner).unwrap();
1261 assert_eq!(String::from_utf8(ser.finish()).unwrap(), expected);
1262 }
1263
1264 #[test]
1265 fn multiple_fields_with_list_and_scalar() {
1266 let mut expected = String::new();
1267 let mut writer = QueryWriter::new(&mut expected, "Op", "1.0");
1268 writer.prefix("Name").string("Alice");
1269 let mut list = writer.prefix("Tags").start_list(false, None);
1270 list.entry().string("admin");
1271 list.entry().string("user");
1272 list.finish();
1273 writer
1274 .prefix("Age")
1275 .number(aws_smithy_types::Number::PosInt(30));
1276 writer.finish();
1277
1278 static NAME: Schema<'static> =
1279 Schema::new_member(shape_id!("t", "S"), ShapeType::String, "Name", 0);
1280 static TAGS: Schema<'static> =
1281 Schema::new_member(shape_id!("t", "S"), ShapeType::List, "Tags", 1);
1282 static AGE: Schema<'static> =
1283 Schema::new_member(shape_id!("t", "S"), ShapeType::Integer, "Age", 2);
1284 static SCHEMA: Schema<'static> = Schema::new_struct(
1285 shape_id!("t", "S"),
1286 ShapeType::Structure,
1287 &[&NAME, &TAGS, &AGE],
1288 );
1289
1290 struct Input;
1291 impl SerializableStruct for Input {
1292 fn schema(&self) -> &Schema<'_> {
1293 &SCHEMA
1294 }
1295
1296 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1297 s.write_string(&NAME, "Alice")?;
1298 s.write_list(&TAGS, &|s| {
1299 s.write_string(&aws_smithy_schema::prelude::STRING, "admin")?;
1300 s.write_string(&aws_smithy_schema::prelude::STRING, "user")
1301 })?;
1302 s.write_integer(&AGE, 30)
1303 }
1304 }
1305
1306 let mut ser = QueryShapeSerializer::new("Op", "1.0");
1307 ser.write_struct(&SCHEMA, &Input).unwrap();
1308 assert_eq!(String::from_utf8(ser.finish()).unwrap(), expected);
1309 }
1310
1311 #[test]
1312 fn list_of_struct_emits_per_element_index() {
1313 static NAME: Schema<'static> =
1314 Schema::new_member(shape_id!("test", "Item"), ShapeType::String, "Name", 0);
1315 static ITEM_SCHEMA: Schema<'static> =
1316 Schema::new_struct(shape_id!("test", "Item"), ShapeType::Structure, &[&NAME]);
1317 static ITEMS_LIST: Schema<'static> =
1318 Schema::new_member(shape_id!("test", "I"), ShapeType::List, "Items", 0);
1319
1320 struct Item(&'static str);
1321 impl SerializableStruct for Item {
1322 fn schema(&self) -> &Schema<'_> {
1323 &ITEM_SCHEMA
1324 }
1325
1326 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1327 s.write_string(&NAME, self.0)
1328 }
1329 }
1330
1331 let mut ser = QueryShapeSerializer::new("Op", "1.0");
1332 ser.write_list(&ITEMS_LIST, &|s| {
1333 s.write_struct(&ITEM_SCHEMA, &Item("X1"))?;
1334 s.write_struct(&ITEM_SCHEMA, &Item("X2"))
1335 })
1336 .unwrap();
1337
1338 assert_eq!(
1339 String::from_utf8(ser.finish()).unwrap(),
1340 "Action=Op&Version=1.0&Items.member.1.Name=X1&Items.member.2.Name=X2",
1341 );
1342 }
1343
1344 #[test]
1345 fn list_of_list() {
1346 static OUTER: Schema<'static> =
1347 Schema::new_member(shape_id!("test", "I"), ShapeType::List, "Outer", 0);
1348 static INNER_LIST: Schema<'static> = Schema::new_list(
1349 shape_id!("test", "InnerList"),
1350 &aws_smithy_schema::prelude::STRING,
1351 );
1352
1353 let mut ser = QueryShapeSerializer::new("Op", "1.0");
1354 ser.write_list(&OUTER, &|s_outer| {
1355 s_outer.write_list(&INNER_LIST, &|s_inner| {
1356 s_inner.write_string(&aws_smithy_schema::prelude::STRING, "a")?;
1357 s_inner.write_string(&aws_smithy_schema::prelude::STRING, "b")
1358 })?;
1359 s_outer.write_list(&INNER_LIST, &|s_inner| {
1360 s_inner.write_string(&aws_smithy_schema::prelude::STRING, "c")
1361 })
1362 })
1363 .unwrap();
1364
1365 assert_eq!(
1366 String::from_utf8(ser.finish()).unwrap(),
1367 "Action=Op&Version=1.0\
1368 &Outer.member.1.member.1=a\
1369 &Outer.member.1.member.2=b\
1370 &Outer.member.2.member.1=c",
1371 );
1372 }
1373
1374 #[test]
1375 fn map_of_struct_emits_per_entry_index() {
1376 static NAME: Schema<'static> =
1377 Schema::new_member(shape_id!("test", "Val"), ShapeType::String, "Name", 0);
1378 static VAL_SCHEMA: Schema<'static> =
1379 Schema::new_struct(shape_id!("test", "Val"), ShapeType::Structure, &[&NAME]);
1380 static MAP_MEMBER: Schema<'static> =
1381 Schema::new_member(shape_id!("test", "I"), ShapeType::Map, "Things", 0);
1382
1383 struct Val(&'static str);
1384 impl SerializableStruct for Val {
1385 fn schema(&self) -> &Schema<'_> {
1386 &VAL_SCHEMA
1387 }
1388
1389 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1390 s.write_string(&NAME, self.0)
1391 }
1392 }
1393
1394 let mut ser = QueryShapeSerializer::new("Op", "1.0");
1395 ser.write_map(&MAP_MEMBER, &|s| {
1396 s.write_string(&aws_smithy_schema::prelude::STRING, "k1")?;
1397 s.write_struct(&VAL_SCHEMA, &Val("v1"))?;
1398 s.write_string(&aws_smithy_schema::prelude::STRING, "k2")?;
1399 s.write_struct(&VAL_SCHEMA, &Val("v2"))
1400 })
1401 .unwrap();
1402
1403 assert_eq!(
1404 String::from_utf8(ser.finish()).unwrap(),
1405 "Action=Op&Version=1.0\
1406 &Things.entry.1.key=k1&Things.entry.1.value.Name=v1\
1407 &Things.entry.2.key=k2&Things.entry.2.value.Name=v2",
1408 );
1409 }
1410
1411 #[test]
1412 fn map_nested_in_list() {
1413 static MAP_SCHEMA: Schema<'static> = Schema::new_map(
1414 shape_id!("test", "M"),
1415 &aws_smithy_schema::prelude::STRING,
1416 &aws_smithy_schema::prelude::STRING,
1417 );
1418 static OUTER: Schema<'static> =
1419 Schema::new_member(shape_id!("test", "I"), ShapeType::List, "Items", 0);
1420
1421 let mut ser = QueryShapeSerializer::new("Op", "1.0");
1422 ser.write_list(&OUTER, &|s| {
1423 s.write_map(&MAP_SCHEMA, &|m| {
1424 m.write_string(&aws_smithy_schema::prelude::STRING, "k1")?;
1425 m.write_string(&aws_smithy_schema::prelude::STRING, "v1")
1426 })?;
1427 s.write_map(&MAP_SCHEMA, &|m| {
1428 m.write_string(&aws_smithy_schema::prelude::STRING, "k2")?;
1429 m.write_string(&aws_smithy_schema::prelude::STRING, "v2")
1430 })
1431 })
1432 .unwrap();
1433
1434 assert_eq!(
1435 String::from_utf8(ser.finish()).unwrap(),
1436 "Action=Op&Version=1.0\
1437 &Items.member.1.entry.1.key=k1&Items.member.1.entry.1.value=v1\
1438 &Items.member.2.entry.1.key=k2&Items.member.2.entry.1.value=v2",
1439 );
1440 }
1441
1442 #[test]
1443 fn float_whole_number_keeps_decimal() {
1444 static M: Schema<'static> =
1445 Schema::new_member(shape_id!("test", "I"), ShapeType::Float, "Val", 0);
1446 let mut ser = QueryShapeSerializer::new("Op", "1.0");
1447 ser.write_double(&M, 5.0).unwrap();
1448 assert_eq!(
1449 String::from_utf8(ser.finish()).unwrap(),
1450 "Action=Op&Version=1.0&Val=5.0"
1451 );
1452 }
1453
1454 #[test]
1455 fn float_special_values() {
1456 static M: Schema<'static> =
1457 Schema::new_member(shape_id!("test", "I"), ShapeType::Float, "Val", 0);
1458 let mut ser = QueryShapeSerializer::new("Op", "1.0");
1459 ser.write_float(&M, f32::INFINITY).unwrap();
1460 assert_eq!(
1461 String::from_utf8(ser.finish()).unwrap(),
1462 "Action=Op&Version=1.0&Val=Infinity"
1463 );
1464
1465 let mut ser = QueryShapeSerializer::new("Op", "1.0");
1466 ser.write_float(&M, f32::NEG_INFINITY).unwrap();
1467 assert_eq!(
1468 String::from_utf8(ser.finish()).unwrap(),
1469 "Action=Op&Version=1.0&Val=-Infinity"
1470 );
1471
1472 let mut ser = QueryShapeSerializer::new("Op", "1.0");
1473 ser.write_float(&M, f32::NAN).unwrap();
1474 assert_eq!(
1475 String::from_utf8(ser.finish()).unwrap(),
1476 "Action=Op&Version=1.0&Val=NaN"
1477 );
1478 }
1479
1480 #[test]
1481 fn double_special_values() {
1482 static M: Schema<'static> =
1483 Schema::new_member(shape_id!("test", "I"), ShapeType::Double, "Val", 0);
1484 let mut ser = QueryShapeSerializer::new("Op", "1.0");
1485 ser.write_double(&M, f64::INFINITY).unwrap();
1486 assert_eq!(
1487 String::from_utf8(ser.finish()).unwrap(),
1488 "Action=Op&Version=1.0&Val=Infinity"
1489 );
1490
1491 let mut ser = QueryShapeSerializer::new("Op", "1.0");
1492 ser.write_double(&M, f64::NEG_INFINITY).unwrap();
1493 assert_eq!(
1494 String::from_utf8(ser.finish()).unwrap(),
1495 "Action=Op&Version=1.0&Val=-Infinity"
1496 );
1497 }
1498
1499 #[test]
1500 fn blob_base64_encoded() {
1501 static M: Schema<'static> =
1502 Schema::new_member(shape_id!("test", "I"), ShapeType::Blob, "Data", 0);
1503 let mut ser = QueryShapeSerializer::new("Op", "1.0");
1504 ser.write_blob(&M, aws_smithy_types::Blob::new(&b"hello"[..]))
1505 .unwrap();
1506 assert_eq!(
1507 String::from_utf8(ser.finish()).unwrap(),
1508 "Action=Op&Version=1.0&Data=aGVsbG8%3D"
1509 );
1510 }
1511
1512 #[test]
1513 fn document_returns_error() {
1514 static M: Schema<'static> =
1515 Schema::new_member(shape_id!("test", "I"), ShapeType::Document, "Doc", 0);
1516 let mut ser = QueryShapeSerializer::new("Op", "1.0");
1517 let result = ser.write_document(&M, &Document::Null);
1518 assert!(result.is_err());
1519 }
1520
1521 #[test]
1522 fn write_null_is_noop() {
1523 static M: Schema<'static> =
1524 Schema::new_member(shape_id!("test", "I"), ShapeType::String, "Val", 0);
1525 let mut ser = QueryShapeSerializer::new("Op", "1.0");
1526 ser.write_null(&M).unwrap();
1527 assert_eq!(
1528 String::from_utf8(ser.finish()).unwrap(),
1529 "Action=Op&Version=1.0"
1530 );
1531 }
1532
1533 #[test]
1534 fn numeric_types() {
1535 static B: Schema<'static> =
1536 Schema::new_member(shape_id!("t", "S"), ShapeType::Byte, "B", 0);
1537 static S: Schema<'static> =
1538 Schema::new_member(shape_id!("t", "S"), ShapeType::Short, "S", 0);
1539 static L: Schema<'static> =
1540 Schema::new_member(shape_id!("t", "S"), ShapeType::Long, "L", 0);
1541 let mut ser = QueryShapeSerializer::new("Op", "1.0");
1542 ser.write_byte(&B, -1).unwrap();
1543 ser.write_short(&S, 32000).unwrap();
1544 ser.write_long(&L, 9999999999).unwrap();
1545 assert_eq!(
1546 String::from_utf8(ser.finish()).unwrap(),
1547 "Action=Op&Version=1.0&B=-1&S=32000&L=9999999999"
1548 );
1549 }
1550}