1use aws_smithy_runtime_api::http::Headers;
7use aws_smithy_schema::codec::Codec;
8use aws_smithy_schema::serde::{SerdeError, SerializableStruct, ShapeDeserializer};
9use aws_smithy_schema::{shape_id, Schema, ShapeId};
10use aws_smithy_types::{BigDecimal, BigInteger, Blob, DateTime, Document};
11
12use crate::body::BoxBody;
13use crate::protocol::rest_xml::rejection::RequestRejection;
14use crate::protocol::rest_xml::runtime_error::RuntimeError;
15use crate::response::{IntoResponse, Response};
16use crate::schema::{DeserializeError, HttpModeledError};
17
18use super::request::payload_member;
19use super::response::{log_serialize_failure, stamp_error_extension};
20use super::rest::RestPolicy;
21use super::{BodyDirective, EventStreamFraming, MetadataRoutedProtocol, ServerProtocol};
22
23#[derive(Debug)]
29pub struct RestXml {
30 pub(crate) inner: crate::schema::protocol::rest::RestProtocol<aws_smithy_xml::codec::XmlCodec>,
31 exception_codec: aws_smithy_xml::codec::XmlCodec,
34 no_error_wrapping: bool,
36}
37
38impl RestXml {
39 fn new(
40 strict_collection_element_names: bool,
41 validate_document: bool,
42 accept_text_xml: bool,
43 no_error_wrapping: bool,
44 ) -> Self {
45 let codec = |validate_root_name: bool| {
46 aws_smithy_xml::codec::XmlCodec::new(
47 aws_smithy_xml::codec::XmlCodecSettings::builder()
48 .error_root_name("Error")
49 .validate_root_name(validate_root_name)
50 .validate_document(validate_document)
51 .strict_collection_element_names(strict_collection_element_names)
52 .build(),
53 )
54 };
55 Self {
56 exception_codec: codec(false),
57 no_error_wrapping,
58 inner: crate::schema::protocol::rest::RestProtocol::new(
59 codec(true),
60 RestPolicy {
61 request_content_type_aliases: if accept_text_xml {
62 REQUEST_CONTENT_TYPE_ALIASES
63 } else {
64 &[]
65 },
66 ..POLICY
67 },
68 ),
69 }
70 }
71}
72
73impl Default for RestXml {
74 fn default() -> Self {
75 Self::new(true, false, false, false)
76 }
77}
78
79static PROTOCOL_ID: ShapeId<'static> = shape_id!("aws.protocols", "restXml");
80const CONTENT_TYPE: &str = "application/xml";
81
82const REQUEST_CONTENT_TYPE_ALIASES: &[&str] = &["text/xml"];
84
85pub(crate) const POLICY: RestPolicy = RestPolicy {
89 payload_deserializer: Some(deserialize_payload),
90 codec_content_type: CONTENT_TYPE,
91 request_content_type_aliases: &[],
92 default_response_content_type: None,
93 untyped_blob_payload_content_type: Some("application/octet-stream"),
94 empty_document: false,
95};
96
97fn deserialize_payload(
98 member: &Schema<'_>,
99 deserializer: &mut dyn ShapeDeserializer,
100 consumer: &mut dyn FnMut(&Schema<'_>, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
101) -> Result<(), SerdeError> {
102 let Some(metadata) = crate::schema::traits::XmlMetadata::from_schema(member) else {
103 return consumer(member, deserializer);
104 };
105 consumer(
106 member,
107 &mut StructuredPayloadDeserializer {
108 inner: deserializer,
109 root: metadata.payload_schema(),
110 },
111 )
112}
113
114impl MetadataRoutedProtocol for RestXml {
115 fn from_build_context(
116 ctx: &crate::schema::ProtocolBuildContext<'_>,
117 ) -> Result<Self, crate::schema::routing::RouterBuildError> {
118 let mut protocol = Self::new(
119 crate::schema::settings::get::<bool>(ctx.settings, "strictCollectionElementNames")?.unwrap_or(true),
120 crate::schema::settings::get::<bool>(ctx.settings, "validateDocument")?.unwrap_or(false),
121 crate::schema::settings::get::<bool>(ctx.settings, "acceptTextXml")?.unwrap_or(false),
122 crate::schema::traits::RestXmlTrait::from_service(ctx.service)
124 .is_some_and(crate::schema::traits::RestXmlTrait::no_error_wrapping),
125 );
126 protocol
127 .inner
128 .prepare_response_plans(ctx.service)
129 .map_err(|err| crate::schema::routing::RouterBuildError::Configuration(err.to_string()))?;
130 Ok(protocol)
131 }
132
133 fn build_router(
134 &self,
135 ctx: crate::schema::routing::RouterBuildContext<'_>,
136 ) -> Result<
137 impl crate::schema::routing::MetadataProtocolRouter + 'static + use<>,
138 crate::schema::routing::RouterBuildError,
139 > {
140 crate::schema::routing::rest_router(&ctx, CONTENT_TYPE, self.inner.policy().request_content_type_aliases)
141 }
142
143 fn event_stream_framing(&self) -> Option<EventStreamFraming<'_>> {
144 Some(
145 EventStreamFraming::new(self.inner.codec(), CONTENT_TYPE)
146 .unset_initial_response_payload(b"")
147 .exception_http_bindings(true)
148 .xml_exceptions(&self.exception_codec, !self.no_error_wrapping),
149 )
150 }
151}
152
153impl ServerProtocol for RestXml {
154 fn protocol_id(&self) -> &'static ShapeId<'static> {
155 &PROTOCOL_ID
156 }
157
158 fn validate_request_headers(
159 &self,
160 operation: &crate::schema::OperationSchema<'_>,
161 headers: &Headers,
162 ) -> Result<(), DeserializeError> {
163 self.inner.check_accept(operation.output(), headers)
164 }
165
166 fn request_body_requirement(&self, operation: &crate::schema::OperationSchema<'_>) -> BodyDirective {
167 if self.inner.reads_request_body(operation.input()) {
168 BodyDirective::Collect
169 } else {
170 BodyDirective::Skip
171 }
172 }
173
174 fn deserialize_request<'a>(
175 &'a self,
176 input: &Schema<'_>,
177 request: &'a aws_smithy_runtime_api::http::Request<bytes::Bytes>,
178 ) -> Result<Box<dyn ShapeDeserializer + 'a>, DeserializeError> {
179 self.inner.deserialize_request(input, request)
180 }
181
182 fn serialize_response(&self, output: &Schema<'_>, value: &dyn SerializableStruct) -> Response {
183 self.inner
184 .serialize_response_with_codec(&ResponseXmlCodec::new(self.inner.codec(), output), output, value)
185 .unwrap_or_else(serialization_failure)
186 }
187
188 fn serialize_streaming_response(
189 &self,
190 output: &Schema<'_>,
191 value: &dyn SerializableStruct,
192 _streaming_member: &Schema<'_>,
193 body: Option<BoxBody>,
194 ) -> Response {
195 self.inner
196 .serialize_streaming_response(output, value, body.unwrap_or_else(crate::body::empty))
197 .unwrap_or_else(serialization_failure)
198 }
199
200 fn serialize_error(&self, error: &dyn HttpModeledError) -> Response {
201 let schema = error.schema();
203 let content_type = match error.operation_schema() {
205 Some(operation) => self.inner.response_content_type(operation.output()),
206 None => Some(CONTENT_TYPE),
207 };
208 self.inner
209 .serialize_modeled_error(
210 &ResponseXmlCodec::for_error(self.inner.codec()),
211 schema,
212 error,
213 error.status_code(),
214 content_type,
215 )
216 .map(|response| stamp_error_extension(response, schema.shape_id().shape_name()))
217 .unwrap_or_else(serialization_failure)
218 }
219
220 fn serialize_routing_error(&self, err: &crate::schema::routing::RoutingError) -> Response {
221 use crate::protocol::rest::router::Error;
222 use crate::schema::routing::RoutingErrorKind;
223 if err.kind() == RoutingErrorKind::MissingContentType {
224 return self.serialize_rejection(
225 crate::rejection::MissingContentTypeReason::UnexpectedMimeType {
226 expected_mime: err.expected_content_type().and_then(|expected| expected.parse().ok()),
227 found_mime: None,
228 }
229 .into(),
230 );
231 }
232 let error = match err.kind() {
233 RoutingErrorKind::MethodNotAllowed => Error::MethodNotAllowed,
234 _ => Error::NotFound,
235 };
236 IntoResponse::<RestXml>::into_response(error)
237 }
238
239 fn serialize_runtime_error(&self, error: crate::runtime_error::RuntimeError) -> Response {
240 IntoResponse::<RestXml>::into_response(RuntimeError::from(error))
241 }
242
243 fn serialize_rejection(&self, err: DeserializeError) -> Response {
249 match err {
250 DeserializeError::InitialRequestFrame(reason) => {
251 IntoResponse::<RestXml>::into_response(RuntimeError::Validation(reason))
252 }
253 DeserializeError::Serde(err) => {
254 IntoResponse::<RestXml>::into_response(RuntimeError::Serialization(crate::Error::new(err)))
255 }
256 DeserializeError::BodyCollection(err) => {
257 IntoResponse::<RestXml>::into_response(RuntimeError::Serialization(crate::Error::new(err)))
258 }
259 DeserializeError::UnsupportedMediaType(reason) => IntoResponse::<RestXml>::into_response(
260 RuntimeError::from(RequestRejection::MissingContentType(*reason)),
261 ),
262 DeserializeError::NotAcceptable => {
263 IntoResponse::<RestXml>::into_response(RuntimeError::from(RequestRejection::NotAcceptable))
264 }
265 DeserializeError::ConstraintViolation(_) => {
267 IntoResponse::<RestXml>::into_response(RuntimeError::Validation(String::new()))
268 }
269 DeserializeError::InternalFailure(err) => {
270 IntoResponse::<RestXml>::into_response(RuntimeError::InternalFailure(err))
271 }
272 }
273 }
274}
275
276struct ResponseXmlCodec<'a> {
279 inner: &'a aws_smithy_xml::codec::XmlCodec,
280 payload_member: Option<&'a Schema<'a>>,
281}
282
283impl<'a> ResponseXmlCodec<'a> {
284 fn new(inner: &'a aws_smithy_xml::codec::XmlCodec, output: &'a Schema<'a>) -> Self {
285 Self {
286 inner,
287 payload_member: payload_member(output),
288 }
289 }
290
291 fn for_error(inner: &'a aws_smithy_xml::codec::XmlCodec) -> Self {
294 Self {
295 inner,
296 payload_member: None,
297 }
298 }
299}
300
301impl Codec for ResponseXmlCodec<'_> {
302 type Serializer = aws_smithy_xml::codec::XmlSerializer;
303 type Deserializer<'a> = aws_smithy_xml::codec::XmlDeserializer<'a>;
304
305 fn create_serializer(&self) -> Self::Serializer {
306 let mut serializer = self.inner.create_serializer();
307 if let Some(name) = self.payload_member.and_then(|member| member.xml_name()) {
310 serializer.set_next_root_xml_name(name.value().to_owned());
311 }
312 serializer
313 }
314
315 fn create_deserializer<'a>(&self, input: &'a [u8]) -> Self::Deserializer<'a> {
316 self.inner.create_deserializer(input)
317 }
318}
319
320fn serialization_failure(err: SerdeError) -> Response {
321 log_serialize_failure(&err);
322 IntoResponse::<RestXml>::into_response(RuntimeError::Serialization(crate::Error::new(err)))
323}
324
325macro_rules! unsupported_reads {
326 ($why:literal; $($method:ident -> $ret:ty),+ $(,)?) => {
327 $(
328 fn $method(&mut self, _schema: &Schema<'_>) -> Result<$ret, SerdeError> {
329 Err(SerdeError::unsupported($why))
330 }
331 )+
332 };
333}
334
335struct StructuredPayloadDeserializer<'a> {
352 inner: &'a mut dyn ShapeDeserializer,
353 root: &'static Schema<'static>,
354}
355
356impl ShapeDeserializer for StructuredPayloadDeserializer<'_> {
357 fn read_struct(
358 &mut self,
359 _schema: &Schema<'_>,
360 consumer: &mut dyn FnMut(&Schema<'_>, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
361 ) -> Result<(), SerdeError> {
362 self.inner.read_struct(self.root, consumer)
363 }
364
365 fn read_list(
366 &mut self,
367 schema: &Schema<'_>,
368 consumer: &mut dyn FnMut(&mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
369 ) -> Result<(), SerdeError> {
370 self.inner.read_list(schema, consumer)
371 }
372
373 fn read_map(
374 &mut self,
375 schema: &Schema<'_>,
376 consumer: &mut dyn FnMut(String, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
377 ) -> Result<(), SerdeError> {
378 self.inner.read_map(schema, consumer)
379 }
380
381 unsupported_reads! {
382 "expected a structured payload";
383 read_boolean -> bool,
384 read_byte -> i8,
385 read_short -> i16,
386 read_integer -> i32,
387 read_long -> i64,
388 read_float -> f32,
389 read_double -> f64,
390 read_big_integer -> BigInteger,
391 read_big_decimal -> BigDecimal,
392 read_blob -> Blob,
393 read_timestamp -> DateTime,
394 read_string -> String,
395 }
396 fn read_document(&mut self, schema: &Schema<'_>) -> Result<Document, SerdeError> {
397 self.inner.read_document(schema)
398 }
399 fn is_null(&self) -> bool {
400 self.inner.is_null()
401 }
402 fn container_size(&self) -> Option<usize> {
403 self.inner.container_size()
404 }
405}
406
407#[cfg(test)]
408mod tests {
409 use super::*;
410 use aws_smithy_schema::ShapeType;
411
412 #[test]
413 fn structured_payload_preserves_target_metadata_when_aliasing_xml_root() {
414 static TRAITS: std::sync::LazyLock<aws_smithy_schema::TraitMap> = std::sync::LazyLock::new(|| {
415 let mut traits = aws_smithy_schema::TraitMap::new();
416 traits.insert(Box::new(aws_smithy_schema::StringTrait::new(
417 aws_smithy_schema::shape_id!("test", "custom"),
418 "preserved",
419 )));
420 traits
421 });
422 static CHILD: Schema = Schema::new_member(
423 aws_smithy_schema::shape_id!("test", "Target", "value"),
424 ShapeType::String,
425 "value",
426 0,
427 )
428 .with_xml_name("Child");
429 static TARGET: Schema = Schema::new_struct(
430 aws_smithy_schema::shape_id!("test", "Target"),
431 ShapeType::Structure,
432 &[&CHILD],
433 )
434 .with_xml_name("TargetRoot")
435 .with_original_name("OriginalTarget")
436 .with_xml_namespace("urn:test:target", Some("t"))
437 .with_sensitive()
438 .with_xml_unwrapped_output()
439 .with_traits(&TRAITS);
440
441 struct MetadataCheckingDeserializer;
444 impl ShapeDeserializer for MetadataCheckingDeserializer {
445 fn read_struct(
446 &mut self,
447 schema: &Schema<'_>,
448 consumer: &mut dyn FnMut(&Schema<'_>, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
449 ) -> Result<(), SerdeError> {
450 assert_eq!(schema.shape_id(), TARGET.shape_id());
451 assert_eq!(schema.shape_type(), TARGET.shape_type());
452 assert_eq!(schema.xml_name().unwrap().value(), "PayloadRoot");
453 assert_eq!(schema.original_name(), Some("OriginalTarget"));
454 let namespace = schema.xml_namespace().expect("target namespace preserved");
455 assert_eq!(namespace.uri(), "urn:test:target");
456 assert_eq!(namespace.prefix(), Some("t"));
457 assert!(schema.sensitive().is_some());
458 assert!(schema.xml_unwrapped_output());
459 assert!(std::ptr::eq(schema.traits().unwrap(), TARGET.traits().unwrap()));
460 assert_eq!(schema.members().len(), 1);
461 assert!(std::ptr::eq(schema.members()[0], &CHILD));
462 consumer(schema.members()[0], self)
463 }
464
465 fn read_list(
466 &mut self,
467 _: &Schema<'_>,
468 _: &mut dyn FnMut(&mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
469 ) -> Result<(), SerdeError> {
470 unreachable!()
471 }
472
473 fn read_map(
474 &mut self,
475 _: &Schema<'_>,
476 _: &mut dyn FnMut(String, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
477 ) -> Result<(), SerdeError> {
478 unreachable!()
479 }
480
481 unsupported_reads! {
482 "metadata checking only";
483 read_boolean -> bool,
484 read_byte -> i8,
485 read_short -> i16,
486 read_integer -> i32,
487 read_long -> i64,
488 read_float -> f32,
489 read_double -> f64,
490 read_big_integer -> BigInteger,
491 read_big_decimal -> BigDecimal,
492 read_blob -> Blob,
493 read_timestamp -> DateTime,
494 read_document -> Document,
495 }
496
497 fn read_string(&mut self, schema: &Schema<'_>) -> Result<String, SerdeError> {
498 assert!(std::ptr::eq(schema, &CHILD));
499 Ok("decoded".into())
500 }
501
502 fn is_null(&self) -> bool {
503 false
504 }
505
506 fn container_size(&self) -> Option<usize> {
507 None
508 }
509 }
510
511 static PAYLOAD: Schema = Schema::new_struct(
512 aws_smithy_schema::shape_id!("test", "Target"),
513 ShapeType::Structure,
514 &[&CHILD],
515 )
516 .with_xml_name("PayloadRoot")
517 .with_original_name("OriginalTarget")
518 .with_xml_namespace("urn:test:target", Some("t"))
519 .with_sensitive()
520 .with_xml_unwrapped_output()
521 .with_traits(&TRAITS);
522 static MEMBER_TRAITS: std::sync::LazyLock<aws_smithy_schema::TraitMap> = std::sync::LazyLock::new(|| {
523 let mut traits = aws_smithy_schema::TraitMap::new();
524 traits.insert(Box::new(crate::schema::traits::XmlMetadata::new(&PAYLOAD)));
525 traits
526 });
527 static MEMBER_WITH_METADATA: Schema = Schema::new_member(
528 aws_smithy_schema::shape_id!("test", "Input", "data"),
529 ShapeType::Structure,
530 "data",
531 0,
532 )
533 .with_xml_name("PayloadRoot")
534 .with_traits(&MEMBER_TRAITS);
535
536 let member = &MEMBER_WITH_METADATA;
537 let mut inner = MetadataCheckingDeserializer;
538 let mut value = None;
539 deserialize_payload(member, &mut inner, &mut |actual_member, payload| {
540 assert!(std::ptr::eq(actual_member, member));
541 payload.read_struct(&TARGET, &mut |child, deser| {
542 value = Some(deser.read_string(child)?);
543 Ok(())
544 })
545 })
546 .unwrap();
547 assert_eq!(value.as_deref(), Some("decoded"));
548 assert_eq!(TARGET.xml_name().unwrap().value(), "TargetRoot");
549 }
550}