Skip to main content

aws_smithy_schema/schema/
trait_map.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6use crate::{ShapeId, Trait};
7use std::collections::HashMap;
8
9/// A map of traits keyed by their Shape ID.
10///
11/// This provides efficient lookup of traits during serialization and deserialization.
12///
13/// # Cross-lifetime lookup
14///
15/// Internal storage is keyed by `ShapeId<'static>` because traits come from
16/// codegen-emitted schemas (themselves `'static`). The public `get` and
17/// `contains` accessors accept `&ShapeId<'_>` of any lifetime — runtime-
18/// built shape IDs (e.g. parsed from a wire-format `__type` field) are
19/// looked up against the `'static`-keyed table via `ShapeId`'s
20/// `Borrow<str>` impl, which keys on the fully qualified name.
21#[derive(Debug)]
22pub struct TraitMap {
23    // Wrapped in `Option` because `HashMap::new()` is not `const fn` in stable Rust,
24    // allowing `TraitMap::EMPTY` to be used in const contexts (e.g. prelude schemas).
25    traits: Option<HashMap<ShapeId<'static>, Box<dyn Trait>>>,
26}
27
28impl Default for TraitMap {
29    fn default() -> Self {
30        Self::new()
31    }
32}
33
34impl TraitMap {
35    /// An empty trait map for use in const contexts (e.g. prelude schemas).
36    pub const EMPTY: Self = Self { traits: None };
37
38    /// Creates a new empty TraitMap.
39    pub fn new() -> Self {
40        Self {
41            traits: Some(HashMap::new()),
42        }
43    }
44
45    /// Inserts a trait into the map.
46    pub fn insert(&mut self, trait_obj: Box<dyn Trait>) {
47        let id = trait_obj.trait_id().clone();
48        self.traits
49            .get_or_insert_with(HashMap::new)
50            .insert(id, trait_obj);
51    }
52
53    /// Gets a trait by its Shape ID.
54    ///
55    /// Accepts a [`ShapeId`] of any lifetime — lookup is by fully
56    /// qualified name. A `ShapeId<'static>` constructed via the
57    /// `shape_id!` macro and a runtime-built `ShapeId<'_>` with the
58    /// same FQN resolve to the same entry.
59    pub fn get(&self, id: &ShapeId<'_>) -> Option<&dyn Trait> {
60        // `id.as_str()` returns `&str`; the map's `Borrow<str>` impl
61        // (via `ShapeId`'s `Borrow<str>`) makes this a direct lookup.
62        self.traits.as_ref()?.get(id.as_str()).map(|t| t.as_ref())
63    }
64
65    /// Looks up a trait by its fully qualified name (e.g.
66    /// `"smithy.api#sensitive"`).
67    ///
68    /// Equivalent to [`Self::get`] when the caller already has the FQN
69    /// as a string slice — for example, after extracting the
70    /// `__type` field from a JSON document.
71    ///
72    /// # Example
73    ///
74    /// ```
75    /// use aws_smithy_schema::traits::JsonNameTrait;
76    /// use aws_smithy_schema::{shape_id, ShapeId, TraitMap};
77    ///
78    /// let mut traits = TraitMap::new();
79    /// traits.insert(Box::new(JsonNameTrait::new("custom_name")));
80    ///
81    /// // Cross-lifetime: a static-lifetime ShapeId from `shape_id!` and a
82    /// // runtime-built ShapeId<'_> with the same FQN both resolve via
83    /// // `Borrow<str>`. The companion `_fqn` accessor takes `&str` directly.
84    /// assert!(traits.get(&shape_id!("smithy.api", "jsonName")).is_some());
85    /// assert!(traits.get_fqn("smithy.api#jsonName").is_some());
86    /// ```
87    pub fn get_fqn(&self, fqn: &str) -> Option<&dyn Trait> {
88        self.traits.as_ref()?.get(fqn).map(|t| t.as_ref())
89    }
90
91    /// Returns true if the map contains a trait with the given Shape ID.
92    ///
93    /// Accepts a [`ShapeId`] of any lifetime; see [`Self::get`].
94    pub fn contains(&self, id: &ShapeId<'_>) -> bool {
95        self.traits
96            .as_ref()
97            .is_some_and(|m| m.contains_key(id.as_str()))
98    }
99
100    /// Returns true if the map contains a trait with the given fully
101    /// qualified name. See [`Self::get_fqn`].
102    pub fn contains_fqn(&self, fqn: &str) -> bool {
103        self.traits.as_ref().is_some_and(|m| m.contains_key(fqn))
104    }
105
106    /// Returns the number of traits in the map.
107    pub fn len(&self) -> usize {
108        self.traits.as_ref().map_or(0, |m| m.len())
109    }
110
111    /// Returns true if the map is empty.
112    pub fn is_empty(&self) -> bool {
113        self.traits.as_ref().is_none_or(|m| m.is_empty())
114    }
115}