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 + |
|
8 + | use crate::serde::{ShapeDeserializer, ShapeSerializer};
|
9 + | use crate::Schema;
|
10 + | use aws_smithy_types::{BigDecimal, BigInteger, Blob, DateTime, Document};
|
11 + | use std::error::Error;
|
12 + | use std::fmt;
|
13 + |
|
14 + | /// Error type for HTTP string serialization/deserialization.
|
15 + | #[derive(Debug)]
|
16 + | pub struct HttpStringCodecError {
|
17 + | message: String,
|
18 + | }
|
19 + |
|
20 + | impl HttpStringCodecError {
|
21 + | fn new(message: impl Into<String>) -> Self {
|
22 + | Self {
|
23 + | message: message.into(),
|
24 + | }
|
25 + | }
|
26 + | }
|
27 + |
|
28 + | impl 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 + |
|
34 + | impl Error for HttpStringCodecError {}
|
35 + |
|
36 + | /// Serializer for converting Smithy types to strings (for HTTP headers, query params, labels).
|
37 + | pub struct HttpStringSerializer {
|
38 + | output: String,
|
39 + | }
|
40 + |
|
41 + | impl HttpStringSerializer {
|
42 + | /// Creates a new HTTP string serializer.
|
43 + | pub fn new() -> Self {
|
44 + | Self {
|
45 + | output: String::new(),
|
46 + | }
|
47 + | }
|
48 + | }
|
49 + |
|
50 + | impl Default for HttpStringSerializer {
|
51 + | fn default() -> Self {
|
52 + | Self::new()
|
53 + | }
|
54 + | }
|
55 + |
|
56 + | impl 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.
|
247 + | pub struct HttpStringDeserializer<'a> {
|
248 + | input: std::borrow::Cow<'a, str>,
|
249 + | position: usize,
|
250 + | }
|
251 + |
|
252 + | impl<'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 + |
|
282 + | impl<'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.
|
461 + | pub struct HttpStringCodec;
|
462 + |
|
463 + | impl 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)]
|
481 + | mod 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 + | }
|