typeshare_model/
parsed_data.rs

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