Aleph-Alpha / ts-rs

Generate TypeScript bindings from Rust types
MIT License
1.06k stars 107 forks source link

Is it possible to generate numeric enums? #140

Open ottosson opened 1 year ago

ottosson commented 1 year ago

Hi. thanks for this awesome library!

Is it possible to generate numeric enums in typescript from my rust enums?

#[derive(Serialize, Deserialize, TS, Debug)]
#[serde(rename_all = "camelCase")]
#[ts(export, export_to = "../src/types/interfaces/")]
pub enum PlotType {
  Display = 0,
  Extents = 1,
  Limits = 2,
  View = 3,
  Window = 4,
  Layout = 5,
}

becomes

export type PlotType = "display" | "extents" | "limits" | "view" | "window" | "layout";

I would expect it to become something like

export const enum PlotType = {
  Display = 0,
  Extents = 1,
  Limits = 2,
  View = 3,
  Window = 4,
  Layout = 5,
}

Is this possible?

ottosson commented 1 year ago

I guess this is how enums are handled in JSON. Not an issue with ts-rs.

percy507 commented 1 year ago

@ottosson How do you solve this?

ottosson commented 1 year ago

@percy507 in my case I didn't have to use the numbers really, but could use the string representation of the enum in TS. Not optimal but it works.

You can convert the enum value to number with serde so I think it should be supported by ts-rs.

percy507 commented 1 year ago

@ottosson Thanks.

I also solve this. I don't use ts-rs. I usetsync. But I think below steps also works for ts-rs.

1. use `serde_repr` crate
2. modify the code
    - `#[derive(Serialize, Deserialize, TS, Debug)]` to `#[derive(Serialize_repr, Deserialize_repr, TS, Debug)]`
setoelkahfi commented 1 month ago

@ottosson Thanks.

I also solve this. I don't use ts-rs. I use'tsync. But I think below steps also works forts-rs`.

"`shell

  1. use serde_repr crate
  2. modify the code
    • #[derive(Serialize, Deserialize, TS, Debug)] to #[derive(Serialize_repr, Deserialize_repr, TS, Debug)]

@percy507 Sadly, this doesn't work. While the rust part works, the generated bindings result in the same format as the other example:

use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use ts_rs::TS;

#[derive(Serialize, Deserialize, TS, Debug)]
#[ts(export)]
pub struct ErrorResponse {
  pub error_code: ErrorCode,
  pub message: String,
}

#[repr(i32)]
#[derive(Serialize_repr, Deserialize_repr, TS, Debug)]
#[ts(export)]
pub enum ErrorCode {
    // User defined error codes starts from 1000
    UserNotFound = 1000,
    InvalidCredentials = 1001,

    // Generic error codes starts from 1
    InvalidRequest = 1,
    ParseError = 2,
    NetworkError = 3,
}

Binding:

// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type ErrorCode = "UserNotFound" | "InvalidCredentials" | "InvalidRequest" | "ParseError" | "NetworkError";

Is this possible with the current version? @ottosson, Could you reopen this issue? Otherwise, I'll open a new issue.

gustavo-shigueo commented 1 month ago

As stated before, we really dislike TS enums (This comment is a very thorough explanation on the reasons to prefer type unions over TS enums), but we could try to figrure out some way to work with serde-repr. No idea how yet, so no promises.

In the meantime you could do

use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use ts_rs::TS;

#[derive(Serialize, Deserialize, TS, Debug)]
#[ts(export)]
pub struct ErrorResponse {
  pub error_code: ErrorCode,
  pub message: String,
}

#[repr(i32)]
#[derive(Serialize_repr, Deserialize_repr, TS, Debug)]
#[ts(export, type = "1000 | 1001 | 1 | 2 | 3")]
pub enum ErrorCode {
    // User defined error codes starts from 1000
    UserNotFound = 1000,
    InvalidCredentials = 1001,

    // Generic error codes starts from 1
    InvalidRequest = 1,
    ParseError = 2,
    NetworkError = 3,
}

Which should generate

export type ErrorCode = 1000 | 1000 | 1 | 2 | 3;
NyxCode commented 1 month ago

Hey!

While I share @gustavo-shigueo's sentiment, i still think supporting this would be pretty cool.
Maybe we could add a #[ts(repr)], which would try to be compatible with serde_repr?
Anyways, I think now is a good time to take a 2nd look at this, after we shipped the new enum handeling.

gustavo-shigueo commented 1 month ago

Maybe we could have it as #[ts(repr = "enum")] and make that incompatible with #[ts(tag)], #[ts(content)], #[ts(type)], #[ts(as)] and any other attribute that wouldn't make sense on a TS enum. That should help us avoid having to change the enum_def and format_variant functions, which have very specific logic for dealing with tagging representations.

We could create a new enum_repr function that parses this type of enum, demanding that all variants are unit variants, with optional = number, making this a completely separate code path

setoelkahfi commented 1 month ago

Hey!

While I share @gustavo-shigueo's sentiment, i still think supporting this would be pretty cool. Maybe we could add a #[ts(repr)], which would try to be compatible with serde_repr? Anyways, I think now is a good time to take a 2nd look at this, after we shipped the new enum handeling.

I don't know how this crate works internally, but in the meantime, I'm using tsync as @percy507 suggested. I use their simple #[tsync] macro, which works out of the box. I can't help with development, but I could help with testing it.