specta-rs / tauri-specta

Completely typesafe Tauri commands
MIT License
382 stars 41 forks source link

bigint types? #6

Closed probablykasper closed 1 year ago

probablykasper commented 1 year ago

Some types, like u64 and i64, bind to BigInt. Does Tauri ever actually serialize to BigInt? My OS webview version doesn't support it

Brendonovich commented 1 year ago

Hmm, this is interesting. ts-rs and specta convert those to bigint since it's supposedly what serde converts to. Your webview not supporting it is a bit of a worry though.

probablykasper commented 1 year ago

In my webview, it just serializes into a number. Would you be able to check if you have typeof x === 'bigint' for command arguments or return types?

Brendonovich commented 1 year ago

I'm honestly at a loss at how Specta should handle u64 and the like. ts-rs does bigint, JSON.parse does number with a loss of precision, and typeshare just don't allow generating bindings for u64/usize in TS (they allow it in Kotlin and stuff bc they have dedicated large number types).

probablykasper commented 1 year ago

If tauri parses it into number, then I'd say it should be typed as a number. Maybe there's a way to require an attribute like #[deserialize_lossy_number] to be explicit about it

oscartbeaumont commented 1 year ago

It is very tough and I am also not sure what is a good solution. I have put some of my thoughts below.

An idea I had is for Specta to:

This makes the exporting process very explicit but it could also run into issues if multiple libraries based on Specta are used in your project and they cause both arbitrary-precision and bigints to be enabled which would be a compiler error and break the project. It would also be weird with exporting of non-typescript languages which isn't optimal. We could try to control this through a runtime config which could be provided to the Typescript exporter which would deal with those limitations but this would mean runtime instead of compile time errors which also kinda sucks (although type exporting is generally done at program startup which might mitigate the downside of runtime errors).

If we did something like that it could be nice if tauri-specta could enable the bigints feature for Specta but serialize the number as a string internally and decode it to a bigint on the front end. However, I have no idea how hard/possible this will be to implement given Tauri have control of the serialization process. I also don't know if this is me overreaching the scope of this library. If we were going to do this, one solution would be to enable the arbitrary-precision feature on Serde in tauri-specta to make it encode the bigints as a string but this risks affecting other crates using serde in your project due to how Cargo handles features. I also think enabling arbitrary-precision on serde will cause issues for languages other than Typescript so this probably isn't an approach we could take. I wonder if we could do some macro magic with our command macro to make this work, idk.

@probablykasper I am not a huge fan of just typing it as a number because if you look at this playground you can see serde_json is encoding the whole number correctly, the issue is with JSON.parse. It is entirely possible someone could use specta/tauri-specta with json-bigint which I am under the impression could correctly decode the number as a bigint (although I haven't tested it). I would be curious about your thoughts given that.

For now, type overrides can be used to workaround this problem until a good solution can be found.

probablykasper commented 1 year ago

This makes the exporting process very explicit but it could also run into issues if multiple libraries based on Specta are used in your project and they cause both arbitrary-precision and bigints to be enabled which would be a compiler error and break the project.

Would it be possible to have an arbitrary-precision field attribute, like Serde does? Something like #[serde(serialize_with = "as_full_precision_string")].

arbitrary-precision on serde will cause issues for languages other than Typescript so this probably isn't an approach we could take.

Even using Serde, I think you only get to choose one way to serialize a field. If you need to serialize to different targets, you'd have to handle them separately.

@probablykasper I am not a huge fan of just typing it as a number because if you look at this playground you can see serde_json is encoding the whole number correctly, the issue is with JSON.parse. It is entirely possible someone could use specta/tauri-specta with json-bigint which I am under the impression could correctly decode the number as a bigint (although I haven't tested it). I would be curious about your thoughts given that.

Ah, interesting! The frontend deserializer is what decides your types in the end.

As long as Tauri is responsible for deserialization, it'll give you a number anyway. If there's no clear/easy way to automatically deserialize to bigint, I think it's fine to not have a clear/easy way to specify the bigint type. So I'd say for now at least, number is probably ok.

You could also expose a u53/i53 type for people to use safely

oscartbeaumont commented 1 year ago

Would it be possible to have an arbitrary-precision field attribute, like Serde does? Something like #[serde(serialize_with = "as_full_precision_string")].

Do you have any reference for as_full_precision_string because serialize_with expects a function in the local scope, doesn't it? I couldn't find any references to it in Serde or online but maybe I just missed it.

Even using Serde, I think you only get to choose one way to serialize a field. If you need to serialize to different targets, you'd have to handle them separately.

You make a very fair point, maybe it just isn't possible.

You could also expose a u53/i53 type for people to use safely

Is u53 and i53 from a crate like ux? We could definitely look at supporting that.

oscartbeaumont commented 1 year ago

https://discord.com/channels/616186924390023171/986184168998330371/1050735500278894643

Thoughts on that being the solution?

After more thinking that wouldn't work as much as I wish it was possible.

Brendonovich commented 1 year ago

We've decided that the default behaviour for Specta with u64/u128 and the like will be to panic, indicating that TypeScript doesn't support 64 bit integers. Using #[serde(with = "int_string_serde")] and #[specta(type = String)] overrides would still allow for serializing to strings in order to preserve precision, and we may make it easier to properly emit bigint for use cases which support it.

probablykasper commented 1 year ago

What would be the recommended way to go if you just want your u64 converted to number and don't care if it's lossy?

probablykasper commented 1 year ago

Do you have any reference for as_full_precision_string because serialize_with expects a function in the local scope, doesn't it? I couldn't find any references to it in Serde or online but maybe I just missed it.

Sorry, as_full_precision_string was just an example of how it could work

Is u53 and i53 from a crate like ux? We could definitely look at supporting that.

ux has those, yeah. Not sure if it's best to re-export from ux or create a custom one

Brendonovich commented 1 year ago

What would be the recommended way to go if you just want your u64 converted to number and don't care if it's lossy?

For now probs just a type override,#[specta(type = u32)]. Will probs want ts-specific type overrides so that other languages can have more accurate types at the same time.

probablykasper commented 1 year ago

That's not something that's supported yet, right?

Brendonovich commented 1 year ago

#[specta(type = u32)] works, but language specific overrides aren't implemented yet. Edit: It doesn't yet work for function args, but it works for struck and enum fields

probablykasper commented 1 year ago

Is there an example anywhere of how to use it? Tried adding it to a struct field, but gave me errors

Brendonovich commented 1 year ago

Is there an example anywhere of how to use it? Tried adding it to a struct field, but gave me errors

Hm, would you mind sharing the error? If you #[derive(Type)] then the attribute should be valid.

probablykasper commented 1 year ago

My bad, must've used it incorrectly the first time. Works perfectly!

Brendonovich commented 1 year ago

We've decided that the default behaviour for Specta with u64/u128 and the like will be to panic, indicating that TypeScript doesn't support 64 bit integers. Using #[serde(with = "int_string_serde")] and #[specta(type = String)] overrides would still allow for serializing to strings in order to preserve precision, and we may make it easier to properly emit bigint for use cases which support it.

This panic has been implemented with some info about string serialization here.

desprit commented 1 year ago
#[derive(Debug, Clone, Serialize, Deserialize, Type)]
pub struct MessageBlock {
    pub id: String,
    pub text: String,
    pub language: String,
    pub block_type: String,
    pub created_at: i64,
    pub updated_at: i64,
    pub message_id: String,
}

BigIntForbidden(MessageBlock.created_at -> i64)

I'm on MacOS M1. Can't figure out if this is something OS-related or something else. Unfortunately I can't easily change the model to i32.

Brendonovich commented 1 year ago

@desprit BigInt exporting will fail by default, you can override the behaviour via ExportConfiguration

desprit commented 1 year ago

@Brendonovich Thank you, it worked.

JulianKominovic commented 3 months ago

Hi! I'm leaving a little example :)

fn main() {
    let specta_config = ExportConfiguration::new().bigint(specta::ts::BigIntExportBehavior::Number);
    #[cfg(debug_assertions)]
    ts::export_with_cfg(
        collect_types![greet, fire].unwrap(),
        specta_config,
        "../src/bindings.ts",
    )
    .unwrap();

    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![greet, fire])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

-- Edit by @oscartbeaumont WARNING: Your numbers may be lossily decoded on the frontend if you do this!