Skip to main content

aws_smithy_schema/schema/codec/
http_string.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! String codec for HTTP bindings (headers, query params, URI labels).
7//!
8//! A value written outside a list is emitted verbatim, and a value read outside a list is the
9//! whole input, as for a scalar HTTP header. List elements follow the header-list rules the
10//! generated (non-schema) code has always used: they are joined with `", "`, quoted and escaped
11//! with [`quote_header_value`] when needed, and read back with the RFC 7230 tokenizer from
12//! [`header_parse`], which trims surrounding whitespace and unescapes quoted strings. An empty
13//! element is written as `""` so that it survives the round trip.
14
15use crate::serde::{SerdeError, SerializableStruct, ShapeDeserializer, ShapeSerializer};
16use crate::Schema;
17use aws_smithy_types::{BigDecimal, BigInteger, Blob, DateTime};
18
19use aws_smithy_runtime_api::http::header_parse::{self, quote_header_value};
20use aws_smithy_types::Document;
21use std::borrow::Cow;
22
23/// Serializer for converting Smithy types to strings (for HTTP headers, query params, labels).
24#[derive(Debug)]
25pub struct HttpStringSerializer {
26    output: String,
27    /// True while `write_list` is running its element writer.
28    in_list: bool,
29    /// True once a value has been written at the current level.
30    wrote_value: bool,
31}
32
33impl HttpStringSerializer {
34    /// Creates a new HTTP string serializer.
35    pub fn new() -> Self {
36        Self {
37            output: String::new(),
38            in_list: false,
39            wrote_value: false,
40        }
41    }
42
43    /// Writes one textual value, as a list element when inside a list.
44    fn push_value(&mut self, value: &str) -> Result<(), SerdeError> {
45        if self.in_list {
46            if self.wrote_value {
47                self.output.push_str(", ");
48            }
49            if value.is_empty() {
50                self.output.push_str("\"\"");
51            } else {
52                self.output.push_str(&quote_header_value(value));
53            }
54        } else if self.wrote_value {
55            return Err(SerdeError::write_failed(
56                "only one value can be written outside a list",
57            ));
58        } else {
59            self.output.push_str(value);
60        }
61        self.wrote_value = true;
62        Ok(())
63    }
64
65    /// Finalizes the serialization and returns the output string.
66    pub fn finish(self) -> String {
67        self.output
68    }
69}
70
71impl super::FinishSerializer for HttpStringSerializer {
72    fn finish(self) -> Vec<u8> {
73        self.output.into_bytes()
74    }
75}
76
77impl Default for HttpStringSerializer {
78    fn default() -> Self {
79        Self::new()
80    }
81}
82
83impl ShapeSerializer for HttpStringSerializer {
84    fn write_struct(
85        &mut self,
86        _schema: &Schema<'_>,
87        _value: &dyn SerializableStruct,
88    ) -> Result<(), SerdeError> {
89        Err(SerdeError::unsupported(
90            "structures cannot be serialized to strings",
91        ))
92    }
93
94    fn write_list(
95        &mut self,
96        _schema: &Schema<'_>,
97        write_elements: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
98    ) -> Result<(), SerdeError> {
99        if self.in_list {
100            return Err(SerdeError::unsupported(
101                "nested lists cannot be serialized to strings",
102            ));
103        }
104        if self.wrote_value {
105            return Err(SerdeError::write_failed(
106                "only one value can be written outside a list",
107            ));
108        }
109        self.in_list = true;
110        let result = write_elements(self);
111        self.in_list = false;
112        // The list is the single top-level value, even when it has no elements.
113        self.wrote_value = true;
114        result
115    }
116
117    fn write_map(
118        &mut self,
119        _schema: &Schema<'_>,
120        _write_entries: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
121    ) -> Result<(), SerdeError> {
122        Err(SerdeError::unsupported(
123            "maps cannot be serialized to strings",
124        ))
125    }
126
127    fn write_boolean(&mut self, _schema: &Schema<'_>, value: bool) -> Result<(), SerdeError> {
128        self.push_value(if value { "true" } else { "false" })
129    }
130
131    fn write_byte(&mut self, _schema: &Schema<'_>, value: i8) -> Result<(), SerdeError> {
132        self.push_value(&value.to_string())
133    }
134
135    fn write_short(&mut self, _schema: &Schema<'_>, value: i16) -> Result<(), SerdeError> {
136        self.push_value(&value.to_string())
137    }
138
139    fn write_integer(&mut self, _schema: &Schema<'_>, value: i32) -> Result<(), SerdeError> {
140        self.push_value(&value.to_string())
141    }
142
143    fn write_long(&mut self, _schema: &Schema<'_>, value: i64) -> Result<(), SerdeError> {
144        self.push_value(&value.to_string())
145    }
146
147    fn write_float(&mut self, _schema: &Schema<'_>, value: f32) -> Result<(), SerdeError> {
148        if value.is_nan() {
149            self.push_value("NaN")
150        } else if value.is_infinite() {
151            self.push_value(if value.is_sign_positive() {
152                "Infinity"
153            } else {
154                "-Infinity"
155            })
156        } else {
157            self.push_value(&value.to_string())
158        }
159    }
160
161    fn write_double(&mut self, _schema: &Schema<'_>, value: f64) -> Result<(), SerdeError> {
162        if value.is_nan() {
163            self.push_value("NaN")
164        } else if value.is_infinite() {
165            self.push_value(if value.is_sign_positive() {
166                "Infinity"
167            } else {
168                "-Infinity"
169            })
170        } else {
171            self.push_value(&value.to_string())
172        }
173    }
174
175    fn write_big_integer(
176        &mut self,
177        _schema: &Schema<'_>,
178        value: &BigInteger,
179    ) -> Result<(), SerdeError> {
180        self.push_value(value.as_ref())
181    }
182
183    fn write_big_decimal(
184        &mut self,
185        _schema: &Schema<'_>,
186        value: &BigDecimal,
187    ) -> Result<(), SerdeError> {
188        self.push_value(value.as_ref())
189    }
190
191    fn write_string(&mut self, _schema: &Schema<'_>, value: &str) -> Result<(), SerdeError> {
192        self.push_value(value)
193    }
194
195    fn write_blob(&mut self, _schema: &Schema<'_>, value: Blob) -> Result<(), SerdeError> {
196        // Blobs are base64-encoded for string serialization
197        self.push_value(&aws_smithy_types::base64::encode(value.as_ref()))
198    }
199
200    fn write_timestamp(
201        &mut self,
202        _schema: &Schema<'_>,
203        value: &DateTime,
204    ) -> Result<(), SerdeError> {
205        // Default to HTTP date format for string serialization
206        // TODO(schema): Check schema for timestampFormat trait
207        let formatted = value
208            .fmt(aws_smithy_types::date_time::Format::HttpDate)
209            .map_err(|e| SerdeError::write_failed(format!("failed to format timestamp: {e}")))?;
210        // An HTTP date contains a comma, so inside a list it is quoted.
211        self.push_value(&formatted)
212    }
213
214    fn write_document(
215        &mut self,
216        _schema: &Schema<'_>,
217        _value: &Document,
218    ) -> Result<(), SerdeError> {
219        Err(SerdeError::unsupported(
220            "documents cannot be serialized to strings",
221        ))
222    }
223
224    fn write_null(&mut self, _schema: &Schema<'_>) -> Result<(), SerdeError> {
225        Err(SerdeError::unsupported(
226            "null cannot be serialized to strings",
227        ))
228    }
229}
230
231/// Deserializer for parsing Smithy types from strings.
232#[derive(Debug)]
233pub struct HttpStringDeserializer<'a> {
234    input: Cow<'a, str>,
235    /// Whether the single top-level value has been read.
236    consumed: bool,
237    /// The remaining elements while `read_list` is running its consumer.
238    list: Option<std::vec::IntoIter<String>>,
239}
240
241impl<'a> HttpStringDeserializer<'a> {
242    /// Creates a new HTTP string deserializer from the given input.
243    pub fn new(input: &'a str) -> Self {
244        Self {
245            input: Cow::Borrowed(input),
246            consumed: false,
247            list: None,
248        }
249    }
250
251    /// Splits the input into list elements with the RFC 7230 header-list rules.
252    fn list_elements(&self) -> Result<Vec<String>, SerdeError> {
253        if self.input.is_empty() {
254            return Ok(Vec::new());
255        }
256        header_parse::read_many_from_str_bytes::<String>(std::iter::once(self.input.as_bytes()))
257            .map_err(|e| SerdeError::invalid_input(format!("invalid list: {e}")))
258    }
259
260    /// The next value to read: the next element inside a list, else the whole input.
261    fn next_value(&mut self, what: &str) -> Result<Cow<'_, str>, SerdeError> {
262        let value = match &mut self.list {
263            Some(elements) => elements.next().map(Cow::Owned),
264            None if self.consumed => None,
265            None => {
266                self.consumed = true;
267                Some(Cow::Borrowed(self.input.as_ref()))
268            }
269        };
270        value.ok_or_else(|| SerdeError::invalid_input(format!("expected {what} value")))
271    }
272}
273
274impl<'a> ShapeDeserializer for HttpStringDeserializer<'a> {
275    fn read_struct(
276        &mut self,
277        _schema: &Schema<'_>,
278        _consumer: &mut dyn FnMut(
279            &Schema<'_>,
280            &mut dyn ShapeDeserializer,
281        ) -> Result<(), SerdeError>,
282    ) -> Result<(), SerdeError> {
283        Err(SerdeError::unsupported(
284            "structures cannot be deserialized from strings",
285        ))
286    }
287
288    fn read_list(
289        &mut self,
290        _schema: &Schema<'_>,
291        consumer: &mut dyn FnMut(&mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
292    ) -> Result<(), SerdeError> {
293        // Invoke the consumer once per element. Each call drives a single element read
294        // (e.g. `read_string`), which takes the next element via `next_value`. An empty
295        // input is an empty list.
296        if self.list.is_some() {
297            return Err(SerdeError::unsupported(
298                "nested lists cannot be deserialized from strings",
299            ));
300        }
301        let elements = self.list_elements()?;
302        self.consumed = true;
303        let count = elements.len();
304        self.list = Some(elements.into_iter());
305        let mut result = Ok(());
306        for _ in 0..count {
307            result = consumer(self);
308            if result.is_err() {
309                break;
310            }
311        }
312        self.list = None;
313        result
314    }
315
316    fn read_map(
317        &mut self,
318        _schema: &Schema<'_>,
319        _consumer: &mut dyn FnMut(String, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
320    ) -> Result<(), SerdeError> {
321        Err(SerdeError::unsupported(
322            "maps cannot be deserialized from strings",
323        ))
324    }
325
326    fn read_boolean(&mut self, _schema: &Schema<'_>) -> Result<bool, SerdeError> {
327        let value = self.next_value("boolean")?;
328        let value = value.as_ref();
329        value
330            .parse()
331            .map_err(|_| SerdeError::invalid_input(format!("invalid boolean: {value}")))
332    }
333
334    fn read_byte(&mut self, _schema: &Schema<'_>) -> Result<i8, SerdeError> {
335        let value = self.next_value("byte")?;
336        let value = value.as_ref();
337        value
338            .parse()
339            .map_err(|_| SerdeError::invalid_input(format!("invalid byte: {value}")))
340    }
341
342    fn read_short(&mut self, _schema: &Schema<'_>) -> Result<i16, SerdeError> {
343        let value = self.next_value("short")?;
344        let value = value.as_ref();
345        value
346            .parse()
347            .map_err(|_| SerdeError::invalid_input(format!("invalid short: {value}")))
348    }
349
350    fn read_integer(&mut self, _schema: &Schema<'_>) -> Result<i32, SerdeError> {
351        let value = self.next_value("integer")?;
352        let value = value.as_ref();
353        value
354            .parse()
355            .map_err(|_| SerdeError::invalid_input(format!("invalid integer: {value}")))
356    }
357
358    fn read_long(&mut self, _schema: &Schema<'_>) -> Result<i64, SerdeError> {
359        let value = self.next_value("long")?;
360        let value = value.as_ref();
361        value
362            .parse()
363            .map_err(|_| SerdeError::invalid_input(format!("invalid long: {value}")))
364    }
365
366    fn read_float(&mut self, _schema: &Schema<'_>) -> Result<f32, SerdeError> {
367        let value = self.next_value("float")?;
368        let value = value.as_ref();
369        match value {
370            "NaN" => Ok(f32::NAN),
371            "Infinity" => Ok(f32::INFINITY),
372            "-Infinity" => Ok(f32::NEG_INFINITY),
373            _ => value
374                .parse()
375                .map_err(|_| SerdeError::invalid_input(format!("invalid float: {value}"))),
376        }
377    }
378
379    fn read_double(&mut self, _schema: &Schema<'_>) -> Result<f64, SerdeError> {
380        let value = self.next_value("double")?;
381        let value = value.as_ref();
382        match value {
383            "NaN" => Ok(f64::NAN),
384            "Infinity" => Ok(f64::INFINITY),
385            "-Infinity" => Ok(f64::NEG_INFINITY),
386            _ => value
387                .parse()
388                .map_err(|_| SerdeError::invalid_input(format!("invalid double: {value}"))),
389        }
390    }
391
392    fn read_big_integer(&mut self, _schema: &Schema<'_>) -> Result<BigInteger, SerdeError> {
393        let value = self.next_value("big integer")?;
394        let value = value.as_ref();
395        use std::str::FromStr;
396        BigInteger::from_str(value)
397            .map_err(|_| SerdeError::invalid_input(format!("invalid big integer: {value}")))
398    }
399
400    fn read_big_decimal(&mut self, _schema: &Schema<'_>) -> Result<BigDecimal, SerdeError> {
401        let value = self.next_value("big decimal")?;
402        let value = value.as_ref();
403        use std::str::FromStr;
404        BigDecimal::from_str(value)
405            .map_err(|_| SerdeError::invalid_input(format!("invalid big decimal: {value}")))
406    }
407
408    fn read_string(&mut self, _schema: &Schema<'_>) -> Result<String, SerdeError> {
409        self.next_value("string").map(Cow::into_owned)
410    }
411
412    fn read_blob(&mut self, _schema: &Schema<'_>) -> Result<Blob, SerdeError> {
413        let value = self.next_value("blob")?;
414        let value = value.as_ref();
415        let decoded = aws_smithy_types::base64::decode(value)
416            .map_err(|e| SerdeError::invalid_input(format!("invalid base64: {e}")))?;
417        Ok(Blob::new(decoded))
418    }
419
420    fn read_timestamp(&mut self, _schema: &Schema<'_>) -> Result<DateTime, SerdeError> {
421        let value = self.next_value("timestamp")?;
422        let value = value.as_ref();
423        // Try HTTP date format first, then fall back to other formats
424        // TODO(schema): Check schema for timestampFormat trait
425        DateTime::from_str(value, aws_smithy_types::date_time::Format::HttpDate)
426            .or_else(|_| DateTime::from_str(value, aws_smithy_types::date_time::Format::DateTime))
427            .map_err(|e| SerdeError::invalid_input(format!("invalid timestamp: {e}")))
428    }
429
430    fn read_document(&mut self, _schema: &Schema<'_>) -> Result<Document, SerdeError> {
431        Err(SerdeError::unsupported(
432            "documents cannot be deserialized from strings",
433        ))
434    }
435
436    fn is_null(&self) -> bool {
437        // List elements cannot be null; a top-level value is null when the input is empty.
438        self.list.is_none() && self.input.is_empty()
439    }
440
441    /// Nothing to advance outside a list. This deserializer holds a single already-extracted
442    /// value and cannot read structures at all, so it is never the parent of a member
443    /// consumer; declining the value simply means not reading it. Inside a list, skipping
444    /// consumes one element so the next read sees the following one.
445    fn skip_value(&mut self) -> Result<(), SerdeError> {
446        if let Some(elements) = &mut self.list {
447            elements.next();
448        }
449        Ok(())
450    }
451
452    fn container_size(&self) -> Option<usize> {
453        match &self.list {
454            Some(elements) => Some(elements.len()),
455            None => self.list_elements().ok().map(|elements| elements.len()),
456        }
457    }
458}
459
460/// HTTP string codec for serializing/deserializing to/from strings.
461#[derive(Debug)]
462pub struct HttpStringCodec;
463
464impl crate::codec::Codec for HttpStringCodec {
465    type Serializer = HttpStringSerializer;
466    type Deserializer<'a> = HttpStringDeserializer<'a>;
467
468    fn create_serializer(&self) -> Self::Serializer {
469        HttpStringSerializer::new()
470    }
471
472    fn create_deserializer<'a>(&self, input: &'a [u8]) -> Self::Deserializer<'a> {
473        let input_str = std::str::from_utf8(input).unwrap_or("");
474        HttpStringDeserializer::new(input_str)
475    }
476}
477
478#[cfg(test)]
479mod tests {
480    use super::*;
481    use crate::prelude::*;
482
483    #[test]
484    fn test_serialize_boolean() {
485        let mut ser = HttpStringSerializer::new();
486        ser.write_boolean(&BOOLEAN, true).unwrap();
487        assert_eq!(ser.finish(), "true");
488
489        let mut ser = HttpStringSerializer::new();
490        ser.write_boolean(&BOOLEAN, false).unwrap();
491        assert_eq!(ser.finish(), "false");
492    }
493
494    #[test]
495    fn test_serialize_integers() {
496        let mut ser = HttpStringSerializer::new();
497        ser.write_byte(&BYTE, 42).unwrap();
498        assert_eq!(ser.finish(), "42");
499
500        let mut ser = HttpStringSerializer::new();
501        ser.write_integer(&INTEGER, -123).unwrap();
502        assert_eq!(ser.finish(), "-123");
503
504        let mut ser = HttpStringSerializer::new();
505        ser.write_long(&LONG, 9876543210).unwrap();
506        assert_eq!(ser.finish(), "9876543210");
507    }
508
509    #[test]
510    fn test_serialize_floats() {
511        let mut ser = HttpStringSerializer::new();
512        ser.write_float(&FLOAT, 3.15).unwrap();
513        assert_eq!(ser.finish(), "3.15");
514
515        let mut ser = HttpStringSerializer::new();
516        ser.write_float(&FLOAT, f32::NAN).unwrap();
517        assert_eq!(ser.finish(), "NaN");
518
519        let mut ser = HttpStringSerializer::new();
520        ser.write_float(&FLOAT, f32::INFINITY).unwrap();
521        assert_eq!(ser.finish(), "Infinity");
522    }
523
524    #[test]
525    fn test_serialize_string() {
526        let mut ser = HttpStringSerializer::new();
527        ser.write_string(&STRING, "hello world").unwrap();
528        assert_eq!(ser.finish(), "hello world");
529    }
530
531    #[test]
532    fn test_serialize_list() {
533        let mut ser = HttpStringSerializer::new();
534        ser.write_list(&STRING, &|s: &mut dyn ShapeSerializer| {
535            s.write_string(&STRING, "a")?;
536            s.write_string(&STRING, "b")?;
537            s.write_string(&STRING, "c")?;
538            Ok(())
539        })
540        .unwrap();
541        assert_eq!(ser.finish(), "a, b, c");
542    }
543
544    #[test]
545    fn test_serialize_blob() {
546        let mut ser = HttpStringSerializer::new();
547        let blob = Blob::new(vec![1, 2, 3, 4]);
548        ser.write_blob(&BLOB, blob).unwrap();
549        // Base64 encoding of [1, 2, 3, 4]
550        assert_eq!(ser.finish(), "AQIDBA==");
551    }
552
553    #[test]
554    fn test_deserialize_boolean() {
555        let mut deser = HttpStringDeserializer::new("true");
556        assert!(deser.read_boolean(&BOOLEAN).unwrap());
557
558        let mut deser = HttpStringDeserializer::new("false");
559        assert!(!(deser.read_boolean(&BOOLEAN).unwrap()));
560    }
561
562    #[test]
563    fn test_deserialize_integers() {
564        let mut deser = HttpStringDeserializer::new("42");
565        assert_eq!(deser.read_byte(&BYTE).unwrap(), 42);
566
567        let mut deser = HttpStringDeserializer::new("-123");
568        assert_eq!(deser.read_integer(&INTEGER).unwrap(), -123);
569
570        let mut deser = HttpStringDeserializer::new("9876543210");
571        assert_eq!(deser.read_long(&LONG).unwrap(), 9876543210);
572    }
573
574    #[test]
575    fn test_deserialize_floats() {
576        let mut deser = HttpStringDeserializer::new("3.15");
577        assert!((deser.read_float(&FLOAT).unwrap() - 3.15).abs() < 0.01);
578
579        let mut deser = HttpStringDeserializer::new("NaN");
580        assert!(deser.read_float(&FLOAT).unwrap().is_nan());
581
582        let mut deser = HttpStringDeserializer::new("Infinity");
583        assert_eq!(deser.read_float(&FLOAT).unwrap(), f32::INFINITY);
584    }
585
586    #[test]
587    fn test_deserialize_string() {
588        let mut deser = HttpStringDeserializer::new("hello world");
589        assert_eq!(deser.read_string(&STRING).unwrap(), "hello world");
590    }
591
592    #[test]
593    fn scalar_read_is_the_whole_value() {
594        // Outside a list there is no comma splitting, as for a scalar HTTP header.
595        let mut deser = HttpStringDeserializer::new("a, b,c");
596        assert_eq!(deser.read_string(&STRING).unwrap(), "a, b,c");
597        assert!(
598            deser.read_string(&STRING).is_err(),
599            "the single value was already read"
600        );
601    }
602
603    #[test]
604    fn only_one_value_can_be_written_outside_a_list() {
605        let mut ser = HttpStringSerializer::new();
606        ser.write_string(&STRING, "a").unwrap();
607        assert!(ser.write_string(&STRING, "b").is_err());
608    }
609
610    fn string_list() -> Schema<'static> {
611        Schema::new_list(crate::shape_id!("ns", "StringList"), &STRING)
612    }
613
614    fn write_strings(items: &[&str]) -> String {
615        let mut ser = HttpStringSerializer::new();
616        ser.write_list(&string_list(), &|e| {
617            for it in items {
618                e.write_string(&STRING, it)?;
619            }
620            Ok(())
621        })
622        .unwrap();
623        ser.finish()
624    }
625
626    fn read_strings(wire: &str) -> Result<Vec<String>, SerdeError> {
627        let mut de = HttpStringDeserializer::new(wire);
628        let mut out = Vec::new();
629        de.read_list(&string_list(), &mut |x| {
630            out.push(x.read_string(&STRING)?);
631            Ok(())
632        })?;
633        Ok(out)
634    }
635
636    #[test]
637    fn string_list_round_trips() {
638        // From review of PR #4871, extended with whitespace, quotes, parentheses and a
639        // single empty element.
640        let cases: [&[&str]; 11] = [
641            &["a", "b", "c"],
642            &["", "b"],
643            &["a", ""],
644            &["a,b"],
645            &["a, b"],
646            &[" a"],
647            &["a "],
648            &["\"q\""],
649            &["(x)"],
650            &[""],
651            &[],
652        ];
653        for case in cases {
654            let wire = write_strings(case);
655            let res = read_strings(&wire);
656            assert!(res.is_ok(), "read_list failed on wire {wire:?}: {res:?}");
657            assert_eq!(
658                res.unwrap(),
659                case,
660                "round trip changed the list; wire was {wire:?}"
661            );
662        }
663    }
664
665    #[test]
666    fn list_wire_format_matches_header_lists() {
667        assert_eq!(write_strings(&["a", "b", "c"]), "a, b, c");
668        assert_eq!(write_strings(&["a,b", ""]), "\"a,b\", \"\"");
669        // Lists written by other header writers read back with the same rules.
670        assert_eq!(
671            read_strings("a,b , \"c,d\"").unwrap(),
672            vec!["a", "b", "c,d"]
673        );
674        assert!(read_strings("\"unterminated").is_err());
675    }
676
677    #[test]
678    fn timestamp_list_round_trips() {
679        // An HTTP date contains a comma, so it must be quoted inside a list.
680        let list = Schema::new_list(crate::shape_id!("ns", "TsList"), &TIMESTAMP);
681        let times = [DateTime::from_secs(0), DateTime::from_secs(1515531081)];
682        let mut ser = HttpStringSerializer::new();
683        ser.write_list(&list, &|e| {
684            for t in &times {
685                e.write_timestamp(&TIMESTAMP, t)?;
686            }
687            Ok(())
688        })
689        .unwrap();
690        let wire = ser.finish();
691        let mut de = HttpStringDeserializer::new(&wire);
692        let mut out = Vec::new();
693        de.read_list(&list, &mut |x| {
694            out.push(x.read_timestamp(&TIMESTAMP)?);
695            Ok(())
696        })
697        .unwrap();
698        assert_eq!(out, times, "wire was {wire:?}");
699    }
700
701    #[test]
702    fn integer_list_round_trips() {
703        let list = Schema::new_list(crate::shape_id!("ns", "IntList"), &INTEGER);
704        let mut ser = HttpStringSerializer::new();
705        ser.write_list(&list, &|e| {
706            e.write_integer(&INTEGER, 1)?;
707            e.write_integer(&INTEGER, -2)
708        })
709        .unwrap();
710        let wire = ser.finish();
711        assert_eq!(wire, "1, -2");
712        let mut de = HttpStringDeserializer::new(&wire);
713        let mut out = Vec::new();
714        de.read_list(&list, &mut |x| {
715            out.push(x.read_integer(&INTEGER)?);
716            Ok(())
717        })
718        .unwrap();
719        assert_eq!(out, [1, -2]);
720    }
721
722    #[test]
723    fn skip_value_inside_a_list_consumes_one_element() {
724        let mut de = HttpStringDeserializer::new("a, b, c");
725        let mut out = Vec::new();
726        let mut first = true;
727        de.read_list(&string_list(), &mut |x| {
728            if std::mem::take(&mut first) {
729                x.skip_value()
730            } else {
731                out.push(x.read_string(&STRING)?);
732                Ok(())
733            }
734        })
735        .unwrap();
736        assert_eq!(out, ["b", "c"]);
737    }
738
739    #[test]
740    fn test_read_list_drives_consumer_per_element() {
741        let mut deser = HttpStringDeserializer::new("a,b,c");
742        let mut collected = Vec::new();
743        deser
744            .read_list(&STRING, &mut |d: &mut dyn ShapeDeserializer| {
745                collected.push(d.read_string(&STRING)?);
746                Ok(())
747            })
748            .unwrap();
749        assert_eq!(collected, vec!["a", "b", "c"]);
750    }
751
752    #[test]
753    fn test_read_list_empty_input_is_empty_list() {
754        let mut deser = HttpStringDeserializer::new("");
755        let mut count = 0;
756        deser
757            .read_list(&STRING, &mut |_d: &mut dyn ShapeDeserializer| {
758                count += 1;
759                Ok(())
760            })
761            .unwrap();
762        assert_eq!(count, 0);
763    }
764
765    #[test]
766    fn test_deserialize_blob() {
767        let mut deser = HttpStringDeserializer::new("AQIDBA==");
768        let blob = deser.read_blob(&BLOB).unwrap();
769        assert_eq!(blob.as_ref(), &[1, 2, 3, 4]);
770    }
771
772    #[test]
773    fn test_container_size() {
774        let deser = HttpStringDeserializer::new("a,b,c");
775        assert_eq!(deser.container_size(), Some(3));
776
777        let deser = HttpStringDeserializer::new("single");
778        assert_eq!(deser.container_size(), Some(1));
779    }
780
781    #[test]
782    fn test_is_null() {
783        let deser = HttpStringDeserializer::new("");
784        assert!(deser.is_null());
785
786        let deser = HttpStringDeserializer::new("value");
787        assert!(!deser.is_null());
788    }
789
790    #[test]
791    fn test_codec_trait() {
792        use crate::codec::Codec;
793
794        let codec = HttpStringCodec;
795
796        // Test serialization through codec
797        let mut ser = codec.create_serializer();
798        ser.write_string(&STRING, "test").unwrap();
799        let output = ser.finish();
800        assert_eq!(output, "test");
801
802        // Test deserialization through codec
803        let input = b"hello";
804        let mut deser = codec.create_deserializer(input);
805        let result = deser.read_string(&STRING).unwrap();
806        assert_eq!(result, "hello");
807    }
808}
809
810/// Tests for the [`ShapeDeserializer::skip_value`] contract.
811#[cfg(test)]
812mod skip_value_contract {
813    use super::*;
814    use crate::prelude::*;
815    use crate::serde::ShapeDeserializer;
816
817    #[test]
818    fn skip_value_is_a_no_op_and_leaves_the_value_readable() {
819        // This deserializer holds a single already-extracted value and cannot read
820        // structures, so it is never the parent of a member consumer. Skipping means
821        // "don't read it", with nothing to advance — and notably must not fall through to
822        // the allocating trait default, whose `read_document` this type rejects.
823        let mut deser = HttpStringDeserializer::new("hello");
824        let dynamic: &mut dyn ShapeDeserializer = &mut deser;
825        dynamic.skip_value().expect("skip must succeed");
826        assert_eq!(deser.read_string(&STRING).unwrap(), "hello");
827    }
828
829    #[test]
830    fn skip_value_succeeds_where_the_default_would_fail() {
831        // Guards the override: `read_document` is unsupported here, so if the override
832        // were removed this would start returning an error.
833        let mut deser = HttpStringDeserializer::new("value");
834        assert!(
835            deser.read_document(&DOCUMENT).is_err(),
836            "precondition: this deserializer cannot produce documents"
837        );
838        let mut deser = HttpStringDeserializer::new("value");
839        deser
840            .skip_value()
841            .expect("override must not use read_document");
842    }
843}