1use aws_smithy_schema::serde::ShapeSerializer;
9use aws_smithy_schema::{Schema, ShapeId};
10use aws_smithy_types::date_time::Format as TimestampFormat;
11use aws_smithy_types::{BigDecimal, BigInteger, Blob, DateTime, Document};
12use std::fmt;
13
14use crate::codec::JsonCodecSettings;
15
16#[derive(Debug)]
18pub enum JsonSerializerError {
19 WriteError(String),
21}
22
23impl fmt::Display for JsonSerializerError {
24 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25 match self {
26 Self::WriteError(msg) => write!(f, "JSON write error: {}", msg),
27 }
28 }
29}
30
31impl std::error::Error for JsonSerializerError {}
32
33pub struct JsonSerializer {
35 output: String,
36 settings: JsonCodecSettings,
37}
38
39impl JsonSerializer {
40 pub fn new(settings: JsonCodecSettings) -> Self {
42 Self {
43 output: String::new(),
44 settings,
45 }
46 }
47
48 fn get_timestamp_format(&self, schema: &dyn Schema) -> TimestampFormat {
50 let timestamp_format_trait_id = ShapeId::new("smithy.api#timestampFormat");
51 if let Some(trait_obj) = schema.traits().get(×tamp_format_trait_id) {
52 if let Some(format_str) = trait_obj.as_any().downcast_ref::<String>() {
53 return match format_str.as_str() {
54 "epoch-seconds" => TimestampFormat::EpochSeconds,
55 "http-date" => TimestampFormat::HttpDate,
56 "date-time" => TimestampFormat::DateTime,
57 _ => self.settings.default_timestamp_format,
58 };
59 }
60 }
61 self.settings.default_timestamp_format
62 }
63
64 fn write_json_value(&mut self, doc: &Document) {
65 use crate::serialize::JsonValueWriter;
66 let writer = JsonValueWriter::new(&mut self.output);
67 writer.document(doc);
68 }
69}
70
71impl ShapeSerializer for JsonSerializer {
72 type Output = Vec<u8>;
73 type Error = JsonSerializerError;
74
75 fn finish(self) -> Result<Self::Output, Self::Error> {
76 Ok(self.output.into_bytes())
77 }
78
79 fn write_struct<F>(&mut self, _schema: &dyn Schema, write_members: F) -> Result<(), Self::Error>
80 where
81 F: FnOnce(&mut Self) -> Result<(), Self::Error>,
82 {
83 self.output.push('{');
84 write_members(self)?;
85 self.output.push('}');
86 Ok(())
87 }
88
89 fn write_list<F>(&mut self, _schema: &dyn Schema, write_elements: F) -> Result<(), Self::Error>
90 where
91 F: FnOnce(&mut Self) -> Result<(), Self::Error>,
92 {
93 self.output.push('[');
94 write_elements(self)?;
95 self.output.push(']');
96 Ok(())
97 }
98
99 fn write_map<F>(&mut self, _schema: &dyn Schema, write_entries: F) -> Result<(), Self::Error>
100 where
101 F: FnOnce(&mut Self) -> Result<(), Self::Error>,
102 {
103 self.output.push('{');
104 write_entries(self)?;
105 self.output.push('}');
106 Ok(())
107 }
108
109 fn write_boolean(&mut self, _schema: &dyn Schema, value: bool) -> Result<(), Self::Error> {
110 self.output.push_str(if value { "true" } else { "false" });
111 Ok(())
112 }
113
114 fn write_byte(&mut self, _schema: &dyn Schema, value: i8) -> Result<(), Self::Error> {
115 use std::fmt::Write;
116 write!(&mut self.output, "{}", value)
117 .map_err(|e| JsonSerializerError::WriteError(e.to_string()))
118 }
119
120 fn write_short(&mut self, _schema: &dyn Schema, value: i16) -> Result<(), Self::Error> {
121 use std::fmt::Write;
122 write!(&mut self.output, "{}", value)
123 .map_err(|e| JsonSerializerError::WriteError(e.to_string()))
124 }
125
126 fn write_integer(&mut self, _schema: &dyn Schema, value: i32) -> Result<(), Self::Error> {
127 use std::fmt::Write;
128 write!(&mut self.output, "{}", value)
129 .map_err(|e| JsonSerializerError::WriteError(e.to_string()))
130 }
131
132 fn write_long(&mut self, _schema: &dyn Schema, value: i64) -> Result<(), Self::Error> {
133 use std::fmt::Write;
134 write!(&mut self.output, "{}", value)
135 .map_err(|e| JsonSerializerError::WriteError(e.to_string()))
136 }
137
138 fn write_float(&mut self, _schema: &dyn Schema, value: f32) -> Result<(), Self::Error> {
139 use std::fmt::Write;
140 write!(&mut self.output, "{}", value)
141 .map_err(|e| JsonSerializerError::WriteError(e.to_string()))
142 }
143
144 fn write_double(&mut self, _schema: &dyn Schema, value: f64) -> Result<(), Self::Error> {
145 use std::fmt::Write;
146 write!(&mut self.output, "{}", value)
147 .map_err(|e| JsonSerializerError::WriteError(e.to_string()))
148 }
149
150 fn write_big_integer(
151 &mut self,
152 _schema: &dyn Schema,
153 value: &BigInteger,
154 ) -> Result<(), Self::Error> {
155 self.output.push_str(value.as_ref());
156 Ok(())
157 }
158
159 fn write_big_decimal(
160 &mut self,
161 _schema: &dyn Schema,
162 value: &BigDecimal,
163 ) -> Result<(), Self::Error> {
164 self.output.push_str(value.as_ref());
165 Ok(())
166 }
167
168 fn write_string(&mut self, _schema: &dyn Schema, value: &str) -> Result<(), Self::Error> {
169 use crate::escape::escape_string;
170 self.output.push('"');
171 self.output.push_str(&escape_string(value));
172 self.output.push('"');
173 Ok(())
174 }
175
176 fn write_blob(&mut self, _schema: &dyn Schema, value: &Blob) -> Result<(), Self::Error> {
177 use aws_smithy_types::base64;
178 let encoded = base64::encode(value.as_ref());
179 self.output.push('"');
180 self.output.push_str(&encoded);
181 self.output.push('"');
182 Ok(())
183 }
184
185 fn write_timestamp(
186 &mut self,
187 schema: &dyn Schema,
188 value: &DateTime,
189 ) -> Result<(), Self::Error> {
190 let format = self.get_timestamp_format(schema);
191 let formatted = value.fmt(format).map_err(|e| {
192 JsonSerializerError::WriteError(format!("Failed to format timestamp: {}", e))
193 })?;
194
195 match format {
196 TimestampFormat::EpochSeconds => {
197 self.output.push_str(&formatted);
199 }
200 _ => {
201 self.output.push('"');
203 self.output.push_str(&formatted);
204 self.output.push('"');
205 }
206 }
207 Ok(())
208 }
209
210 fn write_document(
211 &mut self,
212 _schema: &dyn Schema,
213 value: &Document,
214 ) -> Result<(), Self::Error> {
215 self.write_json_value(value);
216 Ok(())
217 }
218
219 fn write_null(&mut self, _schema: &dyn Schema) -> Result<(), Self::Error> {
220 self.output.push_str("null");
221 Ok(())
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228 use aws_smithy_schema::prelude::*;
229
230 #[test]
231 fn test_write_boolean() {
232 let mut ser = JsonSerializer::new(JsonCodecSettings::default());
233 ser.write_boolean(&BOOLEAN, true).unwrap();
234 let output = ser.finish().unwrap();
235 assert_eq!(String::from_utf8(output).unwrap(), "true");
236 }
237
238 #[test]
239 fn test_write_string() {
240 let mut ser = JsonSerializer::new(JsonCodecSettings::default());
241 ser.write_string(&STRING, "hello").unwrap();
242 let output = ser.finish().unwrap();
243 assert_eq!(String::from_utf8(output).unwrap(), "\"hello\"");
244 }
245
246 #[test]
247 fn test_write_integer() {
248 let mut ser = JsonSerializer::new(JsonCodecSettings::default());
249 ser.write_integer(&INTEGER, 42).unwrap();
250 let output = ser.finish().unwrap();
251 assert_eq!(String::from_utf8(output).unwrap(), "42");
252 }
253
254 #[test]
255 fn test_write_null() {
256 let mut ser = JsonSerializer::new(JsonCodecSettings::default());
257 ser.write_null(&STRING).unwrap();
258 let output = ser.finish().unwrap();
259 assert_eq!(String::from_utf8(output).unwrap(), "null");
260 }
261
262 #[test]
263 fn test_write_list() {
264 let mut ser = JsonSerializer::new(JsonCodecSettings::default());
265 let list_schema = aws_smithy_schema::prelude::PreludeSchema::new(
267 aws_smithy_schema::ShapeId::new("test#List"),
268 aws_smithy_schema::ShapeType::List,
269 );
270 ser.write_list(&list_schema, |s| {
271 s.write_integer(&INTEGER, 1)?;
272 s.output.push(',');
273 s.write_integer(&INTEGER, 2)?;
274 s.output.push(',');
275 s.write_integer(&INTEGER, 3)?;
276 Ok(())
277 })
278 .unwrap();
279 let output = ser.finish().unwrap();
280 assert_eq!(String::from_utf8(output).unwrap(), "[1,2,3]");
281 }
282
283 #[test]
284 fn test_write_full_object() {
285 let mut ser = JsonSerializer::new(JsonCodecSettings::default());
286 let struct_schema = aws_smithy_schema::prelude::PreludeSchema::new(
287 aws_smithy_schema::ShapeId::new("test#Struct"),
288 aws_smithy_schema::ShapeType::Structure,
289 );
290 let list_schema = aws_smithy_schema::prelude::PreludeSchema::new(
291 aws_smithy_schema::ShapeId::new("test#List"),
292 aws_smithy_schema::ShapeType::List,
293 );
294 ser.write_struct(&struct_schema, |s| {
295 s.output.push_str("\"active\":");
296 s.write_boolean(&BOOLEAN, true)?;
297 s.output.push(',');
298 s.output.push_str("\"name\":");
299 s.write_string(&STRING, "test")?;
300 s.output.push(',');
301 s.output.push_str("\"count\":");
302 s.write_integer(&INTEGER, 42)?;
303 s.output.push(',');
304 s.output.push_str("\"price\":");
305 s.write_float(&FLOAT, 3.14)?;
306 s.output.push(',');
307 s.output.push_str("\"items\":");
308 s.write_list(&list_schema, |ls| {
309 ls.write_integer(&INTEGER, 1)?;
310 ls.output.push(',');
311 ls.write_integer(&INTEGER, 2)?;
312 Ok(())
313 })?;
314 Ok(())
315 })
316 .unwrap();
317 let output = ser.finish().unwrap();
318 assert_eq!(
319 String::from_utf8(output).unwrap(),
320 "{\"active\":true,\"name\":\"test\",\"count\":42,\"price\":3.14,\"items\":[1,2]}"
321 );
322 }
323
324 #[test]
325 fn test_nested_complex_serialization() {
326 let mut ser = JsonSerializer::new(JsonCodecSettings::default());
327 let struct_schema = aws_smithy_schema::prelude::PreludeSchema::new(
328 aws_smithy_schema::ShapeId::new("test#User"),
329 aws_smithy_schema::ShapeType::Structure,
330 );
331 let list_schema = aws_smithy_schema::prelude::PreludeSchema::new(
332 aws_smithy_schema::ShapeId::new("test#List"),
333 aws_smithy_schema::ShapeType::List,
334 );
335 let map_schema = aws_smithy_schema::prelude::PreludeSchema::new(
336 aws_smithy_schema::ShapeId::new("test#Map"),
337 aws_smithy_schema::ShapeType::Map,
338 );
339
340 ser.write_struct(&struct_schema, |s| {
341 s.output.push_str("\"id\":");
342 s.write_long(&LONG, 12345)?;
343 s.output.push(',');
344 s.output.push_str("\"name\":");
345 s.write_string(&STRING, "John Doe")?;
346 s.output.push(',');
347 s.output.push_str("\"scores\":");
348 s.write_list(&list_schema, |ls| {
349 ls.write_double(&DOUBLE, 95.5)?;
350 ls.output.push(',');
351 ls.write_double(&DOUBLE, 87.3)?;
352 ls.output.push(',');
353 ls.write_double(&DOUBLE, 92.1)?;
354 Ok(())
355 })?;
356 s.output.push(',');
357 s.output.push_str("\"address\":");
358 s.write_struct(&struct_schema, |addr| {
359 addr.output.push_str("\"street\":");
360 addr.write_string(&STRING, "123 Main St")?;
361 addr.output.push(',');
362 addr.output.push_str("\"city\":");
363 addr.write_string(&STRING, "Seattle")?;
364 addr.output.push(',');
365 addr.output.push_str("\"zip\":");
366 addr.write_integer(&INTEGER, 98101)?;
367 Ok(())
368 })?;
369 s.output.push(',');
370 s.output.push_str("\"companies\":");
371 s.write_list(&list_schema, |ls| {
372 ls.write_struct(&struct_schema, |comp| {
373 comp.output.push_str("\"name\":");
374 comp.write_string(&STRING, "TechCorp")?;
375 comp.output.push(',');
376 comp.output.push_str("\"employees\":");
377 comp.write_list(&list_schema, |emp| {
378 emp.write_string(&STRING, "Alice")?;
379 emp.output.push(',');
380 emp.write_string(&STRING, "Bob")?;
381 Ok(())
382 })?;
383 comp.output.push(',');
384 comp.output.push_str("\"metadata\":");
385 comp.write_map(&map_schema, |meta| {
386 meta.output.push_str("\"founded\":");
387 meta.write_integer(&INTEGER, 2010)?;
388 meta.output.push(',');
389 meta.output.push_str("\"size\":");
390 meta.write_integer(&INTEGER, 500)?;
391 Ok(())
392 })?;
393 comp.output.push(',');
394 comp.output.push_str("\"active\":");
395 comp.write_boolean(&BOOLEAN, true)?;
396 Ok(())
397 })?;
398 ls.output.push(',');
399 ls.write_struct(&struct_schema, |comp| {
400 comp.output.push_str("\"name\":");
401 comp.write_string(&STRING, "StartupInc")?;
402 comp.output.push(',');
403 comp.output.push_str("\"employees\":");
404 comp.write_list(&list_schema, |emp| {
405 emp.write_string(&STRING, "Charlie")?;
406 Ok(())
407 })?;
408 comp.output.push(',');
409 comp.output.push_str("\"metadata\":");
410 comp.write_map(&map_schema, |meta| {
411 meta.output.push_str("\"founded\":");
412 meta.write_integer(&INTEGER, 2020)?;
413 Ok(())
414 })?;
415 comp.output.push(',');
416 comp.output.push_str("\"active\":");
417 comp.write_boolean(&BOOLEAN, false)?;
418 Ok(())
419 })?;
420 Ok(())
421 })?;
422 s.output.push(',');
423 s.output.push_str("\"tags\":");
424 s.write_map(&map_schema, |tags| {
425 tags.output.push_str("\"role\":");
426 tags.write_string(&STRING, "admin")?;
427 tags.output.push(',');
428 tags.output.push_str("\"level\":");
429 tags.write_string(&STRING, "senior")?;
430 Ok(())
431 })?;
432 Ok(())
433 })
434 .unwrap();
435
436 let output = String::from_utf8(ser.finish().unwrap()).unwrap();
437 let expected = r#"{"id":12345,"name":"John Doe","scores":[95.5,87.3,92.1],"address":{"street":"123 Main St","city":"Seattle","zip":98101},"companies":[{"name":"TechCorp","employees":["Alice","Bob"],"metadata":{"founded":2010,"size":500},"active":true},{"name":"StartupInc","employees":["Charlie"],"metadata":{"founded":2020},"active":false}],"tags":{"role":"admin","level":"senior"}}"#;
438 assert_eq!(output, expected);
439 }
440}