aws_sdk_ssooidc/
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, which is done by removing the longest suffix
21    // beginning with a `:`
22    let error_code = match error_code.find(':') {
23        Some(idx) => &error_code[..idx],
24        None => error_code,
25    };
26
27    // Trim a prefixing namespace from the error code, beginning with a `#`
28    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    // services like lambda use an alternate `Message` instead of `message`
156    #[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}