aws_smithy_http_server_python/
error.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! Python error definition.
7
8#![allow(non_local_definitions)]
9
10use aws_smithy_http_server::{
11    body::{to_boxed, BoxBody},
12    protocol::{
13        aws_json_10::AwsJson1_0, aws_json_11::AwsJson1_1, rest_json_1::RestJson1, rest_xml::RestXml,
14    },
15    response::IntoResponse,
16};
17use aws_smithy_types::date_time::{ConversionError, DateTimeParseError};
18use pyo3::{create_exception, exceptions::PyException as BasePyException, prelude::*};
19use thiserror::Error;
20
21/// Python error that implements foreign errors.
22#[derive(Error, Debug)]
23pub enum PyError {
24    /// Implements `From<aws_smithy_types::date_time::DateTimeParseError>`.
25    #[error("DateTimeConversion: {0}")]
26    DateTimeConversion(#[from] ConversionError),
27    /// Implements `From<aws_smithy_types::date_time::ConversionError>`.
28    #[error("DateTimeParse: {0}")]
29    DateTimeParse(#[from] DateTimeParseError),
30}
31
32create_exception!(smithy, PyException, BasePyException);
33
34impl From<PyError> for PyErr {
35    fn from(other: PyError) -> PyErr {
36        PyException::new_err(other.to_string())
37    }
38}
39
40/// Exception that can be thrown from a Python middleware.
41///
42/// It allows to specify a message and HTTP status code and implementing protocol specific capabilities
43/// to build a [aws_smithy_http_server::response::Response] from it.
44///
45/// :param message str:
46/// :param status_code typing.Optional\[int\]:
47/// :rtype None:
48#[pyclass(name = "MiddlewareException", extends = BasePyException)]
49#[derive(Debug, Clone)]
50pub struct PyMiddlewareException {
51    /// :type str:
52    #[pyo3(get, set)]
53    message: String,
54
55    /// :type int:
56    #[pyo3(get, set)]
57    status_code: u16,
58}
59
60#[pymethods]
61impl PyMiddlewareException {
62    /// Create a new [PyMiddlewareException].
63    #[pyo3(text_signature = "($self, message, status_code=None)")]
64    #[new]
65    fn newpy(message: String, status_code: Option<u16>) -> Self {
66        Self {
67            message,
68            status_code: status_code.unwrap_or(500),
69        }
70    }
71}
72
73impl From<PyErr> for PyMiddlewareException {
74    fn from(other: PyErr) -> Self {
75        // Try to extract `PyMiddlewareException` from `PyErr` and use that if succeed
76        let middleware_err = Python::with_gil(|py| other.to_object(py).extract::<Self>(py));
77        match middleware_err {
78            Ok(err) => err,
79            Err(_) => Self::newpy(other.to_string(), None),
80        }
81    }
82}
83
84impl IntoResponse<RestJson1> for PyMiddlewareException {
85    fn into_response(self) -> http::Response<BoxBody> {
86        http::Response::builder()
87            .status(self.status_code)
88            .header("Content-Type", "application/json")
89            .header("X-Amzn-Errortype", "MiddlewareException")
90            .body(to_boxed(self.json_body()))
91            .expect("invalid HTTP response for `MiddlewareException`; please file a bug report under https://github.com/smithy-lang/smithy-rs/issues")
92    }
93}
94
95impl IntoResponse<RestXml> for PyMiddlewareException {
96    fn into_response(self) -> http::Response<BoxBody> {
97        http::Response::builder()
98            .status(self.status_code)
99            .header("Content-Type", "application/xml")
100            .body(to_boxed(self.xml_body()))
101            .expect("invalid HTTP response for `MiddlewareException`; please file a bug report under https://github.com/smithy-lang/smithy-rs/issues")
102    }
103}
104
105impl IntoResponse<AwsJson1_0> for PyMiddlewareException {
106    fn into_response(self) -> http::Response<BoxBody> {
107        http::Response::builder()
108            .status(self.status_code)
109            .header("Content-Type", "application/x-amz-json-1.0")
110            // See https://smithy.io/2.0/aws/protocols/aws-json-1_0-protocol.html#empty-body-serialization
111            .body(to_boxed(self.json_body()))
112            .expect("invalid HTTP response for `MiddlewareException`; please file a bug report under https://github.com/smithy-lang/smithy-rs/issues")
113    }
114}
115
116impl IntoResponse<AwsJson1_1> for PyMiddlewareException {
117    fn into_response(self) -> http::Response<BoxBody> {
118        http::Response::builder()
119            .status(self.status_code)
120            .header("Content-Type", "application/x-amz-json-1.1")
121            // See https://smithy.io/2.0/aws/protocols/aws-json-1_1-protocol.html#empty-body-serialization
122            .body(to_boxed(self.json_body()))
123            .expect("invalid HTTP response for `MiddlewareException`; please file a bug report under https://github.com/smithy-lang/smithy-rs/issues")
124    }
125}
126
127impl PyMiddlewareException {
128    /// Serialize the body into a JSON object.
129    fn json_body(&self) -> String {
130        let mut out = String::new();
131        let mut object = aws_smithy_json::serialize::JsonObjectWriter::new(&mut out);
132        object.key("message").string(self.message.as_str());
133        object.finish();
134        out
135    }
136
137    /// Serialize the body into a XML object.
138    fn xml_body(&self) -> String {
139        let mut out = String::new();
140        {
141            let mut writer = aws_smithy_xml::encode::XmlWriter::new(&mut out);
142            let root = writer
143                .start_el("Error")
144                .write_ns("http://s3.amazonaws.com/doc/2006-03-01/", None);
145            let mut scope = root.finish();
146            {
147                let mut inner_writer = scope.start_el("Message").finish();
148                inner_writer.data(self.message.as_ref());
149            }
150            scope.finish();
151        }
152        out
153    }
154}