aws_smithy_schema/schema/
traits.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! Typed runtime representations of Smithy serialization traits.
7//!
8//! These types allow type-safe downcasting from `&dyn Trait` via `as_any()`,
9//! enabling protocol implementations to read trait values without string-matching
10//! on Shape IDs.
11
12use crate::{ShapeId, Trait};
13use std::any::Any;
14
15macro_rules! annotation_trait {
16    ($(#[$meta:meta])* $name:ident, $ns:literal, $trait_name:literal) => {
17        $(#[$meta])*
18        #[derive(Debug, Clone)]
19        #[allow(dead_code)] // Used by generated schema code
20        pub struct $name;
21
22        impl $name {
23            /// The Shape ID for this trait.
24            pub const TRAIT_ID: ShapeId = crate::shape_id!($ns, $trait_name);
25        }
26
27        impl Trait for $name {
28            fn trait_id(&self) -> &ShapeId { &Self::TRAIT_ID }
29            fn as_any(&self) -> &dyn Any { self }
30        }
31    };
32}
33
34macro_rules! string_trait {
35    ($(#[$meta:meta])* $name:ident, $ns:literal, $trait_name:literal) => {
36        $(#[$meta])*
37        #[derive(Debug, Clone)]
38        #[allow(dead_code)] // Used by generated schema code
39        pub struct $name {
40            value: &'static str,
41        }
42
43        #[allow(dead_code)] // Used by generated schema code
44        impl $name {
45            /// The Shape ID for this trait.
46            pub const TRAIT_ID: ShapeId = crate::shape_id!($ns, $trait_name);
47
48            /// Creates a new instance.
49            pub const fn new(value: &'static str) -> Self {
50                Self { value }
51            }
52
53            /// Returns the trait value.
54            pub fn value(&self) -> &str {
55                self.value
56            }
57        }
58
59        impl Trait for $name {
60            fn trait_id(&self) -> &ShapeId { &Self::TRAIT_ID }
61            fn as_any(&self) -> &dyn Any { self }
62        }
63    };
64}
65
66// --- Serialization & Protocol traits ---
67
68string_trait!(
69    /// The `@jsonName` trait — overrides the JSON key for a member.
70    JsonNameTrait,
71    "smithy.api", "jsonName"
72);
73
74string_trait!(
75    /// The `@xmlName` trait — overrides the XML element name.
76    XmlNameTrait,
77    "smithy.api", "xmlName"
78);
79
80string_trait!(
81    /// The `@mediaType` trait — specifies the media type of a blob/string.
82    MediaTypeTrait,
83    "smithy.api", "mediaType"
84);
85
86annotation_trait!(
87    /// The `@xmlAttribute` trait — serializes a member as an XML attribute.
88    XmlAttributeTrait,
89    "smithy.api", "xmlAttribute"
90);
91
92annotation_trait!(
93    /// The `@xmlFlattened` trait — removes the wrapping element for lists/maps in XML.
94    XmlFlattenedTrait,
95    "smithy.api", "xmlFlattened"
96);
97
98// xmlNamespace is a structured trait (uri + optional prefix). For now we only
99// need its ShapeId for lookups; the full value can be stored as a DocumentTrait.
100annotation_trait!(
101    /// The `@xmlNamespace` trait — adds an XML namespace to an element.
102    XmlNamespaceTrait,
103    "smithy.api", "xmlNamespace"
104);
105
106// --- Timestamp ---
107
108/// The `@timestampFormat` trait — specifies the serialization format for timestamps.
109#[derive(Debug, Clone, Copy)]
110#[allow(dead_code)] // Used by generated schema code
111pub struct TimestampFormatTrait {
112    format: TimestampFormat,
113}
114
115/// Timestamp serialization formats.
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117pub enum TimestampFormat {
118    /// Epoch seconds (e.g. `1515531081.123`).
119    EpochSeconds,
120    /// RFC 3339 date-time (e.g. `2018-01-09T18:47:00Z`).
121    DateTime,
122    /// RFC 7231 HTTP date (e.g. `Tue, 09 Jan 2018 18:47:00 GMT`).
123    HttpDate,
124}
125
126#[allow(dead_code)] // Used by generated schema code
127impl TimestampFormatTrait {
128    /// The Shape ID for this trait.
129    pub const TRAIT_ID: ShapeId = crate::shape_id!("smithy.api", "timestampFormat");
130
131    /// Creates a new instance.
132    pub const fn new(format: TimestampFormat) -> Self {
133        Self { format }
134    }
135
136    /// Returns the timestamp format.
137    pub fn format(&self) -> TimestampFormat {
138        self.format
139    }
140}
141
142impl Trait for TimestampFormatTrait {
143    fn trait_id(&self) -> &ShapeId {
144        &Self::TRAIT_ID
145    }
146    fn as_any(&self) -> &dyn Any {
147        self
148    }
149}
150
151// --- HTTP binding traits ---
152
153string_trait!(
154    /// The `@httpHeader` trait — binds a member to an HTTP header.
155    HttpHeaderTrait,
156    "smithy.api", "httpHeader"
157);
158
159string_trait!(
160    /// The `@httpQuery` trait — binds a member to a query parameter.
161    HttpQueryTrait,
162    "smithy.api", "httpQuery"
163);
164
165string_trait!(
166    /// The `@httpPrefixHeaders` trait — binds a map to prefixed HTTP headers.
167    HttpPrefixHeadersTrait,
168    "smithy.api", "httpPrefixHeaders"
169);
170
171annotation_trait!(
172    /// The `@httpLabel` trait — binds a member to a URI label.
173    HttpLabelTrait,
174    "smithy.api", "httpLabel"
175);
176
177annotation_trait!(
178    /// The `@httpPayload` trait — binds a member to the HTTP body.
179    HttpPayloadTrait,
180    "smithy.api", "httpPayload"
181);
182
183annotation_trait!(
184    /// The `@httpQueryParams` trait — binds a map to query parameters.
185    HttpQueryParamsTrait,
186    "smithy.api", "httpQueryParams"
187);
188
189annotation_trait!(
190    /// The `@httpResponseCode` trait — binds a member to the HTTP status code.
191    HttpResponseCodeTrait,
192    "smithy.api", "httpResponseCode"
193);
194
195/// The `@http` trait — defines the HTTP method, URI pattern, and status code for an operation.
196///
197/// This is an operation-level trait that is included on the input schema for
198/// convenience, so that the protocol serializer can construct the correct
199/// request without needing a separate operation schema.
200///
201/// The URI pattern may contain `{label}` placeholders that are substituted
202/// at serialization time with percent-encoded values from `@httpLabel` members.
203#[derive(Debug, Clone)]
204pub struct HttpTrait {
205    method: &'static str,
206    uri: &'static str,
207    code: u16,
208}
209
210impl HttpTrait {
211    /// Creates a new `HttpTrait`. If `code` is `None`, defaults to `200`.
212    pub const fn new(method: &'static str, uri: &'static str, code: Option<u16>) -> Self {
213        Self {
214            method,
215            uri,
216            code: match code {
217                Some(c) => c,
218                None => 200,
219            },
220        }
221    }
222
223    /// The HTTP method (e.g., `"GET"`, `"POST"`, `"PUT"`).
224    pub fn method(&self) -> &str {
225        self.method
226    }
227
228    /// The URI pattern (e.g., `"/resource/{id}"`).
229    ///
230    /// May contain `{label}` placeholders that correspond to `@httpLabel` members.
231    /// The protocol serializer substitutes these with percent-encoded values
232    /// collected during member serialization.
233    pub fn uri(&self) -> &str {
234        self.uri
235    }
236
237    /// The HTTP status code for a successful response. Defaults to `200`.
238    pub fn code(&self) -> u16 {
239        self.code
240    }
241}
242
243// --- Streaming traits ---
244
245annotation_trait!(
246    /// The `@streaming` trait — marks a blob or union as streaming.
247    StreamingTrait,
248    "smithy.api", "streaming"
249);
250
251annotation_trait!(
252    /// The `@eventHeader` trait — binds a member to an event stream header.
253    EventHeaderTrait,
254    "smithy.api", "eventHeader"
255);
256
257annotation_trait!(
258    /// The `@eventPayload` trait — binds a member to an event stream payload.
259    EventPayloadTrait,
260    "smithy.api", "eventPayload"
261);
262
263// --- Documentation / behavior traits ---
264
265annotation_trait!(
266    /// The `@sensitive` trait — marks data as sensitive for logging redaction.
267    SensitiveTrait,
268    "smithy.api", "sensitive"
269);
270
271// --- Endpoint traits ---
272
273annotation_trait!(
274    /// The `@hostLabel` trait — binds a member to a host prefix label.
275    HostLabelTrait,
276    "smithy.api", "hostLabel"
277);
278
279#[cfg(test)]
280mod tests {
281    use super::*;
282
283    #[test]
284    fn downcast_json_name() {
285        let t: Box<dyn Trait> = Box::new(JsonNameTrait::new("userName"));
286        assert_eq!(t.trait_id().as_str(), "smithy.api#jsonName");
287        let json_name = t.as_any().downcast_ref::<JsonNameTrait>().unwrap();
288        assert_eq!(json_name.value(), "userName");
289    }
290
291    #[test]
292    fn downcast_sensitive() {
293        let t: Box<dyn Trait> = Box::new(SensitiveTrait);
294        assert_eq!(t.trait_id().as_str(), "smithy.api#sensitive");
295        assert!(t.as_any().downcast_ref::<SensitiveTrait>().is_some());
296    }
297
298    #[test]
299    fn timestamp_format_parsing() {
300        let t = TimestampFormatTrait::new(TimestampFormat::EpochSeconds);
301        assert_eq!(t.format(), TimestampFormat::EpochSeconds);
302        assert_eq!(t.trait_id().as_str(), "smithy.api#timestampFormat");
303    }
304}