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