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