aws_smithy_schema/schema/http_protocol/
rpc.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! HTTP RPC protocol for body-only APIs.
7
8use 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/// An HTTP protocol for RPC-style APIs that put everything in the body.
17///
18/// This protocol ignores HTTP binding traits and serializes the entire input
19/// into the request body using the provided codec. Used by protocols like
20/// `awsJson1_0`, `awsJson1_1`, and `rpcv2Cbor`.
21///
22/// # Type parameters
23///
24/// * `C` — the payload codec (ex: `JsonCodec`, `CborCodec`)
25#[derive(Debug)]
26pub struct HttpRpcProtocol<C> {
27    protocol_id: ShapeId,
28    codec: C,
29    content_type: &'static str,
30}
31
32impl<C: Codec> HttpRpcProtocol<C> {
33    /// Creates a new HTTP RPC protocol.
34    pub fn new(protocol_id: ShapeId, codec: C, content_type: &'static str) -> Self {
35        Self {
36            protocol_id,
37            codec,
38            content_type,
39        }
40    }
41}
42
43impl<C> ClientProtocolInner for HttpRpcProtocol<C>
44where
45    C: Codec + Send + Sync + std::fmt::Debug + 'static,
46    for<'a> C::Deserializer<'a>: ShapeDeserializer,
47{
48    type Request = Request;
49    type Response = Response;
50
51    fn protocol_id(&self) -> &ShapeId {
52        &self.protocol_id
53    }
54
55    fn serialize_request(
56        &self,
57        input: &dyn SerializableStruct,
58        input_schema: &Schema,
59        endpoint: &str,
60        _cfg: &ConfigBag,
61    ) -> Result<Request, SerdeError> {
62        let mut serializer = self.codec.create_serializer();
63        serializer.write_struct(input_schema, input)?;
64        let body = serializer.finish();
65
66        let mut request = Request::new(SdkBody::from(body));
67        request
68            .set_method("POST")
69            .map_err(|e| SerdeError::custom(format!("invalid HTTP method: {e}")))?;
70        let uri = if endpoint.is_empty() { "/" } else { endpoint };
71        request
72            .set_uri(uri)
73            .map_err(|e| SerdeError::custom(format!("invalid endpoint URI: {e}")))?;
74        request
75            .headers_mut()
76            .insert("Content-Type", self.content_type);
77        if let Some(len) = request.body().content_length() {
78            request
79                .headers_mut()
80                .insert("Content-Length", len.to_string());
81        }
82        Ok(request)
83    }
84
85    fn deserialize_response<'a>(
86        &self,
87        response: &'a Response,
88        _output_schema: &Schema,
89        _cfg: &ConfigBag,
90    ) -> Result<Box<dyn ShapeDeserializer + 'a>, SerdeError> {
91        let body = response
92            .body()
93            .bytes()
94            .ok_or_else(|| SerdeError::custom("response body is not available as bytes"))?;
95        Ok(Box::new(self.codec.create_deserializer(body)))
96    }
97
98    fn payload_codec(&self) -> Option<&dyn crate::codec::DynCodec> {
99        Some(&self.codec)
100    }
101
102    fn update_endpoint(
103        &self,
104        request: &mut Request,
105        endpoint: &aws_smithy_types::endpoint::Endpoint,
106        cfg: &ConfigBag,
107    ) -> Result<(), SerdeError> {
108        apply_http_endpoint(request, endpoint, cfg)
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115    use crate::serde::SerializableStruct;
116    use crate::{prelude::*, ShapeType};
117
118    struct TestSerializer {
119        output: Vec<u8>,
120    }
121
122    impl FinishSerializer for TestSerializer {
123        fn finish(self) -> Vec<u8> {
124            self.output
125        }
126    }
127
128    impl ShapeSerializer for TestSerializer {
129        fn write_struct(
130            &mut self,
131            _: &Schema,
132            value: &dyn SerializableStruct,
133        ) -> Result<(), SerdeError> {
134            self.output.push(b'{');
135            value.serialize_members(self)?;
136            self.output.push(b'}');
137            Ok(())
138        }
139        fn write_list(
140            &mut self,
141            _: &Schema,
142            _: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
143        ) -> Result<(), SerdeError> {
144            Ok(())
145        }
146        fn write_map(
147            &mut self,
148            _: &Schema,
149            _: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
150        ) -> Result<(), SerdeError> {
151            Ok(())
152        }
153        fn write_boolean(&mut self, _: &Schema, _: bool) -> Result<(), SerdeError> {
154            Ok(())
155        }
156        fn write_byte(&mut self, _: &Schema, _: i8) -> Result<(), SerdeError> {
157            Ok(())
158        }
159        fn write_short(&mut self, _: &Schema, _: i16) -> Result<(), SerdeError> {
160            Ok(())
161        }
162        fn write_integer(&mut self, _: &Schema, _: i32) -> Result<(), SerdeError> {
163            Ok(())
164        }
165        fn write_long(&mut self, _: &Schema, _: i64) -> Result<(), SerdeError> {
166            Ok(())
167        }
168        fn write_float(&mut self, _: &Schema, _: f32) -> Result<(), SerdeError> {
169            Ok(())
170        }
171        fn write_double(&mut self, _: &Schema, _: f64) -> Result<(), SerdeError> {
172            Ok(())
173        }
174        fn write_big_integer(
175            &mut self,
176            _: &Schema,
177            _: &aws_smithy_types::BigInteger,
178        ) -> Result<(), SerdeError> {
179            Ok(())
180        }
181        fn write_big_decimal(
182            &mut self,
183            _: &Schema,
184            _: &aws_smithy_types::BigDecimal,
185        ) -> Result<(), SerdeError> {
186            Ok(())
187        }
188        fn write_string(&mut self, _: &Schema, v: &str) -> Result<(), SerdeError> {
189            self.output.extend_from_slice(v.as_bytes());
190            Ok(())
191        }
192        fn write_blob(&mut self, _: &Schema, _: &aws_smithy_types::Blob) -> Result<(), SerdeError> {
193            Ok(())
194        }
195        fn write_timestamp(
196            &mut self,
197            _: &Schema,
198            _: &aws_smithy_types::DateTime,
199        ) -> Result<(), SerdeError> {
200            Ok(())
201        }
202        fn write_document(
203            &mut self,
204            _: &Schema,
205            _: &aws_smithy_types::Document,
206        ) -> Result<(), SerdeError> {
207            Ok(())
208        }
209        fn write_null(&mut self, _: &Schema) -> Result<(), SerdeError> {
210            Ok(())
211        }
212    }
213
214    struct TestDeserializer<'a> {
215        input: &'a [u8],
216    }
217
218    impl ShapeDeserializer for TestDeserializer<'_> {
219        fn read_struct(
220            &mut self,
221            _: &Schema,
222            _: &mut dyn FnMut(&Schema, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
223        ) -> Result<(), SerdeError> {
224            Ok(())
225        }
226        fn read_list(
227            &mut self,
228            _: &Schema,
229            _: &mut dyn FnMut(&mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
230        ) -> Result<(), SerdeError> {
231            Ok(())
232        }
233        fn read_map(
234            &mut self,
235            _: &Schema,
236            _: &mut dyn FnMut(String, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
237        ) -> Result<(), SerdeError> {
238            Ok(())
239        }
240        fn read_boolean(&mut self, _: &Schema) -> Result<bool, SerdeError> {
241            Ok(false)
242        }
243        fn read_byte(&mut self, _: &Schema) -> Result<i8, SerdeError> {
244            Ok(0)
245        }
246        fn read_short(&mut self, _: &Schema) -> Result<i16, SerdeError> {
247            Ok(0)
248        }
249        fn read_integer(&mut self, _: &Schema) -> Result<i32, SerdeError> {
250            Ok(0)
251        }
252        fn read_long(&mut self, _: &Schema) -> Result<i64, SerdeError> {
253            Ok(0)
254        }
255        fn read_float(&mut self, _: &Schema) -> Result<f32, SerdeError> {
256            Ok(0.0)
257        }
258        fn read_double(&mut self, _: &Schema) -> Result<f64, SerdeError> {
259            Ok(0.0)
260        }
261        fn read_big_integer(
262            &mut self,
263            _: &Schema,
264        ) -> Result<aws_smithy_types::BigInteger, SerdeError> {
265            use std::str::FromStr;
266            Ok(aws_smithy_types::BigInteger::from_str("0").unwrap())
267        }
268        fn read_big_decimal(
269            &mut self,
270            _: &Schema,
271        ) -> Result<aws_smithy_types::BigDecimal, SerdeError> {
272            use std::str::FromStr;
273            Ok(aws_smithy_types::BigDecimal::from_str("0").unwrap())
274        }
275        fn read_string(&mut self, _: &Schema) -> Result<String, SerdeError> {
276            Ok(String::from_utf8_lossy(self.input).into_owned())
277        }
278        fn read_blob(&mut self, _: &Schema) -> Result<aws_smithy_types::Blob, SerdeError> {
279            Ok(aws_smithy_types::Blob::new(vec![]))
280        }
281        fn read_timestamp(&mut self, _: &Schema) -> Result<aws_smithy_types::DateTime, SerdeError> {
282            Ok(aws_smithy_types::DateTime::from_secs(0))
283        }
284        fn read_document(&mut self, _: &Schema) -> Result<aws_smithy_types::Document, SerdeError> {
285            Ok(aws_smithy_types::Document::Null)
286        }
287        fn is_null(&self) -> bool {
288            false
289        }
290        fn container_size(&self) -> Option<usize> {
291            None
292        }
293    }
294
295    #[derive(Debug)]
296    struct TestCodec;
297
298    impl Codec for TestCodec {
299        type Serializer = TestSerializer;
300        type Deserializer<'a> = TestDeserializer<'a>;
301        fn create_serializer(&self) -> Self::Serializer {
302            TestSerializer { output: Vec::new() }
303        }
304        fn create_deserializer<'a>(&self, input: &'a [u8]) -> Self::Deserializer<'a> {
305            TestDeserializer { input }
306        }
307    }
308
309    static TEST_SCHEMA: Schema =
310        Schema::new(crate::shape_id!("test", "TestStruct"), ShapeType::Structure);
311
312    struct EmptyStruct;
313    impl SerializableStruct for EmptyStruct {
314        fn serialize_members(&self, _: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
315            Ok(())
316        }
317    }
318
319    static NAME_MEMBER: Schema = Schema::new_member(
320        crate::shape_id!("test", "TestStruct"),
321        ShapeType::String,
322        "name",
323        0,
324    );
325    static MEMBERS: &[&Schema] = &[&NAME_MEMBER];
326    static STRUCT_WITH_MEMBER: Schema = Schema::new_struct(
327        crate::shape_id!("test", "TestStruct"),
328        ShapeType::Structure,
329        MEMBERS,
330    );
331
332    struct NameStruct;
333    impl SerializableStruct for NameStruct {
334        fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
335            s.write_string(&NAME_MEMBER, "Alice")
336        }
337    }
338
339    #[test]
340    fn serialize_sets_content_type() {
341        let protocol = HttpRpcProtocol::new(
342            crate::shape_id!("test", "rpc"),
343            TestCodec,
344            "application/x-amz-json-1.0",
345        );
346        let request = protocol
347            .serialize_request(
348                &EmptyStruct,
349                &TEST_SCHEMA,
350                "https://example.com",
351                &ConfigBag::base(),
352            )
353            .unwrap();
354        assert_eq!(
355            request.headers().get("Content-Type").unwrap(),
356            "application/x-amz-json-1.0"
357        );
358    }
359
360    #[test]
361    fn serialize_body() {
362        let protocol = HttpRpcProtocol::new(
363            crate::shape_id!("test", "rpc"),
364            TestCodec,
365            "application/x-amz-json-1.0",
366        );
367        let request = protocol
368            .serialize_request(
369                &NameStruct,
370                &STRUCT_WITH_MEMBER,
371                "https://example.com",
372                &ConfigBag::base(),
373            )
374            .unwrap();
375        assert_eq!(request.body().bytes().unwrap(), b"{Alice}");
376    }
377
378    #[test]
379    fn serialize_empty_endpoint_defaults_to_root() {
380        let protocol = HttpRpcProtocol::new(
381            crate::shape_id!("test", "rpc"),
382            TestCodec,
383            "application/x-amz-json-1.0",
384        );
385        let request = protocol
386            .serialize_request(&EmptyStruct, &TEST_SCHEMA, "", &ConfigBag::base())
387            .unwrap();
388        assert_eq!(request.uri(), "/");
389    }
390
391    #[test]
392    fn deserialize_response() {
393        let protocol = HttpRpcProtocol::new(
394            crate::shape_id!("test", "rpc"),
395            TestCodec,
396            "application/x-amz-json-1.0",
397        );
398        let response = Response::new(
399            200u16.try_into().unwrap(),
400            SdkBody::from(r#"{"result":42}"#),
401        );
402        let mut deser = protocol
403            .deserialize_response(&response, &TEST_SCHEMA, &ConfigBag::base())
404            .unwrap();
405        assert_eq!(deser.read_string(&STRING).unwrap(), r#"{"result":42}"#);
406    }
407
408    #[test]
409    fn update_endpoint() {
410        let protocol = HttpRpcProtocol::new(
411            crate::shape_id!("test", "rpc"),
412            TestCodec,
413            "application/x-amz-json-1.0",
414        );
415        let mut request = protocol
416            .serialize_request(
417                &EmptyStruct,
418                &TEST_SCHEMA,
419                "https://old.example.com",
420                &ConfigBag::base(),
421            )
422            .unwrap();
423        let endpoint = aws_smithy_types::endpoint::Endpoint::builder()
424            .url("https://new.example.com")
425            .build();
426        protocol
427            .update_endpoint(&mut request, &endpoint, &ConfigBag::base())
428            .unwrap();
429        assert_eq!(request.uri(), "https://new.example.com/");
430    }
431
432    #[test]
433    fn protocol_id() {
434        let protocol = HttpRpcProtocol::new(
435            crate::shape_id!("aws.protocols", "awsJson1_0"),
436            TestCodec,
437            "application/x-amz-json-1.0",
438        );
439        assert_eq!(protocol.protocol_id().as_str(), "aws.protocols#awsJson1_0");
440    }
441}