aws_smithy_http_server/schema/protocol/
aws_json.rs1use aws_smithy_runtime_api::http::Headers;
10use aws_smithy_schema::serde::{SerdeError, SerializableStruct, ShapeDeserializer};
11use aws_smithy_schema::{shape_id, Schema, ShapeId};
12
13use crate::body::BoxBody;
14use crate::protocol::aws_json::rejection::RequestRejection;
15use crate::protocol::aws_json::runtime_error::RuntimeError;
16use crate::response::{IntoResponse, Response};
17use crate::schema::response_bindings::{serialize_response_parts, ResponseValueKind};
18use crate::schema::{DeserializeError, HttpModeledError};
19
20use super::discriminator::{BodyDiscriminator, TypeValue};
21use super::response::{
22 assemble_response, assemble_streaming_response, log_serialize_failure, resolve_status,
23 serialize_modeled_error_response, stamp_error_extension, stamp_validation_extension, ResponseBindings,
24};
25use super::{BodyDirective, EventStreamFraming, MetadataRoutedProtocol, ServerProtocol};
26
27#[derive(Debug)]
29pub struct AwsJson1_0 {
40 pub(crate) inner: crate::schema::protocol::rpc::RpcProtocol<aws_smithy_json::codec::JsonCodec>,
41 response_bindings: ResponseBindingsMode,
42}
43
44impl Default for AwsJson1_0 {
45 fn default() -> Self {
46 Self::new(false, ResponseBindingsMode::Historical)
47 }
48}
49
50impl AwsJson1_0 {
51 fn new(validate_skipped_values: bool, response_bindings: ResponseBindingsMode) -> Self {
52 Self {
53 response_bindings,
54 inner: crate::schema::protocol::rpc::RpcProtocol::new(
55 schema_codec(validate_skipped_values),
56 "application/x-amz-json-1.0",
57 Some("application/x-amz-json-1.0"),
58 crate::schema::protocol::rpc::RpcAccept::Always,
59 crate::schema::protocol::rpc::RpcStreaming::CodecContentType,
60 ),
61 }
62 }
63}
64
65#[derive(Debug)]
67pub struct AwsJson1_1 {
78 pub(crate) inner: crate::schema::protocol::rpc::RpcProtocol<aws_smithy_json::codec::JsonCodec>,
79 response_bindings: ResponseBindingsMode,
80}
81
82impl Default for AwsJson1_1 {
83 fn default() -> Self {
84 Self::new(false, ResponseBindingsMode::Historical)
85 }
86}
87
88impl AwsJson1_1 {
89 fn new(validate_skipped_values: bool, response_bindings: ResponseBindingsMode) -> Self {
90 Self {
91 response_bindings,
92 inner: crate::schema::protocol::rpc::RpcProtocol::new(
93 schema_codec(validate_skipped_values),
94 "application/x-amz-json-1.1",
95 Some("application/x-amz-json-1.1"),
96 crate::schema::protocol::rpc::RpcAccept::Always,
97 crate::schema::protocol::rpc::RpcStreaming::CodecContentType,
98 ),
99 }
100 }
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105enum ResponseBindingsMode {
106 Historical,
108 SmithySpecs,
110}
111
112fn response_bindings_mode(
114 settings: Option<&aws_smithy_types::Document>,
115) -> Result<ResponseBindingsMode, crate::schema::routing::RouterBuildError> {
116 match crate::schema::settings::get::<&str>(settings, "responseBindings")? {
117 None | Some("historical") => Ok(ResponseBindingsMode::Historical),
118 Some("smithy-specs") => Ok(ResponseBindingsMode::SmithySpecs),
119 Some(other) => Err(crate::schema::routing::RouterBuildError::Configuration(format!(
120 "setting `responseBindings` must be \"historical\" or \"smithy-specs\", got {other:?}"
121 ))),
122 }
123}
124
125fn schema_codec(validate_skipped_values: bool) -> aws_smithy_json::codec::JsonCodec {
126 aws_smithy_json::codec::JsonCodec::new(
127 aws_smithy_json::codec::JsonCodecSettings::builder()
128 .use_json_name(false)
129 .default_timestamp_format(aws_smithy_types::date_time::Format::EpochSeconds)
130 .enforce_strictness(true)
131 .allow_leading_zeros(true)
132 .allow_trailing_decimal_point(true)
133 .allow_empty_integer_part(true)
134 .normalize_arbitrary_precision(true)
135 .validate_skipped_values(validate_skipped_values)
136 .validate_skipped_string_encoding(true)
137 .allow_integral_float_numbers(true)
138 .strict_timestamp_formats(true)
139 .dispatch_null_members(true)
140 .build(),
141 )
142}
143
144fn classification_status(schema: &Schema<'_>) -> Result<u16, SerdeError> {
146 let classification = schema
147 .traits()
148 .and_then(|traits| traits.get(&shape_id!("smithy.api", "error")))
149 .and_then(|value| value.as_any().downcast_ref::<aws_smithy_schema::StringTrait>())
150 .map(|value| value.value());
151 match classification {
152 Some("client") => Ok(400),
153 Some("server") => Ok(500),
154 _ => Err(SerdeError::custom("missing or invalid @error trait")),
155 }
156}
157
158fn serialize_error<P>(
159 codec: &aws_smithy_json::codec::JsonCodec,
160 error: &dyn HttpModeledError,
161 content_type: &'static str,
162 discriminator: BodyDiscriminator,
163 mode: ResponseBindingsMode,
164) -> Response
165where
166 RuntimeError: IntoResponse<P>,
167{
168 let schema = error.schema();
169 let status = match mode {
170 ResponseBindingsMode::Historical => error.status_code(),
171 ResponseBindingsMode::SmithySpecs => match classification_status(schema) {
172 Ok(status) => status,
173 Err(error) => return serialization_failure::<P>(error),
174 },
175 };
176 let framed = discriminator.frame(schema, error);
177 let bindings = match mode {
178 ResponseBindingsMode::Historical => ResponseBindings::LegacyRpc,
179 ResponseBindingsMode::SmithySpecs => ResponseBindings::BodyOnly,
180 };
181 serialize_modeled_error_response(codec, schema, &framed, status, bindings, content_type)
182 .map(|response| stamp_error_extension(response, schema.shape_id().shape_name()))
183 .unwrap_or_else(serialization_failure::<P>)
184}
185
186fn serialization_failure<P>(err: SerdeError) -> Response
187where
188 RuntimeError: IntoResponse<P>,
189{
190 log_serialize_failure(&err);
191 IntoResponse::<P>::into_response(RuntimeError::Serialization(crate::Error::new(err)))
192}
193
194macro_rules! aws_json_protocol {
195 ($protocol:ty, $marker:ty, $protocol_id:expr, $content_type:literal, $type_value:expr) => {
196 impl MetadataRoutedProtocol for $protocol {
197 fn from_build_context(
198 ctx: &crate::schema::ProtocolBuildContext<'_>,
199 ) -> Result<Self, crate::schema::routing::RouterBuildError> {
200 Ok(Self::new(
201 crate::schema::settings::get::<bool>(ctx.settings, "validateSkippedValues")?.unwrap_or(false),
202 response_bindings_mode(ctx.settings)?,
203 ))
204 }
205
206 fn build_router(
207 &self,
208 ctx: crate::schema::routing::RouterBuildContext<'_>,
209 ) -> Result<
210 impl crate::schema::routing::MetadataProtocolRouter + 'static + use<>,
211 crate::schema::routing::RouterBuildError,
212 > {
213 crate::schema::routing::aws_json_router(&ctx, $content_type)
214 }
215
216 fn event_stream_framing(&self) -> Option<EventStreamFraming<'_>> {
217 Some(
218 EventStreamFraming::new(self.inner.codec(), "application/json")
219 .initial_messages_in_frames(true)
220 .exception_discriminator(BodyDiscriminator::new($type_value)),
221 )
222 }
223 }
224
225 impl ServerProtocol for $protocol {
226 fn protocol_id(&self) -> &'static ShapeId<'static> {
227 static PROTOCOL_ID: ShapeId<'static> = $protocol_id;
228 &PROTOCOL_ID
229 }
230
231 fn validate_request_headers(
232 &self,
233 operation: &crate::schema::OperationSchema<'_>,
234 headers: &Headers,
235 ) -> Result<(), DeserializeError> {
236 self.inner.check_accept(operation.output(), headers)
237 }
238
239 fn request_body_requirement(&self, operation: &crate::schema::OperationSchema<'_>) -> BodyDirective {
240 if self.inner.reads_request_body(operation.input()) {
241 BodyDirective::Collect
242 } else {
243 BodyDirective::Skip
244 }
245 }
246
247 fn deserialize_request<'a>(
248 &'a self,
249 input: &Schema<'_>,
250 request: &'a aws_smithy_runtime_api::http::Request<bytes::Bytes>,
251 ) -> Result<Box<dyn ShapeDeserializer + 'a>, DeserializeError> {
252 self.inner.deserialize_request(input, request)
253 }
254
255 fn serialize_response(&self, output: &Schema<'_>, value: &dyn SerializableStruct) -> Response {
256 if self.response_bindings == ResponseBindingsMode::Historical {
257 return serialize_response_parts(
258 self.inner.codec(),
259 output,
260 value,
261 ResponseBindings::LegacyRpc,
262 ResponseValueKind::OperationOutput { empty_document: true },
263 )
264 .and_then(|parts| {
265 assemble_response(parts, resolve_status(None, output.http()), Some($content_type))
266 })
267 .unwrap_or_else(serialization_failure::<$marker>);
268 }
269 self.inner
270 .serialize_response(output, value)
271 .unwrap_or_else(serialization_failure::<$marker>)
272 }
273
274 fn serialize_streaming_response(
275 &self,
276 output: &Schema<'_>,
277 value: &dyn SerializableStruct,
278 _streaming_member: &Schema<'_>,
279 body: Option<BoxBody>,
280 ) -> Response {
281 if self.response_bindings == ResponseBindingsMode::Historical {
282 return serialize_response_parts(
283 self.inner.codec(),
284 output,
285 value,
286 ResponseBindings::LegacyRpc,
287 ResponseValueKind::StreamingOutput,
288 )
289 .and_then(|parts| {
290 assemble_streaming_response(
291 parts,
292 resolve_status(None, output.http()),
293 Some($content_type),
294 body.unwrap_or_else(crate::body::empty),
295 )
296 })
297 .unwrap_or_else(serialization_failure::<$marker>);
298 }
299 self.inner
300 .serialize_streaming_response(output, value, body.unwrap_or_else(crate::body::empty))
301 .unwrap_or_else(serialization_failure::<$marker>)
302 }
303
304 fn serialize_error(&self, error: &dyn HttpModeledError) -> Response {
305 serialize_error::<$marker>(
306 self.inner.codec(),
307 error,
308 $content_type,
309 BodyDiscriminator::new($type_value),
310 self.response_bindings,
311 )
312 }
313
314 fn serialize_routing_error(&self, err: &crate::schema::routing::RoutingError) -> Response {
315 use crate::protocol::aws_json::router::Error;
316 use crate::schema::routing::RoutingErrorKind;
317 let error = match err.kind() {
318 RoutingErrorKind::MethodNotAllowed => Error::MethodNotAllowed,
319 _ => Error::NotFound,
320 };
321 IntoResponse::<$marker>::into_response(error)
322 }
323
324 fn serialize_runtime_error(&self, error: crate::runtime_error::RuntimeError) -> Response {
325 IntoResponse::<$marker>::into_response(RuntimeError::from(error))
326 }
327
328 fn serialize_rejection(&self, err: DeserializeError) -> Response {
333 match err {
334 DeserializeError::InitialRequestFrame(reason) => {
335 IntoResponse::<$marker>::into_response(RuntimeError::Validation(reason))
336 }
337 DeserializeError::Serde(err) => {
338 IntoResponse::<$marker>::into_response(RuntimeError::Serialization(crate::Error::new(err)))
339 }
340 DeserializeError::BodyCollection(err) => {
341 IntoResponse::<$marker>::into_response(RuntimeError::Serialization(crate::Error::new(err)))
342 }
343 DeserializeError::UnsupportedMediaType(reason) => IntoResponse::<$marker>::into_response(
344 RuntimeError::from(RequestRejection::MissingContentType(*reason)),
345 ),
346 DeserializeError::NotAcceptable => {
347 IntoResponse::<$marker>::into_response(RuntimeError::from(RequestRejection::NotAcceptable))
348 }
349 DeserializeError::ConstraintViolation(err) => {
350 stamp_validation_extension(self.serialize_error(&*err))
351 }
352 DeserializeError::InternalFailure(err) => {
353 IntoResponse::<$marker>::into_response(RuntimeError::InternalFailure(err))
354 }
355 }
356 }
357 }
358 };
359}
360
361aws_json_protocol!(
362 AwsJson1_0,
363 AwsJson1_0,
364 shape_id!("aws.protocols", "awsJson1_0"),
365 "application/x-amz-json-1.0",
366 TypeValue::FullShapeId
367);
368aws_json_protocol!(
369 AwsJson1_1,
370 AwsJson1_1,
371 shape_id!("aws.protocols", "awsJson1_1"),
372 "application/x-amz-json-1.1",
373 TypeValue::ShapeName
374);