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