1use crate::codec::{Codec, FinishSerializer};
9use crate::protocol::{apply_http_endpoint, ClientProtocolInner};
10use crate::serde::{SerdeError, SerializableStruct, ShapeDeserializer, ShapeSerializer};
11use crate::{Schema, ShapeId};
12use aws_smithy_runtime_api::http::{Request, Response};
13use aws_smithy_types::body::SdkBody;
14use aws_smithy_types::config_bag::ConfigBag;
15
16#[derive(Debug)]
26pub struct HttpRpcProtocol<C> {
27 protocol_id: ShapeId<'static>,
28 codec: C,
29 content_type: &'static str,
30}
31
32impl<C: Codec> HttpRpcProtocol<C> {
33 pub fn new(protocol_id: ShapeId<'static>, codec: C, content_type: &'static str) -> Self {
35 Self {
36 protocol_id,
37 codec,
38 content_type,
39 }
40 }
41
42 pub fn codec(&self) -> &C {
46 &self.codec
47 }
48
49 pub fn content_type(&self) -> &'static str {
53 self.content_type
54 }
55
56 pub fn with_codec(self, codec: C) -> Self {
60 Self {
61 protocol_id: self.protocol_id,
62 codec,
63 content_type: self.content_type,
64 }
65 }
66}
67
68impl<C> ClientProtocolInner for HttpRpcProtocol<C>
69where
70 C: Codec + Send + Sync + std::fmt::Debug + 'static,
71 for<'a> C::Deserializer<'a>: ShapeDeserializer,
72{
73 type Request = Request;
74 type Response = Response;
75
76 fn protocol_id(&self) -> &ShapeId<'static> {
77 &self.protocol_id
78 }
79
80 fn serialize_request(
81 &self,
82 input: &dyn SerializableStruct,
83 input_schema: &Schema<'_>,
84 endpoint: &str,
85 cfg: &ConfigBag,
86 ) -> Result<Request, SerdeError> {
87 let mut serializer = self.codec.create_serializer();
88 serializer.write_struct(input_schema, input)?;
89 let body = serializer.finish();
90
91 let mut request = Request::new(SdkBody::from(body));
92 request
93 .set_method("POST")
94 .map_err(|e| SerdeError::custom(format!("invalid HTTP method: {e}")))?;
95 let uri = if endpoint.is_empty() { "/" } else { endpoint };
96 request
97 .set_uri(uri)
98 .map_err(|e| SerdeError::custom(format!("invalid endpoint URI: {e}")))?;
99
100 let omit = cfg.load::<crate::header_omit_settings::SharedHeaderOmitSettings>();
106 let omit_content_type = omit
107 .map(|s| s.should_omit_default_content_type())
108 .unwrap_or(false);
109 let omit_content_length = omit
110 .map(|s| s.should_omit_default_content_length())
111 .unwrap_or(false);
112 if !omit_content_type {
113 request
114 .headers_mut()
115 .insert("Content-Type", self.content_type);
116 }
117 if !omit_content_length {
118 if let Some(len) = request.body().content_length() {
119 request
120 .headers_mut()
121 .insert("Content-Length", len.to_string());
122 }
123 }
124 Ok(request)
125 }
126
127 fn deserialize_response<'a>(
128 &self,
129 response: &'a Response,
130 _output_schema: &Schema<'_>,
131 _cfg: &'a ConfigBag,
132 ) -> Result<Box<dyn ShapeDeserializer + 'a>, SerdeError> {
133 let body = response.body().bytes().unwrap_or(&[]);
139 Ok(Box::new(self.codec.create_deserializer(body)))
140 }
141
142 fn payload_codec(&self) -> Option<&dyn crate::codec::DynCodec> {
143 Some(&self.codec)
144 }
145
146 fn update_endpoint(
147 &self,
148 request: &mut Request,
149 endpoint: &aws_smithy_types::endpoint::Endpoint,
150 cfg: &ConfigBag,
151 ) -> Result<(), SerdeError> {
152 apply_http_endpoint(request, endpoint, cfg)
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159 use crate::serde::SerializableStruct;
160 use crate::{prelude::*, ShapeType};
161
162 struct TestSerializer {
163 output: Vec<u8>,
164 }
165
166 impl FinishSerializer for TestSerializer {
167 fn finish(self) -> Vec<u8> {
168 self.output
169 }
170 }
171
172 impl ShapeSerializer for TestSerializer {
173 fn write_struct(
174 &mut self,
175 _: &Schema<'_>,
176 value: &dyn SerializableStruct,
177 ) -> Result<(), SerdeError> {
178 self.output.push(b'{');
179 value.serialize_members(self)?;
180 self.output.push(b'}');
181 Ok(())
182 }
183 fn write_list(
184 &mut self,
185 _: &Schema<'_>,
186 _: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
187 ) -> Result<(), SerdeError> {
188 Ok(())
189 }
190 fn write_map(
191 &mut self,
192 _: &Schema<'_>,
193 _: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
194 ) -> Result<(), SerdeError> {
195 Ok(())
196 }
197 fn write_boolean(&mut self, _: &Schema<'_>, _: bool) -> Result<(), SerdeError> {
198 Ok(())
199 }
200 fn write_byte(&mut self, _: &Schema<'_>, _: i8) -> Result<(), SerdeError> {
201 Ok(())
202 }
203 fn write_short(&mut self, _: &Schema<'_>, _: i16) -> Result<(), SerdeError> {
204 Ok(())
205 }
206 fn write_integer(&mut self, _: &Schema<'_>, _: i32) -> Result<(), SerdeError> {
207 Ok(())
208 }
209 fn write_long(&mut self, _: &Schema<'_>, _: i64) -> Result<(), SerdeError> {
210 Ok(())
211 }
212 fn write_float(&mut self, _: &Schema<'_>, _: f32) -> Result<(), SerdeError> {
213 Ok(())
214 }
215 fn write_double(&mut self, _: &Schema<'_>, _: f64) -> Result<(), SerdeError> {
216 Ok(())
217 }
218 fn write_big_integer(
219 &mut self,
220 _: &Schema<'_>,
221 _: &aws_smithy_types::BigInteger,
222 ) -> Result<(), SerdeError> {
223 Ok(())
224 }
225 fn write_big_decimal(
226 &mut self,
227 _: &Schema<'_>,
228 _: &aws_smithy_types::BigDecimal,
229 ) -> Result<(), SerdeError> {
230 Ok(())
231 }
232 fn write_string(&mut self, _: &Schema<'_>, v: &str) -> Result<(), SerdeError> {
233 self.output.extend_from_slice(v.as_bytes());
234 Ok(())
235 }
236 fn write_blob(
237 &mut self,
238 _: &Schema<'_>,
239 _: aws_smithy_types::Blob,
240 ) -> Result<(), SerdeError> {
241 Ok(())
242 }
243 fn write_timestamp(
244 &mut self,
245 _: &Schema<'_>,
246 _: &aws_smithy_types::DateTime,
247 ) -> Result<(), SerdeError> {
248 Ok(())
249 }
250 fn write_document(
251 &mut self,
252 _: &Schema<'_>,
253 _: &aws_smithy_types::Document,
254 ) -> Result<(), SerdeError> {
255 Ok(())
256 }
257 fn write_null(&mut self, _: &Schema<'_>) -> Result<(), SerdeError> {
258 Ok(())
259 }
260 }
261
262 struct TestDeserializer<'a> {
263 input: &'a [u8],
264 }
265
266 impl ShapeDeserializer for TestDeserializer<'_> {
267 fn read_struct(
275 &mut self,
276 schema: &Schema<'_>,
277 consumer: &mut dyn FnMut(
278 &Schema<'_>,
279 &mut dyn ShapeDeserializer,
280 ) -> Result<(), SerdeError>,
281 ) -> Result<(), SerdeError> {
282 for member in schema.members() {
283 consumer(member, self)?;
284 }
285 Ok(())
286 }
287 fn read_list(
288 &mut self,
289 _: &Schema<'_>,
290 _: &mut dyn FnMut(&mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
291 ) -> Result<(), SerdeError> {
292 Ok(())
293 }
294 fn read_map(
295 &mut self,
296 _: &Schema<'_>,
297 _: &mut dyn FnMut(String, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
298 ) -> Result<(), SerdeError> {
299 Ok(())
300 }
301 fn read_boolean(&mut self, _: &Schema<'_>) -> Result<bool, SerdeError> {
302 Ok(false)
303 }
304 fn read_byte(&mut self, _: &Schema<'_>) -> Result<i8, SerdeError> {
305 Ok(0)
306 }
307 fn read_short(&mut self, _: &Schema<'_>) -> Result<i16, SerdeError> {
308 Ok(0)
309 }
310 fn read_integer(&mut self, _: &Schema<'_>) -> Result<i32, SerdeError> {
311 Ok(0)
312 }
313 fn read_long(&mut self, _: &Schema<'_>) -> Result<i64, SerdeError> {
314 Ok(0)
315 }
316 fn read_float(&mut self, _: &Schema<'_>) -> Result<f32, SerdeError> {
317 Ok(0.0)
318 }
319 fn read_double(&mut self, _: &Schema<'_>) -> Result<f64, SerdeError> {
320 Ok(0.0)
321 }
322 fn read_big_integer(
323 &mut self,
324 _: &Schema<'_>,
325 ) -> Result<aws_smithy_types::BigInteger, SerdeError> {
326 use std::str::FromStr;
327 Ok(aws_smithy_types::BigInteger::from_str("0").unwrap())
328 }
329 fn read_big_decimal(
330 &mut self,
331 _: &Schema<'_>,
332 ) -> Result<aws_smithy_types::BigDecimal, SerdeError> {
333 use std::str::FromStr;
334 Ok(aws_smithy_types::BigDecimal::from_str("0").unwrap())
335 }
336 fn read_string(&mut self, _: &Schema<'_>) -> Result<String, SerdeError> {
337 Ok(String::from_utf8_lossy(self.input).into_owned())
338 }
339 fn read_blob(&mut self, _: &Schema<'_>) -> Result<aws_smithy_types::Blob, SerdeError> {
340 Ok(aws_smithy_types::Blob::new(vec![]))
341 }
342 fn read_timestamp(
343 &mut self,
344 _: &Schema<'_>,
345 ) -> Result<aws_smithy_types::DateTime, SerdeError> {
346 Ok(aws_smithy_types::DateTime::from_secs(0))
347 }
348 fn read_document(
349 &mut self,
350 _: &Schema<'_>,
351 ) -> Result<aws_smithy_types::Document, SerdeError> {
352 Ok(aws_smithy_types::Document::Null)
353 }
354 fn is_null(&self) -> bool {
355 false
356 }
357 fn container_size(&self) -> Option<usize> {
358 None
359 }
360 }
361
362 #[derive(Debug)]
363 struct TestCodec;
364
365 impl Codec for TestCodec {
366 type Serializer = TestSerializer;
367 type Deserializer<'a> = TestDeserializer<'a>;
368 fn create_serializer(&self) -> Self::Serializer {
369 TestSerializer { output: Vec::new() }
370 }
371 fn create_deserializer<'a>(&self, input: &'a [u8]) -> Self::Deserializer<'a> {
372 TestDeserializer { input }
373 }
374 }
375
376 static TEST_SCHEMA: Schema<'static> =
377 Schema::new(crate::shape_id!("test", "TestStruct"), ShapeType::Structure);
378
379 struct EmptyStruct;
380 impl SerializableStruct for EmptyStruct {
381 fn schema(&self) -> &Schema<'_> {
382 &TEST_SCHEMA
383 }
384
385 fn serialize_members(&self, _: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
386 Ok(())
387 }
388 }
389
390 static NAME_MEMBER: Schema<'static> = Schema::new_member(
391 crate::shape_id!("test", "TestStruct"),
392 ShapeType::String,
393 "name",
394 0,
395 );
396 static MEMBERS: &[&Schema<'_>] = &[&NAME_MEMBER];
397 static STRUCT_WITH_MEMBER: Schema<'static> = Schema::new_struct(
398 crate::shape_id!("test", "TestStruct"),
399 ShapeType::Structure,
400 MEMBERS,
401 );
402
403 struct NameStruct;
404 impl SerializableStruct for NameStruct {
405 fn schema(&self) -> &Schema<'_> {
406 &STRUCT_WITH_MEMBER
407 }
408
409 fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
410 s.write_string(&NAME_MEMBER, "Alice")
411 }
412 }
413
414 static BOUND_NAME: Schema<'static> = Schema::new_member(
417 crate::shape_id!("test", "BoundStruct"),
418 ShapeType::String,
419 "name",
420 0,
421 )
422 .with_http_header("x-name");
423 static BOUND_STATUS: Schema<'static> = Schema::new_member(
424 crate::shape_id!("test", "BoundStruct"),
425 ShapeType::Integer,
426 "status",
427 1,
428 )
429 .with_http_response_code();
430 static BOUND_MEMBERS: &[&Schema<'_>] = &[&BOUND_NAME, &BOUND_STATUS];
431 static BOUND_STRUCT: Schema<'static> = Schema::new_struct(
432 crate::shape_id!("test", "BoundStruct"),
433 ShapeType::Structure,
434 BOUND_MEMBERS,
435 );
436
437 fn bound_response(body: &'static str) -> Response {
440 let response = http::Response::builder()
441 .status(418)
442 .header("x-name", "from-header")
443 .body(SdkBody::from(body))
444 .expect("response");
445 Response::try_from(response).expect("convertible")
446 }
447
448 fn collect<'c>(
449 strings: &'c mut Vec<(String, String)>,
450 integers: &'c mut Vec<(String, i32)>,
451 ) -> impl FnMut(&Schema<'_>, &mut dyn ShapeDeserializer) -> Result<(), SerdeError> + 'c {
452 move |member, deser| {
453 let name = member.member_name().unwrap_or("?").to_string();
454 match member.shape_type() {
455 ShapeType::Integer => integers.push((name, deser.read_integer(member)?)),
456 _ => strings.push((name, deser.read_string(member)?)),
457 }
458 Ok(())
459 }
460 }
461
462 #[test]
471 fn deserialize_response_ignores_http_response_bindings() {
472 let protocol = HttpRpcProtocol::new(
473 crate::shape_id!("test", "rpc"),
474 TestCodec,
475 "application/x-amz-json-1.0",
476 );
477 let response = bound_response("body-value");
478 let mut strings = Vec::new();
479 let mut integers = Vec::new();
480 protocol
481 .deserialize_response(&response, &BOUND_STRUCT, &ConfigBag::base())
482 .unwrap()
483 .read_struct(&BOUND_STRUCT, &mut collect(&mut strings, &mut integers))
484 .unwrap();
485
486 assert_eq!(
490 strings,
491 vec![("name".to_string(), "body-value".to_string())],
492 "a bound member must be read from the body, not from the header"
493 );
494 assert_eq!(integers, vec![("status".to_string(), 0)]);
497 }
498
499 #[test]
507 fn deserialize_error_response_ignores_http_response_bindings() {
508 let protocol = HttpRpcProtocol::new(
509 crate::shape_id!("test", "rpc"),
510 TestCodec,
511 "application/x-amz-json-1.0",
512 );
513 let response = bound_response("body-value");
514 let mut strings = Vec::new();
515 let mut integers = Vec::new();
516 protocol
517 .deserialize_error_response(&response, &ConfigBag::base())
518 .unwrap()
519 .read_struct(&BOUND_STRUCT, &mut collect(&mut strings, &mut integers))
520 .unwrap();
521
522 assert_eq!(
523 strings,
524 vec![("name".to_string(), "body-value".to_string())],
525 "a bound error member must be read from the body, not from the header"
526 );
527 assert_eq!(integers, vec![("status".to_string(), 0)]);
528 }
529
530 #[test]
531 fn serialize_sets_content_type() {
532 let protocol = HttpRpcProtocol::new(
533 crate::shape_id!("test", "rpc"),
534 TestCodec,
535 "application/x-amz-json-1.0",
536 );
537 let request = protocol
538 .serialize_request(
539 &EmptyStruct,
540 &TEST_SCHEMA,
541 "https://example.com",
542 &ConfigBag::base(),
543 )
544 .unwrap();
545 assert_eq!(
546 request.headers().get("Content-Type").unwrap(),
547 "application/x-amz-json-1.0"
548 );
549 }
550
551 #[test]
552 fn serialize_body() {
553 let protocol = HttpRpcProtocol::new(
554 crate::shape_id!("test", "rpc"),
555 TestCodec,
556 "application/x-amz-json-1.0",
557 );
558 let request = protocol
559 .serialize_request(
560 &NameStruct,
561 &STRUCT_WITH_MEMBER,
562 "https://example.com",
563 &ConfigBag::base(),
564 )
565 .unwrap();
566 assert_eq!(request.body().bytes().unwrap(), b"{Alice}");
567 }
568
569 #[test]
570 fn serialize_empty_endpoint_defaults_to_root() {
571 let protocol = HttpRpcProtocol::new(
572 crate::shape_id!("test", "rpc"),
573 TestCodec,
574 "application/x-amz-json-1.0",
575 );
576 let request = protocol
577 .serialize_request(&EmptyStruct, &TEST_SCHEMA, "", &ConfigBag::base())
578 .unwrap();
579 assert_eq!(request.uri(), "/");
580 }
581
582 #[test]
583 fn deserialize_response() {
584 let protocol = HttpRpcProtocol::new(
585 crate::shape_id!("test", "rpc"),
586 TestCodec,
587 "application/x-amz-json-1.0",
588 );
589 let response = Response::new(
590 200u16.try_into().unwrap(),
591 SdkBody::from(r#"{"result":42}"#),
592 );
593 let base_cfg = ConfigBag::base();
594 let mut deser = protocol
595 .deserialize_response(&response, &TEST_SCHEMA, &base_cfg)
596 .unwrap();
597 assert_eq!(deser.read_string(&STRING).unwrap(), r#"{"result":42}"#);
598 }
599
600 #[test]
601 fn update_endpoint() {
602 let protocol = HttpRpcProtocol::new(
603 crate::shape_id!("test", "rpc"),
604 TestCodec,
605 "application/x-amz-json-1.0",
606 );
607 let mut request = protocol
608 .serialize_request(
609 &EmptyStruct,
610 &TEST_SCHEMA,
611 "https://old.example.com",
612 &ConfigBag::base(),
613 )
614 .unwrap();
615 let endpoint = aws_smithy_types::endpoint::Endpoint::builder()
616 .url("https://new.example.com")
617 .build();
618 protocol
619 .update_endpoint(&mut request, &endpoint, &ConfigBag::base())
620 .unwrap();
621 assert_eq!(request.uri(), "https://new.example.com/");
622 }
623
624 #[test]
625 fn protocol_id() {
626 let protocol = HttpRpcProtocol::new(
627 crate::shape_id!("aws.protocols", "awsJson1_0"),
628 TestCodec,
629 "application/x-amz-json-1.0",
630 );
631 assert_eq!(protocol.protocol_id().as_str(), "aws.protocols#awsJson1_0");
632 }
633
634 #[test]
635 fn serialize_honors_header_omit_settings() {
636 use crate::header_omit_settings::{HeaderOmitSettings, SharedHeaderOmitSettings};
637 use aws_smithy_types::config_bag::Layer;
638
639 #[derive(Debug)]
640 struct OmitBoth;
641 impl HeaderOmitSettings for OmitBoth {
642 fn should_omit_default_content_type(&self) -> bool {
643 true
644 }
645 fn should_omit_default_content_length(&self) -> bool {
646 true
647 }
648 }
649
650 let protocol = HttpRpcProtocol::new(
651 crate::shape_id!("test", "rpc"),
652 TestCodec,
653 "application/x-amz-json-1.0",
654 );
655 let mut layer = Layer::new("test");
656 layer.store_put(SharedHeaderOmitSettings::new(OmitBoth));
657 let cfg = ConfigBag::of_layers(vec![layer]);
658
659 let request = protocol
660 .serialize_request(
661 &NameStruct,
662 &STRUCT_WITH_MEMBER,
663 "https://example.com",
664 &cfg,
665 )
666 .unwrap();
667 assert!(request.headers().get("Content-Type").is_none());
668 assert!(request.headers().get("Content-Length").is_none());
669 }
670}