Closed probablykasper closed 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.
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?
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
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:
arbitrary-precision
feature on Specta to make large numbers export as a string typebigints
(maybe give it a better name) feature on Specta to enable large numbers to export as a bigint typeThis 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.
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
andbigints
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 seeserde_json
is encoding the whole number correctly, the issue is withJSON.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
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.
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.
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.
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?
Do you have any reference for
as_full_precision_string
becauseserialize_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
andi53
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
What would be the recommended way to go if you just want your
u64
converted tonumber
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.
That's not something that's supported yet, right?
#[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
Is there an example anywhere of how to use it? Tried adding it to a struct field, but gave me errors
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.
My bad, must've used it incorrectly the first time. Works perfectly!
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.
#[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.
@desprit BigInt exporting will fail by default, you can override the behaviour via ExportConfiguration
@Brendonovich Thank you, it worked.
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!
Some types, like
u64
andi64
, bind to BigInt. Does Tauri ever actually serialize to BigInt? My OS webview version doesn't support it