From 7b6d2d4b25765980e028fe44f7df6aa6fdaef6d3 Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Tue, 15 Nov 2022 12:35:55 -0800 Subject: [PATCH] Revamp errors in `aws-smithy-json` (#1888) --- .../aws-config/src/json_credentials.rs | 4 +- .../codegen/core/smithy/protocols/AwsJson.kt | 2 +- .../codegen/core/smithy/protocols/RestJson.kt | 2 +- .../protocols/parse/JsonParserGenerator.kt | 6 +- .../ServerHttpBoundProtocolGenerator.kt | 6 +- .../aws-smithy-http-server/src/rejection.rs | 2 +- .../aws-smithy-json/src/deserialize.rs | 268 ++++++++++-------- .../aws-smithy-json/src/deserialize/error.rs | 116 +++++--- .../aws-smithy-json/src/deserialize/token.rs | 249 ++++++++-------- rust-runtime/aws-smithy-json/src/escape.rs | 64 +++-- .../inlineable/src/endpoint_lib/partition.rs | 50 ++-- rust-runtime/inlineable/src/json_errors.rs | 2 +- 12 files changed, 424 insertions(+), 347 deletions(-) diff --git a/aws/rust-runtime/aws-config/src/json_credentials.rs b/aws/rust-runtime/aws-config/src/json_credentials.rs index 8e0650ac7a..d8d696a584 100644 --- a/aws/rust-runtime/aws-config/src/json_credentials.rs +++ b/aws/rust-runtime/aws-config/src/json_credentials.rs @@ -36,8 +36,8 @@ impl From for InvalidJsonCredentials { } } -impl From for InvalidJsonCredentials { - fn from(err: aws_smithy_json::deserialize::Error) -> Self { +impl From for InvalidJsonCredentials { + fn from(err: aws_smithy_json::deserialize::error::DeserializeError) -> Self { InvalidJsonCredentials::JsonError(err.into()) } } diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/AwsJson.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/AwsJson.kt index d527c0bc76..814aa64e68 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/AwsJson.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/AwsJson.kt @@ -114,7 +114,7 @@ open class AwsJson( "Bytes" to RuntimeType.Bytes, "Error" to RuntimeType.GenericError(runtimeConfig), "HeaderMap" to RuntimeType.http.member("HeaderMap"), - "JsonError" to CargoDependency.smithyJson(runtimeConfig).asType().member("deserialize::Error"), + "JsonError" to CargoDependency.smithyJson(runtimeConfig).asType().member("deserialize::error::DeserializeError"), "Response" to RuntimeType.http.member("Response"), "json_errors" to RuntimeType.jsonErrors(runtimeConfig), ) diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/RestJson.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/RestJson.kt index 31c5ddae5c..aa8e0f84b9 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/RestJson.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/RestJson.kt @@ -69,7 +69,7 @@ open class RestJson(val codegenContext: CodegenContext) : Protocol { "Bytes" to RuntimeType.Bytes, "Error" to RuntimeType.GenericError(runtimeConfig), "HeaderMap" to RuntimeType.http.member("HeaderMap"), - "JsonError" to CargoDependency.smithyJson(runtimeConfig).asType().member("deserialize::Error"), + "JsonError" to CargoDependency.smithyJson(runtimeConfig).asType().member("deserialize::error::DeserializeError"), "Response" to RuntimeType.http.member("Response"), "json_errors" to RuntimeType.jsonErrors(runtimeConfig), ) diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/parse/JsonParserGenerator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/parse/JsonParserGenerator.kt index a69a8e9651..bafb8fb67e 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/parse/JsonParserGenerator.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/parse/JsonParserGenerator.kt @@ -25,6 +25,7 @@ import software.amazon.smithy.model.traits.TimestampFormatTrait import software.amazon.smithy.rust.codegen.core.rustlang.Attribute import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency import software.amazon.smithy.rust.codegen.core.rustlang.RustModule +import software.amazon.smithy.rust.codegen.core.rustlang.RustType import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter import software.amazon.smithy.rust.codegen.core.rustlang.Writable import software.amazon.smithy.rust.codegen.core.rustlang.asType @@ -100,8 +101,7 @@ class JsonParserGenerator( private val jsonDeserModule = RustModule.private("json_deser") private val typeConversionGenerator = TypeConversionGenerator(model, symbolProvider, runtimeConfig) private val codegenScope = arrayOf( - "Error" to smithyJson.member("deserialize::Error"), - "ErrorReason" to smithyJson.member("deserialize::ErrorReason"), + "Error" to smithyJson.member("deserialize::error::DeserializeError"), "expect_blob_or_null" to smithyJson.member("deserialize::token::expect_blob_or_null"), "expect_bool_or_null" to smithyJson.member("deserialize::token::expect_bool_or_null"), "expect_document" to smithyJson.member("deserialize::token::expect_document"), @@ -422,7 +422,7 @@ class JsonParserGenerator( *codegenScope, ) { startObjectOrNull { - rust("let mut map = #T::new();", software.amazon.smithy.rust.codegen.core.rustlang.RustType.HashMap.RuntimeType) + rust("let mut map = #T::new();", RustType.HashMap.RuntimeType) objectKeyLoop(hasMembers = true) { withBlock("let key =", "?;") { deserializeStringInner(keyTarget, "key") diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/protocols/ServerHttpBoundProtocolGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/protocols/ServerHttpBoundProtocolGenerator.kt index 4cc16278cb..3543d96782 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/protocols/ServerHttpBoundProtocolGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/protocols/ServerHttpBoundProtocolGenerator.kt @@ -1246,12 +1246,12 @@ private class ServerHttpBoundProtocolTraitImplGenerator( if (model.expectShape(binding.member.target) is StringShape) { return ServerRuntimeType.RequestRejection(runtimeConfig) } - when (codegenContext.protocol) { + return when (codegenContext.protocol) { RestJson1Trait.ID, AwsJson1_0Trait.ID, AwsJson1_1Trait.ID -> { - return CargoDependency.smithyJson(runtimeConfig).asType().member("deserialize").member("Error") + CargoDependency.smithyJson(runtimeConfig).asType().member("deserialize::error::DeserializeError") } RestXmlTrait.ID -> { - return CargoDependency.smithyXml(runtimeConfig).asType().member("decode").member("XmlError") + CargoDependency.smithyXml(runtimeConfig).asType().member("decode").member("XmlError") } else -> { TODO("Protocol ${codegenContext.protocol} not supported yet") diff --git a/rust-runtime/aws-smithy-http-server/src/rejection.rs b/rust-runtime/aws-smithy-http-server/src/rejection.rs index 35d16d4e7e..f714b314fc 100644 --- a/rust-runtime/aws-smithy-http-server/src/rejection.rs +++ b/rust-runtime/aws-smithy-http-server/src/rejection.rs @@ -232,7 +232,7 @@ impl From for RequestRejection { // type. Generated functions that use [crate::rejection::RequestRejection] can thus use `?` to // bubble up instead of having to sprinkle things like [`Result::map_err`] everywhere. -convert_to_request_rejection!(aws_smithy_json::deserialize::Error, JsonDeserialize); +convert_to_request_rejection!(aws_smithy_json::deserialize::error::DeserializeError, JsonDeserialize); convert_to_request_rejection!(aws_smithy_xml::decode::XmlError, XmlDeserialize); convert_to_request_rejection!(aws_smithy_http::header::ParseError, HeaderParse); convert_to_request_rejection!(aws_smithy_types::date_time::DateTimeParseError, DateTimeParse); diff --git a/rust-runtime/aws-smithy-json/src/deserialize.rs b/rust-runtime/aws-smithy-json/src/deserialize.rs index ac6d0b337c..64e30cb404 100644 --- a/rust-runtime/aws-smithy-json/src/deserialize.rs +++ b/rust-runtime/aws-smithy-json/src/deserialize.rs @@ -3,16 +3,15 @@ * SPDX-License-Identifier: Apache-2.0 */ +use crate::deserialize::error::{DeserializeError as Error, DeserializeErrorKind as ErrorKind}; use aws_smithy_types::Number; +use ErrorKind::*; -mod error; +pub mod error; pub mod token; -pub use error::{Error, ErrorReason}; pub use token::{EscapeError, EscapedStr, Offset, Token}; -use ErrorReason::*; - /// JSON token parser as a Rust iterator /// /// This parser will parse and yield exactly one [`Token`] per iterator `next()` call. @@ -96,13 +95,13 @@ impl<'a> JsonTokenIterator<'a> { } /// Creates an error at the given `offset` in the stream. - fn error_at(&self, offset: usize, reason: ErrorReason) -> Error { - Error::new(reason, Some(offset)) + fn error_at(&self, offset: usize, kind: ErrorKind) -> Error { + Error::new(kind, Some(offset)) } /// Creates an error at the current offset in the stream. - fn error(&self, reason: ErrorReason) -> Error { - self.error_at(self.index, reason) + fn error(&self, kind: ErrorKind) -> Error { + self.error_at(self.index, kind) } /// Advances until it hits a non-whitespace character or the end of the slice. @@ -504,59 +503,77 @@ fn must_not_be_finite(f: f64) -> Result { #[cfg(test)] mod tests { + use crate::deserialize::error::{DeserializeError as Error, DeserializeErrorKind as ErrorKind}; use crate::deserialize::token::test::{ end_array, end_object, object_key, start_array, start_object, value_bool, value_null, value_number, value_string, }; - use crate::deserialize::{json_token_iter, Error, ErrorReason, EscapedStr, Token}; + use crate::deserialize::{json_token_iter, EscapedStr, Token}; use aws_smithy_types::Number; use proptest::prelude::*; + #[track_caller] + fn expect_token(expected: Option>, actual: Option>) { + let (expected, actual) = ( + expected.transpose().expect("err in expected"), + actual.transpose().expect("err in actual"), + ); + assert_eq!(expected, actual); + } + + macro_rules! expect_err { + ($kind:pat, $offset:expr, $value:expr) => { + let err: Error = $value.transpose().err().expect("expected error"); + assert!(matches!(err.kind, $kind)); + assert_eq!($offset, err.offset); + }; + } + #[test] fn test_empty() { - assert_eq!(None, json_token_iter(b"").next()); - assert_eq!(None, json_token_iter(b" ").next()); - assert_eq!(None, json_token_iter(b"\t").next()); + assert!(json_token_iter(b"").next().is_none()); + assert!(json_token_iter(b" ").next().is_none()); + assert!(json_token_iter(b"\t").next().is_none()); } #[test] fn test_empty_string() { let mut iter = json_token_iter(b"\"\""); - assert_eq!(value_string(0, ""), iter.next()); - assert_eq!(None, iter.next()); + expect_token(value_string(0, ""), iter.next()); + expect_token(None, iter.next()); let mut iter = json_token_iter(b" \r\n\t \"\" "); - assert_eq!(value_string(5, ""), iter.next()); - assert_eq!(None, iter.next()); + expect_token(value_string(5, ""), iter.next()); + expect_token(None, iter.next()); } #[test] fn test_empty_array() { let mut iter = json_token_iter(b"[]"); - assert_eq!(start_array(0), iter.next()); - assert_eq!(end_array(1), iter.next()); - assert_eq!(None, iter.next()); + expect_token(start_array(0), iter.next()); + expect_token(end_array(1), iter.next()); + expect_token(None, iter.next()); } #[test] fn test_empty_object() { let mut iter = json_token_iter(b"{}"); - assert_eq!(start_object(0), iter.next()); - assert_eq!(end_object(1), iter.next()); - assert_eq!(None, iter.next()); + expect_token(start_object(0), iter.next()); + expect_token(end_object(1), iter.next()); + expect_token(None, iter.next()); } #[test] fn test_null() { - assert_eq!(value_null(1), json_token_iter(b" null ").next()); + expect_token(value_null(1), json_token_iter(b" null ").next()); let mut iter = json_token_iter(b"[null, null,null]"); - assert_eq!(start_array(0), iter.next()); - assert_eq!(value_null(1), iter.next()); - assert_eq!(value_null(7), iter.next()); - assert_eq!(value_null(12), iter.next()); - assert_eq!(end_array(16), iter.next()); - assert_eq!(None, iter.next()); + expect_token(start_array(0), iter.next()); + expect_token(value_null(1), iter.next()); + expect_token(value_null(7), iter.next()); + expect_token(value_null(12), iter.next()); + expect_token(end_array(16), iter.next()); + expect_token(None, iter.next()); assert!(json_token_iter(b"n").next().unwrap().is_err()); assert!(json_token_iter(b"nul").next().unwrap().is_err()); @@ -569,15 +586,15 @@ mod tests { assert!(json_token_iter(b"truee").next().unwrap().is_err()); assert!(json_token_iter(b"f").next().unwrap().is_err()); assert!(json_token_iter(b"falsee").next().unwrap().is_err()); - assert_eq!(value_bool(1, true), json_token_iter(b" true ").next()); - assert_eq!(value_bool(0, false), json_token_iter(b"false").next()); + expect_token(value_bool(1, true), json_token_iter(b" true ").next()); + expect_token(value_bool(0, false), json_token_iter(b"false").next()); let mut iter = json_token_iter(b"[true,false]"); - assert_eq!(start_array(0), iter.next()); - assert_eq!(value_bool(1, true), iter.next()); - assert_eq!(value_bool(6, false), iter.next()); - assert_eq!(end_array(11), iter.next()); - assert_eq!(None, iter.next()); + expect_token(start_array(0), iter.next()); + expect_token(value_bool(1, true), iter.next()); + expect_token(value_bool(6, false), iter.next()); + expect_token(end_array(11), iter.next()); + expect_token(None, iter.next()); } proptest! { @@ -585,8 +602,8 @@ mod tests { fn string_prop_test(input in ".*") { let json: String = serde_json::to_string(&input).unwrap(); let mut iter = json_token_iter(json.as_bytes()); - assert_eq!(value_string(0, &json[1..(json.len() - 1)]), iter.next()); - assert_eq!(None, iter.next()); + expect_token(value_string(0, &json[1..(json.len() - 1)]), iter.next()); + expect_token(None, iter.next()); } #[test] @@ -598,23 +615,23 @@ mod tests { } else { Number::PosInt(input as u64) }; - assert_eq!(value_number(0, expected), iter.next()); - assert_eq!(None, iter.next()); + expect_token(value_number(0, expected), iter.next()); + expect_token(None, iter.next()); } #[test] fn float_prop_test(input: f64) { let json = serde_json::to_string(&input).unwrap(); let mut iter = json_token_iter(json.as_bytes()); - assert_eq!(value_number(0, Number::Float(input)), iter.next()); - assert_eq!(None, iter.next()); + expect_token(value_number(0, Number::Float(input)), iter.next()); + expect_token(None, iter.next()); } } #[test] fn valid_numbers() { let expect = |number, input| { - assert_eq!(value_number(0, number), json_token_iter(input).next()); + expect_token(value_number(0, number), json_token_iter(input).next()); }; expect(Number::Float(0.0), b"0."); expect(Number::Float(0.0), b"0e0"); @@ -635,7 +652,7 @@ mod tests { #[test] fn invalid_numbers_we_are_intentionally_accepting() { let expect = |number, input| { - assert_eq!(value_number(0, number), json_token_iter(input).next()); + expect_token(value_number(0, number), json_token_iter(input).next()); }; expect(Number::NegInt(-1), b"-01"); @@ -651,39 +668,50 @@ mod tests { #[test] fn invalid_numbers() { - let unexpected_token = |input, token, offset, msg| { - let tokens: Vec> = json_token_iter(input).collect(); - assert_eq!( - vec![Err(Error::new( - ErrorReason::UnexpectedToken(token, msg), - Some(offset) - ))], - tokens, - "input: \"{}\"", - std::str::from_utf8(input).unwrap(), - ); - }; + macro_rules! unexpected_token { + ($input:expr, $token:pat, $offset:expr, $msg:pat) => { + let tokens: Vec> = json_token_iter($input).collect(); + assert_eq!(1, tokens.len()); + expect_err!( + ErrorKind::UnexpectedToken($token, $msg), + Some($offset), + tokens.into_iter().next() + ); + }; + } let invalid_number = |input, offset| { let tokens: Vec> = json_token_iter(input).collect(); - assert_eq!( - vec![Err(Error::new(ErrorReason::InvalidNumber, Some(offset)))], - tokens, - "input: \"{}\"", - std::str::from_utf8(input).unwrap(), + assert_eq!(1, tokens.len()); + expect_err!( + ErrorKind::InvalidNumber, + Some(offset), + tokens.into_iter().next() ); }; - let unexpected_trailer = ", '}', ']', ','"; - let unexpected_start = "'{', '[', '\"', 'null', 'true', 'false', "; - - unexpected_token(b".", '.', 0, unexpected_start); - unexpected_token(b".0", '.', 0, unexpected_start); - unexpected_token(b"0-05", '-', 1, unexpected_trailer); - unexpected_token(b"0x05", 'x', 1, unexpected_trailer); - unexpected_token(b"123.invalid", 'i', 4, unexpected_trailer); - unexpected_token(b"123invalid", 'i', 3, unexpected_trailer); - unexpected_token(b"asdf", 'a', 0, unexpected_start); + unexpected_token!( + b".", + '.', + 0, + "'{', '[', '\"', 'null', 'true', 'false', " + ); + unexpected_token!( + b".0", + '.', + 0, + "'{', '[', '\"', 'null', 'true', 'false', " + ); + unexpected_token!(b"0-05", '-', 1, ", '}', ']', ','"); + unexpected_token!(b"0x05", 'x', 1, ", '}', ']', ','"); + unexpected_token!(b"123.invalid", 'i', 4, ", '}', ']', ','"); + unexpected_token!(b"123invalid", 'i', 3, ", '}', ']', ','"); + unexpected_token!( + b"asdf", + 'a', + 0, + "'{', '[', '\"', 'null', 'true', 'false', " + ); invalid_number(b"-a", 0); invalid_number(b"1e", 0); @@ -696,25 +724,22 @@ mod tests { #[test] fn test_unclosed_array() { let mut iter = json_token_iter(br#" [null "#); - assert_eq!(start_array(1), iter.next()); - assert_eq!(value_null(2), iter.next()); - assert_eq!( - Some(Err(Error::new(ErrorReason::UnexpectedEos, Some(7)))), - iter.next() - ); + expect_token(start_array(1), iter.next()); + expect_token(value_null(2), iter.next()); + expect_err!(ErrorKind::UnexpectedEos, Some(7), iter.next()); } #[test] fn test_array_with_items() { let mut iter = json_token_iter(b"[[], {}, \"test\"]"); - assert_eq!(start_array(0), iter.next()); - assert_eq!(start_array(1), iter.next()); - assert_eq!(end_array(2), iter.next()); - assert_eq!(start_object(5), iter.next()); - assert_eq!(end_object(6), iter.next()); - assert_eq!(value_string(9, "test"), iter.next()); - assert_eq!(end_array(15), iter.next()); - assert_eq!(None, iter.next()); + expect_token(start_array(0), iter.next()); + expect_token(start_array(1), iter.next()); + expect_token(end_array(2), iter.next()); + expect_token(start_object(5), iter.next()); + expect_token(end_object(6), iter.next()); + expect_token(value_string(9, "test"), iter.next()); + expect_token(end_array(15), iter.next()); + expect_token(None, iter.next()); } #[test] @@ -728,57 +753,52 @@ mod tests { "some_struct": { "nested": "asdf" }, "some_array": ["one", "two"] }"#, ); - assert_eq!(start_object(0), tokens.next()); - assert_eq!(object_key(2, "some_int"), tokens.next()); - assert_eq!(value_number(14, Number::PosInt(5)), tokens.next()); - assert_eq!(object_key(35, "some_float"), tokens.next()); - assert_eq!(value_number(49, Number::Float(5.2)), tokens.next()); - assert_eq!(object_key(72, "some_negative"), tokens.next()); - assert_eq!(value_number(89, Number::NegInt(-5)), tokens.next()); - assert_eq!(object_key(111, "some_negative_float"), tokens.next()); - assert_eq!(value_number(134, Number::Float(-2.4)), tokens.next()); - assert_eq!(object_key(158, "some_string"), tokens.next()); - assert_eq!(value_string(173, "test"), tokens.next()); - assert_eq!(object_key(199, "some_struct"), tokens.next()); - assert_eq!(start_object(214), tokens.next()); - assert_eq!(object_key(216, "nested"), tokens.next()); - assert_eq!(value_string(226, "asdf"), tokens.next()); - assert_eq!(end_object(233), tokens.next()); - assert_eq!(object_key(254, "some_array"), tokens.next()); - assert_eq!(start_array(268), tokens.next()); - assert_eq!(value_string(269, "one"), tokens.next()); - assert_eq!(value_string(276, "two"), tokens.next()); - assert_eq!(end_array(281), tokens.next()); - assert_eq!(end_object(283), tokens.next()); - assert_eq!(None, tokens.next()); + expect_token(start_object(0), tokens.next()); + expect_token(object_key(2, "some_int"), tokens.next()); + expect_token(value_number(14, Number::PosInt(5)), tokens.next()); + expect_token(object_key(35, "some_float"), tokens.next()); + expect_token(value_number(49, Number::Float(5.2)), tokens.next()); + expect_token(object_key(72, "some_negative"), tokens.next()); + expect_token(value_number(89, Number::NegInt(-5)), tokens.next()); + expect_token(object_key(111, "some_negative_float"), tokens.next()); + expect_token(value_number(134, Number::Float(-2.4)), tokens.next()); + expect_token(object_key(158, "some_string"), tokens.next()); + expect_token(value_string(173, "test"), tokens.next()); + expect_token(object_key(199, "some_struct"), tokens.next()); + expect_token(start_object(214), tokens.next()); + expect_token(object_key(216, "nested"), tokens.next()); + expect_token(value_string(226, "asdf"), tokens.next()); + expect_token(end_object(233), tokens.next()); + expect_token(object_key(254, "some_array"), tokens.next()); + expect_token(start_array(268), tokens.next()); + expect_token(value_string(269, "one"), tokens.next()); + expect_token(value_string(276, "two"), tokens.next()); + expect_token(end_array(281), tokens.next()); + expect_token(end_object(283), tokens.next()); + expect_token(None, tokens.next()); } #[test] fn test_object_trailing_comma() { let mut iter = json_token_iter(br#" { "test": "trailing", } "#); - assert_eq!(start_object(1), iter.next()); - assert_eq!(object_key(3, "test"), iter.next()); - assert_eq!(value_string(11, "trailing"), iter.next()); - assert_eq!( - Some(Err(Error::new( - ErrorReason::UnexpectedToken('}', "'\"'"), - Some(23), - ))), + expect_token(start_object(1), iter.next()); + expect_token(object_key(3, "test"), iter.next()); + expect_token(value_string(11, "trailing"), iter.next()); + expect_err!( + ErrorKind::UnexpectedToken('}', "'\"'"), + Some(23), iter.next() ); - assert_eq!(None, iter.next()); + assert!(iter.next().is_none()); } #[test] fn test_object_no_colon() { let mut iter = json_token_iter(br#" {"test" "#); - assert_eq!(start_object(1), iter.next()); - assert_eq!(object_key(2, "test"), iter.next()); - assert_eq!( - Some(Err(Error::new(ErrorReason::UnexpectedEos, Some(9),))), - iter.next() - ); - assert_eq!(None, iter.next()); + expect_token(start_object(1), iter.next()); + expect_token(object_key(2, "test"), iter.next()); + expect_err!(ErrorKind::UnexpectedEos, Some(9), iter.next()); + expect_token(None, iter.next()); } #[test] diff --git a/rust-runtime/aws-smithy-json/src/deserialize/error.rs b/rust-runtime/aws-smithy-json/src/deserialize/error.rs index 957effcc5b..9ae8e5b546 100644 --- a/rust-runtime/aws-smithy-json/src/deserialize/error.rs +++ b/rust-runtime/aws-smithy-json/src/deserialize/error.rs @@ -5,12 +5,16 @@ use crate::escape::EscapeError; use std::borrow::Cow; +use std::error::Error as StdError; use std::fmt; use std::str::Utf8Error; -#[derive(Debug, PartialEq, Eq)] -pub enum ErrorReason { - Custom(Cow<'static, str>), +#[derive(Debug)] +pub(in crate::deserialize) enum DeserializeErrorKind { + Custom { + message: Cow<'static, str>, + source: Option>, + }, ExpectedLiteral(String), InvalidEscape(char), InvalidNumber, @@ -20,73 +24,115 @@ pub enum ErrorReason { UnexpectedEos, UnexpectedToken(char, &'static str), } -use ErrorReason::*; -#[derive(Debug, PartialEq, Eq)] -pub struct Error { - reason: ErrorReason, - offset: Option, +#[derive(Debug)] +pub struct DeserializeError { + pub(in crate::deserialize) kind: DeserializeErrorKind, + pub(in crate::deserialize) offset: Option, } -impl Error { - pub fn new(reason: ErrorReason, offset: Option) -> Self { - Error { reason, offset } +impl DeserializeError { + pub(in crate::deserialize) fn new(kind: DeserializeErrorKind, offset: Option) -> Self { + Self { kind, offset } } /// Returns a custom error without an offset. - pub fn custom(message: impl Into>) -> Error { - Error::new(ErrorReason::Custom(message.into()), None) + pub fn custom(message: impl Into>) -> Self { + Self::new( + DeserializeErrorKind::Custom { + message: message.into(), + source: None, + }, + None, + ) + } + + /// Returns a custom error with an error source without an offset. + pub fn custom_source( + message: impl Into>, + source: impl Into>, + ) -> Self { + Self::new( + DeserializeErrorKind::Custom { + message: message.into(), + source: Some(source.into()), + }, + None, + ) + } + + /// Adds an offset to the error. + pub fn with_offset(mut self, offset: usize) -> Self { + self.offset = Some(offset); + self } } -impl std::error::Error for Error {} +impl StdError for DeserializeError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use DeserializeErrorKind::*; + match &self.kind { + UnescapeFailed(source) => Some(source), + Custom { + source: Some(source), + .. + } => Some(source.as_ref()), + Custom { source: None, .. } + | ExpectedLiteral(_) + | InvalidEscape(_) + | InvalidNumber + | InvalidUtf8 + | UnexpectedControlCharacter(_) + | UnexpectedToken(..) + | UnexpectedEos => None, + } + } +} -impl fmt::Display for Error { +impl fmt::Display for DeserializeError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use DeserializeErrorKind::*; if let Some(offset) = self.offset { write!(f, "Error at offset {}: ", offset)?; } - match &self.reason { - Custom(msg) => write!(f, "failed to parse JSON: {}", msg), - ExpectedLiteral(literal) => write!(f, "expected literal: {}", literal), - InvalidEscape(escape) => write!(f, "invalid JSON escape: \\{}", escape), + match &self.kind { + Custom { message, .. } => write!(f, "failed to parse JSON: {message}"), + ExpectedLiteral(literal) => write!(f, "expected literal: {literal}"), + InvalidEscape(escape) => write!(f, "invalid JSON escape: \\{escape}"), InvalidNumber => write!(f, "invalid number"), InvalidUtf8 => write!(f, "invalid UTF-8 codepoint in JSON stream"), - UnescapeFailed(err) => write!(f, "failed to unescape JSON string: {}", err), + UnescapeFailed(_) => write!(f, "failed to unescape JSON string"), UnexpectedControlCharacter(value) => write!( f, - "encountered unescaped control character in string: 0x{:X}", - value - ), - UnexpectedToken(token, expected) => write!( - f, - "unexpected token '{}'. Expected one of {}", - token, expected + "encountered unescaped control character in string: 0x{value:X}" ), + UnexpectedToken(token, expected) => { + write!(f, "unexpected token '{token}'. Expected one of {expected}",) + } UnexpectedEos => write!(f, "unexpected end of stream"), } } } -impl From for ErrorReason { +impl From for DeserializeErrorKind { fn from(_: Utf8Error) -> Self { - InvalidUtf8 + DeserializeErrorKind::InvalidUtf8 } } -impl From for Error { +impl From for DeserializeError { fn from(err: EscapeError) -> Self { - Error { - reason: ErrorReason::UnescapeFailed(err), + Self { + kind: DeserializeErrorKind::UnescapeFailed(err), offset: None, } } } -impl From for Error { +impl From for DeserializeError { fn from(_: aws_smithy_types::TryFromNumberError) -> Self { - Error { - reason: ErrorReason::InvalidNumber, + Self { + kind: DeserializeErrorKind::InvalidNumber, offset: None, } } diff --git a/rust-runtime/aws-smithy-json/src/deserialize/token.rs b/rust-runtime/aws-smithy-json/src/deserialize/token.rs index 3bead63fad..0bed404d8d 100644 --- a/rust-runtime/aws-smithy-json/src/deserialize/token.rs +++ b/rust-runtime/aws-smithy-json/src/deserialize/token.rs @@ -3,15 +3,14 @@ * SPDX-License-Identifier: Apache-2.0 */ -use crate::deserialize::error::{Error, ErrorReason}; +use crate::deserialize::error::DeserializeError as Error; +use crate::deserialize::must_not_be_finite; use crate::escape::unescape_string; +pub use crate::escape::EscapeError; use aws_smithy_types::date_time::Format; +use aws_smithy_types::primitive::Parse; use aws_smithy_types::{base64, Blob, DateTime, Document, Number}; use std::borrow::Cow; - -use crate::deserialize::must_not_be_finite; -pub use crate::escape::EscapeError; -use aws_smithy_types::primitive::Parse; use std::collections::HashMap; use std::iter::Peekable; @@ -44,7 +43,7 @@ pub struct Offset(pub usize); impl Offset { /// Creates a custom error from the offset pub fn error(&self, msg: Cow<'static, str>) -> Error { - Error::new(ErrorReason::Custom(msg), Some(self.0)) + Error::custom(msg).with_offset(self.0) } } @@ -116,10 +115,7 @@ macro_rules! expect_fn { Some(token) => { Err(token.error(Cow::Borrowed(concat!("expected ", stringify!($token))))) } - None => Err(Error::new( - ErrorReason::Custom(Cow::Borrowed(concat!("expected ", stringify!($token)))), - None, - )), + None => Err(Error::custom(concat!("expected ", stringify!($token)))), } } }; @@ -167,9 +163,7 @@ pub fn expect_number_or_null( Some(Token::ValueNull { .. }) => Ok(None), Some(Token::ValueNumber { value, .. }) => Ok(Some(value)), Some(Token::ValueString { value, offset }) => match value.to_unescaped() { - Err(err) => Err(Error::new( - ErrorReason::Custom(format!("expected a valid string, escape was invalid: {}", err).into()), Some(offset.0)) - ), + Err(err) => Err(Error::custom_source( "expected a valid string, escape was invalid", err).with_offset(offset.0)), Ok(v) => f64::parse_smithy_primitive(v.as_ref()) // disregard the exact error .map_err(|_|()) @@ -178,13 +172,11 @@ pub fn expect_number_or_null( .map(|float| Some(aws_smithy_types::Number::Float(float))) // convert to a helpful error .map_err(|_| { - Error::new( - ErrorReason::Custom(Cow::Owned(format!( + Error::custom( + format!( "only `Infinity`, `-Infinity`, `NaN` can represent a float as a string but found `{}`", v - ))), - Some(offset.0), - ) + )).with_offset(offset.0) }), }, _ => Err(Error::custom( @@ -196,14 +188,10 @@ pub fn expect_number_or_null( /// Expects a [Token::ValueString] or [Token::ValueNull]. If the value is a string, it interprets it as a base64 encoded [Blob] value. pub fn expect_blob_or_null(token: Option, Error>>) -> Result, Error> { Ok(match expect_string_or_null(token)? { - Some(value) => Some(Blob::new(base64::decode(value.as_escaped_str()).map_err( - |err| { - Error::new( - ErrorReason::Custom(Cow::Owned(format!("failed to decode base64: {}", err))), - None, - ) - }, - )?)), + Some(value) => Some(Blob::new( + base64::decode(value.as_escaped_str()) + .map_err(|err| Error::custom_source("failed to decode base64", err))?, + )), None => None, }) } @@ -220,17 +208,9 @@ pub fn expect_timestamp_or_null( .map(|v| v.to_f64_lossy()) .map(|v| { if v.is_nan() { - Err(Error::new( - ErrorReason::Custom(Cow::Owned("NaN is not a valid epoch".to_string())), - None, - )) + Err(Error::custom("NaN is not a valid epoch")) } else if v.is_infinite() { - Err(Error::new( - ErrorReason::Custom(Cow::Owned( - "Infinity is not a valid epoch".to_string(), - )), - None, - )) + Err(Error::custom("infinity is not a valid epoch")) } else { Ok(DateTime::from_secs_f64(v)) } @@ -239,12 +219,7 @@ pub fn expect_timestamp_or_null( Format::DateTime | Format::HttpDate => expect_string_or_null(token)? .map(|v| DateTime::from_str(v.as_escaped_str(), timestamp_format)) .transpose() - .map_err(|err| { - Error::new( - ErrorReason::Custom(Cow::Owned(format!("failed to parse timestamp: {}", err))), - None, - ) - })?, + .map_err(|err| Error::custom_source("failed to parse timestamp", err))?, }) } @@ -359,7 +334,8 @@ fn skip_inner<'a>( #[cfg(test)] pub mod test { use super::*; - use crate::deserialize::error::ErrorReason::UnexpectedToken; + use crate::deserialize::error::DeserializeErrorKind as ErrorKind; + use crate::deserialize::error::DeserializeErrorKind::UnexpectedToken; use crate::deserialize::json_token_iter; pub fn start_array<'a>(offset: usize) -> Option, Error>> { @@ -420,6 +396,16 @@ pub mod test { })) } + #[track_caller] + fn expect_err_custom(message: &str, offset: Option, result: Result) { + let err = result.err().expect("expected error"); + let (actual_message, actual_offset) = match &err.kind { + ErrorKind::Custom { message, .. } => (message.as_ref(), err.offset), + _ => panic!("expected ErrorKind::Custom, got {:?}", err), + }; + assert_eq!((message, offset), (actual_message, actual_offset)); + } + #[test] fn skip_simple_value() { let mut tokens = json_token_iter(b"null true"); @@ -477,18 +463,27 @@ pub mod test { fn mismatched_braces() { // The skip_value function doesn't need to explicitly handle these cases since // token iterator's parser handles them. This test confirms that assumption. - assert_eq!( - Err(Error::new(UnexpectedToken(']', "'}', ','"), Some(10),)), - skip_value(&mut json_token_iter(br#"[{"foo": 5]}"#)) - ); - assert_eq!( - Err(Error::new(UnexpectedToken(']', "'}', ','"), Some(9),)), - skip_value(&mut json_token_iter(br#"{"foo": 5]}"#)) - ); - assert_eq!( - Err(Error::new(UnexpectedToken('}', "']', ','"), Some(4),)), - skip_value(&mut json_token_iter(br#"[5,6}"#)) - ); + assert!(matches!( + skip_value(&mut json_token_iter(br#"[{"foo": 5]}"#)), + Err(Error { + kind: UnexpectedToken(']', "'}', ','"), + offset: Some(10) + }) + )); + assert!(matches!( + skip_value(&mut json_token_iter(br#"{"foo": 5]}"#)), + Err(Error { + kind: UnexpectedToken(']', "'}', ','"), + offset: Some(9) + }) + )); + assert!(matches!( + skip_value(&mut json_token_iter(br#"[5,6}"#)), + Err(Error { + kind: UnexpectedToken('}', "']', ','"), + offset: Some(4) + }) + )); } #[test] @@ -510,61 +505,58 @@ pub mod test { #[test] fn test_expect_start_object() { - assert_eq!( - Err(Error::new( - ErrorReason::Custom("expected StartObject".into()), - Some(2) - )), - expect_start_object(value_bool(2, true)) + expect_err_custom( + "expected StartObject", + Some(2), + expect_start_object(value_bool(2, true)), ); - assert_eq!(Ok(()), expect_start_object(start_object(0))); + assert!(expect_start_object(start_object(0)).is_ok()); } #[test] fn test_expect_start_array() { - assert_eq!( - Err(Error::new( - ErrorReason::Custom("expected StartArray".into()), - Some(2) - )), - expect_start_array(value_bool(2, true)) + expect_err_custom( + "expected StartArray", + Some(2), + expect_start_array(value_bool(2, true)), ); - assert_eq!(Ok(()), expect_start_array(start_array(0))); + assert!(expect_start_array(start_array(0)).is_ok()); } #[test] fn test_expect_string_or_null() { - assert_eq!(Ok(None), expect_string_or_null(value_null(0))); + assert_eq!(None, expect_string_or_null(value_null(0)).unwrap()); assert_eq!( - Ok(Some(EscapedStr("test\\n"))), - expect_string_or_null(value_string(0, "test\\n")) + Some(EscapedStr("test\\n")), + expect_string_or_null(value_string(0, "test\\n")).unwrap() ); - assert_eq!( - Err(Error::custom("expected ValueString or ValueNull")), - expect_string_or_null(value_bool(0, true)) + expect_err_custom( + "expected ValueString or ValueNull", + None, + expect_string_or_null(value_bool(0, true)), ); } #[test] fn test_expect_number_or_null() { - assert_eq!(Ok(None), expect_number_or_null(value_null(0))); + assert_eq!(None, expect_number_or_null(value_null(0)).unwrap()); assert_eq!( - Ok(Some(Number::PosInt(5))), - expect_number_or_null(value_number(0, Number::PosInt(5))) + Some(Number::PosInt(5)), + expect_number_or_null(value_number(0, Number::PosInt(5))).unwrap() ); - assert_eq!( - Err(Error::custom( - "expected ValueString, ValueNumber, or ValueNull" - )), - expect_number_or_null(value_bool(0, true)) + expect_err_custom( + "expected ValueString, ValueNumber, or ValueNull", + None, + expect_number_or_null(value_bool(0, true)), ); assert_eq!( - Ok(Some(Number::Float(f64::INFINITY))), - expect_number_or_null(value_string(0, "Infinity")) + Some(Number::Float(f64::INFINITY)), + expect_number_or_null(value_string(0, "Infinity")).unwrap() ); - assert_eq!( - Err(Error::new(ErrorReason::Custom("only `Infinity`, `-Infinity`, `NaN` can represent a float as a string but found `123`".into()), Some(0))), - expect_number_or_null(value_string(0, "123")) + expect_err_custom( + "only `Infinity`, `-Infinity`, `NaN` can represent a float as a string but found `123`", + Some(0), + expect_number_or_null(value_string(0, "123")), ); match expect_number_or_null(value_string(0, "NaN")) { Ok(Some(Number::Float(v))) if v.is_nan() => { @@ -578,63 +570,62 @@ pub mod test { #[test] fn test_expect_blob_or_null() { - assert_eq!(Ok(None), expect_blob_or_null(value_null(0))); + assert_eq!(None, expect_blob_or_null(value_null(0)).unwrap()); assert_eq!( - Ok(Some(Blob::new(b"hello!".to_vec()))), - expect_blob_or_null(value_string(0, "aGVsbG8h")) + Some(Blob::new(b"hello!".to_vec())), + expect_blob_or_null(value_string(0, "aGVsbG8h")).unwrap() ); - assert_eq!( - Err(Error::custom("expected ValueString or ValueNull")), - expect_blob_or_null(value_bool(0, true)) + expect_err_custom( + "expected ValueString or ValueNull", + None, + expect_blob_or_null(value_bool(0, true)), ); } #[test] fn test_expect_timestamp_or_null() { assert_eq!( - Ok(None), - expect_timestamp_or_null(value_null(0), Format::HttpDate) + None, + expect_timestamp_or_null(value_null(0), Format::HttpDate).unwrap() ); - for &invalid in &["NaN", "Infinity", "-Infinity"] { - assert_eq!( - Err(Error::new( - ErrorReason::Custom(Cow::Owned(format!( - "{} is not a valid epoch", - invalid.replace('-', "") - ))), - None, - )), - expect_timestamp_or_null(value_string(0, invalid), Format::EpochSeconds) + for (invalid, display_name) in &[ + ("NaN", "NaN"), + ("Infinity", "infinity"), + ("-Infinity", "infinity"), + ] { + expect_err_custom( + format!("{display_name} is not a valid epoch").as_str(), + None, + expect_timestamp_or_null(value_string(0, invalid), Format::EpochSeconds), ); } assert_eq!( - Ok(Some(DateTime::from_secs_f64(2048.0))), + Some(DateTime::from_secs_f64(2048.0)), expect_timestamp_or_null(value_number(0, Number::Float(2048.0)), Format::EpochSeconds) + .unwrap() ); assert_eq!( - Ok(Some(DateTime::from_secs_f64(1445412480.0))), + Some(DateTime::from_secs_f64(1445412480.0)), expect_timestamp_or_null( value_string(0, "Wed, 21 Oct 2015 07:28:00 GMT"), Format::HttpDate ) + .unwrap() ); assert_eq!( - Ok(Some(DateTime::from_secs_f64(1445412480.0))), + Some(DateTime::from_secs_f64(1445412480.0)), expect_timestamp_or_null(value_string(0, "2015-10-21T07:28:00Z"), Format::DateTime) + .unwrap() ); - let err = Error::new( - ErrorReason::Custom( - "only `Infinity`, `-Infinity`, `NaN` can represent a float as a string but found `wrong`".into(), - ), - Some(0), - ); - assert_eq!( - Err(err), + expect_err_custom( + "only `Infinity`, `-Infinity`, `NaN` can represent a float as a string but found `wrong`", + Some(0), expect_timestamp_or_null(value_string(0, "wrong"), Format::EpochSeconds) ); - assert_eq!( - Err(Error::custom("expected ValueString or ValueNull")), - expect_timestamp_or_null(value_number(0, Number::Float(0.0)), Format::DateTime) + expect_err_custom( + "expected ValueString or ValueNull", + None, + expect_timestamp_or_null(value_number(0, Number::Float(0.0)), Format::DateTime), ); } @@ -709,22 +700,20 @@ pub mod test { let mut value = String::new(); value.extend(std::iter::repeat('[').take(300)); value.extend(std::iter::repeat(']').take(300)); - assert_eq!( - Err(Error::custom( - "exceeded max recursion depth while parsing document" - )), - expect_document(&mut json_token_iter(value.as_bytes()).peekable()) + expect_err_custom( + "exceeded max recursion depth while parsing document", + None, + expect_document(&mut json_token_iter(value.as_bytes()).peekable()), ); value = String::new(); value.extend(std::iter::repeat("{\"t\":").take(300)); value.push('1'); value.extend(std::iter::repeat('}').take(300)); - assert_eq!( - Err(Error::custom( - "exceeded max recursion depth while parsing document" - )), - expect_document(&mut json_token_iter(value.as_bytes()).peekable()) + expect_err_custom( + "exceeded max recursion depth while parsing document", + None, + expect_document(&mut json_token_iter(value.as_bytes()).peekable()), ); } } diff --git a/rust-runtime/aws-smithy-json/src/escape.rs b/rust-runtime/aws-smithy-json/src/escape.rs index 08ea6ae3cb..4643880a66 100644 --- a/rust-runtime/aws-smithy-json/src/escape.rs +++ b/rust-runtime/aws-smithy-json/src/escape.rs @@ -7,7 +7,7 @@ use std::borrow::Cow; use std::fmt; #[derive(Debug, PartialEq, Eq)] -pub enum EscapeError { +enum EscapeErrorKind { ExpectedSurrogatePair(String), InvalidEscapeCharacter(char), InvalidSurrogatePair(u16, u16), @@ -16,12 +16,18 @@ pub enum EscapeError { UnexpectedEndOfString, } +#[derive(Debug)] +#[cfg_attr(test, derive(PartialEq, Eq))] +pub struct EscapeError { + kind: EscapeErrorKind, +} + impl std::error::Error for EscapeError {} impl fmt::Display for EscapeError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use EscapeError::*; - match self { + use EscapeErrorKind::*; + match &self.kind { ExpectedSurrogatePair(low) => { write!( f, @@ -40,6 +46,12 @@ impl fmt::Display for EscapeError { } } +impl From for EscapeError { + fn from(kind: EscapeErrorKind) -> Self { + Self { kind } + } +} + /// Escapes a string for embedding in a JSON string value. pub fn escape_string(value: &str) -> Cow { let bytes = value.as_bytes(); @@ -102,7 +114,7 @@ fn unescape_string_inner(start: &[u8], rest: &[u8]) -> Result { index += 1; if index == rest.len() { - return Err(EscapeError::UnexpectedEndOfString); + return Err(EscapeErrorKind::UnexpectedEndOfString.into()); } match rest[index] { b'u' => { @@ -119,7 +131,11 @@ fn unescape_string_inner(start: &[u8], rest: &[u8]) -> Result unescaped.push(b'\n'), b'r' => unescaped.push(b'\r'), b't' => unescaped.push(b'\t'), - _ => return Err(EscapeError::InvalidEscapeCharacter(byte.into())), + _ => { + return Err( + EscapeErrorKind::InvalidEscapeCharacter(byte.into()).into() + ) + } } index += 1; } @@ -132,7 +148,7 @@ fn unescape_string_inner(start: &[u8], rest: &[u8]) -> Result bool { @@ -145,24 +161,26 @@ fn is_utf16_high_surrogate(codepoint: u16) -> bool { fn read_codepoint(rest: &[u8]) -> Result { if rest.len() < 6 { - return Err(EscapeError::UnexpectedEndOfString); + return Err(EscapeErrorKind::UnexpectedEndOfString.into()); } if &rest[0..2] != b"\\u" { // The first codepoint is always prefixed with "\u" since unescape_string_inner does // that check, so this error will always be for the low word of a surrogate pair. - return Err(EscapeError::ExpectedSurrogatePair( + return Err(EscapeErrorKind::ExpectedSurrogatePair( String::from_utf8_lossy(&rest[0..6]).into(), - )); + ) + .into()); } - let codepoint_str = std::str::from_utf8(&rest[2..6]).map_err(|_| EscapeError::InvalidUtf8)?; + let codepoint_str = + std::str::from_utf8(&rest[2..6]).map_err(|_| EscapeErrorKind::InvalidUtf8)?; // Error on characters `u16::from_str_radix` would otherwise accept, such as `+` if codepoint_str .bytes() .any(|byte| !matches!(byte, b'0'..=b'9' | b'a'..=b'f' | b'A'..=b'F')) { - return Err(EscapeError::InvalidUnicodeEscape(codepoint_str.into())); + return Err(EscapeErrorKind::InvalidUnicodeEscape(codepoint_str.into()).into()); } Ok(u16::from_str_radix(codepoint_str, 16).expect("hex string is valid 16-bit value")) } @@ -174,16 +192,16 @@ fn read_unicode_escapes(bytes: &[u8], into: &mut Vec) -> Result) -> Result Result { + pub(crate) fn deserialize_partitions( + value: &[u8], + ) -> Result { let mut tokens_owned = json_token_iter(value).peekable(); let tokens = &mut tokens_owned; expect_start_object(tokens.next())?; @@ -234,7 +236,7 @@ mod deser { _ => skip_value(tokens)?, }, other => { - return Err(Error::custom(format!( + return Err(DeserializeError::custom(format!( "expected object key or end object, found: {:?}", other ))) @@ -242,18 +244,18 @@ mod deser { } } if tokens.next().is_some() { - return Err(Error::custom( + return Err(DeserializeError::custom( "found more JSON tokens after completing parsing", )); } - resolver.ok_or_else(|| Error::custom("did not find partitions array")) + resolver.ok_or_else(|| DeserializeError::custom("did not find partitions array")) } fn deser_partitions<'a, I>( tokens: &mut std::iter::Peekable, - ) -> Result, Error> + ) -> Result, DeserializeError> where - I: Iterator, Error>>, + I: Iterator, DeserializeError>>, { match tokens.next().transpose()? { Some(Token::StartArray { .. }) => { @@ -271,15 +273,15 @@ mod deser { } Ok(items) } - _ => Err(Error::custom("expected start array")), + _ => Err(DeserializeError::custom("expected start array")), } } pub(crate) fn deser_partition<'a, I>( tokens: &mut std::iter::Peekable, - ) -> Result + ) -> Result where - I: Iterator, Error>>, + I: Iterator, DeserializeError>>, { match tokens.next().transpose()? { Some(Token::StartObject { .. }) => { @@ -295,7 +297,7 @@ mod deser { builder.region_regex = token_to_str(tokens.next())? .map(|region_regex| Regex::new(®ion_regex)) .transpose() - .map_err(|e| Error::custom("invalid regex"))?; + .map_err(|e| DeserializeError::custom("invalid regex"))?; } "regions" => { builder.regions = deser_explicit_regions(tokens)?; @@ -306,7 +308,7 @@ mod deser { _ => skip_value(tokens)?, }, other => { - return Err(Error::custom(format!( + return Err(DeserializeError::custom(format!( "expected object key or end object, found: {:?}", other ))) @@ -315,16 +317,16 @@ mod deser { } Ok(builder.build()) } - _ => Err(Error::custom("expected start object")), + _ => Err(DeserializeError::custom("expected start object")), } } #[allow(clippy::type_complexity, non_snake_case)] pub(crate) fn deser_explicit_regions<'a, I>( tokens: &mut std::iter::Peekable, - ) -> Result, Error> + ) -> Result, DeserializeError> where - I: Iterator, Error>>, + I: Iterator, DeserializeError>>, { match tokens.next().transpose()? { Some(Token::StartObject { .. }) => { @@ -340,7 +342,7 @@ mod deser { } } other => { - return Err(Error::custom(format!( + return Err(DeserializeError::custom(format!( "expected object key or end object, found: {:?}", other ))) @@ -349,12 +351,14 @@ mod deser { } Ok(map) } - _ => Err(Error::custom("expected start object")), + _ => Err(DeserializeError::custom("expected start object")), } } /// Convert a token to `Str` (a potentially static String) - fn token_to_str(token: Option>) -> Result, Error> { + fn token_to_str( + token: Option>, + ) -> Result, DeserializeError> { Ok(expect_string_or_null(token)? .map(|s| s.to_unescaped().map(|u| u.into_owned())) .transpose()? @@ -363,9 +367,9 @@ mod deser { fn deser_outputs<'a, I>( tokens: &mut std::iter::Peekable, - ) -> Result, Error> + ) -> Result, DeserializeError> where - I: Iterator, Error>>, + I: Iterator, DeserializeError>>, { match tokens.next().transpose()? { Some(Token::StartObject { .. }) => { @@ -393,7 +397,7 @@ mod deser { _ => skip_value(tokens)?, }, other => { - return Err(Error::custom(format!( + return Err(DeserializeError::custom(format!( "expected object key or end object, found: {:?}", other ))) @@ -402,7 +406,7 @@ mod deser { } Ok(Some(builder)) } - _ => Err(Error::custom("expected start object")), + _ => Err(DeserializeError::custom("expected start object")), } } } diff --git a/rust-runtime/inlineable/src/json_errors.rs b/rust-runtime/inlineable/src/json_errors.rs index f20c16b1c2..ea13da3ba8 100644 --- a/rust-runtime/inlineable/src/json_errors.rs +++ b/rust-runtime/inlineable/src/json_errors.rs @@ -4,7 +4,7 @@ */ use aws_smithy_json::deserialize::token::skip_value; -use aws_smithy_json::deserialize::{json_token_iter, Error as DeserializeError, Token}; +use aws_smithy_json::deserialize::{error::DeserializeError, json_token_iter, Token}; use aws_smithy_types::Error as SmithyError; use bytes::Bytes; use http::header::ToStrError;