typeshare_model/
parsed_data.rs

1use std::{
2    borrow::{Borrow, Cow},
3    cmp::Ord,
4    fmt::{self, Display},
5    path::{Component, Path},
6};
7
8use crate::decorator::DecoratorSet;
9
10/// A crate name.
11#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
12pub struct CrateName(String);
13
14impl Display for CrateName {
15    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16        write!(f, "{}", self.0)
17    }
18}
19
20impl CrateName {
21    pub const fn new(name: String) -> Self {
22        Self(name)
23    }
24
25    /// View this crate name as a string slice.
26    pub fn as_str(&self) -> &str {
27        self.0.as_str()
28    }
29
30    /// Extract the crate name from a give path to a rust source file. This is
31    /// defined as the name of the directory one level above the `src` directory
32    /// that cotains this source file, with any `-` replaced with `_`.
33    pub fn find_crate_name(path: &Path) -> Option<Self> {
34        path.components()
35            .rev()
36            // Only find paths that use normal components in the suffix. If we
37            // hit something like `..` or `C:\`, end the search immediately.
38            .take_while(|c| matches!(c, Component::Normal(_) | Component::CurDir))
39            // Skip `.` paths entirely
40            .filter_map(|c| match c {
41                Component::Normal(name) => Some(name),
42                _ => None,
43            })
44            // Find the `src` directory in our ancestors
45            .skip_while(|&name| name != "src")
46            // Find the first directory preceeding the `src` directory
47            .find(|&name| name != "src")?
48            // Convert this directory name to a string; fail if it isn't
49            // stringable
50            .to_str()
51            // Fix dashes
52            .map(|name| name.replace("-", "_"))
53            .map(CrateName)
54    }
55}
56
57impl PartialEq<str> for CrateName {
58    fn eq(&self, other: &str) -> bool {
59        self.as_str() == other
60    }
61}
62
63impl PartialEq<&str> for CrateName {
64    fn eq(&self, other: &&str) -> bool {
65        self == *other
66    }
67}
68
69/// Identifier used in Rust structs, enums, and fields. It includes the
70/// `original` name and the `renamed` value after the transformation based on `serde` attributes.
71#[derive(Debug, Clone, PartialEq, Eq)]
72pub struct Id {
73    /// The original identifier name
74    pub original: TypeName,
75    /// The renamed identifier, based on serde attributes.
76    /// If there is no re-naming going on, this will be identical to
77    /// `original`.
78    pub renamed: TypeName,
79}
80
81impl std::fmt::Display for Id {
82    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83        if self.original == self.renamed {
84            write!(f, "({})", self.original)
85        } else {
86            write!(f, "({}, {})", self.original, self.renamed)
87        }
88    }
89}
90
91/// Rust struct.
92#[derive(Debug, Clone, PartialEq)]
93pub struct RustStruct {
94    /// The identifier for the struct.
95    pub id: Id,
96    /// The generic parameters that come after the struct name.
97    pub generic_types: Vec<TypeName>,
98    /// The fields of the struct.
99    pub fields: Vec<RustField>,
100    /// Comments that were in the struct source.
101    /// We copy comments over to the typeshared files,
102    /// so we need to collect them here.
103    pub comments: Vec<String>,
104    /// Attributes that exist for this struct.
105    pub decorators: DecoratorSet,
106}
107
108/// Rust type alias.
109/// ```
110/// pub struct MasterPassword(String);
111/// ```
112#[derive(Debug, Clone, PartialEq)]
113pub struct RustTypeAlias {
114    /// The identifier for the alias.
115    pub id: Id,
116    /// The generic parameters that come after the type alias name.
117    pub generic_types: Vec<TypeName>,
118    /// The type identifier that this type alias is aliasing
119    pub ty: RustType,
120    /// Comments that were in the type alias source.
121    pub comments: Vec<String>,
122}
123
124/// Rust field definition.
125#[derive(Debug, Clone, PartialEq, Eq)]
126pub struct RustField {
127    /// Identifier for the field.
128    pub id: Id,
129    /// Type of the field.
130    pub ty: RustType,
131    /// Comments that were in the original source.
132    pub comments: Vec<String>,
133    /// This will be true if the field has a `serde(default)` decorator.
134    /// Even if the field's type is not optional, we need to make it optional
135    /// for the languages we generate code for.
136    pub has_default: bool,
137    /// Language-specific decorators assigned to a given field.
138    /// The keys are language names (e.g. SupportedLanguage::TypeScript), the values are field decorators (e.g. readonly)
139    pub decorators: DecoratorSet,
140}
141
142/// A named Rust type.
143#[derive(Debug, Clone, PartialEq, Eq)]
144pub enum RustType {
145    /// A type with generic parameters. Consists of a type ID + parameters that come
146    /// after in angled brackets. Examples include:
147    /// - `SomeStruct<String>`
148    /// - `SomeEnum<u32>`
149    /// - `SomeTypeAlias<(), &str>`
150    /// However, there are some generic types that are considered to be _special_. These
151    /// include `Vec<T>` `HashMap<K, V>`, and `Option<T>`, which are part of `SpecialRustType` instead
152    /// of `RustType::Generic`.
153    ///
154    /// If a generic type is type-mapped via `typeshare.toml`, the generic parameters will be dropped automatically.
155    Generic {
156        #[allow(missing_docs)]
157        id: TypeName,
158        #[allow(missing_docs)]
159        parameters: Vec<RustType>,
160    },
161    /// A type that requires a special transformation to its respective language. This includes
162    /// many core types, like string types, basic container types, numbers, and other primitives.
163    Special(SpecialRustType),
164    /// A type with no generic parameters that is not considered a **special** type. This includes
165    /// all user-generated types and some types from the standard library or third-party crates.
166    /// However, these types can still be transformed as part of the type-map in `typeshare.toml`.
167    Simple {
168        #[allow(missing_docs)]
169        id: TypeName,
170    },
171}
172
173/// A special rust type that needs a manual type conversion
174#[derive(Debug, Clone, PartialEq, Eq)]
175#[non_exhaustive]
176pub enum SpecialRustType {
177    /// Represents `Vec<T>` from the standard library
178    Vec(Box<RustType>),
179    /// Represents `[T; N]` from the standard library
180    Array(Box<RustType>, usize),
181    /// Represents `&[T]` from the standard library
182    Slice(Box<RustType>),
183    /// Represents `HashMap<K, V>` from the standard library
184    HashMap(Box<RustType>, Box<RustType>),
185    /// Represents `Option<T>` from the standard library
186    Option(Box<RustType>),
187    /// Represents `()`
188    Unit,
189    /// Represents `String` from the standard library
190    String,
191    /// Represents `char`
192    Char,
193    /// Represents `i8`
194    I8,
195    /// Represents `i16`
196    I16,
197    /// Represents `i32`
198    I32,
199    /// Represents `i64`
200    I64,
201    /// Represents `u8`
202    U8,
203    /// Represents `u16`
204    U16,
205    /// Represents `u32`
206    U32,
207    /// Represents `u64`
208    U64,
209    /// Represents `isize`
210    ISize,
211    /// Represents `usize`
212    USize,
213    /// Represents `bool`
214    Bool,
215    /// Represents `f32`
216    F32,
217    /// Represents `f64`
218    F64,
219    /// Represents `I54` from `typeshare::I54`
220    I54,
221    /// Represents `U53` from `typeshare::U53`
222    U53,
223}
224
225impl RustType {
226    /// Check if a type contains a type with an ID that matches `ty`.
227    /// For example, `Box<String>` contains the types `Box` and `String`. Similarly,
228    /// `Vec<Option<HashMap<String, Url>>>` contains the types `Vec`, `Option`, `HashMap`,
229    /// `String`, and `Url`.
230    pub fn contains_type(&self, ty: &TypeName) -> bool {
231        match &self {
232            Self::Simple { id } => id == ty,
233            Self::Generic { id, parameters } => {
234                id == ty || parameters.iter().any(|p| p.contains_type(ty))
235            }
236            Self::Special(special) => special.contains_type(ty),
237        }
238    }
239
240    /// Get the ID (AKA name) of the type.
241    pub fn id(&self) -> &TypeName {
242        match &self {
243            Self::Simple { id } | Self::Generic { id, .. } => id,
244            Self::Special(special) => special.id(),
245        }
246    }
247    /// Check if the type is `Option<T>`
248    pub fn is_optional(&self) -> bool {
249        matches!(self, Self::Special(SpecialRustType::Option(_)))
250    }
251
252    /// Check if the type is `Option<Option<T>>`
253    pub fn is_double_optional(&self) -> bool {
254        match &self {
255            RustType::Special(SpecialRustType::Option(t)) => {
256                matches!(t.as_ref(), RustType::Special(SpecialRustType::Option(_)))
257            }
258            _ => false,
259        }
260    }
261    /// Check if the type is `Vec<T>`
262    pub fn is_vec(&self) -> bool {
263        matches!(self, Self::Special(SpecialRustType::Vec(_)))
264    }
265    /// Check if the type is `HashMap<K, V>`
266    pub fn is_hash_map(&self) -> bool {
267        matches!(self, Self::Special(SpecialRustType::HashMap(_, _)))
268    }
269
270    /// Get the generic parameters for this type. Returns an empty iterator if there are none.
271    /// For example, `Vec<String>`'s generic parameters would be `[String]`.
272    /// Meanwhile, `HashMap<i64, u32>`'s generic parameters would be `[i64, u32]`.
273    /// Finally, a type like `String` would have no generic parameters.
274    pub fn parameters(&self) -> Box<dyn Iterator<Item = &Self> + '_> {
275        match &self {
276            Self::Simple { .. } => Box::new(std::iter::empty()),
277            Self::Generic { parameters, .. } => Box::new(parameters.iter()),
278            Self::Special(special) => special.parameters(),
279        }
280    }
281
282    // /// Yield all the type names including nested generic types.
283    // pub fn all_reference_type_names(&self) -> impl Iterator<Item = &'_ str> + '_ {
284    //     RustRefTypeIter {
285    //         ty: Some(self),
286    //         parameters: Vec::new(),
287    //     }
288    //     .filter(|s| accept_type(s))
289    // }
290}
291
292impl SpecialRustType {
293    /// Check if this type is equivalent to or contains `ty` in one of its generic parameters.
294    pub fn contains_type(&self, ty: &TypeName) -> bool {
295        match self {
296            Self::Vec(rty) | Self::Array(rty, _) | Self::Slice(rty) | Self::Option(rty) => {
297                rty.contains_type(ty)
298            }
299            Self::HashMap(rty1, rty2) => rty1.contains_type(ty) || rty2.contains_type(ty),
300            Self::Unit
301            | Self::String
302            | Self::Char
303            | Self::I8
304            | Self::I16
305            | Self::I32
306            | Self::I64
307            | Self::U8
308            | Self::U16
309            | Self::U32
310            | Self::U64
311            | Self::ISize
312            | Self::USize
313            | Self::Bool
314            | Self::F32
315            | Self::F64
316            | Self::I54
317            | Self::U53 => ty == self.id(),
318        }
319    }
320
321    /// Returns the Rust identifier for this special type.
322    pub const fn id(&self) -> &'static TypeName {
323        // Helper macro to handle the tedium of repeating the `const` block
324        // in each match arm
325        macro_rules! match_block {
326            {
327                match $this:ident {
328                    $($pattern:pat => $out:literal,)*
329                }
330            } => {
331                match $this {
332                    $($pattern => const {&TypeName(Cow::Borrowed($out))},)*
333                }
334            }
335        }
336
337        // TODO: I suspect there are bugs related to strings like `[]` being
338        // returned from this function (non-identifier strings) but it seems
339        // to work fine so I'll leave it for now.
340        match_block! {
341            match self {
342                Self::Unit => "()",
343                Self::F64 => "f64",
344                Self::F32 => "f32",
345                Self::Vec(_) => "Vec",
346                Self::Array(_, _) => "[]",
347                Self::Slice(_) => "&[]",
348                Self::Option(_) => "Option",
349                Self::HashMap(_, _) => "HashMap",
350                Self::String => "String",
351                Self::Char => "char",
352                Self::Bool => "bool",
353                Self::I8 => "i8",
354                Self::I16 => "i16",
355                Self::I32 => "i32",
356                Self::I64 => "i64",
357                Self::U8 => "u8",
358                Self::U16 => "u16",
359                Self::U32 => "u32",
360                Self::U64 => "u64",
361                Self::ISize => "isize",
362                Self::USize => "usize",
363                Self::U53 => "U53",
364                Self::I54 => "I54",
365            }
366        }
367    }
368
369    /// Iterate over the generic parameters for this type. Returns an empty iterator
370    /// if there are none.
371    pub fn parameters(&self) -> Box<dyn Iterator<Item = &RustType> + '_> {
372        match &self {
373            Self::Vec(rtype) | Self::Array(rtype, _) | Self::Slice(rtype) | Self::Option(rtype) => {
374                Box::new(std::iter::once(rtype.as_ref()))
375            }
376            Self::HashMap(rtype1, rtype2) => {
377                Box::new([rtype1.as_ref(), rtype2.as_ref()].into_iter())
378            }
379            Self::Unit
380            | Self::String
381            | Self::Char
382            | Self::I8
383            | Self::I16
384            | Self::I32
385            | Self::I64
386            | Self::U8
387            | Self::U16
388            | Self::U32
389            | Self::U64
390            | Self::ISize
391            | Self::USize
392            | Self::Bool
393            | Self::F32
394            | Self::F64
395            | Self::I54
396            | Self::U53 => Box::new(std::iter::empty()),
397        }
398    }
399}
400
401/// Parsed information about a Rust enum definition
402#[derive(Debug, Clone, PartialEq)]
403pub enum RustEnum {
404    /// A unit enum
405    ///
406    /// An example of such an enum:
407    ///
408    /// ```
409    /// enum UnitEnum {
410    ///     Variant,
411    ///     AnotherVariant,
412    ///     Yay,
413    /// }
414    /// ```
415    Unit {
416        /// Shared context for this enum
417        shared: RustEnumShared,
418
419        /// All of the variants for this enum. This is a Unit enum, so all
420        /// of these variants have only unit data available.
421        unit_variants: Vec<RustEnumVariantShared>,
422    },
423
424    /// An algebraic enum
425    ///
426    /// An example of such an enum:
427    ///
428    /// ```
429    /// struct AssociatedData { /* ... */ }
430    ///
431    /// enum AlgebraicEnum {
432    ///     UnitVariant,
433    ///     TupleVariant(AssociatedData),
434    ///     AnonymousStruct {
435    ///         field: String,
436    ///         another_field: bool,
437    ///     },
438    /// }
439    /// ```
440    Algebraic {
441        /// The parsed value of the `#[serde(tag = "...")]` attribute
442        tag_key: String,
443        /// The parsed value of the `#[serde(content = "...")]` attribute
444        content_key: String,
445        /// Shared context for this enum.
446        shared: RustEnumShared,
447        /// The variants on this enum
448        variants: Vec<RustEnumVariant>,
449    },
450}
451
452impl RustEnum {
453    /// Get a reference to the inner shared content
454    pub fn shared(&self) -> &RustEnumShared {
455        match self {
456            Self::Unit { shared, .. } | Self::Algebraic { shared, .. } => shared,
457        }
458    }
459}
460
461/// Enum information shared among different enum types
462#[derive(Debug, Clone, PartialEq)]
463pub struct RustEnumShared {
464    /// The enum's ident
465    pub id: Id,
466    /// Generic parameters for the enum, e.g. `SomeEnum<T>` would produce `vec!["T"]`
467    pub generic_types: Vec<TypeName>,
468    /// Comments on the enum definition itself
469    pub comments: Vec<String>,
470
471    /// Decorators applied to the enum for generation in other languages
472    ///
473    /// Example: `#[typeshare(swift = "Equatable, Comparable, Hashable")]`.
474    pub decorators: DecoratorSet,
475    /// True if this enum references itself in any field of any variant
476    /// Swift needs the special keyword `indirect` for this case
477    pub is_recursive: bool,
478}
479
480/// Parsed information about a Rust enum variant
481#[derive(Debug, Clone, PartialEq)]
482#[non_exhaustive]
483pub enum RustEnumVariant {
484    /// A unit variant
485    Unit(RustEnumVariantShared),
486    /// A newtype tuple variant
487    Tuple {
488        /// The type of the single tuple field
489        ty: RustType,
490        /// Shared context for this enum.
491        shared: RustEnumVariantShared,
492    },
493    /// An anonymous struct variant
494    AnonymousStruct {
495        /// The fields of the anonymous struct
496        fields: Vec<RustField>,
497        /// Shared context for this enum.
498        shared: RustEnumVariantShared,
499    },
500}
501
502impl RustEnumVariant {
503    /// Get a reference to the inner shared content
504    pub fn shared(&self) -> &RustEnumVariantShared {
505        match self {
506            Self::Unit(shared)
507            | Self::Tuple { shared, .. }
508            | Self::AnonymousStruct { shared, .. } => shared,
509        }
510    }
511}
512
513/// Variant information shared among different variant types
514#[derive(Debug, Clone, PartialEq)]
515pub struct RustEnumVariantShared {
516    /// The variant's ident
517    pub id: Id,
518    /// Comments applied to the variant
519    pub comments: Vec<String>,
520}
521
522/// Rust const variable.
523///
524/// Typeshare can only handle numeric and string constants.
525/// ```
526/// pub const MY_CONST: &str = "constant value";
527/// ```
528#[derive(Debug, Clone, PartialEq)]
529pub struct RustConst {
530    /// The identifier for the constant.
531    pub id: Id,
532    /// The type identifier that this constant is referring to.
533    pub ty: RustType,
534    /// The expression that the constant contains.
535    pub expr: RustConstExpr,
536}
537
538/// A constant expression that can be shared via a constant variable across the typeshare
539/// boundary.
540#[derive(Debug, Clone, PartialEq)]
541#[non_exhaustive]
542pub enum RustConstExpr {
543    /// Expression represents an integer.
544    Int(i128),
545}
546
547/// An imported type reference.
548#[derive(Debug, Clone, PartialEq, Eq, Hash)]
549pub struct ImportedType {
550    /// Crate this type belongs to.
551    pub base_crate: CrateName,
552    /// Type name.
553    pub type_name: TypeName,
554}
555
556// TODO: replace this `Cow` with a pair of owned/borrowed types
557/// A type name.
558#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
559pub struct TypeName(Cow<'static, str>);
560
561impl TypeName {
562    #[inline]
563    #[must_use]
564    pub fn as_str(&self) -> &str {
565        self.0.as_ref()
566    }
567
568    #[inline]
569    #[must_use]
570    pub fn new(ident: &proc_macro2::Ident) -> Self {
571        Self::new_string(ident.to_string())
572    }
573
574    #[inline]
575    #[must_use]
576    pub fn new_string(ident: String) -> Self {
577        Self(Cow::Owned(ident))
578    }
579
580    #[inline]
581    #[must_use]
582    pub const fn new_static(ident: &'static str) -> Self {
583        Self(Cow::Borrowed(ident))
584    }
585}
586
587impl AsRef<str> for TypeName {
588    #[inline]
589    #[must_use]
590    fn as_ref(&self) -> &str {
591        self.as_str()
592    }
593}
594
595impl Borrow<str> for TypeName {
596    #[inline]
597    #[must_use]
598    fn borrow(&self) -> &str {
599        self.as_str()
600    }
601}
602
603impl fmt::Display for TypeName {
604    #[inline]
605    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
606        fmt::Display::fmt(&self.0, f)
607    }
608}
609
610impl PartialEq<str> for TypeName {
611    #[inline]
612    #[must_use]
613    fn eq(&self, other: &str) -> bool {
614        self.as_str() == other
615    }
616}
617
618impl PartialEq<&str> for TypeName {
619    #[inline]
620    #[must_use]
621    fn eq(&self, other: &&str) -> bool {
622        self == *other
623    }
624}
625
626#[cfg(test)]
627mod test {
628    use super::CrateName;
629    use std::path::Path;
630
631    #[test]
632    fn test_crate_name() {
633        let path = Path::new("/some/path/to/projects/core/foundation/op-proxy/src/android.rs");
634        assert_eq!(CrateName::find_crate_name(path).unwrap(), "op_proxy",);
635    }
636
637    #[test]
638    fn skip_curdir() {
639        let path = Path::new("/path/to/crate-name/./src/main.rs");
640        assert_eq!(CrateName::find_crate_name(path).unwrap(), "crate_name")
641    }
642
643    #[test]
644    fn bail_on_parent_dir() {
645        let path = Path::new("/path/to/crate-name/src/foo/../stuff.rs");
646        assert!(CrateName::find_crate_name(path).is_none());
647    }
648
649    #[test]
650    fn accept_parent_dir_before_crate() {
651        let path = Path::new("/path/to/../crate/src/foo/bar/stuff.rs");
652        assert_eq!(CrateName::find_crate_name(path).unwrap(), "crate");
653    }
654
655    #[test]
656    fn reject_rooted_src() {
657        let path = Path::new("/src/foo.rs");
658        assert!(CrateName::find_crate_name(path).is_none());
659    }
660}