xsd_parser/models/code/
ident_path.rs

1use std::fmt::{Display, Formatter, Result as FmtResult};
2use std::ops::{Deref, DerefMut};
3use std::str::FromStr;
4
5use proc_macro2::{Ident as Ident2, TokenStream};
6use quote::{format_ident, quote, ToTokens};
7use smallvec::SmallVec;
8use thiserror::Error;
9
10use crate::models::{
11    meta::MetaTypes,
12    schema::{NamespaceId, SchemaId},
13    Ident,
14};
15
16/// Represents an identifier path.
17///
18/// A identifier path is the full path of a specific identifier in the code,
19/// like `std::str::FromStr`. The identified object can be a constant, a type,
20/// a trait or anything else that is defined within a module.
21///
22/// The identifier path contains two parts:
23/// - The identifier itself, which is more or less the name of the object to identify, and
24/// - the path of the module the object is provided at.
25#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
26pub struct IdentPath {
27    path: Option<ModulePath>,
28    ident: Ident2,
29    is_absolute: bool,
30}
31
32/// Represents a path of a module.
33///
34/// The module path is a chain ob module names separated by a double colon like
35/// `std::str`. It is used to identify modules inside the code.
36#[derive(Default, Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
37pub struct ModulePath(pub SmallVec<[Ident2; 2]>);
38
39/// Error that is raised by [`IdentPath`] if parsing the value from string failed.
40#[derive(Debug, Error)]
41#[error("Invalid identifier path: {0}")]
42pub struct InvalidIdentPath(pub String);
43
44/// Identifies the module the code gets rendered to.
45#[derive(Debug, Clone, Copy, Eq, PartialEq)]
46pub enum ModuleIdent {
47    /// The root module.
48    Root,
49
50    /// The module of the namespace of the current type.
51    Namespace(NamespaceId),
52
53    /// The module of the schema of the current type.
54    Schema(SchemaId),
55
56    /// The module of the namespace and the schema of the current type.
57    Both(NamespaceId, SchemaId),
58}
59
60impl IdentPath {
61    /// Crates a new [`IdentPath`] instance from the passed module `path` and the
62    /// `ident`ifier of the object to refer to.
63    #[must_use]
64    pub fn from_parts<I>(path: I, ident: Ident2) -> Self
65    where
66        I: IntoIterator<Item = Ident2>,
67    {
68        Self::from_ident(ident).with_path(path)
69    }
70
71    /// Creates a new [`IdentPath`] from the passed object `ident`ifier without a
72    /// module path.
73    #[must_use]
74    pub fn from_ident(ident: Ident2) -> Self {
75        Self {
76            ident,
77            path: None,
78            is_absolute: false,
79        }
80    }
81
82    /// Changes the identifier of this identifier path to the passed `ident`.
83    #[must_use]
84    pub fn with_ident(mut self, ident: Ident2) -> Self {
85        self.ident = ident;
86
87        self
88    }
89
90    /// Changes the module path of this identifier path to the passed `path`.
91    #[must_use]
92    pub fn with_path<I>(mut self, path: I) -> Self
93    where
94        I: IntoIterator<Item = Ident2>,
95    {
96        self.path = Some(ModulePath(path.into_iter().collect()));
97
98        self
99    }
100
101    /// Splits this identifier path into it's two main parts, the identifier
102    /// and the module path.
103    #[must_use]
104    pub fn into_parts(self) -> (Ident2, Option<ModulePath>, bool) {
105        let Self {
106            ident,
107            path,
108            is_absolute,
109        } = self;
110
111        (ident, path, is_absolute)
112    }
113
114    /// Returns the identifier of this identifier path.
115    #[must_use]
116    pub fn ident(&self) -> &Ident2 {
117        &self.ident
118    }
119
120    /// Returns the module path for this identifier path.
121    #[must_use]
122    pub fn module(&self) -> Option<&ModulePath> {
123        self.path.as_ref()
124    }
125
126    /// Returns `true` if the path is absolute, `false` otherwise.
127    #[must_use]
128    pub fn is_absolute(&self) -> bool {
129        self.is_absolute
130    }
131
132    /// Creates a [`TokenStream`] that is relative to the passed `dst` module path.
133    ///
134    /// This uses the `super` keyword to create a relative path from the passed `dst` module path
135    /// and this identifier path. The relative path is returned as token stream.
136    #[must_use]
137    pub fn relative_to(&self, dst: &ModulePath) -> TokenStream {
138        let ident = &self.ident;
139
140        let Some(src) = &self.path else {
141            return quote!(#ident);
142        };
143
144        let mut ret = TokenStream::new();
145        if self.is_absolute {
146            for p in src.0.iter() {
147                ret.extend(quote!(::#p));
148            }
149
150            return quote!(#ret::#ident);
151        }
152
153        let mut src = src.0.iter().fuse();
154        let mut dst = dst.0.iter().fuse();
155
156        macro_rules! push {
157            ($x:expr) => {{
158                let x = $x;
159                if ret.is_empty() {
160                    ret.extend(x)
161                } else {
162                    ret.extend(quote!(::#x))
163                }
164            }};
165        }
166
167        loop {
168            match (src.next(), dst.next()) {
169                (Some(a), Some(b)) if a == b => {}
170                (Some(a), Some(_)) => {
171                    push!(quote!(super));
172                    while dst.next().is_some() {
173                        push!(quote!(super));
174                    }
175
176                    push!(quote!(#a));
177                    for a in src {
178                        push!(quote!(#a));
179                    }
180
181                    push!(quote!(#ident));
182
183                    return ret;
184                }
185                (Some(a), None) => push!(quote!(#a)),
186                (None, Some(_)) => push!(quote!(super)),
187                (None, None) => {
188                    push!(quote!(#ident));
189                    return ret;
190                }
191            }
192        }
193    }
194}
195
196impl TryFrom<&str> for IdentPath {
197    type Error = InvalidIdentPath;
198
199    fn try_from(value: &str) -> Result<Self, Self::Error> {
200        Self::from_str(value)
201    }
202}
203
204impl TryFrom<String> for IdentPath {
205    type Error = InvalidIdentPath;
206
207    fn try_from(value: String) -> Result<Self, Self::Error> {
208        Self::from_str(&value)
209    }
210}
211
212impl FromStr for IdentPath {
213    type Err = InvalidIdentPath;
214
215    fn from_str(s: &str) -> Result<Self, Self::Err> {
216        let mut ident = None;
217        let mut path = ModulePath::default();
218        let mut is_absolute = false;
219
220        for part in s.split("::") {
221            let part = part.trim();
222            if part.is_empty() {
223                if path.is_empty() && ident.is_none() {
224                    is_absolute = true;
225                }
226
227                continue;
228            }
229
230            if let Some(ident) = ident.take() {
231                path.0.push(ident);
232            }
233
234            ident = Some(format_ident!("{part}"));
235        }
236
237        Ok(Self {
238            ident: ident.ok_or_else(|| InvalidIdentPath(s.into()))?,
239            path: Some(path),
240            is_absolute,
241        })
242    }
243}
244
245impl Display for IdentPath {
246    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
247        if let Some(path) = &self.path {
248            for module in &path.0 {
249                write!(f, "{module}::")?;
250            }
251        }
252
253        write!(f, "{}", self.ident)
254    }
255}
256
257impl ToTokens for IdentPath {
258    fn to_tokens(&self, tokens: &mut TokenStream) {
259        if self.is_absolute {
260            tokens.extend(quote!(::));
261        }
262
263        if let Some(path) = &self.path {
264            for module in &path.0 {
265                tokens.extend(quote!(#module::));
266            }
267        }
268
269        let ident = self.ident();
270
271        tokens.extend(quote!(#ident));
272    }
273}
274
275impl ModulePath {
276    /// Create a new [`ModulePath`] instance from the passed namespace id `ns` and the
277    /// `types` information.
278    ///
279    /// This tries to look up the passed namespace id in the types information and create
280    /// a module path for it.
281    #[must_use]
282    pub fn from_ident(types: &MetaTypes, ident: ModuleIdent) -> Self {
283        let (namespace, schema) = match ident {
284            ModuleIdent::Root => (None, None),
285            ModuleIdent::Namespace(n) => (Some(n), None),
286            ModuleIdent::Schema(s) => (None, Some(s)),
287            ModuleIdent::Both(n, s) => (Some(n), Some(s)),
288        };
289
290        let namespace = namespace
291            .and_then(|id| types.modules.get(&id))
292            .and_then(|module| module.name.as_ref())
293            .map(|name| types.naming.format_module_ident(name));
294        let schema = schema
295            .and_then(|id| types.schemas.get(&id))
296            .and_then(|schema| schema.name.as_ref())
297            .map(|name| types.naming.format_module_ident(name));
298
299        Self(namespace.into_iter().chain(schema).collect())
300    }
301
302    /// Add a module to the module
303    #[must_use]
304    pub fn join(mut self, other: Ident2) -> Self {
305        self.0.push(other);
306
307        self
308    }
309}
310
311impl Deref for ModulePath {
312    type Target = SmallVec<[Ident2; 2]>;
313
314    fn deref(&self) -> &Self::Target {
315        &self.0
316    }
317}
318
319impl DerefMut for ModulePath {
320    fn deref_mut(&mut self) -> &mut Self::Target {
321        &mut self.0
322    }
323}
324
325/* ModuleIdent */
326
327impl ModuleIdent {
328    pub(crate) fn new(
329        types: &MetaTypes,
330        ident: &Ident,
331        with_namespace: bool,
332        with_schema: bool,
333    ) -> Self {
334        let schema_count = ident
335            .ns
336            .as_ref()
337            .and_then(|ns| types.modules.get(ns))
338            .map(|m| m.schema_count)
339            .unwrap_or_default();
340        let with_schema = with_schema && schema_count > 1;
341
342        let namespace = with_namespace.then_some(ident.ns).flatten();
343        let schema = with_schema
344            .then(|| types.items.get(ident))
345            .flatten()
346            .and_then(|ty| ty.schema);
347
348        match (namespace, schema) {
349            (None, None) => ModuleIdent::Root,
350            (Some(n), None) => ModuleIdent::Namespace(n),
351            (None, Some(s)) => ModuleIdent::Schema(s),
352            (Some(n), Some(s)) => ModuleIdent::Both(n, s),
353        }
354    }
355}
356
357#[cfg(test)]
358mod tests {
359    use quote::{format_ident, quote};
360
361    use super::{IdentPath, ModulePath};
362
363    #[test]
364    #[rustfmt::skip]
365    fn type_path() {
366        let string = IdentPath::from_ident(format_ident!("String"));
367        let my_type = IdentPath::from_parts(
368            [format_ident!("my_module")],
369            format_ident!("MyType"),
370        );
371        let serializer = IdentPath::from_parts(
372            [
373                format_ident!("my_module"),
374                format_ident!("quick_xml_serialize"),
375            ],
376            format_ident!("MyTypeSerializer"),
377        );
378        let deserializer = IdentPath::from_parts(
379            [
380                format_ident!("my_module"),
381                format_ident!("quick_xml_deserialize"),
382            ],
383            format_ident!("MyTypeDeserializer"),
384        );
385
386        let empty_path = ModulePath::default();
387        let module_path = ModulePath::default().join(format_ident!("my_module"));
388        let other_module_path = ModulePath::default().join(format_ident!("other_module"));
389        let serializer_path = module_path.clone().join(format_ident!("quick_xml_serialize"));
390        let deserializer_path = module_path.clone().join(format_ident!("quick_xml_deserialize"));
391
392        macro_rules! test {
393            ($actual:expr, $( $expected:tt )*) => {{
394                let a = $actual.to_string();
395                let b = quote!($( $expected )*).to_string();
396
397                assert_eq!(a, b);
398            }};
399        }
400
401        /* With modules */
402
403        test!(string.relative_to(&empty_path), String);
404        test!(string.relative_to(&module_path), String);
405        test!(string.relative_to(&other_module_path), String);
406        test!(string.relative_to(&serializer_path), String);
407        test!(string.relative_to(&deserializer_path), String);
408
409        test!(my_type.relative_to(&empty_path), my_module::MyType);
410        test!(my_type.relative_to(&module_path), MyType);
411        test!(my_type.relative_to(&other_module_path), super::my_module::MyType);
412        test!(my_type.relative_to(&serializer_path), super::MyType);
413        test!(my_type.relative_to(&deserializer_path), super::MyType);
414
415        test!(serializer.relative_to(&empty_path), my_module::quick_xml_serialize::MyTypeSerializer);
416        test!(serializer.relative_to(&module_path), quick_xml_serialize::MyTypeSerializer);
417        test!(serializer.relative_to(&other_module_path), super::my_module::quick_xml_serialize::MyTypeSerializer);
418        test!(serializer.relative_to(&serializer_path), MyTypeSerializer);
419        test!(serializer.relative_to(&deserializer_path), super::quick_xml_serialize::MyTypeSerializer);
420
421        test!(deserializer.relative_to(&empty_path), my_module::quick_xml_deserialize::MyTypeDeserializer);
422        test!(deserializer.relative_to(&module_path), quick_xml_deserialize::MyTypeDeserializer);
423        test!(deserializer.relative_to(&other_module_path), super::my_module::quick_xml_deserialize::MyTypeDeserializer);
424        test!(deserializer.relative_to(&serializer_path), super::quick_xml_deserialize::MyTypeDeserializer);
425        test!(deserializer.relative_to(&deserializer_path), MyTypeDeserializer);
426    }
427}