aws_sdk_sso/
json_errors.rs

1// Code generated by software.amazon.smithy.rust.codegen.smithy-rs. DO NOT EDIT.
2/*
3 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7use 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// currently only used by AwsJson
14#[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    // Trim a trailing URL from the error code, beginning with a `:`
21    let error_code = match error_code.find(':') {
22        Some(idx) => &error_code[..idx],
23        None => error_code,
24    };
25
26    // Trim a prefixing namespace from the error code, beginning with a `#`
27    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    // services like lambda use an alternate `Message` instead of `message`
155    #[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}