aws_smithy_schema/schema/
shape_id.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6use std::borrow::Cow;
7
8/// A Smithy Shape ID.
9///
10/// Shape IDs uniquely identify shapes in a Smithy model.
11/// - `fqn` is `"smithy.example#Foo"`
12/// - `namespace` is `"smithy.example"`
13/// - `shape_name` is `"Foo"`
14#[derive(Debug, Clone, PartialEq, Eq, Hash)]
15pub struct ShapeId {
16    fqn: Cow<'static, str>,
17    namespace: Cow<'static, str>,
18    shape_name: Cow<'static, str>,
19}
20
21impl ShapeId {
22    /// Creates a ShapeId from a static string at compile time.
23    ///
24    /// This is used for const initialization of prelude schemas.
25    pub const fn from_static(
26        fqn: &'static str,
27        namespace: &'static str,
28        shape_name: &'static str,
29    ) -> Self {
30        Self {
31            fqn: Cow::Borrowed(fqn),
32            namespace: Cow::Borrowed(namespace),
33            shape_name: Cow::Borrowed(shape_name),
34        }
35    }
36
37    /// Creates a new ShapeId from a namespace and a shape_name.
38    ///
39    /// # Examples
40    /// ```
41    /// use aws_smithy_schema::ShapeId;
42    ///
43    /// let shape_id = ShapeId::new("smithy.api#String");
44    /// ```
45    pub fn new_from_parts(
46        namespace: impl Into<Cow<'static, str>>,
47        shape_name: impl Into<Cow<'static, str>>,
48    ) -> Self {
49        let namespace = namespace.into();
50        let shape_name = shape_name.into();
51        Self {
52            fqn: format!("{}#{}", namespace.as_ref(), shape_name.as_ref()).into(),
53            namespace,
54            shape_name,
55        }
56    }
57
58    /// Creates a new ShapeId from a fully qualified name.
59    pub fn new_from_fqn(fqn: impl Into<Cow<'static, str>>) -> Option<Self> {
60        let fqn = fqn.into();
61        let (namespace, shape_name) = fqn.as_ref().split_once('#').map(|(ns, rest)| {
62            (
63                ns.to_string(),
64                rest.split_once('$')
65                    .map_or(rest, |(name, _)| name)
66                    .to_string(),
67            )
68        })?;
69        Some(Self {
70            fqn,
71            namespace: namespace.into(),
72            shape_name: shape_name.into(),
73        })
74    }
75
76    /// Creates a new ShapeId from a fully qualified name.
77    pub fn new(fqn: impl Into<Cow<'static, str>>) -> Self {
78        Self::new_from_fqn(fqn).expect("invalid shape ID")
79    }
80
81    /// Returns the string representation of this ShapeId.
82    pub fn as_str(&self) -> &str {
83        self.fqn.as_ref()
84    }
85
86    /// Returns the namespace portion of the ShapeId.
87    ///
88    /// # Examples
89    /// ```
90    /// use aws_smithy_schema::ShapeId;
91    ///
92    /// let shape_id = ShapeId::new("smithy.api#String");
93    /// assert_eq!(shape_id.namespace(), "smithy.api");
94    /// ```
95    pub fn namespace(&self) -> &str {
96        self.namespace.as_ref()
97    }
98
99    /// Returns the shape name portion of the ShapeId.
100    ///
101    /// # Examples
102    /// ```
103    /// use aws_smithy_schema::ShapeId;
104    ///
105    /// let shape_id = ShapeId::new("smithy.api#String");
106    /// assert_eq!(shape_id.shape_name(), "String");
107    /// ```
108    pub fn shape_name(&self) -> &str {
109        self.shape_name.as_ref()
110    }
111
112    /// Returns the member name if this is a member shape ID.
113    ///
114    /// # Examples
115    /// ```
116    /// use aws_smithy_schema::ShapeId;
117    ///
118    /// let shape_id = ShapeId::new("com.example#MyStruct$member");
119    /// assert_eq!(shape_id.member_name(), Some("member"));
120    /// ```
121    pub fn member_name(&self) -> Option<&str> {
122        self.fqn
123            .split_once('#')
124            .and_then(|(_, rest)| rest.split_once('$').map(|(_, member)| member))
125    }
126}
127
128impl From<String> for ShapeId {
129    fn from(value: String) -> Self {
130        Self::new(value)
131    }
132}
133
134impl From<&str> for ShapeId {
135    fn from(value: &str) -> Self {
136        Self::new(value.to_string())
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143
144    #[test]
145    fn test_new() {
146        let shape_id = ShapeId::new("smithy.api#String");
147        assert_eq!(shape_id.as_str(), "smithy.api#String");
148    }
149
150    #[test]
151    fn test_namespace() {
152        assert_eq!(ShapeId::new("smithy.api#String").namespace(), "smithy.api");
153        assert_eq!(
154            ShapeId::new("com.example#MyStruct$member").namespace(),
155            "com.example"
156        );
157    }
158
159    #[test]
160    fn test_shape_name() {
161        assert_eq!(ShapeId::new("smithy.api#String").shape_name(), "String");
162        assert_eq!(
163            ShapeId::new("com.example#MyStruct$member").shape_name(),
164            "MyStruct"
165        );
166    }
167
168    #[test]
169    fn test_member_name() {
170        assert_eq!(
171            ShapeId::new("com.example#MyStruct$member").member_name(),
172            Some("member")
173        );
174        assert_eq!(ShapeId::new("smithy.api#String").member_name(), None);
175    }
176
177    #[test]
178    fn test_from_string() {
179        let shape_id: ShapeId = String::from("smithy.api#String").into();
180        assert_eq!(shape_id.as_str(), "smithy.api#String");
181    }
182
183    #[test]
184    fn test_from_str() {
185        let shape_id: ShapeId = "smithy.api#String".into();
186        assert_eq!(shape_id.as_str(), "smithy.api#String");
187    }
188
189    #[test]
190    fn test_clone_and_equality() {
191        let shape_id1 = ShapeId::new("smithy.api#String");
192        let shape_id2 = shape_id1.clone();
193        assert_eq!(shape_id1, shape_id2);
194    }
195}