Skip to main content

aws_smithy_http_server/schema/protocol/
rest_json_1.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6use aws_smithy_runtime_api::http::Headers;
7use aws_smithy_schema::serde::{SerdeError, SerializableStruct, ShapeDeserializer};
8use aws_smithy_schema::{shape_id, Schema, ShapeId};
9
10use crate::body::BoxBody;
11use crate::protocol::rest_json_1::rejection::RequestRejection;
12use crate::protocol::rest_json_1::runtime_error::RuntimeError;
13use crate::response::{IntoResponse, Response};
14use crate::schema::{DeserializeError, HttpModeledError};
15
16use super::response::{log_serialize_failure, stamp_error_extension, stamp_validation_extension};
17use super::rest::RestPolicy;
18use super::{BodyDirective, EventStreamFraming, MetadataRoutedProtocol, ServerProtocol};
19
20/// Stateful schema-driven restJson1 protocol implementation.
21#[derive(Debug)]
22/// Defaults to skipping escape validation in discarded strings.
23/// Set `customizationConfig.protocols` for this protocol to `{"validateSkippedValues":true}`
24/// to validate escape syntax without decoding Unicode escapes.
25/// UTF-8, unescaped control characters, and structural syntax remain checked in either mode.
26pub struct RestJson1 {
27    pub(crate) inner: crate::schema::protocol::rest::RestProtocol<aws_smithy_json::codec::JsonCodec>,
28}
29
30impl Default for RestJson1 {
31    fn default() -> Self {
32        Self::new(false)
33    }
34}
35
36impl RestJson1 {
37    fn new(validate_skipped_values: bool) -> Self {
38        Self {
39            inner: crate::schema::protocol::rest::RestProtocol::new(
40                aws_smithy_json::codec::JsonCodec::new(
41                    aws_smithy_json::codec::JsonCodecSettings::builder()
42                        .use_json_name(true)
43                        .default_timestamp_format(aws_smithy_types::date_time::Format::EpochSeconds)
44                        .enforce_strictness(true)
45                        .allow_leading_zeros(true)
46                        .allow_trailing_decimal_point(true)
47                        .allow_empty_integer_part(true)
48                        .normalize_arbitrary_precision(true)
49                        .validate_skipped_values(validate_skipped_values)
50                        .validate_skipped_string_encoding(true)
51                        .allow_integral_float_numbers(true)
52                        .strict_timestamp_formats(true)
53                        .dispatch_null_members(true)
54                        .build(),
55                ),
56                crate::schema::protocol::rest_json_1::POLICY,
57            ),
58        }
59    }
60}
61
62static PROTOCOL_ID: ShapeId<'static> = shape_id!("aws.protocols", "restJson1");
63const CONTENT_TYPE: &str = "application/json";
64const ERROR_TYPE_HEADER: http::HeaderName = http::HeaderName::from_static("x-amzn-errortype");
65
66/// restJson1 stamps `application/json` on every response nothing else labels, sets no content
67/// type on an untyped blob payload, and answers a user-modeled empty output with `{}`.
68pub(crate) const POLICY: RestPolicy = RestPolicy {
69    payload_deserializer: None,
70    codec_content_type: CONTENT_TYPE,
71    request_content_type_aliases: &[],
72    default_response_content_type: Some(CONTENT_TYPE),
73    untyped_blob_payload_content_type: None,
74    empty_document: true,
75};
76
77impl MetadataRoutedProtocol for RestJson1 {
78    fn from_build_context(
79        ctx: &crate::schema::ProtocolBuildContext<'_>,
80    ) -> Result<Self, crate::schema::routing::RouterBuildError> {
81        let mut protocol =
82            Self::new(crate::schema::settings::get::<bool>(ctx.settings, "validateSkippedValues")?.unwrap_or(false));
83        protocol
84            .inner
85            .prepare_response_plans(ctx.service)
86            .map_err(|err| crate::schema::routing::RouterBuildError::Configuration(err.to_string()))?;
87        Ok(protocol)
88    }
89
90    fn build_router(
91        &self,
92        ctx: crate::schema::routing::RouterBuildContext<'_>,
93    ) -> Result<
94        impl crate::schema::routing::MetadataProtocolRouter + 'static + use<>,
95        crate::schema::routing::RouterBuildError,
96    > {
97        crate::schema::routing::rest_router(&ctx, CONTENT_TYPE, &[])
98    }
99
100    fn event_stream_framing(&self) -> Option<EventStreamFraming<'_>> {
101        Some(
102            EventStreamFraming::new(self.inner.codec(), CONTENT_TYPE)
103                .unset_initial_response_payload(b"{}")
104                .exception_http_bindings(true),
105        )
106    }
107}
108
109impl ServerProtocol for RestJson1 {
110    fn protocol_id(&self) -> &'static ShapeId<'static> {
111        &PROTOCOL_ID
112    }
113
114    fn validate_request_headers(
115        &self,
116        operation: &crate::schema::OperationSchema<'_>,
117        headers: &Headers,
118    ) -> Result<(), DeserializeError> {
119        self.inner.check_accept(operation.output(), headers)
120    }
121
122    fn request_body_requirement(&self, operation: &crate::schema::OperationSchema<'_>) -> BodyDirective {
123        if self.inner.reads_request_body(operation.input()) {
124            BodyDirective::Collect
125        } else {
126            BodyDirective::Skip
127        }
128    }
129
130    fn deserialize_request<'a>(
131        &'a self,
132        input: &Schema<'_>,
133        request: &'a aws_smithy_runtime_api::http::Request<bytes::Bytes>,
134    ) -> Result<Box<dyn ShapeDeserializer + 'a>, DeserializeError> {
135        self.inner.deserialize_request(input, request)
136    }
137
138    fn serialize_response(&self, output: &Schema<'_>, value: &dyn SerializableStruct) -> Response {
139        self.inner
140            .serialize_response(output, value)
141            .unwrap_or_else(serialization_failure)
142    }
143
144    fn serialize_streaming_response(
145        &self,
146        output: &Schema<'_>,
147        value: &dyn SerializableStruct,
148        _streaming_member: &Schema<'_>,
149        body: Option<BoxBody>,
150    ) -> Response {
151        self.inner
152            .serialize_streaming_response(output, value, body.unwrap_or_else(crate::body::empty))
153            .unwrap_or_else(serialization_failure)
154    }
155
156    fn serialize_error(&self, error: &dyn HttpModeledError) -> Response {
157        let schema = error.schema();
158        let name = schema.shape_id().shape_name();
159        // Generated serde uses the operation output's default, even for a modeled error body.
160        let content_type = match error.operation_schema() {
161            Some(operation) => self.inner.response_content_type(operation.output()),
162            None => Some(CONTENT_TYPE),
163        };
164        let result =
165            self.inner
166                .serialize_modeled_error(self.inner.codec(), schema, error, error.status_code(), content_type);
167        match result {
168            Ok(mut response) => {
169                // The discriminator travels in the header, as the shape name only.
170                if !response.headers().contains_key(&ERROR_TYPE_HEADER) {
171                    if let Ok(value) = http::HeaderValue::try_from(name) {
172                        response.headers_mut().insert(ERROR_TYPE_HEADER, value);
173                    }
174                }
175                stamp_error_extension(response, name)
176            }
177            Err(err) => serialization_failure(err),
178        }
179    }
180
181    fn serialize_routing_error(&self, err: &crate::schema::routing::RoutingError) -> Response {
182        use crate::protocol::rest::router::Error;
183        use crate::schema::routing::RoutingErrorKind;
184        if err.kind() == RoutingErrorKind::MissingContentType {
185            return self.serialize_rejection(
186                crate::rejection::MissingContentTypeReason::UnexpectedMimeType {
187                    expected_mime: err.expected_content_type().and_then(|expected| expected.parse().ok()),
188                    found_mime: None,
189                }
190                .into(),
191            );
192        }
193        let error = match err.kind() {
194            RoutingErrorKind::MethodNotAllowed => Error::MethodNotAllowed,
195            _ => Error::NotFound,
196        };
197        IntoResponse::<RestJson1>::into_response(error)
198    }
199
200    fn serialize_runtime_error(&self, error: crate::runtime_error::RuntimeError) -> Response {
201        IntoResponse::<RestJson1>::into_response(RuntimeError::from(error))
202    }
203
204    /// restJson1 is the only protocol that keeps `Accept` and `Content-Type` failures distinct:
205    /// 406 and 415, per its `From<RequestRejection>` mapping. Transport failures answer with the
206    /// `RuntimeError` responses; a constraint violation with the modeled validation error.
207    fn serialize_rejection(&self, err: DeserializeError) -> Response {
208        match err {
209            DeserializeError::InitialRequestFrame(reason) => {
210                IntoResponse::<RestJson1>::into_response(RuntimeError::Validation(reason))
211            }
212            DeserializeError::Serde(err) => {
213                IntoResponse::<RestJson1>::into_response(RuntimeError::Serialization(crate::Error::new(err)))
214            }
215            DeserializeError::BodyCollection(err) => {
216                IntoResponse::<RestJson1>::into_response(RuntimeError::Serialization(crate::Error::new(err)))
217            }
218            DeserializeError::UnsupportedMediaType(reason) => IntoResponse::<RestJson1>::into_response(
219                RuntimeError::from(RequestRejection::MissingContentType(*reason)),
220            ),
221            DeserializeError::NotAcceptable => {
222                IntoResponse::<RestJson1>::into_response(RuntimeError::from(RequestRejection::NotAcceptable))
223            }
224            DeserializeError::ConstraintViolation(err) => stamp_validation_extension(self.serialize_error(&*err)),
225            DeserializeError::InternalFailure(err) => {
226                IntoResponse::<RestJson1>::into_response(RuntimeError::InternalFailure(err))
227            }
228        }
229    }
230}
231
232fn serialization_failure(err: SerdeError) -> Response {
233    log_serialize_failure(&err);
234    IntoResponse::<RestJson1>::into_response(RuntimeError::Serialization(crate::Error::new(err)))
235}