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