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}