aws_smithy_http_server/schema/
traits.rs1use aws_smithy_schema::{Schema, ShapeId, Trait};
9use std::any::Any;
10
11#[derive(Debug, Clone, Copy)]
17pub struct XmlMetadata {
18 payload_schema: &'static Schema<'static>,
19}
20
21impl XmlMetadata {
22 pub const TRAIT_ID: ShapeId<'static> = aws_smithy_schema::shape_id!("smithy.rust.server.xml", "metadata");
24
25 pub const fn new(payload_schema: &'static Schema<'static>) -> Self {
27 Self { payload_schema }
28 }
29
30 pub const fn payload_schema(&self) -> &'static Schema<'static> {
32 self.payload_schema
33 }
34
35 pub fn from_schema<'s>(schema: &'s Schema<'_>) -> Option<&'s Self> {
37 schema.traits()?.get(&Self::TRAIT_ID)?.as_any().downcast_ref()
38 }
39}
40
41impl Trait for XmlMetadata {
42 fn trait_id(&self) -> &ShapeId<'static> {
43 &Self::TRAIT_ID
44 }
45
46 fn as_any(&self) -> &dyn Any {
47 self
48 }
49}
50
51#[derive(Debug, Clone, Copy)]
53pub struct HttpErrorTrait {
54 code: u16,
55}
56
57impl HttpErrorTrait {
58 pub const TRAIT_ID: ShapeId<'static> = aws_smithy_schema::shape_id!("smithy.api", "httpError");
60
61 pub const fn new(code: u16) -> Self {
63 Self { code }
64 }
65
66 pub const fn code(&self) -> u16 {
68 self.code
69 }
70
71 pub fn from_schema<'s>(schema: &'s Schema<'_>) -> Option<&'s Self> {
76 schema.traits()?.get(&Self::TRAIT_ID)?.as_any().downcast_ref()
77 }
78}
79
80impl Trait for HttpErrorTrait {
81 fn trait_id(&self) -> &ShapeId<'static> {
82 &Self::TRAIT_ID
83 }
84
85 fn as_any(&self) -> &dyn Any {
86 self
87 }
88}
89
90#[derive(Debug, Clone, Default, PartialEq, Eq)]
96pub struct HttpConfiguration {
97 http: Option<Vec<String>>,
98 event_stream_http: Option<Vec<String>>,
99}
100
101impl HttpConfiguration {
102 pub fn with_http(mut self, versions: Vec<String>) -> Self {
108 self.http = Some(versions);
109 self
110 }
111
112 pub fn with_event_stream_http(mut self, versions: Vec<String>) -> Self {
114 self.event_stream_http = Some(versions);
115 self
116 }
117
118 pub fn http(&self) -> Option<&[String]> {
120 self.http.as_deref()
121 }
122
123 pub fn event_stream_http(&self) -> Option<&[String]> {
126 self.event_stream_http.as_deref()
127 }
128}
129
130macro_rules! protocol_trait {
131 ($(#[$doc:meta])* $name:ident, $namespace:literal, $trait_name:literal $(, $field:ident: $ty:ty, $setter:ident, $getter:ident, $field_doc:literal)?) => {
132 $(#[$doc])*
133 #[derive(Debug, Clone, Default, PartialEq, Eq)]
134 pub struct $name {
135 http_configuration: HttpConfiguration,
136 $($field: $ty,)?
137 }
138
139 impl $name {
140 pub const TRAIT_ID: ShapeId<'static> = aws_smithy_schema::shape_id!($namespace, $trait_name);
142
143 pub fn new() -> Self {
145 Self::default()
146 }
147
148 pub fn with_http(mut self, versions: Vec<String>) -> Self {
150 self.http_configuration.http = Some(versions);
151 self
152 }
153
154 pub fn with_event_stream_http(mut self, versions: Vec<String>) -> Self {
156 self.http_configuration.event_stream_http = Some(versions);
157 self
158 }
159
160 pub fn http_configuration(&self) -> &HttpConfiguration {
162 &self.http_configuration
163 }
164
165 $(
166 #[doc = concat!("Sets the `", $field_doc, "` member.")]
167 pub fn $setter(mut self, value: $ty) -> Self {
168 self.$field = value;
169 self
170 }
171
172 #[doc = concat!("Returns the `", $field_doc, "` member.")]
173 pub fn $getter(&self) -> $ty {
174 self.$field
175 }
176 )?
177
178 pub fn from_service<'s>(service: &'s crate::schema::ServiceSchema<'_>) -> Option<&'s Self> {
180 service.traits()?.get(&Self::TRAIT_ID)?.as_any().downcast_ref()
181 }
182 }
183
184 impl Trait for $name {
185 fn trait_id(&self) -> &ShapeId<'static> {
186 &Self::TRAIT_ID
187 }
188
189 fn as_any(&self) -> &dyn Any {
190 self
191 }
192 }
193 };
194}
195
196protocol_trait!(
197 AwsJson1_0Trait, "aws.protocols", "awsJson1_0"
199);
200protocol_trait!(
201 AwsJson1_1Trait, "aws.protocols", "awsJson1_1"
203);
204protocol_trait!(
205 RestJson1Trait, "aws.protocols", "restJson1"
207);
208protocol_trait!(
209 RestXmlTrait, "aws.protocols", "restXml",
214 no_error_wrapping: bool, with_no_error_wrapping, no_error_wrapping, "noErrorWrapping"
215);
216protocol_trait!(
217 Rpcv2CborTrait, "smithy.protocols", "rpcv2Cbor"
219);
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224 use aws_smithy_schema::{shape_id, ShapeType, StringTrait, TraitMap};
225 use std::sync::LazyLock;
226
227 static ERROR_TRAITS: LazyLock<TraitMap> = LazyLock::new(|| {
228 let mut map = TraitMap::new();
229 map.insert(Box::new(StringTrait::new(shape_id!("smithy.api", "error"), "client")));
230 map.insert(Box::new(HttpErrorTrait::new(404)));
231 map
232 });
233
234 static ERROR: Schema<'static> =
235 Schema::new_struct(shape_id!("example", "Missing"), ShapeType::Structure, &[]).with_traits(&ERROR_TRAITS);
236
237 static SERVICE_TRAITS: LazyLock<TraitMap> = LazyLock::new(|| {
238 let mut map = TraitMap::new();
239 map.insert(Box::new(
240 RestXmlTrait::new()
241 .with_http(vec!["h2".into(), "http/1.1".into()])
242 .with_event_stream_http(vec!["h2".into()])
243 .with_no_error_wrapping(true),
244 ));
245 map.insert(Box::new(RestJson1Trait::new()));
246 map
247 });
248
249 static SERVICE: crate::schema::ServiceSchema<'static> = crate::schema::ServiceSchema::new(
250 shape_id!("example", "Service"),
251 None,
252 &[RestXmlTrait::TRAIT_ID, RestJson1Trait::TRAIT_ID],
253 &[],
254 )
255 .with_traits(&SERVICE_TRAITS);
256
257 #[test]
258 fn protocol_traits_are_read_from_the_service_schema() {
259 let rest_xml = RestXmlTrait::from_service(&SERVICE).unwrap();
260 assert!(rest_xml.no_error_wrapping());
261 assert_eq!(
262 rest_xml.http_configuration().http(),
263 Some(&["h2".to_owned(), "http/1.1".to_owned()][..])
264 );
265 assert_eq!(
266 rest_xml.http_configuration().event_stream_http(),
267 Some(&["h2".to_owned()][..])
268 );
269 assert_eq!(rest_xml.trait_id().as_str(), "aws.protocols#restXml");
270
271 let rest_json = RestJson1Trait::from_service(&SERVICE).unwrap();
273 assert_eq!(rest_json.http_configuration(), &HttpConfiguration::default());
274 assert_eq!(rest_json.http_configuration().http(), None);
275
276 assert!(Rpcv2CborTrait::from_service(&SERVICE).is_none());
278 static BARE: crate::schema::ServiceSchema<'static> =
279 crate::schema::ServiceSchema::new(shape_id!("example", "Bare"), None, &[], &[]);
280 assert!(RestXmlTrait::from_service(&BARE).is_none());
281 assert_eq!(AwsJson1_0Trait::TRAIT_ID.as_str(), "aws.protocols#awsJson1_0");
282 assert_eq!(AwsJson1_1Trait::TRAIT_ID.as_str(), "aws.protocols#awsJson1_1");
283 assert_eq!(Rpcv2CborTrait::TRAIT_ID.as_str(), "smithy.protocols#rpcv2Cbor");
284 }
285
286 #[test]
287 fn typed_http_error_preserves_trait_identity() {
288 let schema = &ERROR;
289 assert!(schema.http().is_none());
290 let traits = schema.traits().unwrap();
291 assert!(traits.contains_fqn("smithy.api#error"));
292 assert!(traits.contains_fqn("smithy.api#httpError"));
293 assert_eq!(traits.len(), 2);
294 let http_error = HttpErrorTrait::from_schema(schema).unwrap();
295 assert_eq!(http_error.code(), 404);
296 assert_eq!(http_error.trait_id().as_str(), "smithy.api#httpError");
297 }
298
299 #[test]
300 fn absent_or_differently_typed_entries_return_none() {
301 let plain = Schema::new(shape_id!("example", "Plain"), ShapeType::String);
302 assert!(HttpErrorTrait::from_schema(&plain).is_none());
303 let without_http_error: LazyLock<TraitMap> = LazyLock::new(|| {
304 let mut map = TraitMap::new();
305 map.insert(Box::new(StringTrait::new(shape_id!("smithy.api", "error"), "server")));
306 map
307 });
308 assert!(HttpErrorTrait::from_schema(
309 &Schema::new(shape_id!("example", "Plain"), ShapeType::String).with_traits(&without_http_error)
310 )
311 .is_none());
312 let differently_typed: LazyLock<TraitMap> = LazyLock::new(|| {
313 let mut map = TraitMap::new();
314 map.insert(Box::new(StringTrait::new(HttpErrorTrait::TRAIT_ID, "404")));
315 map
316 });
317 assert!(HttpErrorTrait::from_schema(&plain.with_traits(&differently_typed)).is_none());
318 }
319}