aws_smithy_schema/
lib.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6/* Automatically managed default lints */
7#![cfg_attr(docsrs, feature(doc_cfg))]
8/* End of automatically managed default lints */
9
10//! Runtime schema types for Smithy shapes.
11//!
12//! This module provides the core types for representing Smithy schemas at runtime,
13//! enabling protocol-agnostic serialization and deserialization.
14
15mod schema {
16    pub mod shape_id;
17    pub mod shape_type;
18    pub mod trait_map;
19    pub mod trait_type;
20
21    pub mod prelude;
22    pub mod serde;
23}
24
25pub use schema::shape_id::ShapeId;
26pub use schema::shape_type::ShapeType;
27pub use schema::trait_map::TraitMap;
28pub use schema::trait_type::Trait;
29
30pub mod prelude {
31    pub use crate::schema::prelude::*;
32}
33
34pub mod serde {
35    pub use crate::schema::serde::*;
36}
37
38/// Core trait representing a Smithy schema at runtime.
39///
40/// A schema is a lightweight runtime representation of a Smithy shape,
41/// containing the shape's ID, type, traits, and references to member schemas.
42pub trait Schema: Send + Sync {
43    /// Returns the Shape ID of this schema.
44    fn shape_id(&self) -> &ShapeId;
45
46    /// Returns the shape type.
47    fn shape_type(&self) -> ShapeType;
48
49    /// Returns the traits associated with this schema.
50    fn traits(&self) -> &TraitMap;
51
52    /// Returns the member name if this is a member schema.
53    fn member_name(&self) -> Option<&str> {
54        None
55    }
56
57    /// Returns the member schema by name (for structures and unions).
58    fn member_schema(&self, _name: &str) -> Option<&dyn Schema> {
59        None
60    }
61
62    /// Returns the member schema by position index (for structures and unions).
63    ///
64    /// This is an optimization for generated code to avoid string lookups.
65    /// Consumer code should not rely on specific position values as they may change.
66    fn member_schema_by_index(&self, _index: usize) -> Option<&dyn Schema> {
67        None
68    }
69
70    /// Returns the member schema for collections (list member or map value).
71    fn member(&self) -> Option<&dyn Schema> {
72        None
73    }
74
75    /// Returns the key schema for maps.
76    fn key(&self) -> Option<&dyn Schema> {
77        None
78    }
79
80    /// Returns an iterator over member schemas (for structures and unions).
81    fn members(&self) -> Box<dyn Iterator<Item = &dyn Schema> + '_> {
82        Box::new(std::iter::empty())
83    }
84
85    /// Returns the member index for member schemas.
86    ///
87    /// This is used internally by generated code for efficient member lookup.
88    /// Returns None if not applicable or not a member schema.
89    fn member_index(&self) -> Option<usize> {
90        None
91    }
92}
93
94/// Helper methods for Schema trait.
95pub trait SchemaExt: Schema {
96    /// Returns true if this is a member schema.
97    fn is_member(&self) -> bool {
98        self.shape_type().is_member()
99    }
100
101    /// Returns true if this is a structure schema.
102    fn is_structure(&self) -> bool {
103        self.shape_type() == ShapeType::Structure
104    }
105
106    /// Returns true if this is a union schema.
107    fn is_union(&self) -> bool {
108        self.shape_type() == ShapeType::Union
109    }
110
111    /// Returns true if this is a list schema.
112    fn is_list(&self) -> bool {
113        self.shape_type() == ShapeType::List
114    }
115
116    /// Returns true if this is a map schema.
117    fn is_map(&self) -> bool {
118        self.shape_type() == ShapeType::Map
119    }
120
121    /// Returns true if this is a blob schema.
122    fn is_blob(&self) -> bool {
123        self.shape_type() == ShapeType::Blob
124    }
125
126    /// Returns true if this is a string schema.
127    fn is_string(&self) -> bool {
128        self.shape_type() == ShapeType::String
129    }
130}
131
132impl<T: Schema + ?Sized> SchemaExt for T {}
133
134#[cfg(test)]
135mod test {
136    use crate::{Schema, SchemaExt, ShapeId, ShapeType, Trait, TraitMap};
137
138    // Simple test trait implementation
139    #[derive(Debug)]
140    struct TestTrait {
141        id: ShapeId,
142        #[allow(dead_code)]
143        value: String,
144    }
145
146    impl Trait for TestTrait {
147        fn trait_id(&self) -> &ShapeId {
148            &self.id
149        }
150
151        fn as_any(&self) -> &dyn std::any::Any {
152            self
153        }
154    }
155
156    // Simple test schema implementation
157    struct TestSchema {
158        id: ShapeId,
159        shape_type: ShapeType,
160        traits: TraitMap,
161    }
162
163    impl Schema for TestSchema {
164        fn shape_id(&self) -> &ShapeId {
165            &self.id
166        }
167
168        fn shape_type(&self) -> ShapeType {
169            self.shape_type
170        }
171
172        fn traits(&self) -> &TraitMap {
173            &self.traits
174        }
175    }
176
177    #[test]
178    fn test_shape_type_simple() {
179        assert!(ShapeType::String.is_simple());
180        assert!(ShapeType::Integer.is_simple());
181        assert!(ShapeType::Boolean.is_simple());
182        assert!(!ShapeType::Structure.is_simple());
183        assert!(!ShapeType::List.is_simple());
184    }
185
186    #[test]
187    fn test_shape_type_aggregate() {
188        assert!(ShapeType::Structure.is_aggregate());
189        assert!(ShapeType::Union.is_aggregate());
190        assert!(ShapeType::List.is_aggregate());
191        assert!(ShapeType::Map.is_aggregate());
192        assert!(!ShapeType::String.is_aggregate());
193    }
194
195    #[test]
196    fn test_shape_type_member() {
197        assert!(ShapeType::Member.is_member());
198        assert!(!ShapeType::String.is_member());
199        assert!(!ShapeType::Structure.is_member());
200    }
201
202    #[test]
203    fn test_shape_id_parsing() {
204        let id = ShapeId::new("smithy.api#String");
205        assert_eq!(id.namespace(), "smithy.api");
206        assert_eq!(id.shape_name(), "String");
207        assert_eq!(id.member_name(), None);
208    }
209
210    #[test]
211    fn test_shape_id_with_member() {
212        let id = ShapeId::new("com.example#MyStruct$member");
213        assert_eq!(id.namespace(), "com.example");
214        assert_eq!(id.shape_name(), "MyStruct");
215        assert_eq!(id.member_name(), Some("member"));
216    }
217
218    #[test]
219    fn test_trait_map() {
220        let mut map = TraitMap::new();
221        assert!(map.is_empty());
222        assert_eq!(map.len(), 0);
223
224        let trait_id = ShapeId::new("smithy.api#required");
225        let test_trait = Box::new(TestTrait {
226            id: trait_id.clone(),
227            value: "test".to_string(),
228        });
229
230        map.insert(test_trait);
231        assert!(!map.is_empty());
232        assert_eq!(map.len(), 1);
233        assert!(map.contains(&trait_id));
234
235        let retrieved = map.get(&trait_id);
236        assert!(retrieved.is_some());
237    }
238
239    #[test]
240    fn test_schema_ext() {
241        let schema = TestSchema {
242            id: ShapeId::new("com.example#MyStruct"),
243            shape_type: ShapeType::Structure,
244            traits: TraitMap::new(),
245        };
246
247        assert!(schema.is_structure());
248        assert!(!schema.is_union());
249        assert!(!schema.is_list());
250        assert!(!schema.is_member());
251    }
252
253    #[test]
254    fn test_schema_basic() {
255        let schema = TestSchema {
256            id: ShapeId::new("smithy.api#String"),
257            shape_type: ShapeType::String,
258            traits: TraitMap::new(),
259        };
260
261        assert_eq!(schema.shape_id().as_str(), "smithy.api#String");
262        assert_eq!(schema.shape_type(), ShapeType::String);
263        assert!(schema.traits().is_empty());
264        assert!(schema.member_name().is_none());
265        assert!(schema.member_schema("test").is_none());
266        assert!(schema.member_schema_by_index(0).is_none());
267    }
268}