aws_smithy_json/
codec.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! JSON codec implementation for schema-based serialization.
7
8use aws_smithy_schema::codec::Codec;
9use aws_smithy_schema::Schema;
10use aws_smithy_types::date_time::Format as TimestampFormat;
11use std::sync::Arc;
12
13mod deserializer;
14mod serializer;
15
16pub use deserializer::JsonDeserializer;
17pub use serializer::JsonSerializer;
18
19/// Maps between Smithy member names and JSON wire field names.
20///
21/// When `@jsonName` is enabled, the wire name may differ from the member name.
22/// This type handles the mapping in both directions and caches the reverse
23/// lookup (wire name → member index) per struct schema.
24#[derive(Debug)]
25enum JsonFieldMapper {
26    /// Uses member names directly, ignoring `@jsonName`.
27    UseMemberName,
28    /// Uses `@jsonName` trait values when present, falling back to member name.
29    UseJsonName,
30}
31
32impl JsonFieldMapper {
33    /// Returns the JSON wire name for a member schema.
34    fn member_to_field<'a>(&self, member: &'a Schema) -> Option<&'a str> {
35        let name = member.member_name()?;
36        match self {
37            JsonFieldMapper::UseMemberName => Some(name),
38            JsonFieldMapper::UseJsonName => {
39                if let Some(jn) = member.json_name() {
40                    return Some(jn.value());
41                }
42                Some(name)
43            }
44        }
45    }
46
47    /// Resolves a JSON wire field name to a member schema within a struct schema.
48    fn field_to_member<'s>(&self, schema: &'s Schema, field_name: &str) -> Option<&'s Schema> {
49        match self {
50            JsonFieldMapper::UseMemberName => schema.member_schema(field_name),
51            JsonFieldMapper::UseJsonName => {
52                // Check @jsonName on each member. For typical struct sizes
53                // (< 50 members), linear scan is faster than a cached HashMap
54                // behind a Mutex.
55                for member in schema.members() {
56                    if let Some(jn) = member.json_name() {
57                        if jn.value() == field_name {
58                            return Some(member);
59                        }
60                    } else if member.member_name() == Some(field_name) {
61                        return Some(member);
62                    }
63                }
64                None
65            }
66        }
67    }
68}
69
70/// Configuration for JSON codec behavior.
71///
72/// Use the builder methods to construct settings:
73/// ```
74/// use aws_smithy_json::codec::JsonCodecSettings;
75///
76/// let settings = JsonCodecSettings::builder()
77///     .use_json_name(false)
78///     .build();
79/// ```
80#[derive(Debug)]
81pub struct JsonCodecSettings {
82    field_mapper: JsonFieldMapper,
83    default_timestamp_format: TimestampFormat,
84    max_depth: u32,
85}
86
87impl JsonCodecSettings {
88    /// Creates a builder for `JsonCodecSettings`.
89    pub fn builder() -> JsonCodecSettingsBuilder {
90        JsonCodecSettingsBuilder::default()
91    }
92
93    /// Default timestamp format when not specified by `@timestampFormat` trait.
94    pub fn default_timestamp_format(&self) -> TimestampFormat {
95        self.default_timestamp_format
96    }
97
98    /// Maximum aggregate nesting depth the deserializer will accept before
99    /// returning an error. Defends against stack overflow on recursive shapes
100    /// and deeply-nested document payloads.
101    pub fn max_depth(&self) -> u32 {
102        self.max_depth
103    }
104
105    /// Returns the JSON wire name for a member schema.
106    pub(crate) fn member_to_field<'a>(&self, member: &'a Schema) -> Option<&'a str> {
107        self.field_mapper.member_to_field(member)
108    }
109
110    /// Resolves a JSON wire field name to a member schema.
111    pub(crate) fn field_to_member<'s>(
112        &self,
113        schema: &'s Schema,
114        field_name: &str,
115    ) -> Option<&'s Schema> {
116        self.field_mapper.field_to_member(schema, field_name)
117    }
118}
119
120impl Default for JsonCodecSettings {
121    fn default() -> Self {
122        Self {
123            field_mapper: JsonFieldMapper::UseJsonName,
124            default_timestamp_format: TimestampFormat::EpochSeconds,
125            max_depth: crate::codec::deserializer::MAX_DESERIALIZE_DEPTH,
126        }
127    }
128}
129
130/// Builder for [`JsonCodecSettings`].
131#[derive(Debug, Clone)]
132pub struct JsonCodecSettingsBuilder {
133    use_json_name: bool,
134    default_timestamp_format: TimestampFormat,
135    max_depth: u32,
136}
137
138impl Default for JsonCodecSettingsBuilder {
139    fn default() -> Self {
140        Self {
141            use_json_name: true,
142            default_timestamp_format: TimestampFormat::EpochSeconds,
143            max_depth: crate::codec::deserializer::MAX_DESERIALIZE_DEPTH,
144        }
145    }
146}
147
148impl JsonCodecSettingsBuilder {
149    /// Whether to use the `@jsonName` trait for member names.
150    pub fn use_json_name(mut self, value: bool) -> Self {
151        self.use_json_name = value;
152        self
153    }
154
155    /// Default timestamp format when not specified by `@timestampFormat` trait.
156    pub fn default_timestamp_format(mut self, value: TimestampFormat) -> Self {
157        self.default_timestamp_format = value;
158        self
159    }
160
161    /// Sets the maximum aggregate nesting depth the deserializer will accept
162    /// before returning an error. Defaults to 128.
163    pub fn max_depth(mut self, value: u32) -> Self {
164        self.max_depth = value;
165        self
166    }
167
168    /// Builds the settings.
169    pub fn build(self) -> JsonCodecSettings {
170        let field_mapper = if self.use_json_name {
171            JsonFieldMapper::UseJsonName
172        } else {
173            JsonFieldMapper::UseMemberName
174        };
175        JsonCodecSettings {
176            field_mapper,
177            default_timestamp_format: self.default_timestamp_format,
178            max_depth: self.max_depth,
179        }
180    }
181}
182
183/// JSON codec for schema-based serialization and deserialization.
184///
185/// # Examples
186///
187/// ```
188/// use aws_smithy_json::codec::{JsonCodec, JsonCodecSettings};
189/// use aws_smithy_schema::codec::Codec;
190///
191/// // Create codec with default settings (REST JSON style)
192/// let codec = JsonCodec::new(JsonCodecSettings::default());
193///
194/// // Create codec for AWS JSON RPC (no jsonName, epoch-seconds timestamps)
195/// let codec = JsonCodec::new(
196///     JsonCodecSettings::builder()
197///         .use_json_name(false)
198///         .build()
199/// );
200/// ```
201#[derive(Debug)]
202pub struct JsonCodec {
203    settings: Arc<JsonCodecSettings>,
204}
205
206impl JsonCodec {
207    /// Creates a new JSON codec with the given settings.
208    pub fn new(settings: JsonCodecSettings) -> Self {
209        Self {
210            settings: Arc::new(settings),
211        }
212    }
213
214    /// Returns the codec settings.
215    pub fn settings(&self) -> &JsonCodecSettings {
216        &self.settings
217    }
218}
219
220impl Default for JsonCodec {
221    fn default() -> Self {
222        Self::new(JsonCodecSettings::default())
223    }
224}
225
226impl Codec for JsonCodec {
227    type Serializer = JsonSerializer;
228    type Deserializer<'a> = JsonDeserializer<'a>;
229
230    fn create_serializer(&self) -> Self::Serializer {
231        JsonSerializer::new(self.settings.clone())
232    }
233
234    fn create_deserializer<'a>(&self, input: &'a [u8]) -> Self::Deserializer<'a> {
235        JsonDeserializer::new(input, self.settings.clone())
236    }
237}
238
239#[cfg(test)]
240mod tests {
241    use super::*;
242
243    #[test]
244    fn test_default_settings() {
245        let settings = JsonCodecSettings::default();
246        assert_eq!(
247            settings.default_timestamp_format(),
248            TimestampFormat::EpochSeconds
249        );
250    }
251
252    #[test]
253    fn test_builder() {
254        let settings = JsonCodecSettings::builder()
255            .use_json_name(false)
256            .default_timestamp_format(TimestampFormat::DateTime)
257            .build();
258        assert_eq!(
259            settings.default_timestamp_format(),
260            TimestampFormat::DateTime
261        );
262    }
263
264    #[test]
265    fn test_codec_creation() {
266        let codec = JsonCodec::default();
267        let _serializer = codec.create_serializer();
268        let _deserializer = codec.create_deserializer(b"{}");
269    }
270}