Skip to main content

aws_smithy_xml/
codec.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! XML codec implementation for schema-based serialization.
7//!
8//! Provides [`XmlCodec`] — a [`Codec`] producing the [`XmlSerializer`] /
9//! [`XmlDeserializer`] pair used by the AWS REST XML protocol. Per-protocol
10//! behavior (default timestamp format, etc.) is configured via
11//! [`XmlCodecSettings`].
12
13use aws_smithy_schema::codec::Codec;
14use aws_smithy_types::date_time::Format as TimestampFormat;
15use std::sync::Arc;
16
17mod deserializer;
18mod serializer;
19
20pub use deserializer::{find_depth2_element_slice_by, XmlDeserializer};
21pub use serializer::XmlSerializer;
22
23/// Configuration for XML codec behavior.
24///
25/// Use the builder methods to construct settings:
26/// ```
27/// use aws_smithy_xml::codec::XmlCodecSettings;
28///
29/// let settings = XmlCodecSettings::builder().build();
30/// ```
31#[derive(Debug)]
32pub struct XmlCodecSettings {
33    default_timestamp_format: TimestampFormat,
34    max_depth: u32,
35}
36
37impl XmlCodecSettings {
38    /// Creates a builder for `XmlCodecSettings`.
39    pub fn builder() -> XmlCodecSettingsBuilder {
40        XmlCodecSettingsBuilder::default()
41    }
42
43    /// Default timestamp format when not specified by `@timestampFormat` trait.
44    /// REST XML uses `date-time`.
45    pub fn default_timestamp_format(&self) -> TimestampFormat {
46        self.default_timestamp_format
47    }
48
49    /// Maximum aggregate nesting depth the deserializer will accept before
50    /// returning an error. Defends against stack overflow on recursive
51    /// shapes and deeply-nested XML payloads.
52    pub fn max_depth(&self) -> u32 {
53        self.max_depth
54    }
55}
56
57impl Default for XmlCodecSettings {
58    fn default() -> Self {
59        Self {
60            default_timestamp_format: TimestampFormat::DateTime,
61            max_depth: crate::codec::deserializer::MAX_DESERIALIZE_DEPTH,
62        }
63    }
64}
65
66/// Builder for [`XmlCodecSettings`].
67#[derive(Debug, Clone)]
68pub struct XmlCodecSettingsBuilder {
69    default_timestamp_format: TimestampFormat,
70    max_depth: u32,
71}
72
73impl Default for XmlCodecSettingsBuilder {
74    fn default() -> Self {
75        Self {
76            default_timestamp_format: TimestampFormat::DateTime,
77            max_depth: crate::codec::deserializer::MAX_DESERIALIZE_DEPTH,
78        }
79    }
80}
81
82impl XmlCodecSettingsBuilder {
83    /// Default timestamp format when not specified by `@timestampFormat` trait.
84    pub fn default_timestamp_format(mut self, value: TimestampFormat) -> Self {
85        self.default_timestamp_format = value;
86        self
87    }
88
89    /// Sets the maximum aggregate nesting depth the deserializer will accept
90    /// before returning an error. Defaults to 128.
91    pub fn max_depth(mut self, value: u32) -> Self {
92        self.max_depth = value;
93        self
94    }
95
96    /// Builds the settings.
97    pub fn build(self) -> XmlCodecSettings {
98        XmlCodecSettings {
99            default_timestamp_format: self.default_timestamp_format,
100            max_depth: self.max_depth,
101        }
102    }
103}
104
105/// XML codec for schema-based serialization and deserialization.
106///
107/// Used by REST XML to serialize request bodies and deserialize response
108/// bodies. The codec carries no state of its own — each `create_serializer`
109/// and `create_deserializer` call returns a fresh instance whose lifetime
110/// brackets one (de)serialization.
111///
112/// # Examples
113///
114/// ```
115/// use aws_smithy_xml::codec::{XmlCodec, XmlCodecSettings};
116/// use aws_smithy_schema::codec::Codec;
117///
118/// let codec = XmlCodec::new(XmlCodecSettings::default());
119/// let _serializer = codec.create_serializer();
120/// let _deserializer = codec.create_deserializer(b"<Root/>");
121/// ```
122#[derive(Debug)]
123pub struct XmlCodec {
124    settings: Arc<XmlCodecSettings>,
125}
126
127impl XmlCodec {
128    /// Creates a new XML codec with the given settings.
129    pub fn new(settings: XmlCodecSettings) -> Self {
130        Self {
131            settings: Arc::new(settings),
132        }
133    }
134
135    /// Creates a new XML codec from a pre-existing shared settings.
136    pub fn from_shared_settings(settings: Arc<XmlCodecSettings>) -> Self {
137        Self { settings }
138    }
139
140    /// Returns the codec settings.
141    pub fn settings(&self) -> &XmlCodecSettings {
142        &self.settings
143    }
144}
145
146impl Default for XmlCodec {
147    fn default() -> Self {
148        Self::new(XmlCodecSettings::default())
149    }
150}
151
152impl Codec for XmlCodec {
153    type Serializer = XmlSerializer;
154    type Deserializer<'a> = XmlDeserializer<'a>;
155
156    fn create_serializer(&self) -> Self::Serializer {
157        XmlSerializer::new(self.settings.clone())
158    }
159
160    fn create_deserializer<'a>(&self, input: &'a [u8]) -> Self::Deserializer<'a> {
161        XmlDeserializer::new(input, self.settings.clone())
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168
169    #[test]
170    fn test_default_settings() {
171        let settings = XmlCodecSettings::default();
172        assert_eq!(
173            settings.default_timestamp_format(),
174            TimestampFormat::DateTime
175        );
176    }
177
178    #[test]
179    fn test_codec_creation() {
180        let codec = XmlCodec::default();
181        let _serializer = codec.create_serializer();
182        let _deserializer = codec.create_deserializer(b"<Root/>");
183    }
184
185    #[test]
186    fn test_end_to_end_serialize_through_codec() {
187        use aws_smithy_schema::codec::FinishSerializer;
188        use aws_smithy_schema::serde::{SerdeError, SerializableStruct, ShapeSerializer};
189        use aws_smithy_schema::{shape_id, Schema, ShapeType};
190
191        static NAME: Schema =
192            Schema::new_member(shape_id!("test", "X$name"), ShapeType::String, "name", 0);
193        static X_SCHEMA: Schema =
194            Schema::new_struct(shape_id!("test", "X"), ShapeType::Structure, &[&NAME])
195                .with_xml_namespace("urn:test", None);
196
197        struct X;
198        impl SerializableStruct for X {
199            fn serialize_members(&self, ser: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
200                ser.write_string(&NAME, "hello")
201            }
202        }
203
204        let codec = XmlCodec::default();
205        let mut ser = codec.create_serializer();
206        ser.write_struct(&X_SCHEMA, &X).unwrap();
207        let bytes = ser.finish();
208        assert_eq!(
209            String::from_utf8(bytes).unwrap(),
210            "<X xmlns=\"urn:test\"><name>hello</name></X>"
211        );
212    }
213
214    #[test]
215    fn test_end_to_end_deserialize_through_codec() {
216        use aws_smithy_schema::serde::ShapeDeserializer;
217        use aws_smithy_schema::{shape_id, Schema, ShapeType};
218
219        static NAME: Schema =
220            Schema::new_member(shape_id!("test", "X$name"), ShapeType::String, "name", 0);
221        static AGE: Schema =
222            Schema::new_member(shape_id!("test", "X$age"), ShapeType::Integer, "age", 1);
223        static X_SCHEMA: Schema =
224            Schema::new_struct(shape_id!("test", "X"), ShapeType::Structure, &[&NAME, &AGE]);
225
226        let codec = XmlCodec::default();
227        let xml = b"<X><name>Alice</name><age>30</age></X>";
228        let mut deser = codec.create_deserializer(xml);
229
230        let mut name = String::new();
231        let mut age = 0i32;
232        deser
233            .read_struct(&X_SCHEMA, &mut |member, d| {
234                match member.member_name().unwrap() {
235                    "name" => name = d.read_string(member)?,
236                    "age" => age = d.read_integer(member)?,
237                    _ => {}
238                }
239                Ok(())
240            })
241            .unwrap();
242
243        assert_eq!(name, "Alice");
244        assert_eq!(age, 30);
245    }
246}