scale_info/ty/
path.rs

1// Copyright 2019-2022 Parity Technologies (UK) Ltd.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::prelude::{
16    fmt::{Display, Error as FmtError, Formatter},
17    iter,
18    vec::Vec,
19};
20
21use crate::{
22    form::{Form, MetaForm, PortableForm},
23    utils::is_rust_identifier,
24    IntoPortable, Registry,
25};
26use scale::Encode;
27#[cfg(feature = "serde")]
28use serde::{de::DeserializeOwned, Deserialize, Serialize};
29
30/// Represents the path of a type definition.
31///
32/// This consists of several segments that each have to be a valid Rust
33/// identifier. The first segment represents the crate name in which the type
34/// has been defined. The last segment is the identifier accessed with `ident()`.
35///
36/// Rust prelude type may have an empty namespace definition.
37#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
38#[cfg_attr(
39    feature = "serde",
40    serde(bound(
41        serialize = "T::Type: Serialize, T::String: Serialize",
42        deserialize = "T::Type: DeserializeOwned, T::String: DeserializeOwned",
43    ))
44)]
45#[cfg_attr(feature = "serde", serde(transparent))]
46#[cfg_attr(any(feature = "std", feature = "decode"), derive(scale::Decode))]
47#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
48#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Encode)]
49pub struct Path<T: Form = MetaForm> {
50    /// The segments of the namespace.
51    pub segments: Vec<T::String>,
52}
53
54impl<T> Default for Path<T>
55where
56    T: Form,
57{
58    fn default() -> Self {
59        Path {
60            segments: Vec::new(),
61        }
62    }
63}
64
65impl IntoPortable for Path {
66    type Output = Path<PortableForm>;
67
68    fn into_portable(self, _registry: &mut Registry) -> Self::Output {
69        Path {
70            segments: self.segments.into_iter().map(Into::into).collect(),
71        }
72    }
73}
74
75impl Display for Path<PortableForm> {
76    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
77        write!(f, "{}", self.segments.join("::"))
78    }
79}
80
81impl Path<MetaForm> {
82    /// Create a new Path
83    ///
84    /// # Panics
85    ///
86    /// - If the type identifier or module path contain invalid Rust identifiers
87    pub fn new(ident: &'static str, module_path: &'static str) -> Path {
88        let segments = module_path.split("::");
89        Self::from_segments(segments.chain(iter::once(ident)))
90            .expect("All path segments should be valid Rust identifiers")
91    }
92
93    /// Create a new Path
94    ///
95    /// The `segment_replace` is a list of `(search, replace)` items. Every
96    /// `search` item that appears in the `module_path` is replaced by the
97    /// `replace` item. This can be used for example to replace the crate name
98    /// or even the name of the type in the final [`Path`].
99    ///
100    /// # Panics
101    ///
102    /// - If the type identifier, module path or replace contain invalid Rust identifiers
103    pub fn new_with_replace(
104        ident: &'static str,
105        module_path: &'static str,
106        segment_replace: &[(&'static str, &'static str)],
107    ) -> Path {
108        let segments = module_path.split("::");
109        Self::from_segments(
110            segments
111                .chain(iter::once(ident))
112                .map(|s| segment_replace.iter().find(|r| s == r.0).map_or(s, |r| r.1)),
113        )
114        .expect("All path segments should be valid Rust identifiers")
115    }
116
117    /// Create a Path from the given segments
118    ///
119    /// # Errors
120    ///
121    /// - If no segments are supplied
122    /// - If any of the segments are invalid Rust identifiers
123    pub fn from_segments<I>(segments: I) -> Result<Self, PathError>
124    where
125        I: IntoIterator<Item = <MetaForm as Form>::String>,
126    {
127        let segments = segments.into_iter().collect::<Vec<_>>();
128        if segments.is_empty() {
129            return Err(PathError::MissingSegments);
130        }
131        if let Some(err_at) = segments.iter().position(|seg| !is_rust_identifier(seg)) {
132            return Err(PathError::InvalidIdentifier { segment: err_at });
133        }
134        Ok(Path { segments })
135    }
136
137    /// Crate a Path for types in the Prelude namespace
138    ///
139    /// # Panics
140    ///
141    /// - If the supplied ident is not a valid Rust identifier
142    pub(crate) fn prelude(ident: <MetaForm as Form>::String) -> Self {
143        Self::from_segments([ident])
144            .unwrap_or_else(|_| panic!("{ident:?} is not a valid Rust identifier"))
145    }
146}
147
148impl<T> Path<T>
149where
150    T: Form,
151{
152    /// Create an empty path for types which shall not be named
153    #[allow(unused)]
154    pub(crate) fn voldemort() -> Self {
155        Self {
156            segments: Vec::new(),
157        }
158    }
159
160    /// Create a Path from the given segments.
161    ///
162    /// Does *not* check that the segments are valid Rust identifiers.
163    pub fn from_segments_unchecked<I>(segments: I) -> Path<T>
164    where
165        I: IntoIterator<Item = T::String>,
166    {
167        Self {
168            segments: segments.into_iter().collect(),
169        }
170    }
171
172    /// Returns the segments of the Path
173    #[deprecated(
174        since = "2.5.0",
175        note = "Prefer to access the fields directly; this getter will be removed in the next major version"
176    )]
177    pub fn segments(&self) -> &[T::String] {
178        &self.segments
179    }
180
181    /// Returns `true` if the path is empty
182    pub fn is_empty(&self) -> bool {
183        self.segments.is_empty()
184    }
185
186    /// Get the ident segment of the Path
187    pub fn ident(&self) -> Option<T::String> {
188        self.segments.iter().last().cloned()
189    }
190
191    /// Get the namespace segments of the Path
192    pub fn namespace(&self) -> &[T::String] {
193        self.segments.split_last().map(|(_, ns)| ns).unwrap_or(&[])
194    }
195}
196
197/// An error that may be encountered upon constructing namespaces.
198#[derive(PartialEq, Eq, Debug)]
199pub enum PathError {
200    /// If the module path does not at least have one segment.
201    MissingSegments,
202    /// If a segment within a module path is not a proper Rust identifier.
203    InvalidIdentifier {
204        /// The index of the erroneous segment.
205        segment: usize,
206    },
207}
208
209#[cfg(test)]
210mod tests {
211    use super::*;
212
213    #[test]
214    fn path_ok() {
215        assert_eq!(
216            Path::from_segments(vec!["hello"]),
217            Ok(Path {
218                segments: vec!["hello"]
219            })
220        );
221        assert_eq!(
222            Path::from_segments(vec!["Hello", "World"]),
223            Ok(Path {
224                segments: vec!["Hello", "World"]
225            })
226        );
227        assert_eq!(
228            Path::from_segments(vec!["_"]),
229            Ok(Path {
230                segments: vec!["_"]
231            })
232        );
233    }
234
235    #[test]
236    fn path_with_raw_identifers_ok() {
237        assert_eq!(
238            Path::from_segments(vec!["r#mod", "r#Struct"]),
239            Ok(Path {
240                segments: vec!["r#mod", "r#Struct"]
241            })
242        );
243    }
244
245    #[test]
246    fn path_err() {
247        assert_eq!(
248            Path::from_segments(Vec::new()),
249            Err(PathError::MissingSegments)
250        );
251        assert_eq!(
252            Path::from_segments(vec![""]),
253            Err(PathError::InvalidIdentifier { segment: 0 })
254        );
255        assert_eq!(
256            Path::from_segments(vec!["1"]),
257            Err(PathError::InvalidIdentifier { segment: 0 })
258        );
259        assert_eq!(
260            Path::from_segments(vec!["Hello", ", World!"]),
261            Err(PathError::InvalidIdentifier { segment: 1 })
262        );
263    }
264
265    #[test]
266    fn path_from_module_path_and_ident() {
267        assert_eq!(
268            Path::new("Planet", "hello::world"),
269            Path {
270                segments: vec!["hello", "world", "Planet"]
271            }
272        );
273        assert_eq!(
274            Path::from_segments(vec!["Earth", "::world"]),
275            Err(PathError::InvalidIdentifier { segment: 1 })
276        );
277    }
278
279    #[test]
280    fn path_get_namespace_and_ident() {
281        let path = Path::new("Planet", "hello::world");
282        assert_eq!(path.namespace(), &["hello", "world"]);
283        assert_eq!(path.ident(), Some("Planet"));
284    }
285
286    #[test]
287    #[should_panic]
288    fn path_new_panics_with_invalid_identifiers() {
289        Path::new("Planet", "hello$!@$::world");
290    }
291
292    #[test]
293    fn path_display() {
294        let path = Path::new("Planet", "hello::world").into_portable(&mut Default::default());
295        assert_eq!("hello::world::Planet", format!("{}", path))
296    }
297}