aws_sdk_sso/
json_errors.rs
1use aws_smithy_json::deserialize::token::skip_value;
8use aws_smithy_json::deserialize::{error::DeserializeError, json_token_iter, Token};
9use aws_smithy_runtime_api::http::Headers;
10use aws_smithy_types::error::metadata::{Builder as ErrorMetadataBuilder, ErrorMetadata};
11use std::borrow::Cow;
12
13#[allow(unused)]
15pub fn is_error<B>(response: &http::Response<B>) -> bool {
16 !response.status().is_success()
17}
18
19fn sanitize_error_code(error_code: &str) -> &str {
20 let error_code = match error_code.find(':') {
22 Some(idx) => &error_code[..idx],
23 None => error_code,
24 };
25
26 match error_code.find('#') {
28 Some(idx) => &error_code[idx + 1..],
29 None => error_code,
30 }
31}
32
33struct ErrorBody<'a> {
34 code: Option<Cow<'a, str>>,
35 message: Option<Cow<'a, str>>,
36}
37
38fn parse_error_body(bytes: &[u8]) -> Result<ErrorBody, DeserializeError> {
39 let mut tokens = json_token_iter(bytes).peekable();
40 let (mut typ, mut code, mut message) = (None, None, None);
41 if let Some(Token::StartObject { .. }) = tokens.next().transpose()? {
42 loop {
43 match tokens.next().transpose()? {
44 Some(Token::EndObject { .. }) => break,
45 Some(Token::ObjectKey { key, .. }) => {
46 if let Some(Ok(Token::ValueString { value, .. })) = tokens.peek() {
47 match key.as_escaped_str() {
48 "code" => code = Some(value.to_unescaped()?),
49 "__type" => typ = Some(value.to_unescaped()?),
50 "message" | "Message" | "errorMessage" => message = Some(value.to_unescaped()?),
51 _ => {}
52 }
53 }
54 skip_value(&mut tokens)?;
55 }
56 _ => return Err(DeserializeError::custom("expected object key or end object")),
57 }
58 }
59 if tokens.next().is_some() {
60 return Err(DeserializeError::custom("found more JSON tokens after completing parsing"));
61 }
62 }
63 Ok(ErrorBody { code: code.or(typ), message })
64}
65
66pub fn parse_error_metadata(payload: &[u8], headers: &Headers) -> Result<ErrorMetadataBuilder, DeserializeError> {
67 let ErrorBody { code, message } = parse_error_body(payload)?;
68
69 let mut err_builder = ErrorMetadata::builder();
70 if let Some(code) = headers.get("x-amzn-errortype").or(code.as_deref()).map(sanitize_error_code) {
71 err_builder = err_builder.code(code);
72 }
73 if let Some(message) = message {
74 err_builder = err_builder.message(message);
75 }
76 Ok(err_builder)
77}
78
79#[cfg(test)]
80mod test {
81 use crate::json_errors::{parse_error_body, parse_error_metadata, sanitize_error_code};
82 use aws_smithy_runtime_api::client::orchestrator::HttpResponse;
83 use aws_smithy_types::{body::SdkBody, error::ErrorMetadata};
84 use std::borrow::Cow;
85
86 #[test]
87 fn error_metadata() {
88 let response = HttpResponse::try_from(
89 http::Response::builder()
90 .body(SdkBody::from(r#"{ "__type": "FooError", "message": "Go to foo" }"#))
91 .unwrap(),
92 )
93 .unwrap();
94 assert_eq!(
95 parse_error_metadata(response.body().bytes().unwrap(), response.headers())
96 .unwrap()
97 .build(),
98 ErrorMetadata::builder().code("FooError").message("Go to foo").build()
99 )
100 }
101
102 #[test]
103 fn error_type() {
104 assert_eq!(
105 Some(Cow::Borrowed("FooError")),
106 parse_error_body(br#"{ "__type": "FooError" }"#).unwrap().code
107 );
108 }
109
110 #[test]
111 fn code_takes_priority() {
112 assert_eq!(
113 Some(Cow::Borrowed("BarError")),
114 parse_error_body(br#"{ "code": "BarError", "__type": "FooError" }"#).unwrap().code
115 );
116 }
117
118 #[test]
119 fn ignore_unrecognized_fields() {
120 assert_eq!(
121 Some(Cow::Borrowed("FooError")),
122 parse_error_body(br#"{ "__type": "FooError", "asdf": 5, "fdsa": {}, "foo": "1" }"#)
123 .unwrap()
124 .code
125 );
126 }
127
128 #[test]
129 fn sanitize_namespace_and_url() {
130 assert_eq!(
131 sanitize_error_code("aws.protocoltests.restjson#FooError:http://internal.amazon.com/coral/com.amazon.coral.validate/"),
132 "FooError"
133 );
134 }
135
136 #[test]
137 fn sanitize_noop() {
138 assert_eq!(sanitize_error_code("FooError"), "FooError");
139 }
140
141 #[test]
142 fn sanitize_url() {
143 assert_eq!(
144 sanitize_error_code("FooError:http://internal.amazon.com/coral/com.amazon.coral.validate/"),
145 "FooError"
146 );
147 }
148
149 #[test]
150 fn sanitize_namespace() {
151 assert_eq!(sanitize_error_code("aws.protocoltests.restjson#FooError"), "FooError");
152 }
153
154 #[test]
156 fn alternative_error_message_names() {
157 let response = HttpResponse::try_from(
158 http::Response::builder()
159 .header("x-amzn-errortype", "ResourceNotFoundException")
160 .body(SdkBody::from(
161 r#"{
162 "Type": "User",
163 "Message": "Functions from 'us-west-2' are not reachable from us-east-1"
164 }"#,
165 ))
166 .unwrap(),
167 )
168 .unwrap();
169 assert_eq!(
170 parse_error_metadata(response.body().bytes().unwrap(), response.headers())
171 .unwrap()
172 .build(),
173 ErrorMetadata::builder()
174 .code("ResourceNotFoundException")
175 .message("Functions from 'us-west-2' are not reachable from us-east-1")
176 .build()
177 );
178 }
179}