Skip to main content

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        // See `HttpBindingProtocol::deserialize_response` for the rationale
92        // behind tolerating an unreadable (streaming) body. RPC protocols
93        // also flow through `deserialize_with_response` for streaming
94        // outputs and the `&[]` body slice signals "no body members to
95        // read".
96        let body = response.body().bytes().unwrap_or(&[]);
97        Ok(Box::new(self.codec.create_deserializer(body)))
98    }
99
100    fn payload_codec(&self) -> Option<&dyn crate::codec::DynCodec> {
101        Some(&self.codec)
102    }
103
104    fn update_endpoint(
105        &self,
106        request: &mut Request,
107        endpoint: &aws_smithy_types::endpoint::Endpoint,
108        cfg: &ConfigBag,
109    ) -> Result<(), SerdeError> {
110        apply_http_endpoint(request, endpoint, cfg)
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117    use crate::serde::SerializableStruct;
118    use crate::{prelude::*, ShapeType};
119
120    struct TestSerializer {
121        output: Vec<u8>,
122    }
123
124    impl FinishSerializer for TestSerializer {
125        fn finish(self) -> Vec<u8> {
126            self.output
127        }
128    }
129
130    impl ShapeSerializer for TestSerializer {
131        fn write_struct(
132            &mut self,
133            _: &Schema,
134            value: &dyn SerializableStruct,
135        ) -> Result<(), SerdeError> {
136            self.output.push(b'{');
137            value.serialize_members(self)?;
138            self.output.push(b'}');
139            Ok(())
140        }
141        fn write_list(
142            &mut self,
143            _: &Schema,
144            _: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
145        ) -> Result<(), SerdeError> {
146            Ok(())
147        }
148        fn write_map(
149            &mut self,
150            _: &Schema,
151            _: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
152        ) -> Result<(), SerdeError> {
153            Ok(())
154        }
155        fn write_boolean(&mut self, _: &Schema, _: bool) -> Result<(), SerdeError> {
156            Ok(())
157        }
158        fn write_byte(&mut self, _: &Schema, _: i8) -> Result<(), SerdeError> {
159            Ok(())
160        }
161        fn write_short(&mut self, _: &Schema, _: i16) -> Result<(), SerdeError> {
162            Ok(())
163        }
164        fn write_integer(&mut self, _: &Schema, _: i32) -> Result<(), SerdeError> {
165            Ok(())
166        }
167        fn write_long(&mut self, _: &Schema, _: i64) -> Result<(), SerdeError> {
168            Ok(())
169        }
170        fn write_float(&mut self, _: &Schema, _: f32) -> Result<(), SerdeError> {
171            Ok(())
172        }
173        fn write_double(&mut self, _: &Schema, _: f64) -> Result<(), SerdeError> {
174            Ok(())
175        }
176        fn write_big_integer(
177            &mut self,
178            _: &Schema,
179            _: &aws_smithy_types::BigInteger,
180        ) -> Result<(), SerdeError> {
181            Ok(())
182        }
183        fn write_big_decimal(
184            &mut self,
185            _: &Schema,
186            _: &aws_smithy_types::BigDecimal,
187        ) -> Result<(), SerdeError> {
188            Ok(())
189        }
190        fn write_string(&mut self, _: &Schema, v: &str) -> Result<(), SerdeError> {
191            self.output.extend_from_slice(v.as_bytes());
192            Ok(())
193        }
194        fn write_blob(&mut self, _: &Schema, _: &[u8]) -> Result<(), SerdeError> {
195            Ok(())
196        }
197        fn write_timestamp(
198            &mut self,
199            _: &Schema,
200            _: &aws_smithy_types::DateTime,
201        ) -> Result<(), SerdeError> {
202            Ok(())
203        }
204        fn write_document(
205            &mut self,
206            _: &Schema,
207            _: &aws_smithy_types::Document,
208        ) -> Result<(), SerdeError> {
209            Ok(())
210        }
211        fn write_null(&mut self, _: &Schema) -> Result<(), SerdeError> {
212            Ok(())
213        }
214    }
215
216    struct TestDeserializer<'a> {
217        input: &'a [u8],
218    }
219
220    impl ShapeDeserializer for TestDeserializer<'_> {
221        fn read_struct(
222            &mut self,
223            _: &Schema,
224            _: &mut dyn FnMut(&Schema, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
225        ) -> Result<(), SerdeError> {
226            Ok(())
227        }
228        fn read_list(
229            &mut self,
230            _: &Schema,
231            _: &mut dyn FnMut(&mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
232        ) -> Result<(), SerdeError> {
233            Ok(())
234        }
235        fn read_map(
236            &mut self,
237            _: &Schema,
238            _: &mut dyn FnMut(String, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
239        ) -> Result<(), SerdeError> {
240            Ok(())
241        }
242        fn read_boolean(&mut self, _: &Schema) -> Result<bool, SerdeError> {
243            Ok(false)
244        }
245        fn read_byte(&mut self, _: &Schema) -> Result<i8, SerdeError> {
246            Ok(0)
247        }
248        fn read_short(&mut self, _: &Schema) -> Result<i16, SerdeError> {
249            Ok(0)
250        }
251        fn read_integer(&mut self, _: &Schema) -> Result<i32, SerdeError> {
252            Ok(0)
253        }
254        fn read_long(&mut self, _: &Schema) -> Result<i64, SerdeError> {
255            Ok(0)
256        }
257        fn read_float(&mut self, _: &Schema) -> Result<f32, SerdeError> {
258            Ok(0.0)
259        }
260        fn read_double(&mut self, _: &Schema) -> Result<f64, SerdeError> {
261            Ok(0.0)
262        }
263        fn read_big_integer(
264            &mut self,
265            _: &Schema,
266        ) -> Result<aws_smithy_types::BigInteger, SerdeError> {
267            use std::str::FromStr;
268            Ok(aws_smithy_types::BigInteger::from_str("0").unwrap())
269        }
270        fn read_big_decimal(
271            &mut self,
272            _: &Schema,
273        ) -> Result<aws_smithy_types::BigDecimal, SerdeError> {
274            use std::str::FromStr;
275            Ok(aws_smithy_types::BigDecimal::from_str("0").unwrap())
276        }
277        fn read_string(&mut self, _: &Schema) -> Result<String, SerdeError> {
278            Ok(String::from_utf8_lossy(self.input).into_owned())
279        }
280        fn read_blob(&mut self, _: &Schema) -> Result<aws_smithy_types::Blob, SerdeError> {
281            Ok(aws_smithy_types::Blob::new(vec![]))
282        }
283        fn read_timestamp(&mut self, _: &Schema) -> Result<aws_smithy_types::DateTime, SerdeError> {
284            Ok(aws_smithy_types::DateTime::from_secs(0))
285        }
286        fn read_document(&mut self, _: &Schema) -> Result<aws_smithy_types::Document, SerdeError> {
287            Ok(aws_smithy_types::Document::Null)
288        }
289        fn is_null(&self) -> bool {
290            false
291        }
292        fn container_size(&self) -> Option<usize> {
293            None
294        }
295    }
296
297    #[derive(Debug)]
298    struct TestCodec;
299
300    impl Codec for TestCodec {
301        type Serializer = TestSerializer;
302        type Deserializer<'a> = TestDeserializer<'a>;
303        fn create_serializer(&self) -> Self::Serializer {
304            TestSerializer { output: Vec::new() }
305        }
306        fn create_deserializer<'a>(&self, input: &'a [u8]) -> Self::Deserializer<'a> {
307            TestDeserializer { input }
308        }
309    }
310
311    static TEST_SCHEMA: Schema =
312        Schema::new(crate::shape_id!("test", "TestStruct"), ShapeType::Structure);
313
314    struct EmptyStruct;
315    impl SerializableStruct for EmptyStruct {
316        fn serialize_members(&self, _: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
317            Ok(())
318        }
319    }
320
321    static NAME_MEMBER: Schema = Schema::new_member(
322        crate::shape_id!("test", "TestStruct"),
323        ShapeType::String,
324        "name",
325        0,
326    );
327    static MEMBERS: &[&Schema] = &[&NAME_MEMBER];
328    static STRUCT_WITH_MEMBER: Schema = Schema::new_struct(
329        crate::shape_id!("test", "TestStruct"),
330        ShapeType::Structure,
331        MEMBERS,
332    );
333
334    struct NameStruct;
335    impl SerializableStruct for NameStruct {
336        fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
337            s.write_string(&NAME_MEMBER, "Alice")
338        }
339    }
340
341    #[test]
342    fn serialize_sets_content_type() {
343        let protocol = HttpRpcProtocol::new(
344            crate::shape_id!("test", "rpc"),
345            TestCodec,
346            "application/x-amz-json-1.0",
347        );
348        let request = protocol
349            .serialize_request(
350                &EmptyStruct,
351                &TEST_SCHEMA,
352                "https://example.com",
353                &ConfigBag::base(),
354            )
355            .unwrap();
356        assert_eq!(
357            request.headers().get("Content-Type").unwrap(),
358            "application/x-amz-json-1.0"
359        );
360    }
361
362    #[test]
363    fn serialize_body() {
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(
371                &NameStruct,
372                &STRUCT_WITH_MEMBER,
373                "https://example.com",
374                &ConfigBag::base(),
375            )
376            .unwrap();
377        assert_eq!(request.body().bytes().unwrap(), b"{Alice}");
378    }
379
380    #[test]
381    fn serialize_empty_endpoint_defaults_to_root() {
382        let protocol = HttpRpcProtocol::new(
383            crate::shape_id!("test", "rpc"),
384            TestCodec,
385            "application/x-amz-json-1.0",
386        );
387        let request = protocol
388            .serialize_request(&EmptyStruct, &TEST_SCHEMA, "", &ConfigBag::base())
389            .unwrap();
390        assert_eq!(request.uri(), "/");
391    }
392
393    #[test]
394    fn deserialize_response() {
395        let protocol = HttpRpcProtocol::new(
396            crate::shape_id!("test", "rpc"),
397            TestCodec,
398            "application/x-amz-json-1.0",
399        );
400        let response = Response::new(
401            200u16.try_into().unwrap(),
402            SdkBody::from(r#"{"result":42}"#),
403        );
404        let mut deser = protocol
405            .deserialize_response(&response, &TEST_SCHEMA, &ConfigBag::base())
406            .unwrap();
407        assert_eq!(deser.read_string(&STRING).unwrap(), r#"{"result":42}"#);
408    }
409
410    #[test]
411    fn update_endpoint() {
412        let protocol = HttpRpcProtocol::new(
413            crate::shape_id!("test", "rpc"),
414            TestCodec,
415            "application/x-amz-json-1.0",
416        );
417        let mut request = protocol
418            .serialize_request(
419                &EmptyStruct,
420                &TEST_SCHEMA,
421                "https://old.example.com",
422                &ConfigBag::base(),
423            )
424            .unwrap();
425        let endpoint = aws_smithy_types::endpoint::Endpoint::builder()
426            .url("https://new.example.com")
427            .build();
428        protocol
429            .update_endpoint(&mut request, &endpoint, &ConfigBag::base())
430            .unwrap();
431        assert_eq!(request.uri(), "https://new.example.com/");
432    }
433
434    #[test]
435    fn protocol_id() {
436        let protocol = HttpRpcProtocol::new(
437            crate::shape_id!("aws.protocols", "awsJson1_0"),
438            TestCodec,
439            "application/x-amz-json-1.0",
440        );
441        assert_eq!(protocol.protocol_id().as_str(), "aws.protocols#awsJson1_0");
442    }
443}