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