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