Skip to main content

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    pub mod traits;
21
22    pub mod codec;
23    pub mod header_omit_settings;
24    pub mod http_protocol;
25    pub mod prelude;
26    pub mod protocol;
27    pub mod serde;
28}
29
30pub use schema::shape_id::ShapeId;
31pub use schema::shape_type::ShapeType;
32pub use schema::trait_map::TraitMap;
33pub use schema::trait_type::Trait;
34pub use schema::trait_type::{AnnotationTrait, DocumentTrait, StringTrait};
35
36pub mod prelude {
37    pub use crate::schema::prelude::*;
38}
39
40pub mod serde {
41    pub use crate::schema::serde::*;
42}
43
44pub mod traits {
45    pub use crate::schema::traits::*;
46}
47
48pub mod codec {
49    pub use crate::schema::codec::*;
50}
51
52pub mod header_omit_settings {
53    pub use crate::schema::header_omit_settings::*;
54}
55
56pub mod protocol {
57    pub use crate::schema::protocol::*;
58}
59
60pub mod http_protocol {
61    pub use crate::schema::http_protocol::*;
62}
63
64/// A Smithy schema — a lightweight runtime representation of a Smithy shape.
65///
66/// Contains the shape's ID, type, traits relevant to serialization, and
67/// references to member schemas (for aggregate types).
68///
69/// Schemas are constructed at compile time (via `const`) for generated code
70/// and prelude types. The Smithy type system is closed, so no extensibility
71/// via trait objects is needed.
72use schema::traits as trait_types;
73
74#[derive(Debug)]
75pub struct Schema {
76    id: ShapeId,
77    shape_type: ShapeType,
78    /// Member name if this is a member schema.
79    member_name: Option<&'static str>,
80    /// Member index for position-based lookup in generated code.
81    member_index: Option<usize>,
82    /// Shape-type-specific member data.
83    members: SchemaMembers,
84
85    /// The pre-synthesis shape name for synthetic operation input/output
86    /// shapes.
87    ///
88    /// Smithy's `OperationNormalizer` rewrites every operation's input/output
89    /// to a synthetic shape named `OperationNameInput` / `OperationNameOutput`;
90    /// this field surfaces the name component of
91    /// `SyntheticInputTrait::originalId` / `SyntheticOutputTrait::originalId`,
92    /// which preserves the user-authored shape name. `None` for non-synthetic
93    /// shapes and for member schemas.
94    ///
95    /// Currently consumed by the REST XML codec to determine the body root
96    /// element name when no `@xmlName` overrides it. Distinct from `xml_name`,
97    /// which carries an `@xmlName` trait value. Other consumers (logging,
98    /// future protocols) may also read this field.
99    original_name: Option<&'static str>,
100
101    // -- Known serde trait fields (const-constructable) --
102    // IMPORTANT: These fields and their `with_*` setters must stay in sync with
103    // `knownTraitSetter` in `SchemaGenerator.kt`. If a new known trait is added
104    // here, a corresponding entry must be added in the codegen.
105    sensitive: Option<trait_types::SensitiveTrait>,
106    json_name: Option<trait_types::JsonNameTrait>,
107    timestamp_format: Option<trait_types::TimestampFormatTrait>,
108    xml_name: Option<trait_types::XmlNameTrait>,
109    xml_attribute: Option<trait_types::XmlAttributeTrait>,
110    xml_flattened: Option<trait_types::XmlFlattenedTrait>,
111    /// Marks an operation output struct whose XML wire format omits the
112    /// outer wrapper element (set by codegen for operations carrying the
113    /// AWS-customization `S3UnwrappedXmlOutputTrait`). Honored by the XML
114    /// codec; ignored by other codecs (so the schema remains protocol-neutral
115    /// — runtime protocol swap is unaffected).
116    xml_unwrapped_output: bool,
117    /// `true` (the default, conservative) means this struct has at least one
118    /// member that serializes to the request/response body — i.e., a member
119    /// without any HTTP binding trait, OR a member with `@httpPayload`
120    /// targeting a struct/union (which provides body framing through the codec).
121    ///
122    /// `false` (set by codegen via [`with_no_body_members`](Schema::with_no_body_members))
123    /// means every member is HTTP-bound (header / query / label / prefix-headers
124    /// / query-params, or scalar `@httpPayload` whose bytes go into the request
125    /// body raw). The HTTP binding protocol uses this to skip body codec
126    /// invocation entirely on the request side: no XML/JSON wrapper element
127    /// is opened, no `serialize_members` re-entry through the codec proxy
128    /// happens, and the body bytes are never collected (they'd be discarded
129    /// anyway). Saves ~15-20% on header-heavy SER cases like S3 PutObject.
130    has_body_members: bool,
131    xml_namespace: Option<trait_types::XmlNamespaceTrait>,
132    http_header: Option<trait_types::HttpHeaderTrait>,
133    http_label: Option<trait_types::HttpLabelTrait>,
134    http_payload: Option<trait_types::HttpPayloadTrait>,
135    http_prefix_headers: Option<trait_types::HttpPrefixHeadersTrait>,
136    http_query: Option<trait_types::HttpQueryTrait>,
137    http_query_params: Option<trait_types::HttpQueryParamsTrait>,
138    http_response_code: Option<trait_types::HttpResponseCodeTrait>,
139    /// The `@http` trait — an operation-level trait included on the input schema
140    /// for convenience so the protocol serializer can construct the request URI.
141    http: Option<trait_types::HttpTrait>,
142    streaming: Option<trait_types::StreamingTrait>,
143    event_header: Option<trait_types::EventHeaderTrait>,
144    event_payload: Option<trait_types::EventPayloadTrait>,
145    host_label: Option<trait_types::HostLabelTrait>,
146    media_type: Option<trait_types::MediaTypeTrait>,
147
148    /// Fallback for unknown/custom traits. `None` in const contexts (no allocation).
149    traits: Option<&'static std::sync::LazyLock<TraitMap>>,
150}
151
152/// Shape-type-specific member references.
153#[derive(Debug)]
154enum SchemaMembers {
155    /// No members (simple types).
156    None,
157    /// Structure or union members.
158    Struct { members: &'static [&'static Schema] },
159    /// List member schema.
160    List { member: &'static Schema },
161    /// Map key and value schemas.
162    Map {
163        key: &'static Schema,
164        value: &'static Schema,
165    },
166}
167
168impl Schema {
169    /// Default values for all trait fields (should only be used by constructors as a spread source).
170    const EMPTY_TRAITS: Self = Self {
171        id: ShapeId::from_static("", "", ""),
172        shape_type: ShapeType::Boolean,
173        member_name: None,
174        member_index: None,
175        members: SchemaMembers::None,
176        original_name: None,
177        sensitive: None,
178        json_name: None,
179        timestamp_format: None,
180        xml_name: None,
181        xml_attribute: None,
182        xml_flattened: None,
183        xml_unwrapped_output: false,
184        has_body_members: true,
185        xml_namespace: None,
186        http_header: None,
187        http_label: None,
188        http_payload: None,
189        http_prefix_headers: None,
190        http_query: None,
191        http_query_params: None,
192        http_response_code: None,
193        http: None,
194        streaming: None,
195        event_header: None,
196        event_payload: None,
197        host_label: None,
198        media_type: None,
199        traits: None,
200    };
201
202    /// Creates a schema for a simple type (no members).
203    pub const fn new(id: ShapeId, shape_type: ShapeType) -> Self {
204        Self {
205            id,
206            shape_type,
207            ..Self::EMPTY_TRAITS
208        }
209    }
210
211    /// Creates a schema for a structure or union type.
212    pub const fn new_struct(
213        id: ShapeId,
214        shape_type: ShapeType,
215        members: &'static [&'static Schema],
216    ) -> Self {
217        Self {
218            id,
219            shape_type,
220            members: SchemaMembers::Struct { members },
221            ..Self::EMPTY_TRAITS
222        }
223    }
224
225    /// Creates a schema for a list type.
226    pub const fn new_list(id: ShapeId, member: &'static Schema) -> Self {
227        Self {
228            id,
229            shape_type: ShapeType::List,
230            members: SchemaMembers::List { member },
231            ..Self::EMPTY_TRAITS
232        }
233    }
234
235    /// Creates a schema for a map type.
236    pub const fn new_map(id: ShapeId, key: &'static Schema, value: &'static Schema) -> Self {
237        Self {
238            id,
239            shape_type: ShapeType::Map,
240            members: SchemaMembers::Map { key, value },
241            ..Self::EMPTY_TRAITS
242        }
243    }
244
245    /// Creates a member schema wrapping a target schema.
246    pub const fn new_member(
247        id: ShapeId,
248        shape_type: ShapeType,
249        member_name: &'static str,
250        member_index: usize,
251    ) -> Self {
252        Self {
253            id,
254            shape_type,
255            member_name: Some(member_name),
256            member_index: Some(member_index),
257            ..Self::EMPTY_TRAITS
258        }
259    }
260
261    /// Returns the Shape ID of this schema.
262    pub fn shape_id(&self) -> &ShapeId {
263        &self.id
264    }
265
266    /// Returns the shape type.
267    pub fn shape_type(&self) -> ShapeType {
268        self.shape_type
269    }
270
271    /// Returns the fallback trait map for unknown/custom traits.
272    pub fn traits(&self) -> Option<&TraitMap> {
273        self.traits.map(|lazy| &**lazy)
274    }
275
276    // -- Known trait accessors --
277
278    /// Returns the `@sensitive` trait if present.
279    pub fn sensitive(&self) -> Option<&trait_types::SensitiveTrait> {
280        self.sensitive.as_ref()
281    }
282
283    /// Returns the `@jsonName` value if present.
284    pub fn json_name(&self) -> Option<&trait_types::JsonNameTrait> {
285        self.json_name.as_ref()
286    }
287
288    /// Returns the `@timestampFormat` if present.
289    pub fn timestamp_format(&self) -> Option<&trait_types::TimestampFormatTrait> {
290        self.timestamp_format.as_ref()
291    }
292
293    /// Returns the `@xmlName` value if present.
294    pub fn xml_name(&self) -> Option<&trait_types::XmlNameTrait> {
295        self.xml_name.as_ref()
296    }
297
298    /// Returns the `@xmlNamespace` value if present.
299    pub fn xml_namespace(&self) -> Option<&trait_types::XmlNamespaceTrait> {
300        self.xml_namespace.as_ref()
301    }
302
303    /// Returns `true` if this member has the `@xmlAttribute` trait.
304    pub fn xml_attribute(&self) -> bool {
305        self.xml_attribute.is_some()
306    }
307
308    /// Returns `true` if this member has the `@xmlFlattened` trait.
309    pub fn xml_flattened(&self) -> bool {
310        self.xml_flattened.is_some()
311    }
312
313    /// Returns `true` if this struct's XML wire format omits the outer
314    /// wrapper element. See field doc for details.
315    pub fn xml_unwrapped_output(&self) -> bool {
316        self.xml_unwrapped_output
317    }
318
319    /// Returns `true` if this struct has at least one member that serializes
320    /// to the request/response body, `false` if every member is HTTP-bound.
321    pub fn has_body_members(&self) -> bool {
322        self.has_body_members
323    }
324
325    /// Returns the `@httpHeader` value if present.
326    /// Returns `true` if this member schema has any HTTP response binding trait
327    /// (`@httpHeader`, `@httpResponseCode`, `@httpPrefixHeaders`, or `@httpPayload`).
328    pub fn has_http_response_binding(&self) -> bool {
329        self.http_header.is_some()
330            || self.http_response_code.is_some()
331            || self.http_prefix_headers.is_some()
332            || self.http_payload.is_some()
333    }
334
335    pub fn http_header(&self) -> Option<&trait_types::HttpHeaderTrait> {
336        self.http_header.as_ref()
337    }
338
339    /// Returns the `@httpQuery` value if present.
340    pub fn http_query(&self) -> Option<&trait_types::HttpQueryTrait> {
341        self.http_query.as_ref()
342    }
343
344    /// Returns the `@httpLabel` trait if present.
345    pub fn http_label(&self) -> Option<&trait_types::HttpLabelTrait> {
346        self.http_label.as_ref()
347    }
348
349    /// Returns the `@httpPayload` trait if present.
350    pub fn http_payload(&self) -> Option<&trait_types::HttpPayloadTrait> {
351        self.http_payload.as_ref()
352    }
353
354    /// Returns the `@httpPrefixHeaders` value if present.
355    pub fn http_prefix_headers(&self) -> Option<&trait_types::HttpPrefixHeadersTrait> {
356        self.http_prefix_headers.as_ref()
357    }
358
359    /// Returns the `@mediaType` trait if present.
360    pub fn media_type(&self) -> Option<&trait_types::MediaTypeTrait> {
361        self.media_type.as_ref()
362    }
363
364    /// Returns the `@httpQueryParams` trait if present.
365    pub fn http_query_params(&self) -> Option<&trait_types::HttpQueryParamsTrait> {
366        self.http_query_params.as_ref()
367    }
368
369    /// Returns the `@httpResponseCode` trait if present.
370    pub fn http_response_code(&self) -> Option<&trait_types::HttpResponseCodeTrait> {
371        self.http_response_code.as_ref()
372    }
373
374    /// Returns the `@http` trait if present.
375    ///
376    /// This is an operation-level trait included on the input schema for
377    /// convenience so the protocol serializer can construct the request URI.
378    pub fn http(&self) -> Option<&trait_types::HttpTrait> {
379        self.http.as_ref()
380    }
381
382    // -- Const setters for builder-style construction in generated code --
383
384    /// Sets the original (pre-synthesis) shape name for synthetic operation
385    /// input/output shapes. See [`Schema::original_name`] for semantics.
386    pub const fn with_original_name(mut self, name: &'static str) -> Self {
387        self.original_name = Some(name);
388        self
389    }
390
391    /// Attaches key and value member schemas to a map member schema.
392    /// Used by the XML codec to resolve `<key>` and `<value>` element names.
393    pub const fn with_map_members(mut self, key: &'static Schema, value: &'static Schema) -> Self {
394        self.members = SchemaMembers::Map { key, value };
395        self
396    }
397
398    /// Sets the list member schema on a member schema that targets a list.
399    pub const fn with_list_member(mut self, member: &'static Schema) -> Self {
400        self.members = SchemaMembers::List { member };
401        self
402    }
403
404    /// Sets the `@sensitive` trait.
405    pub const fn with_sensitive(mut self) -> Self {
406        self.sensitive = Some(trait_types::SensitiveTrait);
407        self
408    }
409
410    /// Sets the `@jsonName` trait.
411    pub const fn with_json_name(mut self, value: &'static str) -> Self {
412        self.json_name = Some(trait_types::JsonNameTrait::new(value));
413        self
414    }
415
416    /// Sets the `@timestampFormat` trait.
417    pub const fn with_timestamp_format(mut self, format: trait_types::TimestampFormat) -> Self {
418        self.timestamp_format = Some(trait_types::TimestampFormatTrait::new(format));
419        self
420    }
421
422    /// Sets the `@xmlName` trait.
423    pub const fn with_xml_name(mut self, value: &'static str) -> Self {
424        self.xml_name = Some(trait_types::XmlNameTrait::new(value));
425        self
426    }
427
428    /// Sets the `@xmlAttribute` trait.
429    pub const fn with_xml_attribute(mut self) -> Self {
430        self.xml_attribute = Some(trait_types::XmlAttributeTrait);
431        self
432    }
433
434    /// Sets the `@xmlFlattened` trait.
435    pub const fn with_xml_flattened(mut self) -> Self {
436        self.xml_flattened = Some(trait_types::XmlFlattenedTrait);
437        self
438    }
439
440    /// Marks the struct as an unwrapped XML output. See field doc for details.
441    pub const fn with_xml_unwrapped_output(mut self) -> Self {
442        self.xml_unwrapped_output = true;
443        self
444    }
445
446    /// Marks this struct as having no body members — every member is HTTP-bound.
447    /// See [`has_body_members`](Schema::has_body_members) for what this enables.
448    pub const fn with_no_body_members(mut self) -> Self {
449        self.has_body_members = false;
450        self
451    }
452
453    /// Sets the `@httpHeader` trait.
454    pub const fn with_http_header(mut self, value: &'static str) -> Self {
455        self.http_header = Some(trait_types::HttpHeaderTrait::new(value));
456        self
457    }
458
459    /// Sets the `@httpLabel` trait.
460    pub const fn with_http_label(mut self) -> Self {
461        self.http_label = Some(trait_types::HttpLabelTrait);
462        self
463    }
464
465    /// Sets the `@httpPayload` trait.
466    pub const fn with_http_payload(mut self) -> Self {
467        self.http_payload = Some(trait_types::HttpPayloadTrait);
468        self
469    }
470
471    /// Sets the `@httpPrefixHeaders` trait.
472    pub const fn with_http_prefix_headers(mut self, value: &'static str) -> Self {
473        self.http_prefix_headers = Some(trait_types::HttpPrefixHeadersTrait::new(value));
474        self
475    }
476
477    /// Sets the `@httpQuery` trait.
478    pub const fn with_http_query(mut self, value: &'static str) -> Self {
479        self.http_query = Some(trait_types::HttpQueryTrait::new(value));
480        self
481    }
482
483    /// Sets the `@httpQueryParams` trait.
484    pub const fn with_http_query_params(mut self) -> Self {
485        self.http_query_params = Some(trait_types::HttpQueryParamsTrait);
486        self
487    }
488
489    /// Sets the `@httpResponseCode` trait.
490    pub const fn with_http_response_code(mut self) -> Self {
491        self.http_response_code = Some(trait_types::HttpResponseCodeTrait);
492        self
493    }
494
495    /// Sets the `@http` trait (operation-level, included on input schema for convenience).
496    pub const fn with_http(mut self, http: trait_types::HttpTrait) -> Self {
497        self.http = Some(http);
498        self
499    }
500
501    /// Sets the `@streaming` trait.
502    pub const fn with_streaming(mut self) -> Self {
503        self.streaming = Some(trait_types::StreamingTrait);
504        self
505    }
506
507    /// Sets the `@eventHeader` trait.
508    pub const fn with_event_header(mut self) -> Self {
509        self.event_header = Some(trait_types::EventHeaderTrait);
510        self
511    }
512
513    /// Sets the `@eventPayload` trait.
514    pub const fn with_event_payload(mut self) -> Self {
515        self.event_payload = Some(trait_types::EventPayloadTrait);
516        self
517    }
518
519    /// Sets the `@hostLabel` trait.
520    pub const fn with_host_label(mut self) -> Self {
521        self.host_label = Some(trait_types::HostLabelTrait);
522        self
523    }
524
525    /// Sets the `@mediaType` trait.
526    pub const fn with_media_type(mut self, value: &'static str) -> Self {
527        self.media_type = Some(trait_types::MediaTypeTrait::new(value));
528        self
529    }
530
531    /// Sets the `@xmlNamespace` trait.
532    ///
533    /// `uri` is the namespace URI; `prefix` optionally declares the
534    /// `xmlns:prefix` form. Pass `None` for the default (unprefixed)
535    /// `xmlns="uri"` declaration.
536    pub const fn with_xml_namespace(
537        mut self,
538        uri: &'static str,
539        prefix: Option<&'static str>,
540    ) -> Self {
541        self.xml_namespace = Some(trait_types::XmlNamespaceTrait::new(uri, prefix));
542        self
543    }
544
545    /// Sets the fallback trait map for unknown/custom traits.
546    pub const fn with_traits(mut self, traits: &'static std::sync::LazyLock<TraitMap>) -> Self {
547        self.traits = Some(traits);
548        self
549    }
550
551    /// Returns the member name if this is a member schema.
552    ///
553    /// Returns `Option<&'static str>` so callers can store the name in
554    /// `Cow::Borrowed` or other `'static`-lifetime contexts without
555    /// allocating, matching the underlying field type.
556    pub fn member_name(&self) -> Option<&'static str> {
557        self.member_name
558    }
559
560    /// Returns the member index for member schemas.
561    ///
562    /// This is used internally by generated code for efficient member lookup.
563    /// Consumer code should not rely on specific position values as they may change.
564    pub fn member_index(&self) -> Option<usize> {
565        self.member_index
566    }
567
568    /// Returns the original (pre-synthesis) shape name for synthetic operation
569    /// input/output shapes.
570    ///
571    /// `None` for non-synthetic shapes. See the field documentation for full
572    /// semantics.
573    pub fn original_name(&self) -> Option<&str> {
574        self.original_name
575    }
576
577    /// Returns the member schema by name (for structures and unions).
578    pub fn member_schema(&self, name: &str) -> Option<&Schema> {
579        match &self.members {
580            SchemaMembers::Struct { members } => members
581                .iter()
582                .find(|m| m.member_name == Some(name))
583                .copied(),
584            _ => None,
585        }
586    }
587
588    /// Returns the member name and schema by position index (for structures and unions).
589    ///
590    /// This is an optimization for generated code to avoid string lookups.
591    /// Consumer code should not rely on specific position values as they may change.
592    pub fn member_schema_by_index(&self, index: usize) -> Option<&Schema> {
593        match &self.members {
594            SchemaMembers::Struct { members } => members.get(index).copied(),
595            _ => None,
596        }
597    }
598
599    /// Returns the member schemas (for structures and unions).
600    pub fn members(&self) -> &[&Schema] {
601        match &self.members {
602            SchemaMembers::Struct { members } => members,
603            _ => &[],
604        }
605    }
606
607    /// Returns the member schema for collections (list member or map value).
608    pub fn member(&self) -> Option<&Schema> {
609        match &self.members {
610            SchemaMembers::List { member } => Some(member),
611            SchemaMembers::Map { value, .. } => Some(value),
612            _ => None,
613        }
614    }
615
616    /// Like [`member`](Self::member) but returns the `'static` borrow that
617    /// codegen actually stores. Needed when callers (e.g. the XML codec)
618    /// must hold a reference to a value/element member schema across nested
619    /// callbacks without inheriting the parent borrow's lifetime.
620    pub fn member_static(&self) -> Option<&'static Schema> {
621        match &self.members {
622            SchemaMembers::List { member } => Some(*member),
623            SchemaMembers::Map { value, .. } => Some(*value),
624            _ => None,
625        }
626    }
627
628    /// Returns the key schema for maps.
629    pub fn key(&self) -> Option<&Schema> {
630        match &self.members {
631            SchemaMembers::Map { key, .. } => Some(key),
632            _ => None,
633        }
634    }
635
636    /// Like [`key`](Self::key) but returns the `'static` borrow that codegen
637    /// stores. See [`member_static`](Self::member_static).
638    pub fn key_static(&self) -> Option<&'static Schema> {
639        match &self.members {
640            SchemaMembers::Map { key, .. } => Some(*key),
641            _ => None,
642        }
643    }
644
645    // -- convenience predicates --
646
647    /// Returns true if this is a member schema.
648    pub fn is_member(&self) -> bool {
649        self.shape_type.is_member()
650    }
651
652    /// Returns true if this is a structure schema.
653    pub fn is_structure(&self) -> bool {
654        self.shape_type == ShapeType::Structure
655    }
656
657    /// Returns true if this is a union schema.
658    pub fn is_union(&self) -> bool {
659        self.shape_type == ShapeType::Union
660    }
661
662    /// Returns true if this is a list schema.
663    pub fn is_list(&self) -> bool {
664        self.shape_type == ShapeType::List
665    }
666
667    /// Returns true if this is a map schema.
668    pub fn is_map(&self) -> bool {
669        self.shape_type == ShapeType::Map
670    }
671
672    /// Returns true if this is a blob schema.
673    pub fn is_blob(&self) -> bool {
674        self.shape_type == ShapeType::Blob
675    }
676
677    /// Returns true if this is a string schema.
678    pub fn is_string(&self) -> bool {
679        self.shape_type == ShapeType::String
680    }
681}
682
683#[cfg(test)]
684mod test {
685    use crate::{shape_id, Schema, ShapeType, Trait, TraitMap};
686
687    // Simple test trait implementation
688    #[derive(Debug)]
689    struct TestTrait {
690        id: crate::ShapeId,
691        #[allow(dead_code)]
692        value: String,
693    }
694
695    impl Trait for TestTrait {
696        fn trait_id(&self) -> &crate::ShapeId {
697            &self.id
698        }
699
700        fn as_any(&self) -> &dyn std::any::Any {
701            self
702        }
703    }
704
705    #[test]
706    fn test_shape_type_simple() {
707        assert!(ShapeType::String.is_simple());
708        assert!(ShapeType::Integer.is_simple());
709        assert!(ShapeType::Boolean.is_simple());
710        assert!(!ShapeType::Structure.is_simple());
711        assert!(!ShapeType::List.is_simple());
712    }
713
714    #[test]
715    fn test_shape_type_aggregate() {
716        assert!(ShapeType::Structure.is_aggregate());
717        assert!(ShapeType::Union.is_aggregate());
718        assert!(ShapeType::List.is_aggregate());
719        assert!(ShapeType::Map.is_aggregate());
720        assert!(!ShapeType::String.is_aggregate());
721    }
722
723    #[test]
724    fn test_shape_type_member() {
725        assert!(ShapeType::Member.is_member());
726        assert!(!ShapeType::String.is_member());
727        assert!(!ShapeType::Structure.is_member());
728    }
729
730    #[test]
731    fn test_shape_id_parsing() {
732        let id = shape_id!("smithy.api", "String");
733        assert_eq!(id.namespace(), "smithy.api");
734        assert_eq!(id.shape_name(), "String");
735        assert_eq!(id.member_name(), None);
736    }
737
738    #[test]
739    fn test_shape_id_with_member() {
740        let id = shape_id!("com.example", "MyStruct", "member");
741        assert_eq!(id.namespace(), "com.example");
742        assert_eq!(id.shape_name(), "MyStruct");
743        assert_eq!(id.member_name(), Some("member"));
744    }
745
746    #[test]
747    fn test_trait_map() {
748        let mut map = TraitMap::new();
749        assert!(map.is_empty());
750        assert_eq!(map.len(), 0);
751
752        let trait_id = shape_id!("smithy.api", "required");
753        let test_trait = Box::new(TestTrait {
754            id: trait_id,
755            value: "test".to_string(),
756        });
757
758        map.insert(test_trait);
759        assert!(!map.is_empty());
760        assert_eq!(map.len(), 1);
761        assert!(map.contains(&trait_id));
762
763        let retrieved = map.get(&trait_id);
764        assert!(retrieved.is_some());
765    }
766
767    #[test]
768    fn test_schema_predicates() {
769        let schema = Schema::new(shape_id!("com.example", "MyStruct"), ShapeType::Structure);
770
771        assert!(schema.is_structure());
772        assert!(!schema.is_union());
773        assert!(!schema.is_list());
774        assert!(!schema.is_member());
775    }
776
777    #[test]
778    fn test_schema_basic() {
779        let schema = Schema::new(shape_id!("smithy.api", "String"), ShapeType::String);
780
781        assert_eq!(schema.shape_id().as_str(), "smithy.api#String");
782        assert_eq!(schema.shape_type(), ShapeType::String);
783        assert!(schema.traits().is_none());
784        assert!(schema.member_name().is_none());
785        assert!(schema.member_schema("test").is_none());
786        assert!(schema.member_schema_by_index(0).is_none());
787    }
788}