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
8use crate::serde::{ShapeDeserializer, ShapeSerializer};
9use crate::Schema;
10use aws_smithy_types::{BigDecimal, BigInteger, Blob, DateTime, Document};
11use std::error::Error;
12use std::fmt;
13
14/// Error type for HTTP string serialization/deserialization.
15#[derive(Debug)]
16pub struct HttpStringCodecError {
17    message: String,
18}
19
20impl HttpStringCodecError {
21    fn new(message: impl Into<String>) -> Self {
22        Self {
23            message: message.into(),
24        }
25    }
26}
27
28impl fmt::Display for HttpStringCodecError {
29    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30        write!(f, "HTTP string codec error: {}", self.message)
31    }
32}
33
34impl Error for HttpStringCodecError {}
35
36/// Serializer for converting Smithy types to strings (for HTTP headers, query params, labels).
37pub struct HttpStringSerializer {
38    output: String,
39}
40
41impl HttpStringSerializer {
42    /// Creates a new HTTP string serializer.
43    pub fn new() -> Self {
44        Self {
45            output: String::new(),
46        }
47    }
48}
49
50impl Default for HttpStringSerializer {
51    fn default() -> Self {
52        Self::new()
53    }
54}
55
56impl ShapeSerializer for HttpStringSerializer {
57    type Output = String;
58    type Error = HttpStringCodecError;
59
60    fn finish(self) -> Result<Self::Output, Self::Error> {
61        Ok(self.output)
62    }
63
64    fn write_struct<F>(
65        &mut self,
66        _schema: &dyn Schema,
67        _write_members: F,
68    ) -> Result<(), Self::Error>
69    where
70        F: FnOnce(&mut Self) -> Result<(), Self::Error>,
71    {
72        Err(HttpStringCodecError::new(
73            "structures cannot be serialized to strings",
74        ))
75    }
76
77    fn write_list<F>(&mut self, _schema: &dyn Schema, write_elements: F) -> Result<(), Self::Error>
78    where
79        F: FnOnce(&mut Self) -> Result<(), Self::Error>,
80    {
81        // Lists are serialized as comma-separated values
82        write_elements(self)
83    }
84
85    fn write_map<F>(&mut self, _schema: &dyn Schema, _write_entries: F) -> Result<(), Self::Error>
86    where
87        F: FnOnce(&mut Self) -> Result<(), Self::Error>,
88    {
89        Err(HttpStringCodecError::new(
90            "maps cannot be serialized to strings",
91        ))
92    }
93
94    fn write_boolean(&mut self, _schema: &dyn Schema, value: bool) -> Result<(), Self::Error> {
95        if !self.output.is_empty() {
96            self.output.push(',');
97        }
98        self.output.push_str(if value { "true" } else { "false" });
99        Ok(())
100    }
101
102    fn write_byte(&mut self, _schema: &dyn Schema, value: i8) -> Result<(), Self::Error> {
103        if !self.output.is_empty() {
104            self.output.push(',');
105        }
106        self.output.push_str(&value.to_string());
107        Ok(())
108    }
109
110    fn write_short(&mut self, _schema: &dyn Schema, value: i16) -> Result<(), Self::Error> {
111        if !self.output.is_empty() {
112            self.output.push(',');
113        }
114        self.output.push_str(&value.to_string());
115        Ok(())
116    }
117
118    fn write_integer(&mut self, _schema: &dyn Schema, value: i32) -> Result<(), Self::Error> {
119        if !self.output.is_empty() {
120            self.output.push(',');
121        }
122        self.output.push_str(&value.to_string());
123        Ok(())
124    }
125
126    fn write_long(&mut self, _schema: &dyn Schema, value: i64) -> Result<(), Self::Error> {
127        if !self.output.is_empty() {
128            self.output.push(',');
129        }
130        self.output.push_str(&value.to_string());
131        Ok(())
132    }
133
134    fn write_float(&mut self, _schema: &dyn Schema, value: f32) -> Result<(), Self::Error> {
135        if !self.output.is_empty() {
136            self.output.push(',');
137        }
138        if value.is_nan() {
139            self.output.push_str("NaN");
140        } else if value.is_infinite() {
141            self.output.push_str(if value.is_sign_positive() {
142                "Infinity"
143            } else {
144                "-Infinity"
145            });
146        } else {
147            self.output.push_str(&value.to_string());
148        }
149        Ok(())
150    }
151
152    fn write_double(&mut self, _schema: &dyn Schema, value: f64) -> Result<(), Self::Error> {
153        if !self.output.is_empty() {
154            self.output.push(',');
155        }
156        if value.is_nan() {
157            self.output.push_str("NaN");
158        } else if value.is_infinite() {
159            self.output.push_str(if value.is_sign_positive() {
160                "Infinity"
161            } else {
162                "-Infinity"
163            });
164        } else {
165            self.output.push_str(&value.to_string());
166        }
167        Ok(())
168    }
169
170    fn write_big_integer(
171        &mut self,
172        _schema: &dyn Schema,
173        value: &BigInteger,
174    ) -> Result<(), Self::Error> {
175        if !self.output.is_empty() {
176            self.output.push(',');
177        }
178        self.output.push_str(value.as_ref());
179        Ok(())
180    }
181
182    fn write_big_decimal(
183        &mut self,
184        _schema: &dyn Schema,
185        value: &BigDecimal,
186    ) -> Result<(), Self::Error> {
187        if !self.output.is_empty() {
188            self.output.push(',');
189        }
190        self.output.push_str(value.as_ref());
191        Ok(())
192    }
193
194    fn write_string(&mut self, _schema: &dyn Schema, value: &str) -> Result<(), Self::Error> {
195        if !self.output.is_empty() {
196            self.output.push(',');
197        }
198        self.output.push_str(value);
199        Ok(())
200    }
201
202    fn write_blob(&mut self, _schema: &dyn Schema, value: &Blob) -> Result<(), Self::Error> {
203        if !self.output.is_empty() {
204            self.output.push(',');
205        }
206        // Blobs are base64-encoded for string serialization
207        self.output
208            .push_str(&aws_smithy_types::base64::encode(value.as_ref()));
209        Ok(())
210    }
211
212    fn write_timestamp(
213        &mut self,
214        _schema: &dyn Schema,
215        value: &DateTime,
216    ) -> Result<(), Self::Error> {
217        if !self.output.is_empty() {
218            self.output.push(',');
219        }
220        // Default to HTTP date format for string serialization
221        // TODO(schema): Check schema for timestampFormat trait
222        let formatted = value
223            .fmt(aws_smithy_types::date_time::Format::HttpDate)
224            .map_err(|e| HttpStringCodecError::new(format!("failed to format timestamp: {}", e)))?;
225        self.output.push_str(&formatted);
226        Ok(())
227    }
228
229    fn write_document(
230        &mut self,
231        _schema: &dyn Schema,
232        _value: &Document,
233    ) -> Result<(), Self::Error> {
234        Err(HttpStringCodecError::new(
235            "documents cannot be serialized to strings",
236        ))
237    }
238
239    fn write_null(&mut self, _schema: &dyn Schema) -> Result<(), Self::Error> {
240        Err(HttpStringCodecError::new(
241            "null cannot be serialized to strings",
242        ))
243    }
244}
245
246/// Deserializer for parsing Smithy types from strings.
247pub struct HttpStringDeserializer<'a> {
248    input: std::borrow::Cow<'a, str>,
249    position: usize,
250}
251
252impl<'a> HttpStringDeserializer<'a> {
253    /// Creates a new HTTP string deserializer from the given input.
254    pub fn new(input: &'a str) -> Self {
255        Self {
256            input: std::borrow::Cow::Borrowed(input),
257            position: 0,
258        }
259    }
260
261    fn next_value(&mut self) -> Option<&str> {
262        if self.position >= self.input.len() {
263            return None;
264        }
265
266        let start = self.position;
267        if let Some(comma_pos) = self.input[start..].find(',') {
268            let end = start + comma_pos;
269            self.position = end + 1;
270            Some(&self.input[start..end])
271        } else {
272            self.position = self.input.len();
273            Some(&self.input[start..])
274        }
275    }
276
277    fn current_value(&self) -> &str {
278        &self.input[self.position..]
279    }
280}
281
282impl<'a> ShapeDeserializer for HttpStringDeserializer<'a> {
283    type Error = HttpStringCodecError;
284
285    fn read_struct<T, F>(
286        &mut self,
287        _schema: &dyn Schema,
288        _state: T,
289        _consumer: F,
290    ) -> Result<T, Self::Error>
291    where
292        F: FnMut(T, &dyn Schema, &mut Self) -> Result<T, Self::Error>,
293    {
294        Err(HttpStringCodecError::new(
295            "structures cannot be deserialized from strings",
296        ))
297    }
298
299    fn read_list<T, F>(
300        &mut self,
301        _schema: &dyn Schema,
302        state: T,
303        _consumer: F,
304    ) -> Result<T, Self::Error>
305    where
306        F: FnMut(T, &mut Self) -> Result<T, Self::Error>,
307    {
308        // Lists are comma-separated values
309        // The consumer will call read methods for each element
310        Ok(state)
311    }
312
313    fn read_map<T, F>(
314        &mut self,
315        _schema: &dyn Schema,
316        _state: T,
317        _consumer: F,
318    ) -> Result<T, Self::Error>
319    where
320        F: FnMut(T, String, &mut Self) -> Result<T, Self::Error>,
321    {
322        Err(HttpStringCodecError::new(
323            "maps cannot be deserialized from strings",
324        ))
325    }
326
327    fn read_boolean(&mut self, _schema: &dyn Schema) -> Result<bool, Self::Error> {
328        let value = self
329            .next_value()
330            .ok_or_else(|| HttpStringCodecError::new("expected boolean value"))?;
331        value
332            .parse()
333            .map_err(|_| HttpStringCodecError::new(format!("invalid boolean: {}", value)))
334    }
335
336    fn read_byte(&mut self, _schema: &dyn Schema) -> Result<i8, Self::Error> {
337        let value = self
338            .next_value()
339            .ok_or_else(|| HttpStringCodecError::new("expected byte value"))?;
340        value
341            .parse()
342            .map_err(|_| HttpStringCodecError::new(format!("invalid byte: {}", value)))
343    }
344
345    fn read_short(&mut self, _schema: &dyn Schema) -> Result<i16, Self::Error> {
346        let value = self
347            .next_value()
348            .ok_or_else(|| HttpStringCodecError::new("expected short value"))?;
349        value
350            .parse()
351            .map_err(|_| HttpStringCodecError::new(format!("invalid short: {}", value)))
352    }
353
354    fn read_integer(&mut self, _schema: &dyn Schema) -> Result<i32, Self::Error> {
355        let value = self
356            .next_value()
357            .ok_or_else(|| HttpStringCodecError::new("expected integer value"))?;
358        value
359            .parse()
360            .map_err(|_| HttpStringCodecError::new(format!("invalid integer: {}", value)))
361    }
362
363    fn read_long(&mut self, _schema: &dyn Schema) -> Result<i64, Self::Error> {
364        let value = self
365            .next_value()
366            .ok_or_else(|| HttpStringCodecError::new("expected long value"))?;
367        value
368            .parse()
369            .map_err(|_| HttpStringCodecError::new(format!("invalid long: {}", value)))
370    }
371
372    fn read_float(&mut self, _schema: &dyn Schema) -> Result<f32, Self::Error> {
373        let value = self
374            .next_value()
375            .ok_or_else(|| HttpStringCodecError::new("expected float value"))?;
376        match value {
377            "NaN" => Ok(f32::NAN),
378            "Infinity" => Ok(f32::INFINITY),
379            "-Infinity" => Ok(f32::NEG_INFINITY),
380            _ => value
381                .parse()
382                .map_err(|_| HttpStringCodecError::new(format!("invalid float: {}", value))),
383        }
384    }
385
386    fn read_double(&mut self, _schema: &dyn Schema) -> Result<f64, Self::Error> {
387        let value = self
388            .next_value()
389            .ok_or_else(|| HttpStringCodecError::new("expected double value"))?;
390        match value {
391            "NaN" => Ok(f64::NAN),
392            "Infinity" => Ok(f64::INFINITY),
393            "-Infinity" => Ok(f64::NEG_INFINITY),
394            _ => value
395                .parse()
396                .map_err(|_| HttpStringCodecError::new(format!("invalid double: {}", value))),
397        }
398    }
399
400    fn read_big_integer(&mut self, _schema: &dyn Schema) -> Result<BigInteger, Self::Error> {
401        let value = self
402            .next_value()
403            .ok_or_else(|| HttpStringCodecError::new("expected big integer value"))?;
404        use std::str::FromStr;
405        BigInteger::from_str(value)
406            .map_err(|_| HttpStringCodecError::new(format!("invalid big integer: {}", value)))
407    }
408
409    fn read_big_decimal(&mut self, _schema: &dyn Schema) -> Result<BigDecimal, Self::Error> {
410        let value = self
411            .next_value()
412            .ok_or_else(|| HttpStringCodecError::new("expected big decimal value"))?;
413        use std::str::FromStr;
414        BigDecimal::from_str(value)
415            .map_err(|_| HttpStringCodecError::new(format!("invalid big decimal: {}", value)))
416    }
417
418    fn read_string(&mut self, _schema: &dyn Schema) -> Result<String, Self::Error> {
419        self.next_value()
420            .ok_or_else(|| HttpStringCodecError::new("expected string value"))
421            .map(|s| s.to_string())
422    }
423
424    fn read_blob(&mut self, _schema: &dyn Schema) -> Result<Blob, Self::Error> {
425        let value = self
426            .next_value()
427            .ok_or_else(|| HttpStringCodecError::new("expected blob value"))?;
428        let decoded = aws_smithy_types::base64::decode(value)
429            .map_err(|e| HttpStringCodecError::new(format!("invalid base64: {}", e)))?;
430        Ok(Blob::new(decoded))
431    }
432
433    fn read_timestamp(&mut self, _schema: &dyn Schema) -> Result<DateTime, Self::Error> {
434        let value = self
435            .next_value()
436            .ok_or_else(|| HttpStringCodecError::new("expected timestamp value"))?;
437        // Try HTTP date format first, then fall back to other formats
438        // TODO(schema): Check schema for timestampFormat trait
439        DateTime::from_str(value, aws_smithy_types::date_time::Format::HttpDate)
440            .or_else(|_| DateTime::from_str(value, aws_smithy_types::date_time::Format::DateTime))
441            .map_err(|e| HttpStringCodecError::new(format!("invalid timestamp: {}", e)))
442    }
443
444    fn read_document(&mut self, _schema: &dyn Schema) -> Result<Document, Self::Error> {
445        Err(HttpStringCodecError::new(
446            "documents cannot be deserialized from strings",
447        ))
448    }
449
450    fn is_null(&self) -> bool {
451        self.current_value().is_empty()
452    }
453
454    fn container_size(&self) -> Option<usize> {
455        // Count commas + 1 for list size estimation
456        Some(self.input.matches(',').count() + 1)
457    }
458}
459
460/// HTTP string codec for serializing/deserializing to/from strings.
461pub struct HttpStringCodec;
462
463impl crate::codec::Codec for HttpStringCodec {
464    type Serializer = HttpStringSerializer;
465    type Deserializer = HttpStringDeserializer<'static>;
466
467    fn create_serializer(&self) -> Self::Serializer {
468        HttpStringSerializer::new()
469    }
470
471    fn create_deserializer(&self, input: &[u8]) -> Self::Deserializer {
472        let input_str = std::str::from_utf8(input).unwrap_or("").to_string();
473        HttpStringDeserializer {
474            input: std::borrow::Cow::Owned(input_str),
475            position: 0,
476        }
477    }
478}
479
480#[cfg(test)]
481mod tests {
482    use super::*;
483    use crate::prelude::*;
484
485    #[test]
486    fn test_serialize_boolean() {
487        let mut ser = HttpStringSerializer::new();
488        ser.write_boolean(&BOOLEAN, true).unwrap();
489        assert_eq!(ser.finish().unwrap(), "true");
490
491        let mut ser = HttpStringSerializer::new();
492        ser.write_boolean(&BOOLEAN, false).unwrap();
493        assert_eq!(ser.finish().unwrap(), "false");
494    }
495
496    #[test]
497    fn test_serialize_integers() {
498        let mut ser = HttpStringSerializer::new();
499        ser.write_byte(&BYTE, 42).unwrap();
500        assert_eq!(ser.finish().unwrap(), "42");
501
502        let mut ser = HttpStringSerializer::new();
503        ser.write_integer(&INTEGER, -123).unwrap();
504        assert_eq!(ser.finish().unwrap(), "-123");
505
506        let mut ser = HttpStringSerializer::new();
507        ser.write_long(&LONG, 9876543210).unwrap();
508        assert_eq!(ser.finish().unwrap(), "9876543210");
509    }
510
511    #[test]
512    fn test_serialize_floats() {
513        let mut ser = HttpStringSerializer::new();
514        ser.write_float(&FLOAT, 3.14).unwrap();
515        assert_eq!(ser.finish().unwrap(), "3.14");
516
517        let mut ser = HttpStringSerializer::new();
518        ser.write_float(&FLOAT, f32::NAN).unwrap();
519        assert_eq!(ser.finish().unwrap(), "NaN");
520
521        let mut ser = HttpStringSerializer::new();
522        ser.write_float(&FLOAT, f32::INFINITY).unwrap();
523        assert_eq!(ser.finish().unwrap(), "Infinity");
524    }
525
526    #[test]
527    fn test_serialize_string() {
528        let mut ser = HttpStringSerializer::new();
529        ser.write_string(&STRING, "hello world").unwrap();
530        assert_eq!(ser.finish().unwrap(), "hello world");
531    }
532
533    #[test]
534    fn test_serialize_list() {
535        let mut ser = HttpStringSerializer::new();
536        ser.write_list(&STRING, |s| {
537            s.write_string(&STRING, "a")?;
538            s.write_string(&STRING, "b")?;
539            s.write_string(&STRING, "c")?;
540            Ok(())
541        })
542        .unwrap();
543        assert_eq!(ser.finish().unwrap(), "a,b,c");
544    }
545
546    #[test]
547    fn test_serialize_blob() {
548        let mut ser = HttpStringSerializer::new();
549        let blob = Blob::new(vec![1, 2, 3, 4]);
550        ser.write_blob(&BLOB, &blob).unwrap();
551        // Base64 encoding of [1, 2, 3, 4]
552        assert_eq!(ser.finish().unwrap(), "AQIDBA==");
553    }
554
555    #[test]
556    fn test_deserialize_boolean() {
557        let mut deser = HttpStringDeserializer::new("true");
558        assert_eq!(deser.read_boolean(&BOOLEAN).unwrap(), true);
559
560        let mut deser = HttpStringDeserializer::new("false");
561        assert_eq!(deser.read_boolean(&BOOLEAN).unwrap(), false);
562    }
563
564    #[test]
565    fn test_deserialize_integers() {
566        let mut deser = HttpStringDeserializer::new("42");
567        assert_eq!(deser.read_byte(&BYTE).unwrap(), 42);
568
569        let mut deser = HttpStringDeserializer::new("-123");
570        assert_eq!(deser.read_integer(&INTEGER).unwrap(), -123);
571
572        let mut deser = HttpStringDeserializer::new("9876543210");
573        assert_eq!(deser.read_long(&LONG).unwrap(), 9876543210);
574    }
575
576    #[test]
577    fn test_deserialize_floats() {
578        let mut deser = HttpStringDeserializer::new("3.14");
579        assert!((deser.read_float(&FLOAT).unwrap() - 3.14).abs() < 0.01);
580
581        let mut deser = HttpStringDeserializer::new("NaN");
582        assert!(deser.read_float(&FLOAT).unwrap().is_nan());
583
584        let mut deser = HttpStringDeserializer::new("Infinity");
585        assert_eq!(deser.read_float(&FLOAT).unwrap(), f32::INFINITY);
586    }
587
588    #[test]
589    fn test_deserialize_string() {
590        let mut deser = HttpStringDeserializer::new("hello world");
591        assert_eq!(deser.read_string(&STRING).unwrap(), "hello world");
592    }
593
594    #[test]
595    fn test_deserialize_list() {
596        let mut deser = HttpStringDeserializer::new("a,b,c");
597        let mut values = Vec::new();
598
599        // Manually read list elements
600        values.push(deser.read_string(&STRING).unwrap());
601        values.push(deser.read_string(&STRING).unwrap());
602        values.push(deser.read_string(&STRING).unwrap());
603
604        assert_eq!(values, vec!["a", "b", "c"]);
605    }
606
607    #[test]
608    fn test_deserialize_blob() {
609        let mut deser = HttpStringDeserializer::new("AQIDBA==");
610        let blob = deser.read_blob(&BLOB).unwrap();
611        assert_eq!(blob.as_ref(), &[1, 2, 3, 4]);
612    }
613
614    #[test]
615    fn test_container_size() {
616        let deser = HttpStringDeserializer::new("a,b,c");
617        assert_eq!(deser.container_size(), Some(3));
618
619        let deser = HttpStringDeserializer::new("single");
620        assert_eq!(deser.container_size(), Some(1));
621    }
622
623    #[test]
624    fn test_is_null() {
625        let deser = HttpStringDeserializer::new("");
626        assert!(deser.is_null());
627
628        let deser = HttpStringDeserializer::new("value");
629        assert!(!deser.is_null());
630    }
631
632    #[test]
633    fn test_codec_trait() {
634        use crate::codec::Codec;
635
636        let codec = HttpStringCodec;
637
638        // Test serialization through codec
639        let mut ser = codec.create_serializer();
640        ser.write_string(&STRING, "test").unwrap();
641        let output = ser.finish().unwrap();
642        assert_eq!(output, "test");
643
644        // Test deserialization through codec
645        let input = b"hello";
646        let mut deser = codec.create_deserializer(input);
647        let result = deser.read_string(&STRING).unwrap();
648        assert_eq!(result, "hello");
649    }
650}