Skip to content

Commit

Permalink
Type collections
Browse files Browse the repository at this point in the history
  • Loading branch information
oscartbeaumont committed Apr 30, 2024
1 parent 587105f commit a542d55
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 0 deletions.
23 changes: 23 additions & 0 deletions examples/type_collection.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use specta::{ts::ExportConfig, Type, TypeCollection};

#[derive(Type)]
pub struct Hello {
pub a: i32,
pub b: bool,
}

#[derive(Type)]
pub struct Test(Hello);

fn main() {
let code = TypeCollection::default()
.register::<Hello>()
.register::<Test>()
.export_ts(&ExportConfig::default())
.unwrap();

assert_eq!(
code,
"export type Hello = { a: number; b: boolean }\nexport type Test = Hello\n"
)
}
24 changes: 24 additions & 0 deletions src/lang/ts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,30 @@ pub fn export<T: NamedType>(conf: &ExportConfig) -> Output {
result
}

impl TypeCollection {
/// Export a collection into a Typescript string.
pub fn export_ts(&mut self, conf: &ExportConfig) -> Output {
let mut type_map = TypeMap::default();
self.export(&mut type_map);

let mut result = String::new();
for (_, typ) in type_map.iter() {
is_valid_ty(&typ.inner, &type_map)?;

let ty = export_named_datatype(conf, typ, &type_map)?;
if let Some((ty_name, l0, l1)) =
detect_duplicate_type_names(&type_map).into_iter().next()
{
return Err(ExportError::DuplicateTypeName(ty_name, l0, l1));
}
result.push_str(&ty);
result.push('\n');
}

Ok(result)
}
}

/// Convert a type which implements [`Type`](crate::Type) to a TypeScript string.
///
/// Eg. `{ demo: string; };`
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ mod serde;
mod static_types;
/// Contains [`Type`] and everything related to it, including implementations and helper macros
pub mod r#type;
mod type_collection;

pub use crate::serde::*;
#[doc(hidden)] // TODO: Should we actually do this? I think not
Expand All @@ -35,6 +36,7 @@ pub use lang::*;
pub use r#type::*;
pub use selection::*;

Check warning on line 37 in src/lib.rs

View workflow job for this annotation

GitHub Actions / clippy

unused import: `selection::*`

warning: unused import: `selection::*` --> src/lib.rs:37:9 | 37 | pub use selection::*; | ^^^^^^^^^^^^ | = note: `#[warn(unused_imports)]` on by default
pub use static_types::*;
pub use type_collection::TypeCollection;

/// Implements [`Type`] for a given struct or enum.
///
Expand Down
40 changes: 40 additions & 0 deletions src/type_collection.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use std::collections::HashMap;

use crate::{NamedDataType, NamedType, SpectaID, TypeMap};

/// Define a set of types which can be exported together
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TypeCollection {
types: HashMap<SpectaID, fn(&mut TypeMap) -> NamedDataType>,
}

impl Default for TypeCollection {
fn default() -> Self {
Self {
types: HashMap::new(),
}
}
}

Check warning on line 17 in src/type_collection.rs

View workflow job for this annotation

GitHub Actions / clippy

this `impl` can be derived

warning: this `impl` can be derived --> src/type_collection.rs:11:1 | 11 | / impl Default for TypeCollection { 12 | | fn default() -> Self { 13 | | Self { 14 | | types: HashMap::new(), 15 | | } 16 | | } 17 | | } | |_^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#derivable_impls = note: `#[warn(clippy::derivable_impls)]` implied by `#[warn(clippy::all)]` = help: remove the manual implementation... help: ...and instead derive it | 7 + #[derive(Default)] 8 | pub struct TypeCollection { |

impl TypeCollection {
/// Join another type collection into this one.
pub fn join(&mut self, collection: TypeCollection) -> &mut Self {
self.types.extend(collection.types);
self
}

/// Register a type with the collection.
pub fn register<T: NamedType>(&mut self) -> &mut Self {
self.types
.insert(T::sid(), |type_map| T::definition_named_data_type(type_map));
self
}

/// Export all the types in the collection.
pub fn export(&mut self, mut type_map: &mut crate::TypeMap) {
for (sid, export) in self.types.iter() {
let dt = export(&mut type_map);

Check warning on line 36 in src/type_collection.rs

View workflow job for this annotation

GitHub Actions / clippy

this expression creates a reference which is immediately dereferenced by the compiler

warning: this expression creates a reference which is immediately dereferenced by the compiler --> src/type_collection.rs:36:29 | 36 | let dt = export(&mut type_map); | ^^^^^^^^^^^^^ help: change this to: `type_map` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow = note: `#[warn(clippy::needless_borrow)]` implied by `#[warn(clippy::all)]`
type_map.insert(*sid, dt);
}
}
}
1 change: 1 addition & 0 deletions tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ mod transparent;
pub mod ts;
mod ts_rs;
mod ty_override;
mod type_collection;
mod type_map;
mod typescript;
#[cfg(all(feature = "ulid", feature = "typescript"))]
Expand Down
10 changes: 10 additions & 0 deletions tests/macro/compile_error.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@ error: specta: You must apply the #[specta] macro before the #[wasm_bindgen] mac
|
= note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0255]: the name `__specta__fn__testing` is defined multiple times
--> tests/macro/compile_error.rs:110:8
|
109 | #[specta]
| --------- previous definition of the macro `__specta__fn__testing` here
110 | pub fn testing() {}
| ^^^^^^^ `__specta__fn__testing` reimported here
|
= note: `__specta__fn__testing` must be defined only once in the macro namespace of this module

error[E0601]: `main` function not found in crate `$CRATE`
--> tests/macro/compile_error.rs:110:20
|
Expand Down
67 changes: 67 additions & 0 deletions tests/type_collection.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use specta::{Type, TypeCollection, TypeMap};

#[derive(Type)]
struct A2(String);

#[derive(Type)]
struct A {
a: A2,
}

#[derive(Type)]
struct C {
d: String,
}

#[derive(Type)]
struct D(String);

#[test]
fn type_collection_export() {
let mut type_map = TypeMap::default();
TypeCollection::default()
.register::<A>()
.export(&mut type_map);
assert_eq!(type_map.len(), 2);
}

#[test]
fn type_collection_merge() {
let mut a = TypeCollection::default();
a.register::<A>();
let mut b = TypeCollection::default();
b.register::<C>();

let mut type_map = TypeMap::default();
TypeCollection::default()
.register::<D>()
.join(a)
.join(b)
.export(&mut type_map);
assert_eq!(type_map.len(), 4);
}

#[test]
fn type_collection_duplicate_register_ty() {
let mut type_map = TypeMap::default();
TypeCollection::default()
.register::<C>()
.register::<C>()
.export(&mut type_map);
assert_eq!(type_map.len(), 1);
}

#[test]
#[cfg(feature = "typescript")]
fn type_collection_ts() {
let result = TypeCollection::default()
.register::<A>()
.register::<C>()
.register::<D>()
.export_ts(&Default::default())
.unwrap();
assert_eq!(
result,
"export type A = { a: A2 }\nexport type A2 = string\nexport type C = { d: string }\nexport type D = string\n"
);
}

0 comments on commit a542d55

Please sign in to comment.