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 + |
|
8 + | use crate::codec::{Codec, FinishSerializer};
|
9 + | use crate::protocol::ClientProtocol;
|
10 + | use crate::serde::{SerdeError, SerializableStruct, ShapeDeserializer, ShapeSerializer};
|
11 + | use crate::{Schema, ShapeId};
|
12 + | use aws_smithy_runtime_api::http::{Request, Response};
|
13 + | use aws_smithy_types::body::SdkBody;
|
14 + | use 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)]
|
26 + | pub struct HttpRpcProtocol<C> {
|
27 + | protocol_id: ShapeId,
|
28 + | codec: C,
|
29 + | content_type: &'static str,
|
30 + | }
|
31 + |
|
32 + | impl<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 + |
|
43 + | impl<C> ClientProtocol for HttpRpcProtocol<C>
|
44 + | where
|
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)]
|
97 + | mod 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 + | }
|