122 122 | self.depth -= 1;
|
123 123 | return Ok(());
|
124 124 | }
|
125 125 | if self.remaining().first() != Some(&b'{') {
|
126 126 | return Err(SerdeError::TypeMismatch {
|
127 127 | message: "expected object".into(),
|
128 128 | });
|
129 129 | }
|
130 130 | self.advance_by(1);
|
131 131 |
|
132 + | // A union holds exactly one member. Every key that names a member counts as a
|
133 + | // variant, so a second one is an error, as in the token-based parser
|
134 + | // ("encountered mixed variants in union"). `null` values are absent members and
|
135 + | // do not count; `__type` is a discriminator, never a member.
|
136 + | let is_union = matches!(schema.shape_type(), ShapeType::Union);
|
137 + | let mut variant_seen = false;
|
138 + | let mut first = true;
|
132 139 | loop {
|
133 - | self.skip_whitespace();
|
134 - |
|
135 - | // Check for end of object, error on end of input, otherwise
|
136 - | // fall through to parse the next key/value pair.
|
137 - | match self.remaining().first() {
|
138 - | Some(&b'}') => {
|
139 - | self.advance_by(1);
|
140 - | break;
|
141 - | }
|
142 - | None => {
|
143 - | return Err(SerdeError::InvalidInput {
|
144 - | message: "unexpected end of input in object".into(),
|
145 - | });
|
146 - | }
|
147 - | Some(&b'"') => {}
|
148 - | Some(_) => {
|
149 - | return Err(SerdeError::InvalidInput {
|
150 - | message: "expected object key".into(),
|
151 - | });
|
152 - | }
|
140 + | // Stop at the end of the object; otherwise the next key/value pair starts here.
|
141 + | if self.next_element(first, b'}', "object")? {
|
142 + | break;
|
143 + | }
|
144 + | first = false;
|
145 + | if self.remaining().first() != Some(&b'"') {
|
146 + | return Err(SerdeError::InvalidInput {
|
147 + | message: "expected object key".into(),
|
148 + | });
|
153 149 | }
|
154 150 |
|
155 151 | // Parse the key directly from bytes
|
156 152 | let key_str = self.parse_key()?;
|
157 153 |
|
158 154 | // Skip whitespace and expect colon
|
159 155 | self.skip_whitespace();
|
160 156 | if self.remaining().first() != Some(&b':') {
|
161 157 | return Err(SerdeError::InvalidInput {
|
162 158 | message: "expected colon after key".into(),
|
163 159 | });
|
164 160 | }
|
165 161 | self.advance_by(1);
|
166 162 | self.skip_whitespace();
|
167 163 |
|
168 164 | // Process the value — skip nulls (they represent absent optional members)
|
169 165 | let rem = self.remaining();
|
170 166 | if rem.starts_with(b"null") && !rem.get(4).is_some_and(|b| b.is_ascii_alphanumeric()) {
|
171 167 | self.advance_by(4);
|
172 168 | } else if let Some(member_schema) = self.resolve_member(schema, &key_str) {
|
169 + | if is_union {
|
170 + | if variant_seen {
|
171 + | return Err(SerdeError::InvalidInput {
|
172 + | message: "encountered mixed variants in union".into(),
|
173 + | });
|
174 + | }
|
175 + | variant_seen = true;
|
176 + | }
|
173 177 | consumer(member_schema, self)?;
|
178 + | } else if is_union
|
179 + | && &*key_str != "__type"
|
180 + | && self.settings.reject_unknown_union_members()
|
181 + | {
|
182 + | return Err(SerdeError::UnknownMember {
|
183 + | member_name: key_str.into_owned(),
|
184 + | });
|
174 185 | } else {
|
175 186 | self.skip_value()?;
|
176 187 | }
|
177 188 | }
|
178 189 |
|
179 - | self.depth -= 1;
|
190 + | self.leave_container()?;
|
180 191 | Ok(())
|
181 192 | }
|
182 193 |
|
183 194 | fn read_list(
|
184 195 | &mut self,
|
185 196 | _schema: &Schema,
|
186 197 | consumer: &mut dyn FnMut(&mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
|
187 198 | ) -> Result<(), SerdeError> {
|
188 199 | self.depth += 1;
|
189 200 | if self.depth > self.settings.max_depth() {
|
190 201 | return Err(SerdeError::custom("maximum nesting depth exceeded"));
|
191 202 | }
|
192 203 | self.skip_whitespace();
|
193 204 | if self.remaining().first() != Some(&b'[') {
|
194 205 | return Err(SerdeError::TypeMismatch {
|
195 206 | message: "expected array".into(),
|
196 207 | });
|
197 208 | }
|
198 209 | self.advance_by(1);
|
199 210 |
|
211 + | let mut first = true;
|
200 212 | loop {
|
201 - | self.skip_whitespace();
|
202 - | match self.remaining().first() {
|
203 - | Some(&b']') => {
|
204 - | self.advance_by(1);
|
205 - | break;
|
206 - | }
|
207 - | None => {
|
208 - | return Err(SerdeError::InvalidInput {
|
209 - | message: "unexpected end of input in array".into(),
|
210 - | });
|
211 - | }
|
212 - | _ => consumer(self)?,
|
213 + | if self.next_element(first, b']', "array")? {
|
214 + | break;
|
213 215 | }
|
216 + | first = false;
|
217 + | consumer(self)?;
|
214 218 | }
|
215 219 |
|
216 - | self.depth -= 1;
|
220 + | self.leave_container()?;
|
217 221 | Ok(())
|
218 222 | }
|
219 223 |
|
220 224 | fn read_map(
|
221 225 | &mut self,
|
222 226 | _schema: &Schema,
|
223 227 | consumer: &mut dyn FnMut(String, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
|
224 228 | ) -> Result<(), SerdeError> {
|
225 229 | self.depth += 1;
|
226 230 | if self.depth > self.settings.max_depth() {
|
227 231 | return Err(SerdeError::custom("maximum nesting depth exceeded"));
|
228 232 | }
|
229 233 | self.skip_whitespace();
|
230 234 | if self.remaining().first() != Some(&b'{') {
|
231 235 | return Err(SerdeError::TypeMismatch {
|
232 236 | message: "expected object".into(),
|
233 237 | });
|
234 238 | }
|
235 239 | self.advance_by(1);
|
236 240 |
|
241 + | let mut first = true;
|
237 242 | loop {
|
238 - | self.skip_whitespace();
|
239 - | match self.remaining().first() {
|
240 - | Some(&b'}') => {
|
241 - | self.advance_by(1);
|
242 - | break;
|
243 - | }
|
244 - | None => {
|
245 - | return Err(SerdeError::InvalidInput {
|
246 - | message: "unexpected end of input in object".into(),
|
247 - | });
|
248 - | }
|
249 - | Some(&b'"') => {}
|
250 - | Some(_) => {
|
251 - | return Err(SerdeError::InvalidInput {
|
252 - | message: "expected key".into(),
|
253 - | });
|
254 - | }
|
243 + | if self.next_element(first, b'}', "object")? {
|
244 + | break;
|
245 + | }
|
246 + | first = false;
|
247 + | if self.remaining().first() != Some(&b'"') {
|
248 + | return Err(SerdeError::InvalidInput {
|
249 + | message: "expected key".into(),
|
250 + | });
|
255 251 | }
|
256 252 |
|
257 253 | let key = self.parse_key()?;
|
258 254 |
|
259 255 | self.skip_whitespace();
|
260 256 | if self.remaining().first() != Some(&b':') {
|
261 257 | return Err(SerdeError::InvalidInput {
|
262 258 | message: "expected colon".into(),
|
263 259 | });
|
264 260 | }
|
265 261 | self.advance_by(1);
|
266 262 | self.skip_whitespace();
|
267 263 |
|
268 264 | consumer(key.into_owned(), self)?;
|
269 265 | }
|
270 266 |
|
271 - | self.depth -= 1;
|
267 + | self.leave_container()?;
|
272 268 | Ok(())
|
273 269 | }
|
274 270 |
|
275 271 | fn read_boolean(&mut self, _schema: &Schema) -> Result<bool, SerdeError> {
|
276 272 | self.skip_whitespace();
|
277 273 | let rem = self.remaining();
|
278 274 | if rem.starts_with(b"true") {
|
279 275 | self.advance_by(4);
|
280 276 | Ok(true)
|
281 277 | } else if rem.starts_with(b"false") {
|
430 426 | return Err(SerdeError::custom("maximum nesting depth exceeded"));
|
431 427 | }
|
432 428 | self.skip_whitespace();
|
433 429 | if self.remaining().first() != Some(&b'[') {
|
434 430 | return Err(SerdeError::TypeMismatch {
|
435 431 | message: "expected array".into(),
|
436 432 | });
|
437 433 | }
|
438 434 | self.advance_by(1);
|
439 435 | let mut out = Vec::new();
|
436 + | let mut first = true;
|
440 437 | loop {
|
441 - | self.skip_whitespace();
|
442 - | match self.remaining().first() {
|
443 - | Some(&b']') => {
|
444 - | self.advance_by(1);
|
445 - | break;
|
446 - | }
|
447 - | None => {
|
448 - | return Err(SerdeError::InvalidInput {
|
449 - | message: "unexpected end of input in array".into(),
|
450 - | })
|
451 - | }
|
452 - | _ => out.push(self.read_string(_schema)?),
|
438 + | if self.next_element(first, b']', "array")? {
|
439 + | break;
|
453 440 | }
|
441 + | first = false;
|
442 + | out.push(self.read_string(_schema)?);
|
454 443 | }
|
455 - | self.depth -= 1;
|
444 + | self.leave_container()?;
|
456 445 | Ok(out)
|
457 446 | }
|
458 447 |
|
459 448 | fn read_blob_list(&mut self, _schema: &Schema) -> Result<Vec<Blob>, SerdeError> {
|
460 449 | self.depth += 1;
|
461 450 | if self.depth > self.settings.max_depth() {
|
462 451 | return Err(SerdeError::custom("maximum nesting depth exceeded"));
|
463 452 | }
|
464 453 | self.skip_whitespace();
|
465 454 | if self.remaining().first() != Some(&b'[') {
|
466 455 | return Err(SerdeError::TypeMismatch {
|
467 456 | message: "expected array".into(),
|
468 457 | });
|
469 458 | }
|
470 459 | self.advance_by(1);
|
471 460 | let mut out = Vec::new();
|
461 + | let mut first = true;
|
472 462 | loop {
|
473 - | self.skip_whitespace();
|
474 - | match self.remaining().first() {
|
475 - | Some(&b']') => {
|
476 - | self.advance_by(1);
|
477 - | break;
|
478 - | }
|
479 - | None => {
|
480 - | return Err(SerdeError::InvalidInput {
|
481 - | message: "unexpected end of input in array".into(),
|
482 - | })
|
483 - | }
|
484 - | _ => out.push(self.read_blob(_schema)?),
|
463 + | if self.next_element(first, b']', "array")? {
|
464 + | break;
|
485 465 | }
|
466 + | first = false;
|
467 + | out.push(self.read_blob(_schema)?);
|
486 468 | }
|
487 - | self.depth -= 1;
|
469 + | self.leave_container()?;
|
488 470 | Ok(out)
|
489 471 | }
|
490 472 |
|
491 473 | fn read_integer_list(&mut self, _schema: &Schema) -> Result<Vec<i32>, SerdeError> {
|
492 474 | self.depth += 1;
|
493 475 | if self.depth > self.settings.max_depth() {
|
494 476 | return Err(SerdeError::custom("maximum nesting depth exceeded"));
|
495 477 | }
|
496 478 | self.skip_whitespace();
|
497 479 | if self.remaining().first() != Some(&b'[') {
|
498 480 | return Err(SerdeError::TypeMismatch {
|
499 481 | message: "expected array".into(),
|
500 482 | });
|
501 483 | }
|
502 484 | self.advance_by(1);
|
503 485 | let mut out = Vec::new();
|
486 + | let mut first = true;
|
504 487 | loop {
|
505 - | self.skip_whitespace();
|
506 - | match self.remaining().first() {
|
507 - | Some(&b']') => {
|
508 - | self.advance_by(1);
|
509 - | break;
|
510 - | }
|
511 - | None => {
|
512 - | return Err(SerdeError::InvalidInput {
|
513 - | message: "unexpected end of input in array".into(),
|
514 - | })
|
515 - | }
|
516 - | _ => out.push(self.read_integer(_schema)?),
|
488 + | if self.next_element(first, b']', "array")? {
|
489 + | break;
|
517 490 | }
|
491 + | first = false;
|
492 + | out.push(self.read_integer(_schema)?);
|
518 493 | }
|
519 - | self.depth -= 1;
|
494 + | self.leave_container()?;
|
520 495 | Ok(out)
|
521 496 | }
|
522 497 |
|
523 498 | fn read_long_list(&mut self, _schema: &Schema) -> Result<Vec<i64>, SerdeError> {
|
524 499 | self.depth += 1;
|
525 500 | if self.depth > self.settings.max_depth() {
|
526 501 | return Err(SerdeError::custom("maximum nesting depth exceeded"));
|
527 502 | }
|
528 503 | self.skip_whitespace();
|
529 504 | if self.remaining().first() != Some(&b'[') {
|
530 505 | return Err(SerdeError::TypeMismatch {
|
531 506 | message: "expected array".into(),
|
532 507 | });
|
533 508 | }
|
534 509 | self.advance_by(1);
|
535 510 | let mut out = Vec::new();
|
511 + | let mut first = true;
|
536 512 | loop {
|
537 - | self.skip_whitespace();
|
538 - | match self.remaining().first() {
|
539 - | Some(&b']') => {
|
540 - | self.advance_by(1);
|
541 - | break;
|
542 - | }
|
543 - | None => {
|
544 - | return Err(SerdeError::InvalidInput {
|
545 - | message: "unexpected end of input in array".into(),
|
546 - | })
|
547 - | }
|
548 - | _ => out.push(self.read_long(_schema)?),
|
513 + | if self.next_element(first, b']', "array")? {
|
514 + | break;
|
549 515 | }
|
516 + | first = false;
|
517 + | out.push(self.read_long(_schema)?);
|
550 518 | }
|
551 - | self.depth -= 1;
|
519 + | self.leave_container()?;
|
552 520 | Ok(out)
|
553 521 | }
|
554 522 |
|
555 523 | fn read_string_string_map(
|
556 524 | &mut self,
|
557 525 | _schema: &Schema,
|
558 526 | ) -> Result<std::collections::HashMap<String, String>, SerdeError> {
|
559 527 | self.depth += 1;
|
560 528 | if self.depth > self.settings.max_depth() {
|
561 529 | return Err(SerdeError::custom("maximum nesting depth exceeded"));
|
562 530 | }
|
563 531 | self.skip_whitespace();
|
564 532 | if self.remaining().first() != Some(&b'{') {
|
565 533 | return Err(SerdeError::TypeMismatch {
|
566 534 | message: "expected object".into(),
|
567 535 | });
|
568 536 | }
|
569 537 | self.advance_by(1);
|
570 538 | let mut out = std::collections::HashMap::new();
|
539 + | let mut first = true;
|
571 540 | loop {
|
572 - | self.skip_whitespace();
|
573 - | if self.remaining().first() == Some(&b'}') {
|
574 - | self.advance_by(1);
|
541 + | if self.next_element(first, b'}', "object")? {
|
575 542 | break;
|
576 543 | }
|
544 + | first = false;
|
577 545 | if self.remaining().first() != Some(&b'"') {
|
578 546 | return Err(SerdeError::InvalidInput {
|
579 547 | message: "expected key".into(),
|
580 548 | });
|
581 549 | }
|
582 550 | let key = self.parse_key()?;
|
583 551 | self.skip_whitespace();
|
584 552 | if self.remaining().first() != Some(&b':') {
|
585 553 | return Err(SerdeError::InvalidInput {
|
586 554 | message: "expected colon".into(),
|
587 555 | });
|
588 556 | }
|
589 557 | self.advance_by(1);
|
590 558 | self.skip_whitespace();
|
591 559 | let val = self.read_string(_schema)?;
|
592 560 | out.insert(key.into_owned(), val);
|
593 561 | }
|
594 - | self.depth -= 1;
|
562 + | self.leave_container()?;
|
595 563 | Ok(out)
|
596 564 | }
|
597 565 |
|
598 566 | fn read_timestamp(&mut self, schema: &Schema) -> Result<DateTime, SerdeError> {
|
567 + | use aws_smithy_schema::traits::TimestampFormat;
|
568 + | use aws_smithy_types::date_time::Format;
|
569 + |
|
599 570 | self.skip_whitespace();
|
600 - | let rem = self.remaining();
|
601 - | match rem.first() {
|
571 + | let strict = self.settings.strict_timestamp_formats();
|
572 + | // The wire form the member prescribes: its `@timestampFormat`, else the codec default.
|
573 + | let format = match schema.timestamp_format().map(|t| t.format()) {
|
574 + | Some(TimestampFormat::EpochSeconds) => Format::EpochSeconds,
|
575 + | Some(TimestampFormat::DateTime) => Format::DateTime,
|
576 + | Some(TimestampFormat::HttpDate) => Format::HttpDate,
|
577 + | None => self.settings.default_timestamp_format(),
|
578 + | };
|
579 + | match self.remaining().first() {
|
602 580 | Some(b'"') => {
|
603 - | let s = self.read_string(schema)?;
|
604 - | // Determine parse format from @timestampFormat trait or default
|
605 - | let format = if let Some(ts_trait) = schema.timestamp_format() {
|
606 - | match ts_trait.format() {
|
607 - | aws_smithy_schema::traits::TimestampFormat::HttpDate => {
|
608 - | aws_smithy_types::date_time::Format::HttpDate
|
609 - | }
|
610 - | aws_smithy_schema::traits::TimestampFormat::EpochSeconds => {
|
611 - | aws_smithy_types::date_time::Format::EpochSeconds
|
612 - | }
|
613 - | aws_smithy_schema::traits::TimestampFormat::DateTime => {
|
614 - | aws_smithy_types::date_time::Format::DateTimeWithOffset
|
615 - | }
|
581 + | let string_format = if strict {
|
582 + | // Strict: the string must be in exactly the prescribed format, and
|
583 + | // `epoch-seconds` is never a string. This mirrors the token-based
|
584 + | // parser's `expect_timestamp_or_null`, which the legacy server uses.
|
585 + | if matches!(format, Format::EpochSeconds) {
|
586 + | return Err(SerdeError::TypeMismatch {
|
587 + | message: "expected a JSON number for an epoch-seconds timestamp".into(),
|
588 + | });
|
616 589 | }
|
590 + | format
|
617 591 | } else {
|
618 - | // Default: try date-time with offsets allowed
|
619 - | aws_smithy_types::date_time::Format::DateTimeWithOffset
|
592 + | // Lenient (unchanged behavior): an explicit `@timestampFormat` of
|
593 + | // `http-date` or `epoch-seconds` is honored as written; `date-time` and
|
594 + | // members without the trait parse as offset-aware `date-time`.
|
595 + | match schema.timestamp_format().map(|t| t.format()) {
|
596 + | Some(TimestampFormat::HttpDate) => Format::HttpDate,
|
597 + | Some(TimestampFormat::EpochSeconds) => Format::EpochSeconds,
|
598 + | _ => Format::DateTimeWithOffset,
|
599 + | }
|
620 600 | };
|
621 - | DateTime::from_str(&s, format)
|
601 + | let s = self.read_string(schema)?;
|
602 + | DateTime::from_str(&s, string_format)
|
622 603 | .map_err(|e| SerdeError::custom(format!("invalid timestamp string: {e}")))
|
623 604 | }
|
624 605 | Some(b'-') | Some(b'0'..=b'9') => {
|
625 - | // Numeric timestamp — epoch seconds
|
606 + | if strict && !matches!(format, Format::EpochSeconds) {
|
607 + | return Err(SerdeError::TypeMismatch {
|
608 + | message: "expected a JSON string for a date-time or http-date timestamp"
|
609 + | .into(),
|
610 + | });
|
611 + | }
|
612 + | // Numeric timestamp: epoch seconds.
|
626 613 | let start = self.position;
|
627 614 | self.consume_number();
|
628 615 | let num_str =
|
629 616 | std::str::from_utf8(&self.input[start..self.position]).map_err(|e| {
|
630 617 | SerdeError::InvalidInput {
|
631 618 | message: e.to_string(),
|
632 619 | }
|
633 620 | })?;
|
634 621 | if num_str.contains('.') || num_str.contains('e') || num_str.contains('E') {
|
635 622 | let f: f64 = num_str.parse().map_err(|e: std::num::ParseFloatError| {
|
636 623 | SerdeError::InvalidInput {
|
637 624 | message: e.to_string(),
|
638 625 | }
|
639 626 | })?;
|
640 627 | Ok(DateTime::from_secs_f64(f))
|
641 628 | } else if num_str.starts_with('-') {
|
642 629 | let n: i64 = num_str.parse().map_err(|e: std::num::ParseIntError| {
|
643 630 | SerdeError::InvalidInput {
|
644 631 | message: e.to_string(),
|
645 632 | }
|
646 633 | })?;
|
647 634 | Ok(DateTime::from_secs(n))
|
648 635 | } else {
|
649 636 | let n: u64 = num_str.parse().map_err(|e: std::num::ParseIntError| {
|
650 637 | SerdeError::InvalidInput {
|
651 638 | message: e.to_string(),
|
652 639 | }
|
653 640 | })?;
|
654 641 | Ok(DateTime::from_secs(n as i64))
|
655 642 | }
|
656 643 | }
|
657 644 | _ => Err(SerdeError::TypeMismatch {
|
658 645 | message: "expected timestamp".into(),
|
659 646 | }),
|
660 647 | }
|
661 648 | }
|
662 649 |
|
663 650 | fn read_document(&mut self, _schema: &Schema) -> Result<Document, SerdeError> {
|
664 651 | self.depth += 1;
|
665 652 | if self.depth > self.settings.max_depth() {
|
666 653 | return Err(SerdeError::custom("maximum nesting depth exceeded"));
|
667 654 | }
|
668 655 | self.skip_whitespace();
|
669 656 | let result = match self.remaining().first() {
|
670 657 | Some(b'"') => Ok(Document::String(self.read_string(_schema)?)),
|
671 658 | Some(b't') | Some(b'f') => Ok(Document::Bool(self.read_boolean(_schema)?)),
|
672 659 | Some(b'n') => {
|
673 660 | if self.remaining().starts_with(b"null") {
|
674 661 | self.advance_by(4);
|
675 662 | Ok(Document::Null)
|
676 663 | } else {
|
677 664 | Err(SerdeError::InvalidInput {
|
678 665 | message: "unexpected token in document".into(),
|
679 666 | })
|
680 667 | }
|
681 668 | }
|
682 669 | Some(b'{') => {
|
683 670 | self.advance_by(1);
|
684 671 | let mut map = std::collections::HashMap::new();
|
672 + | let mut first = true;
|
685 673 | loop {
|
686 - | self.skip_whitespace();
|
687 - | if self.remaining().first() == Some(&b'}') {
|
688 - | self.advance_by(1);
|
674 + | if self.next_element(first, b'}', "document object")? {
|
689 675 | break;
|
690 676 | }
|
677 + | first = false;
|
691 678 | if self.remaining().first() != Some(&b'"') {
|
692 679 | return Err(SerdeError::InvalidInput {
|
693 680 | message: "expected object key in document".into(),
|
694 681 | });
|
695 682 | }
|
696 683 | let key = self.parse_key()?.into_owned();
|
697 684 | self.skip_whitespace();
|
698 685 | if self.remaining().first() != Some(&b':') {
|
699 686 | return Err(SerdeError::InvalidInput {
|
700 687 | message: "expected colon in document object".into(),
|
701 688 | });
|
702 689 | }
|
703 690 | self.advance_by(1);
|
704 691 | let value = self.read_document(_schema)?;
|
705 692 | map.insert(key, value);
|
706 693 | }
|
707 694 | Ok(Document::Object(map))
|
708 695 | }
|
709 696 | Some(b'[') => {
|
710 697 | self.advance_by(1);
|
711 698 | let mut arr = Vec::new();
|
699 + | let mut first = true;
|
712 700 | loop {
|
713 - | self.skip_whitespace();
|
714 - | match self.remaining().first() {
|
715 - | Some(&b']') => {
|
716 - | self.advance_by(1);
|
717 - | break;
|
718 - | }
|
719 - | None => {
|
720 - | return Err(SerdeError::InvalidInput {
|
721 - | message: "unexpected end of input in document array".into(),
|
722 - | })
|
723 - | }
|
724 - | _ => arr.push(self.read_document(_schema)?),
|
701 + | if self.next_element(first, b']', "document array")? {
|
702 + | break;
|
725 703 | }
|
704 + | first = false;
|
705 + | arr.push(self.read_document(_schema)?);
|
726 706 | }
|
727 707 | Ok(Document::Array(arr))
|
728 708 | }
|
729 709 | Some(c) if *c == b'-' || c.is_ascii_digit() => {
|
730 710 | // Parse number — determine if integer or float
|
731 711 | let rem = self.remaining();
|
732 712 | let mut len = 0;
|
733 713 | let mut is_float = false;
|
734 714 | let mut is_negative = false;
|
735 715 | for (i, &b) in rem.iter().enumerate() {
|
736 716 | if b == b'-' && i == 0 {
|
737 717 | is_negative = true;
|
738 718 | len += 1;
|
739 719 | } else if b.is_ascii_digit() || b == b'+' {
|
740 720 | len += 1;
|
741 721 | } else if b == b'.' || b == b'e' || b == b'E' {
|
742 722 | is_float = true;
|
743 723 | len += 1;
|
744 724 | } else {
|
745 725 | break;
|
746 726 | }
|
747 727 | }
|
748 728 | let pos = self.position;
|
749 729 | self.advance_by(len);
|
750 730 | let s = std::str::from_utf8(&self.input[pos..pos + len]).map_err(|e| {
|
751 731 | SerdeError::InvalidInput {
|
752 732 | message: e.to_string(),
|
753 733 | }
|
754 734 | })?;
|
755 735 | if is_float {
|
756 736 | let f = s.parse::<f64>().map_err(|e| SerdeError::InvalidInput {
|
757 737 | message: e.to_string(),
|
758 738 | })?;
|
759 739 | Ok(Document::Number(aws_smithy_types::Number::Float(f)))
|
760 740 | } else if is_negative {
|
761 741 | let n = s.parse::<i64>().map_err(|e| SerdeError::InvalidInput {
|
762 742 | message: e.to_string(),
|
763 743 | })?;
|
764 744 | Ok(Document::Number(aws_smithy_types::Number::NegInt(n)))
|
765 745 | } else {
|
766 746 | let n = s.parse::<u64>().map_err(|e| SerdeError::InvalidInput {
|
767 747 | message: e.to_string(),
|
768 748 | })?;
|
769 749 | Ok(Document::Number(aws_smithy_types::Number::PosInt(n)))
|
770 750 | }
|
771 751 | }
|
772 752 | _ => Err(SerdeError::InvalidInput {
|
773 753 | message: "unexpected token in document".into(),
|
774 754 | }),
|
775 755 | };
|
776 756 | if result.is_ok() {
|
777 - | self.depth -= 1;
|
757 + | self.leave_container()?;
|
778 758 | }
|
779 759 | result
|
780 760 | }
|
781 761 |
|
782 762 | fn is_null(&self) -> bool {
|
783 763 | let remaining = self.remaining();
|
784 764 | remaining.len() >= 4
|
785 765 | && &remaining[..4] == b"null"
|
786 766 | && !remaining.get(4).is_some_and(|b| b.is_ascii_alphanumeric())
|
787 767 | }
|
847 827 | }
|
848 828 | _ => None,
|
849 829 | }
|
850 830 | }
|
851 831 | }
|
852 832 |
|
853 833 | impl<'a> JsonDeserializer<'a> {
|
854 834 | fn skip_whitespace(&mut self) {
|
855 835 | while self.position < self.input.len() {
|
856 836 | match self.input[self.position] {
|
857 - | b' ' | b'\t' | b'\n' | b'\r' | b',' => self.position += 1,
|
837 + | b' ' | b'\t' | b'\n' | b'\r' => self.position += 1,
|
858 838 | _ => break,
|
859 839 | }
|
860 840 | }
|
861 841 | }
|
862 842 |
|
843 + | /// Positions the deserializer at the next element of a container whose closing
|
844 + | /// delimiter is `close`, enforcing JSON's separator rules: elements are separated by
|
845 + | /// exactly one comma, and no comma may precede the closing delimiter.
|
846 + | ///
|
847 + | /// Returns `true` when the closing delimiter was reached and consumed, `false` when the
|
848 + | /// next element starts at the current position. `first` is whether no element has been
|
849 + | /// read from this container yet.
|
850 + | fn next_element(&mut self, first: bool, close: u8, what: &str) -> Result<bool, SerdeError> {
|
851 + | self.skip_whitespace();
|
852 + | match self.remaining().first().copied() {
|
853 + | Some(b) if b == close => {
|
854 + | self.advance_by(1);
|
855 + | Ok(true)
|
856 + | }
|
857 + | None => Err(SerdeError::InvalidInput {
|
858 + | message: format!("unexpected end of input in {what}"),
|
859 + | }),
|
860 + | Some(b',') if first => Err(SerdeError::InvalidInput {
|
861 + | message: format!("unexpected `,` before the first element of {what}"),
|
862 + | }),
|
863 + | Some(b',') => {
|
864 + | self.advance_by(1);
|
865 + | self.skip_whitespace();
|
866 + | match self.remaining().first().copied() {
|
867 + | Some(b) if b == close => Err(SerdeError::InvalidInput {
|
868 + | message: format!("trailing `,` in {what}"),
|
869 + | }),
|
870 + | Some(b',') => Err(SerdeError::InvalidInput {
|
871 + | message: format!("repeated `,` in {what}"),
|
872 + | }),
|
873 + | None => Err(SerdeError::InvalidInput {
|
874 + | message: format!("unexpected end of input in {what}"),
|
875 + | }),
|
876 + | Some(_) => Ok(false),
|
877 + | }
|
878 + | }
|
879 + | Some(_) if first => Ok(false),
|
880 + | Some(_) => Err(SerdeError::InvalidInput {
|
881 + | message: format!("expected `,` between elements of {what}"),
|
882 + | }),
|
883 + | }
|
884 + | }
|
885 + |
|
886 + | fn enter_container(&mut self) -> Result<(), SerdeError> {
|
887 + | self.depth += 1;
|
888 + | if self.depth > self.settings.max_depth() {
|
889 + | return Err(SerdeError::custom("maximum nesting depth exceeded"));
|
890 + | }
|
891 + | Ok(())
|
892 + | }
|
893 + |
|
894 + | /// Leaves a container. When it was the outermost value of the input, nothing but
|
895 + | /// whitespace may follow it: a body such as `{"a": 1}abc` is malformed JSON and is
|
896 + | /// rejected here, matching the token-based parser's "found more JSON tokens after
|
897 + | /// completing parsing" check.
|
898 + | fn leave_container(&mut self) -> Result<(), SerdeError> {
|
899 + | self.depth -= 1;
|
900 + | if self.depth == 0 {
|
901 + | self.skip_whitespace();
|
902 + | if !self.remaining().is_empty() {
|
903 + | return Err(SerdeError::InvalidInput {
|
904 + | message: "unexpected trailing characters after the JSON value".into(),
|
905 + | });
|
906 + | }
|
907 + | }
|
908 + | Ok(())
|
909 + | }
|
910 + |
|
911 + | /// Skips a JSON number, validating it against the RFC 8259 grammar
|
912 + | /// `-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?`. Whatever follows is left for the
|
913 + | /// enclosing container's separator check, so `01` fails there as "expected `,`".
|
914 + | fn skip_number(&mut self) -> Result<(), SerdeError> {
|
915 + | let rem = self.remaining();
|
916 + | let invalid = || SerdeError::InvalidInput {
|
917 + | message: "invalid number".into(),
|
918 + | };
|
919 + | let mut i = 0;
|
920 + | if rem.get(i) == Some(&b'-') {
|
921 + | i += 1;
|
922 + | }
|
923 + | match rem.get(i) {
|
924 + | Some(b'0') => i += 1,
|
925 + | Some(b'1'..=b'9') => {
|
926 + | while rem.get(i).is_some_and(|b| b.is_ascii_digit()) {
|
927 + | i += 1;
|
928 + | }
|
929 + | }
|
930 + | _ => return Err(invalid()),
|
931 + | }
|
932 + | if rem.get(i) == Some(&b'.') {
|
933 + | i += 1;
|
934 + | let start = i;
|
935 + | while rem.get(i).is_some_and(|b| b.is_ascii_digit()) {
|
936 + | i += 1;
|
937 + | }
|
938 + | if i == start {
|
939 + | return Err(invalid());
|
940 + | }
|
941 + | }
|
942 + | if matches!(rem.get(i), Some(b'e') | Some(b'E')) {
|
943 + | i += 1;
|
944 + | if matches!(rem.get(i), Some(b'+') | Some(b'-')) {
|
945 + | i += 1;
|
946 + | }
|
947 + | let start = i;
|
948 + | while rem.get(i).is_some_and(|b| b.is_ascii_digit()) {
|
949 + | i += 1;
|
950 + | }
|
951 + | if i == start {
|
952 + | return Err(invalid());
|
953 + | }
|
954 + | }
|
955 + | self.advance_by(i);
|
956 + | Ok(())
|
957 + | }
|
958 + |
|
959 + | /// Skips a JSON string, accepting exactly the escape sequences `read_string` accepts.
|
960 + | fn skip_string(&mut self) -> Result<(), SerdeError> {
|
961 + | let rem = self.remaining();
|
962 + | debug_assert_eq!(rem.first(), Some(&b'"'));
|
963 + | let mut i = 1;
|
964 + | loop {
|
965 + | match rem.get(i) {
|
966 + | None => {
|
967 + | return Err(SerdeError::InvalidInput {
|
968 + | message: "unterminated string".into(),
|
969 + | })
|
970 + | }
|
971 + | Some(b'"') => {
|
972 + | i += 1;
|
973 + | break;
|
974 + | }
|
975 + | Some(b'\\') => match rem.get(i + 1) {
|
976 + | Some(b'"' | b'\\' | b'/' | b'b' | b'f' | b'n' | b'r' | b't') => i += 2,
|
977 + | Some(b'u')
|
978 + | if rem
|
979 + | .get(i + 2..i + 6)
|
980 + | .is_some_and(|hex| hex.iter().all(u8::is_ascii_hexdigit)) =>
|
981 + | {
|
982 + | i += 6
|
983 + | }
|
984 + | _ => {
|
985 + | return Err(SerdeError::InvalidInput {
|
986 + | message: "invalid escape sequence in string".into(),
|
987 + | })
|
988 + | }
|
989 + | },
|
990 + | Some(_) => i += 1,
|
991 + | }
|
992 + | }
|
993 + | self.advance_by(i);
|
994 + | Ok(())
|
995 + | }
|
996 + |
|
863 997 | fn consume_number(&mut self) {
|
864 998 | let mut len = 0;
|
865 999 | for &b in self.remaining() {
|
866 1000 | if b.is_ascii_digit() || b == b'-' || b == b'.' || b == b'e' || b == b'E' || b == b'+' {
|
867 1001 | len += 1;
|
868 1002 | } else {
|
869 1003 | break;
|
870 1004 | }
|
871 1005 | }
|
872 1006 | self.advance_by(len);
|
873 1007 | }
|
874 1008 |
|
1009 + | /// Skips one JSON value, validating its syntax as it goes so that an unknown member
|
1010 + | /// cannot smuggle malformed JSON past the deserializer. Like `read_string`, raw control
|
1011 + | /// characters inside strings are not rejected.
|
875 1012 | fn skip_value(&mut self) -> Result<(), SerdeError> {
|
876 1013 | self.skip_whitespace();
|
877 - | let mut depth: usize = 0;
|
878 - | loop {
|
879 - | self.skip_whitespace();
|
880 - | match self.remaining().first().copied() {
|
881 - | Some(b'{') | Some(b'[') => {
|
882 - | self.advance_by(1);
|
883 - | depth += 1;
|
884 - | }
|
885 - | Some(b'}') | Some(b']') => {
|
886 - | if depth == 0 {
|
887 - | return Err(SerdeError::InvalidInput {
|
888 - | message: "unexpected end token".into(),
|
889 - | });
|
890 - | }
|
891 - | self.advance_by(1);
|
892 - | depth -= 1;
|
893 - | if depth == 0 {
|
894 - | return Ok(());
|
895 - | }
|
896 - | }
|
897 - | Some(b'"') => {
|
898 - | // Skip quoted string (handles escapes)
|
899 - | let mut i = 1;
|
900 - | let rem = self.remaining();
|
901 - | while i < rem.len() {
|
902 - | if rem[i] == b'\\' {
|
903 - | i += 2; // skip escape sequence
|
904 - | } else if rem[i] == b'"' {
|
905 - | i += 1;
|
906 - | break;
|
907 - | } else {
|
908 - | i += 1;
|
909 - | }
|
910 - | }
|
911 - | self.advance_by(i);
|
912 - | // After a string inside an object, skip optional ':'
|
913 - | if depth > 0 {
|
914 - | self.skip_whitespace();
|
915 - | if self.remaining().first() == Some(&b':') {
|
916 - | self.advance_by(1);
|
917 - | continue; // read the value after the colon
|
918 - | }
|
919 - | }
|
920 - | if depth == 0 {
|
921 - | return Ok(());
|
922 - | }
|
923 - | }
|
924 - | Some(b't') => {
|
925 - | if !self.remaining().starts_with(b"true") {
|
926 - | return Err(SerdeError::InvalidInput {
|
927 - | message: "expected `true`".into(),
|
928 - | });
|
929 - | }
|
930 - | self.advance_by(4);
|
931 - | if depth == 0 {
|
932 - | return Ok(());
|
1014 + | match self.remaining().first().copied() {
|
1015 + | Some(b'{') => {
|
1016 + | self.enter_container()?;
|
1017 + | self.advance_by(1);
|
1018 + | let mut first = true;
|
1019 + | loop {
|
1020 + | if self.next_element(first, b'}', "object")? {
|
1021 + | break;
|
933 1022 | }
|
934 - | }
|
935 - | Some(b'f') => {
|
936 - | if !self.remaining().starts_with(b"false") {
|
1023 + | first = false;
|
1024 + | if self.remaining().first() != Some(&b'"') {
|
937 1025 | return Err(SerdeError::InvalidInput {
|
938 - | message: "expected `false`".into(),
|
1026 + | message: "expected object key".into(),
|
939 1027 | });
|
940 1028 | }
|
941 - | self.advance_by(5);
|
942 - | if depth == 0 {
|
943 - | return Ok(());
|
944 - | }
|
945 - | }
|
946 - | Some(b'n') => {
|
947 - | if !self.remaining().starts_with(b"null") {
|
1029 + | self.parse_key()?;
|
1030 + | self.skip_whitespace();
|
1031 + | if self.remaining().first() != Some(&b':') {
|
948 1032 | return Err(SerdeError::InvalidInput {
|
949 - | message: "expected `null`".into(),
|
1033 + | message: "expected colon after key".into(),
|
950 1034 | });
|
951 1035 | }
|
952 - | self.advance_by(4);
|
953 - | if depth == 0 {
|
954 - | return Ok(());
|
955 - | }
|
1036 + | self.advance_by(1);
|
1037 + | self.skip_value()?;
|
956 1038 | }
|
957 - | Some(c) if c == b'-' || c.is_ascii_digit() => {
|
958 - | self.consume_number();
|
959 - | if depth == 0 {
|
960 - | return Ok(());
|
1039 + | self.depth -= 1;
|
1040 + | Ok(())
|
1041 + | }
|
1042 + | Some(b'[') => {
|
1043 + | self.enter_container()?;
|
1044 + | self.advance_by(1);
|
1045 + | let mut first = true;
|
1046 + | loop {
|
1047 + | if self.next_element(first, b']', "array")? {
|
1048 + | break;
|
961 1049 | }
|
1050 + | first = false;
|
1051 + | self.skip_value()?;
|
962 1052 | }
|
963 - | Some(_) => {
|
964 - | return Err(SerdeError::InvalidInput {
|
965 - | message: "unexpected token in skip_value".into(),
|
966 - | })
|
967 - | }
|
968 - | None => {
|
969 - | return Err(SerdeError::InvalidInput {
|
970 - | message: "unexpected end of input".into(),
|
971 - | })
|
972 - | }
|
1053 + | self.depth -= 1;
|
1054 + | Ok(())
|
973 1055 | }
|
1056 + | Some(b'"') => self.skip_string(),
|
1057 + | Some(b't') => self.skip_literal(b"true"),
|
1058 + | Some(b'f') => self.skip_literal(b"false"),
|
1059 + | Some(b'n') => self.skip_literal(b"null"),
|
1060 + | Some(c) if c == b'-' || c.is_ascii_digit() => self.skip_number(),
|
1061 + | Some(_) => Err(SerdeError::InvalidInput {
|
1062 + | message: "unexpected token in skip_value".into(),
|
1063 + | }),
|
1064 + | None => Err(SerdeError::InvalidInput {
|
1065 + | message: "unexpected end of input".into(),
|
1066 + | }),
|
974 1067 | }
|
975 1068 | }
|
976 1069 |
|
1070 + | fn skip_literal(&mut self, literal: &'static [u8]) -> Result<(), SerdeError> {
|
1071 + | if !self.remaining().starts_with(literal) {
|
1072 + | return Err(SerdeError::InvalidInput {
|
1073 + | message: format!("expected `{}`", String::from_utf8_lossy(literal)),
|
1074 + | });
|
1075 + | }
|
1076 + | self.advance_by(literal.len());
|
1077 + | Ok(())
|
1078 + | }
|
1079 + |
|
977 1080 | fn read_integer_value(&mut self) -> Result<i64, SerdeError> {
|
978 1081 | self.skip_whitespace();
|
979 1082 | let rem = self.remaining();
|
980 1083 | let mut len = 0;
|
981 1084 | for &b in rem {
|
982 1085 | if b.is_ascii_digit() || b == b'-' || b == b'+' {
|
983 1086 | len += 1;
|
984 1087 | } else {
|
985 1088 | break;
|
986 1089 | }
|
987 1090 | }
|
988 1091 | if len == 0 {
|
989 1092 | return Err(SerdeError::TypeMismatch {
|
990 1093 | message: "expected integer".into(),
|
991 1094 | });
|
992 1095 | }
|
993 1096 | let s = std::str::from_utf8(&rem[..len]).map_err(|e| SerdeError::InvalidInput {
|
994 1097 | message: e.to_string(),
|
995 1098 | })?;
|
996 1099 | let n = s.parse::<i64>().map_err(|e| SerdeError::InvalidInput {
|
997 1100 | message: e.to_string(),
|
998 1101 | })?;
|
999 1102 | self.advance_by(len);
|
1000 1103 | Ok(n)
|
1001 1104 | }
|
1002 1105 |
|
1003 1106 | fn read_float_value(&mut self) -> Result<f64, SerdeError> {
|
1004 1107 | self.skip_whitespace();
|
1005 1108 | let rem = self.remaining();
|
1006 - | // Handle string-encoded special float values: "NaN", "Infinity", "-Infinity"
|
1109 + | // A string may only carry a non-finite value, which JSON cannot express as a number:
|
1110 + | // `"NaN"`, `"Infinity"`, `"-Infinity"`. A quoted finite number such as `"123"` is a
|
1111 + | // type mismatch; the restJson1 protocol tests
|
1112 + | // `RestJsonBodyFloatMalformedValueRejected_case0` and
|
1113 + | // `RestJsonBodyDoubleMalformedValueRejected_case0` require it to be rejected. This
|
1114 + | // mirrors the token-based parser (`expect_number_or_null`), which parses the string
|
1115 + | // with `aws_smithy_types::primitive::Parse` and then rejects finite values, so the
|
1116 + | // spellings Rust's `f64::from_str` accepts for non-finite values (e.g. `"nan"`) are
|
1117 + | // accepted here too.
|
1007 1118 | if rem.first() == Some(&b'"') {
|
1008 1119 | let s = self.read_string(&aws_smithy_schema::prelude::STRING)?;
|
1009 - | return match s.as_str() {
|
1010 - | "NaN" => Ok(f64::NAN),
|
1011 - | "Infinity" => Ok(f64::INFINITY),
|
1012 - | "-Infinity" => Ok(f64::NEG_INFINITY),
|
1013 - | _ => s.parse::<f64>().map_err(|e| SerdeError::InvalidInput {
|
1120 + | let value = match s.as_str() {
|
1121 + | "NaN" => f64::NAN,
|
1122 + | "Infinity" => f64::INFINITY,
|
1123 + | "-Infinity" => f64::NEG_INFINITY,
|
1124 + | other => other.parse::<f64>().map_err(|e| SerdeError::InvalidInput {
|
1014 1125 | message: e.to_string(),
|
1015 - | }),
|
1126 + | })?,
|
1016 1127 | };
|
1128 + | if value.is_finite() {
|
1129 + | return Err(SerdeError::TypeMismatch {
|
1130 + | message: format!(
|
1131 + | "only `Infinity`, `-Infinity`, `NaN` can represent a float as a string but found `{s}`"
|
1132 + | ),
|
1133 + | });
|
1134 + | }
|
1135 + | return Ok(value);
|
1017 1136 | }
|
1018 1137 | let mut len = 0;
|
1019 1138 | for &b in rem {
|
1020 1139 | if b.is_ascii_digit() || b == b'-' || b == b'+' || b == b'.' || b == b'e' || b == b'E' {
|
1021 1140 | len += 1;
|
1022 1141 | } else {
|
1023 1142 | break;
|
1024 1143 | }
|
1025 1144 | }
|
1026 1145 | if len == 0 {
|
2164 2283 | assert_depth_error(err);
|
2165 2284 |
|
2166 2285 | // 10 levels fits inside the custom limit.
|
2167 2286 | let settings_ok = JsonCodecSettings::builder().max_depth(16).build();
|
2168 2287 | let payload_ok = build_nested("[", "]", 10);
|
2169 2288 | let mut deser_ok = JsonDeserializer::new(&payload_ok, Arc::new(settings_ok));
|
2170 2289 | deser_ok
|
2171 2290 | .read_list(dummy_schema(), &mut recursive_list_consumer)
|
2172 2291 | .expect("10-level nesting should succeed under custom limit");
|
2173 2292 | }
|
2293 + |
|
2294 + | mod separators {
|
2295 + | //! JSON syntax the deserializer must reject: separators (exactly one `,` between
|
2296 + | //! elements, none before a closing delimiter), bytes after the top-level value, and
|
2297 + | //! malformed values inside skipped unknown members. Smithy protocol tests
|
2298 + | //! `RestJsonInvalidJsonBody_case7` (`{"int": 10,}`) and `RestJsonInvalidJsonBody_case1`
|
2299 + | //! (`{ "int": 10 }abc`) require the first two.
|
2300 + | use super::*;
|
2301 + | use aws_smithy_schema::{shape_id, ShapeType};
|
2302 + |
|
2303 + | static INT: Schema = Schema::new_member(
|
2304 + | shape_id!("test", "Input", "int"),
|
2305 + | ShapeType::Integer,
|
2306 + | "int",
|
2307 + | 0,
|
2308 + | );
|
2309 + | static LIST: Schema = Schema::new_member(
|
2310 + | shape_id!("test", "Input", "list"),
|
2311 + | ShapeType::List,
|
2312 + | "list",
|
2313 + | 1,
|
2314 + | );
|
2315 + | static INPUT: Schema = Schema::new_struct(
|
2316 + | shape_id!("test", "Input"),
|
2317 + | ShapeType::Structure,
|
2318 + | &[&INT, &LIST],
|
2319 + | );
|
2320 + |
|
2321 + | fn deser(input: &[u8]) -> JsonDeserializer<'_> {
|
2322 + | JsonDeserializer::new(input, Arc::new(JsonCodecSettings::default()))
|
2323 + | }
|
2324 + |
|
2325 + | /// Reads `INPUT` and returns `(int, list)`; unknown members are skipped.
|
2326 + | fn read_input(body: &[u8]) -> Result<(Option<i32>, Vec<i32>), SerdeError> {
|
2327 + | let mut d = deser(body);
|
2328 + | let mut int = None;
|
2329 + | let mut list = Vec::new();
|
2330 + | d.read_struct(&INPUT, &mut |member, d| {
|
2331 + | match member.member_index() {
|
2332 + | Some(0) => int = Some(d.read_integer(member)?),
|
2333 + | Some(1) => d.read_list(member, &mut |d| {
|
2334 + | list.push(d.read_integer(member)?);
|
2335 + | Ok(())
|
2336 + | })?,
|
2337 + | _ => {}
|
2338 + | }
|
2339 + | Ok(())
|
2340 + | })?;
|
2341 + | Ok((int, list))
|
2342 + | }
|
2343 + |
|
2344 + | #[test]
|
2345 + | fn well_formed_input_is_read() {
|
2346 + | assert_eq!(
|
2347 + | read_input(br#"{ "int": 10 , "list": [ 1 , 2 ] }"#).unwrap(),
|
2348 + | (Some(10), vec![1, 2])
|
2349 + | );
|
2350 + | assert_eq!(read_input(br#"{}"#).unwrap(), (None, vec![]));
|
2351 + | assert_eq!(read_input(br#"{"list":[]}"#).unwrap(), (None, vec![]));
|
2352 + | assert_eq!(
|
2353 + | read_input(b" { \"int\": 1 } \n").unwrap(),
|
2354 + | (Some(1), vec![])
|
2355 + | );
|
2356 + | }
|
2357 + |
|
2358 + | #[test]
|
2359 + | fn trailing_comma_in_object_is_rejected() {
|
2360 + | assert!(read_input(br#"{"int": 10,}"#).is_err());
|
2361 + | }
|
2362 + |
|
2363 + | #[test]
|
2364 + | fn leading_and_repeated_commas_in_object_are_rejected() {
|
2365 + | assert!(read_input(br#"{,"int": 10}"#).is_err());
|
2366 + | assert!(read_input(br#"{"int": 10,,"list": []}"#).is_err());
|
2367 + | assert!(read_input(br#"{"int": 10,,}"#).is_err());
|
2368 + | }
|
2369 + |
|
2370 + | #[test]
|
2371 + | fn missing_comma_in_object_is_rejected() {
|
2372 + | assert!(read_input(br#"{"int": 10 "list": []}"#).is_err());
|
2373 + | }
|
2374 + |
|
2375 + | #[test]
|
2376 + | fn commas_in_array_are_checked() {
|
2377 + | assert!(read_input(br#"{"list": [1,]}"#).is_err());
|
2378 + | assert!(read_input(br#"{"list": [,1]}"#).is_err());
|
2379 + | assert!(read_input(br#"{"list": [1,,2]}"#).is_err());
|
2380 + | assert!(read_input(br#"{"list": [1 2]}"#).is_err());
|
2381 + | }
|
2382 + |
|
2383 + | #[test]
|
2384 + | fn skipped_unknown_members_are_still_validated() {
|
2385 + | assert_eq!(
|
2386 + | read_input(br#"{"unknown": {"a": [1, {"b": null}], "c": "x"}, "int": 1}"#).unwrap(),
|
2387 + | (Some(1), vec![])
|
2388 + | );
|
2389 + | assert!(read_input(br#"{"unknown": [1,,2], "int": 1}"#).is_err());
|
2390 + | assert!(read_input(br#"{"unknown": {"a": 1,}, "int": 1}"#).is_err());
|
2391 + | assert!(read_input(br#"{"unknown": "unterminated, "int": 1}"#).is_err());
|
2392 + | assert!(read_input(br#"{"unknown": tru, "int": 1}"#).is_err());
|
2393 + | }
|
2394 + |
|
2395 + | #[test]
|
2396 + | fn trailing_characters_after_the_top_level_value_are_rejected() {
|
2397 + | assert!(read_input(br#"{ "int": 10 }abc"#).is_err());
|
2398 + | assert!(read_input(br#"{ "int": 10 },"#).is_err());
|
2399 + | assert!(read_input(br#"{ "int": 10 }{}"#).is_err());
|
2400 + | assert!(deser(br#"[1]x"#).read_integer_list(&LIST).is_err());
|
2401 + | assert!(deser(br#"{"k":"v"} {}"#)
|
2402 + | .read_string_string_map(&LIST)
|
2403 + | .is_err());
|
2404 + | assert!(deser(br#"{"a":1}]"#).read_document(&INT).is_err());
|
2405 + | assert!(deser(br#"[1,2]"#).read_document(&INT).is_ok());
|
2406 + | }
|
2407 + |
|
2408 + | #[test]
|
2409 + | fn skipped_numbers_must_match_the_json_grammar() {
|
2410 + | for ok in [
|
2411 + | b"0" as &[u8],
|
2412 + | b"-0",
|
2413 + | b"10",
|
2414 + | b"-1.5",
|
2415 + | b"1e3",
|
2416 + | b"1E+3",
|
2417 + | b"2.5e-7",
|
2418 + | ] {
|
2419 + | let body = [b"{\"unknown\": " as &[u8], ok, b", \"int\": 1}"].concat();
|
2420 + | assert_eq!(
|
2421 + | read_input(&body).unwrap(),
|
2422 + | (Some(1), vec![]),
|
2423 + | "{}",
|
2424 + | String::from_utf8_lossy(ok)
|
2425 + | );
|
2426 + | }
|
2427 + | for bad in [
|
2428 + | b"01" as &[u8],
|
2429 + | b"1.",
|
2430 + | b"--1",
|
2431 + | b"+1",
|
2432 + | b".5",
|
2433 + | b"1e",
|
2434 + | b"1e+",
|
2435 + | b"-",
|
2436 + | b"0x1",
|
2437 + | ] {
|
2438 + | let body = [b"{\"unknown\": " as &[u8], bad, b", \"int\": 1}"].concat();
|
2439 + | assert!(
|
2440 + | read_input(&body).is_err(),
|
2441 + | "{}",
|
2442 + | String::from_utf8_lossy(bad)
|
2443 + | );
|
2444 + | }
|
2445 + | }
|
2446 + |
|
2447 + | #[test]
|
2448 + | fn skipped_strings_must_use_valid_escapes() {
|
2449 + | assert_eq!(
|
2450 + | read_input(br#"{"unknown": "a\"b\\c\/\b\f\n\r\t\u00e9", "int": 1}"#).unwrap(),
|
2451 + | (Some(1), vec![])
|
2452 + | );
|
2453 + | assert!(read_input(br#"{"unknown": "\q", "int": 1}"#).is_err());
|
2454 + | assert!(read_input(br#"{"unknown": "\u12", "int": 1}"#).is_err());
|
2455 + | assert!(read_input(br#"{"unknown": "\uZZZZ", "int": 1}"#).is_err());
|
2456 + | assert!(read_input(br#"{"unknown": "abc\", "int": 1}"#).is_err());
|
2457 + | }
|
2458 + |
|
2459 + | #[test]
|
2460 + | fn fast_path_collections_check_separators() {
|
2461 + | assert_eq!(
|
2462 + | deser(br#"["a","b"]"#).read_string_list(&LIST).unwrap(),
|
2463 + | vec!["a", "b"]
|
2464 + | );
|
2465 + | assert!(deser(br#"["a",]"#).read_string_list(&LIST).is_err());
|
2466 + | assert!(deser(br#"[,"a"]"#).read_string_list(&LIST).is_err());
|
2467 + | assert!(deser(br#"[1,,2]"#).read_integer_list(&LIST).is_err());
|
2468 + | assert!(deser(br#"[1,]"#).read_long_list(&LIST).is_err());
|
2469 + | assert!(deser(br#"["YQ==",]"#).read_blob_list(&LIST).is_err());
|
2470 + | assert_eq!(
|
2471 + | deser(br#"{"k":"v"}"#)
|
2472 + | .read_string_string_map(&LIST)
|
2473 + | .unwrap()
|
2474 + | .len(),
|
2475 + | 1
|
2476 + | );
|
2477 + | assert!(deser(br#"{"k":"v",}"#)
|
2478 + | .read_string_string_map(&LIST)
|
2479 + | .is_err());
|
2480 + | assert!(deser(br#"{"k":"v" "j":"w"}"#)
|
2481 + | .read_string_string_map(&LIST)
|
2482 + | .is_err());
|
2483 + | }
|
2484 + |
|
2485 + | #[test]
|
2486 + | fn generic_map_checks_separators() {
|
2487 + | let mut count = 0;
|
2488 + | assert!(deser(br#"{"a":1,"b":2}"#)
|
2489 + | .read_map(&LIST, &mut |_, d| {
|
2490 + | count += 1;
|
2491 + | d.read_integer(&INT).map(|_| ())
|
2492 + | })
|
2493 + | .is_ok());
|
2494 + | assert_eq!(count, 2);
|
2495 + | assert!(deser(br#"{"a":1,}"#)
|
2496 + | .read_map(&LIST, &mut |_, d| d.read_integer(&INT).map(|_| ()))
|
2497 + | .is_err());
|
2498 + | }
|
2499 + |
|
2500 + | #[test]
|
2501 + | fn documents_check_separators() {
|
2502 + | assert!(deser(br#"{"a":[1,2],"b":{"c":true}}"#)
|
2503 + | .read_document(&INT)
|
2504 + | .is_ok());
|
2505 + | assert!(deser(br#"{"a":1,}"#).read_document(&INT).is_err());
|
2506 + | assert!(deser(br#"[1,,2]"#).read_document(&INT).is_err());
|
2507 + | assert!(deser(br#"{,"a":1}"#).read_document(&INT).is_err());
|
2508 + | }
|
2509 + | }
|
2510 + |
|
2511 + | mod float_strings {
|
2512 + | //! A float or double member may carry a string only for a non-finite value
|
2513 + | //! (`"NaN"`, `"Infinity"`, `"-Infinity"`). Smithy protocol tests
|
2514 + | //! `RestJsonBodyFloatMalformedValueRejected_case0` and
|
2515 + | //! `RestJsonBodyDoubleMalformedValueRejected_case0` (`"123"`) require quoted finite
|
2516 + | //! numbers to be rejected.
|
2517 + | use super::*;
|
2518 + | use aws_smithy_schema::{shape_id, ShapeType};
|
2519 + |
|
2520 + | static F: Schema =
|
2521 + | Schema::new_member(shape_id!("test", "Input", "f"), ShapeType::Float, "f", 0);
|
2522 + | static D: Schema =
|
2523 + | Schema::new_member(shape_id!("test", "Input", "d"), ShapeType::Double, "d", 1);
|
2524 + | static INPUT: Schema =
|
2525 + | Schema::new_struct(shape_id!("test", "Input"), ShapeType::Structure, &[&F, &D]);
|
2526 + |
|
2527 + | fn read(body: &[u8]) -> Result<(Option<f32>, Option<f64>), SerdeError> {
|
2528 + | let mut deser = JsonDeserializer::new(body, Arc::new(JsonCodecSettings::default()));
|
2529 + | let (mut f, mut d) = (None, None);
|
2530 + | deser.read_struct(&INPUT, &mut |m, x| {
|
2531 + | match m.member_index() {
|
2532 + | Some(0) => f = Some(x.read_float(m)?),
|
2533 + | Some(1) => d = Some(x.read_double(m)?),
|
2534 + | _ => {}
|
2535 + | }
|
2536 + | Ok(())
|
2537 + | })?;
|
2538 + | Ok((f, d))
|
2539 + | }
|
2540 + |
|
2541 + | #[test]
|
2542 + | fn numbers_and_the_three_special_strings_are_read() {
|
2543 + | assert_eq!(
|
2544 + | read(br#"{"f": 1.5, "d": -2}"#).unwrap(),
|
2545 + | (Some(1.5), Some(-2.0))
|
2546 + | );
|
2547 + | let (f, d) = read(br#"{"f": "NaN", "d": "-Infinity"}"#).unwrap();
|
2548 + | assert!(f.unwrap().is_nan());
|
2549 + | assert_eq!(d, Some(f64::NEG_INFINITY));
|
2550 + | assert_eq!(
|
2551 + | read(br#"{"f": "Infinity"}"#).unwrap().0,
|
2552 + | Some(f32::INFINITY)
|
2553 + | );
|
2554 + | }
|
2555 + |
|
2556 + | #[test]
|
2557 + | fn quoted_finite_numbers_are_rejected() {
|
2558 + | assert!(read(br#"{"f": "123"}"#).is_err());
|
2559 + | assert!(read(br#"{"d": "123"}"#).is_err());
|
2560 + | assert!(read(br#"{"d": "1.5e3"}"#).is_err());
|
2561 + | assert!(read(br#"{"d": "-0"}"#).is_err());
|
2562 + | }
|
2563 + |
|
2564 + | #[test]
|
2565 + | fn non_numeric_strings_are_rejected() {
|
2566 + | assert!(read(br#"{"d": "abc"}"#).is_err());
|
2567 + | assert!(read(br#"{"d": ""}"#).is_err());
|
2568 + | assert!(read(br#"{"d": "1 2"}"#).is_err());
|
2569 + | }
|
2570 + |
|
2571 + | #[test]
|
2572 + | fn alternative_non_finite_spellings_match_the_legacy_parser() {
|
2573 + | // `aws_smithy_types::primitive::Parse` falls back to `f64::from_str`, which
|
2574 + | // accepts these case-insensitively; only finite results are rejected.
|
2575 + | assert!(read(br#"{"f": "nan"}"#).unwrap().0.unwrap().is_nan());
|
2576 + | assert_eq!(read(br#"{"d": "inf"}"#).unwrap().1, Some(f64::INFINITY));
|
2577 + | assert_eq!(
|
2578 + | read(br#"{"d": "-infinity"}"#).unwrap().1,
|
2579 + | Some(f64::NEG_INFINITY)
|
2580 + | );
|
2581 + | }
|
2582 + | }
|
2583 + |
|
2584 + | mod timestamp_formats {
|
2585 + | //! With `strict_timestamp_formats`, a timestamp must use exactly the wire form its
|
2586 + | //! format prescribes, as the restJson1 `MalformedTimestampBody*` protocol tests require.
|
2587 + | //! Without it the lenient client behavior is unchanged.
|
2588 + | use super::*;
|
2589 + | use aws_smithy_schema::traits::TimestampFormat;
|
2590 + | use aws_smithy_schema::{shape_id, ShapeType};
|
2591 + |
|
2592 + | static DEFAULT: Schema =
|
2593 + | Schema::new_member(shape_id!("test", "T", "d"), ShapeType::Timestamp, "d", 0);
|
2594 + | static DATE_TIME: Schema =
|
2595 + | Schema::new_member(shape_id!("test", "T", "dt"), ShapeType::Timestamp, "dt", 1)
|
2596 + | .with_timestamp_format(TimestampFormat::DateTime);
|
2597 + | static HTTP_DATE: Schema =
|
2598 + | Schema::new_member(shape_id!("test", "T", "hd"), ShapeType::Timestamp, "hd", 2)
|
2599 + | .with_timestamp_format(TimestampFormat::HttpDate);
|
2600 + | static EPOCH: Schema =
|
2601 + | Schema::new_member(shape_id!("test", "T", "es"), ShapeType::Timestamp, "es", 3)
|
2602 + | .with_timestamp_format(TimestampFormat::EpochSeconds);
|
2603 + |
|
2604 + | /// 2018-01-09T20:51:21Z
|
2605 + | const T: i64 = 1515531081;
|
2606 + |
|
2607 + | fn read(strict: bool, schema: &Schema, input: &[u8]) -> Result<DateTime, SerdeError> {
|
2608 + | let settings = JsonCodecSettings::builder()
|
2609 + | .strict_timestamp_formats(strict)
|
2610 + | .build();
|
2611 + | JsonDeserializer::new(input, Arc::new(settings)).read_timestamp(schema)
|
2612 + | }
|
2613 + |
|
2614 + | fn assert_rejected(schema: &Schema, inputs: &[&[u8]]) {
|
2615 + | for input in inputs {
|
2616 + | assert!(
|
2617 + | read(true, schema, input).is_err(),
|
2618 + | "{} must be rejected",
|
2619 + | String::from_utf8_lossy(input)
|
2620 + | );
|
2621 + | }
|
2622 + | }
|
2623 + |
|
2624 + | #[test]
|
2625 + | fn strict_epoch_seconds_default_requires_a_number() {
|
2626 + | assert_eq!(
|
2627 + | read(true, &DEFAULT, b"1515531081").unwrap(),
|
2628 + | DateTime::from_secs(T)
|
2629 + | );
|
2630 + | assert_eq!(
|
2631 + | read(true, &DEFAULT, b"1515531081.1234").unwrap(),
|
2632 + | DateTime::from_secs_f64(1515531081.1234)
|
2633 + | );
|
2634 + | assert_rejected(
|
2635 + | &DEFAULT,
|
2636 + | &[
|
2637 + | br#""1985-04-12T23:20:50.52Z""#,
|
2638 + | br#""1985-04-12T23:20:50Z""#,
|
2639 + | br#""1996-12-19T16:39:57-08:00""#,
|
2640 + | br#""1515531081""#,
|
2641 + | br#""1515531081.1234""#,
|
2642 + | br#""Tue, 29 Apr 2014 18:30:38 GMT""#,
|
2643 + | br#""Infinity""#,
|
2644 + | br#""NaN""#,
|
2645 + | b"true",
|
2646 + | ],
|
2647 + | );
|
2648 + | }
|
2649 + |
|
2650 + | #[test]
|
2651 + | fn strict_explicit_epoch_seconds_requires_a_number() {
|
2652 + | assert_eq!(
|
2653 + | read(true, &EPOCH, b"1515531081").unwrap(),
|
2654 + | DateTime::from_secs(T)
|
2655 + | );
|
2656 + | assert_rejected(&EPOCH, &[br#""2018-01-09T20:51:21Z""#, br#""1515531081""#]);
|
2657 + | }
|
2658 + |
|
2659 + | #[test]
|
2660 + | fn strict_date_time_requires_an_rfc3339_utc_string() {
|
2661 + | assert_eq!(
|
2662 + | read(true, &DATE_TIME, br#""2018-01-09T20:51:21Z""#).unwrap(),
|
2663 + | DateTime::from_secs(T)
|
2664 + | );
|
2665 + | assert_rejected(
|
2666 + | &DATE_TIME,
|
2667 + | &[
|
2668 + | b"1515531081",
|
2669 + | b"1515531081.1234",
|
2670 + | br#""1996-12-19T16:39:57-08:00""#,
|
2671 + | br#""1996-12-19T16:39:57+00""#,
|
2672 + | br#""1996-12-19T16:39:57""#,
|
2673 + | br#""Tue, 29 Apr 2014 18:30:38 GMT""#,
|
2674 + | ],
|
2675 + | );
|
2676 + | }
|
2677 + |
|
2678 + | #[test]
|
2679 + | fn strict_http_date_requires_an_imf_fixdate_string() {
|
2680 + | assert_eq!(
|
2681 + | read(true, &HTTP_DATE, br#""Tue, 09 Jan 2018 20:51:21 GMT""#).unwrap(),
|
2682 + | DateTime::from_secs(T)
|
2683 + | );
|
2684 + | assert_rejected(
|
2685 + | &HTTP_DATE,
|
2686 + | &[
|
2687 + | b"1515531081",
|
2688 + | b"1515531081.1234",
|
2689 + | br#""1985-04-12T23:20:50.52Z""#,
|
2690 + | br#""1996-12-19T16:39:57-08:00""#,
|
2691 + | ],
|
2692 + | );
|
2693 + | }
|
2694 + |
|
2695 + | #[test]
|
2696 + | fn lenient_default_keeps_the_client_behavior() {
|
2697 + | // A number is epoch seconds whatever the format; a string for a `date-time`
|
2698 + | // member or a member without the trait parses as offset-aware `date-time`;
|
2699 + | // explicit `http-date` and `epoch-seconds` traits are honored for strings.
|
2700 + | assert_eq!(
|
2701 + | read(false, &DATE_TIME, b"1515531081").unwrap(),
|
2702 + | DateTime::from_secs(T)
|
2703 + | );
|
2704 + | assert_eq!(
|
2705 + | read(false, &HTTP_DATE, b"1515531081").unwrap(),
|
2706 + | DateTime::from_secs(T)
|
2707 + | );
|
2708 + | assert_eq!(
|
2709 + | read(false, &DEFAULT, br#""2018-01-09T20:51:21Z""#).unwrap(),
|
2710 + | DateTime::from_secs(T)
|
2711 + | );
|
2712 + | assert_eq!(
|
2713 + | read(false, &DATE_TIME, br#""2018-01-09T21:51:21+01:00""#).unwrap(),
|
2714 + | DateTime::from_secs(T)
|
2715 + | );
|
2716 + | assert_eq!(
|
2717 + | read(false, &HTTP_DATE, br#""Tue, 09 Jan 2018 20:51:21 GMT""#).unwrap(),
|
2718 + | DateTime::from_secs(T)
|
2719 + | );
|
2720 + | assert_eq!(
|
2721 + | read(false, &EPOCH, br#""1515531081""#).unwrap(),
|
2722 + | DateTime::from_secs(T)
|
2723 + | );
|
2724 + | }
|
2725 + | }
|
2726 + |
|
2727 + | mod union_members {
|
2728 + | //! A union body holds exactly one member. Mixed variants are always an error; an
|
2729 + | //! unknown key is an error only with `reject_unknown_union_members`, which servers
|
2730 + | //! enable for the restJson1 `RestJsonMalformedUnion*` protocol tests.
|
2731 + | use super::*;
|
2732 + | use aws_smithy_schema::shape_id;
|
2733 + |
|
2734 + | static INT: Schema =
|
2735 + | Schema::new_member(shape_id!("test", "U", "int"), ShapeType::Integer, "int", 0);
|
2736 + | static STRING: Schema = Schema::new_member(
|
2737 + | shape_id!("test", "U", "string"),
|
2738 + | ShapeType::String,
|
2739 + | "string",
|
2740 + | 1,
|
2741 + | );
|
2742 + | static UNION: Schema =
|
2743 + | Schema::new_struct(shape_id!("test", "U"), ShapeType::Union, &[&INT, &STRING]);
|
2744 + | static FIELD: Schema = Schema::new_member(
|
2745 + | shape_id!("test", "S", "field"),
|
2746 + | ShapeType::Integer,
|
2747 + | "field",
|
2748 + | 0,
|
2749 + | );
|
2750 + | static STRUCT: Schema =
|
2751 + | Schema::new_struct(shape_id!("test", "S"), ShapeType::Structure, &[&FIELD]);
|
2752 + |
|
2753 + | #[derive(Debug, PartialEq)]
|
2754 + | enum Variant {
|
2755 + | Int(i32),
|
2756 + | String(String),
|
2757 + | }
|
2758 + |
|
2759 + | fn read(strict: bool, body: &[u8]) -> Result<Option<Variant>, SerdeError> {
|
2760 + | let settings = JsonCodecSettings::builder()
|
2761 + | .reject_unknown_union_members(strict)
|
2762 + | .build();
|
2763 + | let mut deser = JsonDeserializer::new(body, Arc::new(settings));
|
2764 + | let mut value = None;
|
2765 + | deser.read_struct(&UNION, &mut |member, d| {
|
2766 + | value = Some(match member.member_index() {
|
2767 + | Some(0) => Variant::Int(d.read_integer(member)?),
|
2768 + | _ => Variant::String(d.read_string(member)?),
|
2769 + | });
|
2770 + | Ok(())
|
2771 + | })?;
|
2772 + | Ok(value)
|
2773 + | }
|
2774 + |
|
2775 + | #[test]
|
2776 + | fn a_single_member_is_read() {
|
2777 + | for strict in [false, true] {
|
2778 + | assert_eq!(
|
2779 + | read(strict, br#"{"int": 2}"#).unwrap(),
|
2780 + | Some(Variant::Int(2))
|
2781 + | );
|
2782 + | assert_eq!(
|
2783 + | read(strict, br#"{"int": null, "string": "x"}"#).unwrap(),
|
2784 + | Some(Variant::String("x".into()))
|
2785 + | );
|
2786 + | assert_eq!(
|
2787 + | read(strict, br#"{"__type": "test#U", "int": 2}"#).unwrap(),
|
2788 + | Some(Variant::Int(2))
|
2789 + | );
|
2790 + | assert_eq!(read(strict, br#"{}"#).unwrap(), None);
|
2791 + | assert_eq!(read(strict, br#"{"int": null}"#).unwrap(), None);
|
2792 + | }
|
2793 + | }
|
2794 + |
|
2795 + | /// RestJsonMalformedUnionMultipleFieldsSet
|
2796 + | #[test]
|
2797 + | fn mixed_variants_are_rejected() {
|
2798 + | for strict in [false, true] {
|
2799 + | assert!(read(strict, br#"{"int": 2, "string": "three"}"#).is_err());
|
2800 + | assert!(read(strict, br#"{"int": 2, "int": 3}"#).is_err());
|
2801 + | }
|
2802 + | }
|
2803 + |
|
2804 + | /// RestJsonMalformedUnionKnownAndUnknownFieldsSet, RestJsonMalformedUnionUnknownMember
|
2805 + | #[test]
|
2806 + | fn unknown_members_are_rejected_only_when_configured() {
|
2807 + | assert_eq!(
|
2808 + | read(false, br#"{"int": 2, "unknownField": "three"}"#).unwrap(),
|
2809 + | Some(Variant::Int(2))
|
2810 + | );
|
2811 + | assert_eq!(read(false, br#"{"unknown": "hello"}"#).unwrap(), None);
|
2812 + | assert!(read(true, br#"{"int": 2, "unknownField": "three"}"#).is_err());
|
2813 + | assert!(read(true, br#"{"unknown": "hello"}"#).is_err());
|
2814 + | }
|
2815 + |
|
2816 + | #[test]
|
2817 + | fn structures_still_skip_unknown_members_when_configured() {
|
2818 + | let settings = JsonCodecSettings::builder()
|
2819 + | .reject_unknown_union_members(true)
|
2820 + | .build();
|
2821 + | let mut deser =
|
2822 + | JsonDeserializer::new(br#"{"unknown": {"a": 1}, "field": 5}"#, Arc::new(settings));
|
2823 + | let mut field = None;
|
2824 + | deser
|
2825 + | .read_struct(&STRUCT, &mut |member, d| {
|
2826 + | field = Some(d.read_integer(member)?);
|
2827 + | Ok(())
|
2828 + | })
|
2829 + | .unwrap();
|
2830 + | assert_eq!(field, Some(5));
|
2831 + | }
|
2832 + | }
|
2174 2833 | }
|