Git Product home page Git Product logo

specta's Introduction

Specta Logo

Specta

Easily export your Rust types to other languages

Discord Crates.io crates.io docs.rs License

Features

  • Export structs and enums to Typescript
  • Get function types to use in libraries like tauri-specta
  • Supports wide range of common crates in Rust ecosystem
  • Supports type inference - can determine type of fn demo() -> impl Type.

Ecosystem

Specta can be used in your application either directly or through a library which simplifies the process of using it.

  • rspc - Easily building end-to-end typesafe APIs
  • tauri-specta - Typesafe Tauri commands and events
  • TauRPC - Tauri extension to give you a fully-typed IPC layer.

Usage

Add specta as a dependency to your project, enabling the languages you want to export to:

cargo add specta --features typescript # only 'typescript' is currently supported

Then you can use Specta like so:

use specta::{ts, Type};

#[derive(Type)]
pub struct TypeOne {
    pub a: String,
    pub b: GenericType<i32>,
    #[serde(rename = "cccccc")]
    pub c: MyEnum,
}

#[derive(Type)]
pub struct GenericType<A> {
    pub my_field: String,
    pub generic: A,
}

#[derive(Type)]
pub enum MyEnum {
    A,
    B,
    C,
}

fn main() {
    assert_eq!(
        ts::export::<TypeOne>(&Default::default()).unwrap(),
        "export type TypeOne = { a: string; b: GenericType<number>; cccccc: MyEnum }".to_string()
    );
}

Check out the docs for more information.

Motivation

This library was originally created to power the type exporting functionality of rspc, but after building it we realized that it could be useful for other projects as well so we decided to move it into a dedicated library.

A huge thanks to Brendonovich for doing a heap of development on this library.

specta's People

Contributors

0hypercube avatar agasparovic avatar avinassh avatar beanow-at-crabnebula avatar bennobuilder avatar brendonovich avatar dependabot[bot] avatar lowr avatar mawallace avatar oscartbeaumont avatar thy486 avatar tonymushah avatar vlad-shcherbina avatar whymidnight avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

specta's Issues

Redo generic handling

I wonder if something like:

#[derive(Type)]
pub struct A {
   a: String
}
#[derive(Type)]
pub struct B {
   #[specta(inline)]
   b: Option<B>
}

should be export A = { b: { a: String } | null } instead of export A = { b: B | null }.

This would require a refactoring of the generic handling because currently the B comes from generics and is always a reference.

It would also be nice to add #[specta(inline_all)] which would opts.any_parent_inlined ? Type::inline : Type::reference for generics. This way.

Redo handling of opts. parent_inline. We should (in most cases) not pass opts directly to child impls because parent_inline is only valid for the type above the current one.

Go Language Exporter

  • Go language support
    • Exporting primitive types
    • Exporting doc comments
    • Export deprecated param from Rust
    • Types that work with serialization/deserialization
    • Fully tested
    • Unit tests using the child language compiler to double-check for valid syntax

`TypeMarker` to avoid bundling types in final build

Specta's Type macro and it's vast number of implementations add code to the build process and the final bundle being shipped to your clients. These types serve no purpose in production builds because you export the types using a development build. It would be nice if they could be removed from the build (without relying on LTO because that increases build time and may not be perfect). I propose:

We introduce a trait as follows:

pub trait TypeMarker {}

Then we modify Type so:

pub trait Type: TypeMarker { ... }

Then we modify the RSPCType macro so that it removes the Type trait implementation for release builds.

Finally rspc can be upgraded to only require TypeMarker at runtime but still have the guarantee that the type supports exporting (unless it were to panic during exporting and I think that's a risk we can live with). This should be configurable on rspc and Specta so that you can force it to include the type information if you really want to do type exporting at runtime.

OpenAPI Language Exporter

Specta already has a prototype of this but more work needs to be done before it can used.

Blocking issues:

  • How to handle generics?

Field keys and string union types block on reserved words list

I'm using specta in a context in which there are many potential collisions with the list of reserved TS words, as defined here. I'm filing this because, to the best of my knowledge, neither object keys nor string union types need to avoid these keywords. For context, I'm generating these types with the specta::export::ts_with_cfg method.

Screenshot_20230224_172427
Needed to change key "from" to "nodeFrom" to get successful compilation due to reserved keyword "from".

Screenshot_20230224_165845
Needed to change string "default" to "defaultMode" to get successful compilation due to reserved keyword "default".

Swift language exporter

  • Swift language support
    • Exporting primitive types
    • Exporting doc comments
    • Export deprecated param from Rust
    • Types that work with serialization/deserialization
    • Fully tested
    • Unit tests using the child language compiler to double-check for valid syntax

Unit test type exporters

  • Rust unit tests
  • Throw a bunch of types into a file and run them through the languages compiler so we can be certain it's valid syntax in the latest release

Typesafe generics on `Type` trait

Currently, methods like Type::inline take &[DataType]. This means you can pass an incorrect number of generics. We have a runtime fallback in case of a mistake but it would be nice to just not allow it to be wrong. Given these methods are an implementation detail is not the end of the world but it would be an extra protection against bugs.

Can probs be fixed by something like:

pub trait Type {
    const GENERICS: usize = 2;

    fn inline(..., generics: [DataType; 2]);
}

Const generics

Eg.

struct Demo<const U: usize> {

}

fn demo<const I: usize>() {

}

Bigint support

Currently, Specta will panic if it encounters a bigint. This is because when using Typescript exporting with JSON and using JSON.parse (like Tauri and rspc use) will cause the number to be incorrect decoded on the frontend. Specta should support bigint because it's transport dependant but it should allow disabling bigint types.

`PartialEq` on exporter error

So assert_eq!(..., Err(...)); works with Specta errors. This really annoying but io::Error doesn't impl PartialEq so will need to work around that by converting it into a custom Specta type.

Rust Language Exporter

  • Rust language support
    • Exporting primitive types
    • Exporting doc comments
    • Export deprecated param from Rust
    • Types that work with serialization/deserialization
    • Fully tested
    • Unit tests using the child language compiler to double-check for valid syntax

Finer grain control over BigInt behavior

Allow modifying the export behavior on a per-type basis and add in all other number types likeu32.

Allow setting a different error message on a per-type basis.

Why? serde_json supports u64 but not u128 by default but Specta treats them the same. This is mostly a feature for library authors but is good to have.

rspc would disable u128 unless Speca (and Serde's) arbitrary-precision feature is enabled. If arbitrary-precision is enabled we should probs change all number types to strings. Idk check that with Serde's docs.

Make it all static

It would be super nice if Specta could work on no_std or at least not allocate memory given all of the structures implemented in the macro are fixed at runtime. The main require change here will be using static slices instead of vectors.

Using references will probably allow removing a lot of the cloning from the Typescript exporter.

None of this should come at the cost of DX, it's a micro-optimization.

const _: () = {
      const TY: ObjectType = ...;

       // Then use something like this in the `Type` impl.
       // fn type() -> &'static ObjectField { &TY }
};

Functions exporting without serde

Right now to export a function all arguments must implement serde::Serialize and all results must implement serde::Deserialize. Can we make it so this bound isn't required but without breaking tauri-specta.

For most cases this will be fine because serde can be used in the transport layer but if you wanna do FFI or use a non-serde compatible encoding you will currently have a bad time.

Protect against recursive inline type

#[derive(Type)]
pub struct MyType {
      #[specta(inline)] // This is the bug
      me: Box<MyType>,
}

This will probs stack overflow and we should detect and prevent it before that stage.

Export contexts

Export subset of types to individual files. Eg.

#[derive(Type)]
#[specta(export  = "api")]
pub struct TypeOne {}

#[derive(Type)]
#[specta(export  = "api")]
pub struct TypeTwo {
     A(TypeTwo), // In the bindings we will need to `import { TypeTwo } from "/microservice.types.ts";` or fail if the user doesn't export `microservice` types.
}

#[derive(Type)]
#[specta(export  = "microservice")]
pub struct TypeTwo {}

// Then when using Specta's export feature you will do something like
export(&[("api", "./api.types.ts"), ("microservice", "./microservice.types.ts")], &[Alias::for::<T>("api")])); // You can omit a context to not export it. Use `Alias` to set the ctx of an external type.

Add an attribute for renaming structs

I am trying to use this crate along with the tauri-specta crate to build an app that is attached to an SQLite database. I am using sea_orm as my ORM and it has an interesting quirk where every model struct must be named Model. For example:

#[derive(Debug, Clone, PartialEq, DeriveEntityModel, Serialize, Deserialize, Type)]
#[sea_orm(table_name = "tests")]
#[serde(rename_all = "camelCase")]
pub struct Model {
    #[sea_orm(primary_key)]
    #[serde(skip_deserializing)]
    pub id: i32,
    pub name: String,
    pub description: String,
}

This is resulting in type declarations that contain errors because the exported types have the same name. I am using this crate to replace a needlessly complicated type generation system using schemars::JsonSchema which allows me to use the attribute: #[schemars(title = "Test")] and the type declaration will have that name. It would make sense to add an attribute within this crate instead of going the route of parsing yet another crates macro attributes, especially if this would be the only attribute you'd borrow from schemars. I will see about implementing this myself and submitting a pull request, and I will update here if I feel it is beyond my capabilities to do so. Thank you :)

Incorrect extra quoting of simple enum variant with whitespaces

[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
specta = { version = "1", features = ["typescript"] }
use specta::{ts, Type};

#[derive(Type)]
#[derive(serde::Serialize)]
enum MyEnum {
    OneWord,
    #[serde(rename = "Two words")]
    TwoWords,
}

fn main() {
    println!("{}", serde_json::to_string(&MyEnum::OneWord).unwrap());
    println!("{}", serde_json::to_string(&MyEnum::TwoWords).unwrap());
    println!("{}", ts::export::<MyEnum>(&Default::default()).unwrap());
}

I got this:

"OneWord"
"Two words"
export type MyEnum = "OneWord" | ""Two words""

Instead it should be

export type MyEnum = "OneWord" | "Two words"

`#[specta(as = Type)]` container attribute

A common pattern I have seen in Spacedrive is:

#[cfg(feature = "specta")]
impl specta::Type for PeerId {
	const NAME: &'static str = "PeerId";

	fn inline(opts: specta::DefOpts, generics: &[specta::DataType]) -> specta::DataType {
		<String as specta::Type>::inline(opts, generics)
	}

	fn reference(opts: specta::DefOpts, generics: &[specta::DataType]) -> specta::DataType {
		<String as specta::Type>::reference(opts, generics)
	}

	fn definition(opts: specta::DefOpts) -> specta::DataType {
		<String as specta::Type>::definition(opts)
	}
}

This could easily be replaced by the macro making for better DX and also less pain when upgrading Specta.

Remove `ExportError` from `Type`

Is it possible to validate enum tagging in the macro such that we don't need to a runtime error for it?

Having the error makes collate_types! cringe.

`ToDataType` exporting

DataTypeExt makes handling exporting of ToDataType stuff complicated and idk how to make it work.

Kotlin language exporter

  • Kotlin language support
    • Exporting primitive types
    • Exporting doc comments
    • Export deprecated param from Rust
    • Types that work with serialization/deserialization
    • Fully tested
    • Unit tests using the child language compiler to double-check for valid syntax

Properly handle advanced trait bounds

Currently, we just throw () inside the generics while exporting. This may not conform to the trait bounds defined by the user on the type. Work around this by requiring the user to specify the type to use either as a default generic or using a Specta derive macro attribute.

Also reenable test_generic_trait_bounds unit test.

Fix Specta macros handling of boolean args

The Specta macros are additive only with boolean args and they shouldn't be. Eg.

#[derive(Type)]
pub struct Demo {
    #[serde(flatten)]
    #[specta(flatten = false)]
    pub demo: SomeType, // We should not flatten this type because Specta takes preference and overrode Serde
}

#[derive(Type)]
#[specta(export = false)]
pub struct Demo2 {
    pub demo: SomeType,
}

Improve TS exporter error handling

  • Custom error type
  • Report name of field in form like object>field>field>field or object>variant>field so that it's easy to know where the error occurred.

Export Rust deprecated parameter

Not working in the macro. Gonna have to open an issue in syn or Rust.

As a workaround you can use the doc comment exporting:

/// Some comments
///
/// @deprecated I hate this type
#[derive(Type)]
pub struct MyType(String);

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.