Skip to main content

aws_smithy_http_server/
runtime_error.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6use crate::{extension::RuntimeErrorExtension, response::IntoResponse};
7
8/// A _protocol-agnostic_ type representing an internal framework error. As of writing, this can only
9/// occur upon failure to extract an [`crate::extension::Extension`] from the request.
10/// This type is converted into protocol-specific error variants. For example, in the
11/// [`crate::protocol::rest_json_1`] protocol, it is converted to the
12/// [`crate::protocol::rest_json_1::runtime_error::RuntimeError::InternalFailure`] variant.
13///
14/// Its response conversions support concrete protocol markers and tuples of two through ten
15/// protocols. Use [`IntoResponse::into_response_with_protocol`] to frame the
16/// response; a direct tuple conversion returns the shared [`RuntimeError`]'s empty 500 fallback.
17pub struct InternalFailureException;
18
19pub const INVALID_HTTP_RESPONSE_FOR_RUNTIME_ERROR_PANIC_MESSAGE: &str = "invalid HTTP response for `RuntimeError`; please file a bug report under https://github.com/smithy-lang/smithy-rs/issues";
20
21/// A protocol-independent extractor rejection framed by the selected schema protocol.
22///
23/// Use this error with `impl<P> FromParts<P>` extractors on single-protocol or multi-protocol
24/// services. Schema upgrades call [`IntoResponse::into_response_with_protocol`], which delegates
25/// to [`crate::schema::ServerProtocol::serialize_runtime_error`]. Existing protocol-specific
26/// runtime-error types and their conversions remain available for explicitly static framing.
27///
28/// A direct [`IntoResponse::into_response`] call has no selected protocol: it returns an empty
29/// response with the semantic status and a [`RuntimeErrorExtension`], without protocol headers.
30/// Supply a protocol to the context-aware method when direct conversion needs protocol framing.
31#[non_exhaustive]
32#[derive(Debug, thiserror::Error)]
33pub enum RuntimeError {
34    /// Request deserialization or response serialization failed.
35    #[error("request failed to deserialize or response failed to serialize: {0}")]
36    Serialization(crate::error::Error),
37    /// Internal framework or service state required by an extractor is unavailable.
38    #[error("internal failure: {0}")]
39    InternalFailure(crate::error::Error),
40    /// The request's `Accept` header cannot accept the response content type.
41    #[error("not acceptable request: request contains an `Accept` header with a MIME type, and the server cannot return a response body adhering to that MIME type")]
42    NotAcceptable,
43    /// The request's `Content-Type` does not match the expected content type.
44    #[error("unsupported media type: request does not contain the expected `Content-Type` header value")]
45    UnsupportedMediaType,
46    /// An extractor rejected the request with a validation reason.
47    ///
48    /// This is a runtime validation response, distinct from a modeled constraint violation.
49    /// Each protocol frames the reason in its response format, or discards it where its
50    /// existing runtime validation response omits the message.
51    /// This variant does not carry a modeled error shape. Custom modeled validation errors
52    /// retain their schema through [`crate::schema::DeserializeError::ConstraintViolation`].
53    #[error("validation failure: {0}")]
54    Validation(String),
55}
56
57impl From<InternalFailureException> for RuntimeError {
58    fn from(_: InternalFailureException) -> Self {
59        Self::InternalFailure(crate::error::Error::new(String::new()))
60    }
61}
62
63impl RuntimeError {
64    /// The error name used by protocol framing and [`RuntimeErrorExtension`].
65    pub fn name(&self) -> &'static str {
66        match self {
67            Self::Serialization(_) => "SerializationException",
68            Self::InternalFailure(_) => "InternalFailureException",
69            Self::NotAcceptable => "NotAcceptableException",
70            Self::UnsupportedMediaType => "UnsupportedMediaTypeException",
71            Self::Validation(_) => "ValidationException",
72        }
73    }
74
75    /// The semantic status used when conversion has no selected protocol.
76    pub fn status_code(&self) -> http::StatusCode {
77        match self {
78            Self::Serialization(_) | Self::Validation(_) => http::StatusCode::BAD_REQUEST,
79            Self::InternalFailure(_) => http::StatusCode::INTERNAL_SERVER_ERROR,
80            Self::NotAcceptable => http::StatusCode::NOT_ACCEPTABLE,
81            Self::UnsupportedMediaType => http::StatusCode::UNSUPPORTED_MEDIA_TYPE,
82        }
83    }
84}
85
86impl<P> IntoResponse<P> for RuntimeError {
87    fn into_response(self) -> crate::response::Response {
88        let mut response = http::Response::new(crate::body::empty());
89        *response.status_mut() = self.status_code();
90        response
91            .extensions_mut()
92            .insert(RuntimeErrorExtension::new(self.name().to_owned()));
93        response
94    }
95
96    fn into_response_with_protocol(self, protocol: &dyn crate::schema::ServerProtocol) -> crate::response::Response {
97        protocol.serialize_runtime_error(self)
98    }
99}
100
101macro_rules! tuple_internal_failure {
102    ($($protocol:ident),+) => {
103        impl<$($protocol),+> IntoResponse<($($protocol,)+)> for InternalFailureException {
104            fn into_response(self) -> crate::response::Response {
105                IntoResponse::<()>::into_response(RuntimeError::from(self))
106            }
107
108            fn into_response_with_protocol(self, protocol: &dyn crate::schema::ServerProtocol) -> crate::response::Response {
109                protocol.serialize_runtime_error(self.into())
110            }
111
112        }
113    };
114}
115
116tuple_internal_failure!(P1, P2);
117tuple_internal_failure!(P1, P2, P3);
118tuple_internal_failure!(P1, P2, P3, P4);
119tuple_internal_failure!(P1, P2, P3, P4, P5);
120tuple_internal_failure!(P1, P2, P3, P4, P5, P6);
121tuple_internal_failure!(P1, P2, P3, P4, P5, P6, P7);
122tuple_internal_failure!(P1, P2, P3, P4, P5, P6, P7, P8);
123tuple_internal_failure!(P1, P2, P3, P4, P5, P6, P7, P8, P9);
124tuple_internal_failure!(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10);
125
126// Reuse each string-based protocol's established response conversion, including its wire quirks.
127// CBOR's conversion separately encodes the reason into its pre-serialized validation body.
128macro_rules! protocol_runtime_error {
129    ($error:ty) => {
130        impl From<RuntimeError> for $error {
131            fn from(error: RuntimeError) -> Self {
132                match error {
133                    RuntimeError::Serialization(error) => Self::Serialization(error),
134                    RuntimeError::InternalFailure(error) => Self::InternalFailure(error),
135                    RuntimeError::NotAcceptable => Self::NotAcceptable,
136                    RuntimeError::UnsupportedMediaType => Self::UnsupportedMediaType,
137                    RuntimeError::Validation(reason) => Self::Validation(reason.into()),
138                }
139            }
140        }
141    };
142}
143protocol_runtime_error!(crate::protocol::rest_json_1::runtime_error::RuntimeError);
144protocol_runtime_error!(crate::protocol::aws_json::runtime_error::RuntimeError);
145protocol_runtime_error!(crate::protocol::rest_xml::runtime_error::RuntimeError);