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