aws_smithy_json/protocol/
aws_json_rpc.rs1use crate::codec::{JsonCodec, JsonCodecSettings};
23use aws_smithy_runtime_api::client::orchestrator::Metadata;
24use aws_smithy_schema::http_protocol::HttpRpcProtocol;
25use aws_smithy_schema::{shape_id, Schema, ShapeId};
26use aws_smithy_types::config_bag::ConfigBag;
27
28#[derive(Debug)]
30pub struct AwsJsonRpcProtocol {
31 inner: HttpRpcProtocol<JsonCodec>,
32 target_prefix: String,
33}
34
35impl AwsJsonRpcProtocol {
36 pub fn aws_json_1_0(target_prefix: impl Into<String>) -> Self {
41 Self::new(
42 shape_id!("aws.protocols", "awsJson1_0"),
43 "application/x-amz-json-1.0",
44 target_prefix.into(),
45 )
46 }
47
48 pub fn aws_json_1_1(target_prefix: impl Into<String>) -> Self {
52 Self::new(
53 shape_id!("aws.protocols", "awsJson1_1"),
54 "application/x-amz-json-1.1",
55 target_prefix.into(),
56 )
57 }
58
59 fn new(protocol_id: ShapeId, content_type: &'static str, target_prefix: String) -> Self {
60 let codec = JsonCodec::new(
61 JsonCodecSettings::builder()
62 .use_json_name(false)
63 .default_timestamp_format(aws_smithy_types::date_time::Format::EpochSeconds)
64 .build(),
65 );
66 Self {
67 inner: HttpRpcProtocol::new(protocol_id, codec, content_type),
68 target_prefix,
69 }
70 }
71}
72
73impl aws_smithy_schema::protocol::ClientProtocolInner for AwsJsonRpcProtocol {
74 type Request = aws_smithy_runtime_api::http::Request;
75 type Response = aws_smithy_runtime_api::http::Response;
76
77 fn protocol_id(&self) -> &ShapeId {
78 self.inner.protocol_id()
79 }
80
81 fn serialize_request(
82 &self,
83 input: &dyn aws_smithy_schema::serde::SerializableStruct,
84 input_schema: &Schema,
85 endpoint: &str,
86 cfg: &ConfigBag,
87 ) -> Result<aws_smithy_runtime_api::http::Request, aws_smithy_schema::serde::SerdeError> {
88 let mut request = self
89 .inner
90 .serialize_request(input, input_schema, endpoint, cfg)?;
91 if let Some(metadata) = cfg.load::<Metadata>() {
92 request.headers_mut().insert(
93 "X-Amz-Target",
94 format!("{}.{}", self.target_prefix, metadata.name()),
95 );
96 }
97 Ok(request)
98 }
99
100 fn deserialize_response<'a>(
101 &self,
102 response: &'a aws_smithy_runtime_api::http::Response,
103 output_schema: &Schema,
104 cfg: &ConfigBag,
105 ) -> Result<
106 Box<dyn aws_smithy_schema::serde::ShapeDeserializer + 'a>,
107 aws_smithy_schema::serde::SerdeError,
108 > {
109 self.inner
110 .deserialize_response(response, output_schema, cfg)
111 }
112
113 fn payload_codec(&self) -> Option<&dyn aws_smithy_schema::codec::DynCodec> {
114 self.inner.payload_codec()
115 }
116
117 fn update_endpoint(
118 &self,
119 request: &mut aws_smithy_runtime_api::http::Request,
120 endpoint: &aws_smithy_types::endpoint::Endpoint,
121 cfg: &ConfigBag,
122 ) -> Result<(), aws_smithy_schema::serde::SerdeError> {
123 self.inner.update_endpoint(request, endpoint, cfg)
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130 use aws_smithy_schema::protocol::ClientProtocolInner;
131 use aws_smithy_schema::serde::{SerdeError, SerializableStruct, ShapeSerializer};
132 use aws_smithy_schema::ShapeType;
133 use aws_smithy_types::config_bag::Layer;
134
135 struct EmptyStruct;
136 impl SerializableStruct for EmptyStruct {
137 fn serialize_members(&self, _: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
138 Ok(())
139 }
140 }
141
142 static TEST_SCHEMA: aws_smithy_schema::Schema =
143 aws_smithy_schema::Schema::new(shape_id!("test", "Input"), ShapeType::Structure);
144
145 fn cfg_with_metadata(service: &str, operation: &str) -> ConfigBag {
146 let mut layer = Layer::new("test");
147 layer.store_put(Metadata::new(operation.to_string(), service.to_string()));
148 ConfigBag::of_layers(vec![layer])
149 }
150
151 #[test]
152 fn json_1_0_content_type() {
153 let request = AwsJsonRpcProtocol::aws_json_1_0("TestService")
154 .serialize_request(
155 &EmptyStruct,
156 &TEST_SCHEMA,
157 "https://example.com",
158 &ConfigBag::base(),
159 )
160 .unwrap();
161 assert_eq!(
162 request.headers().get("Content-Type").unwrap(),
163 "application/x-amz-json-1.0"
164 );
165 }
166
167 #[test]
168 fn json_1_1_content_type() {
169 let request = AwsJsonRpcProtocol::aws_json_1_1("TestService")
170 .serialize_request(
171 &EmptyStruct,
172 &TEST_SCHEMA,
173 "https://example.com",
174 &ConfigBag::base(),
175 )
176 .unwrap();
177 assert_eq!(
178 request.headers().get("Content-Type").unwrap(),
179 "application/x-amz-json-1.1"
180 );
181 }
182
183 #[test]
184 fn sets_x_amz_target() {
185 let cfg = cfg_with_metadata("MyService", "DoThing");
186 let request = AwsJsonRpcProtocol::aws_json_1_0("MyService")
187 .serialize_request(&EmptyStruct, &TEST_SCHEMA, "https://example.com", &cfg)
188 .unwrap();
189 assert_eq!(
190 request.headers().get("X-Amz-Target").unwrap(),
191 "MyService.DoThing"
192 );
193 }
194
195 #[test]
196 fn json_1_0_protocol_id() {
197 assert_eq!(
198 AwsJsonRpcProtocol::aws_json_1_0("Svc")
199 .protocol_id()
200 .as_str(),
201 "aws.protocols#awsJson1_0"
202 );
203 }
204
205 #[test]
206 fn json_1_1_protocol_id() {
207 assert_eq!(
208 AwsJsonRpcProtocol::aws_json_1_1("Svc")
209 .protocol_id()
210 .as_str(),
211 "aws.protocols#awsJson1_1"
212 );
213 }
214}