specta-rs / specta

Easily export your Rust types to other languages
MIT License
293 stars 41 forks source link

Support phase-specific renames #190

Open willcrichton opened 11 months ago

willcrichton commented 11 months ago

I have a type like this:

#[derive(Serialize, Deserialize, specta::Type)]
pub struct Package {
  #[serde(rename(deserialize = "@version"))]
  pub version: String,
}

But specta (2.0.0-rc7) raises the error:

error: specta: expected string literal. Eg. `"somestring"`
  --> crates/bene-epub/src/lib.rs:16:11
   |
16 |   #[serde(rename(deserialize = "@version"))]
   |           ^^^^^^
oscartbeaumont commented 11 months ago

Hmm, how would you expect this to act?

It's a challenge because we export a single named type (export type Package) so we would need to unify the serialize and deserialize types, probably like the following:

export type Package = {
    version: string,
} | {
    "@version": string
};

Is something preventing you from just using a normal rename?

willcrichton commented 11 months ago

I see your point. My use-case is that I am deserializing an XML file in Rust (using quick_xml's naming conventions), and then serializing this type into JSON to pass to Typescript. In my particular use-case, I never need to serialize the type within Typescript, so it would be fine for the Typescript field name to only take the serialize name and not the deserialize. I am trying to avoid having to duplicate the Package type definition.

One possibility is that I could tell specta to ignore/override the serde attribute, like this:

#[derive(Serialize, Deserialize, specta::Type)]
pub struct Package {
  #[serde(rename(deserialize = "@version"))]
  #[specta(rename = "version")]
  pub version: String,
}
oscartbeaumont commented 11 months ago

Thinking about it,

I would be fine adding the per-phase metadata for rename and anything other property that isn't symmetrical. This data would be especially useful in something like rspc which could actually use that information safely given its full control over the transport layer.

For the "less sophisticated" exporters like specta::export I don't exactly know what would be the best way to use this information as we can't guarantee the types will be used for the correct phase. I suspect we would default to the enum of both phases to be conservative.

We could probably allow a complete export for each phase similar to the bigint configuration and leave it up to the user to ensure they are used correctly.

Are you using specta::export, the lower level specta::ts::* api's or another exporter like rspc, tauri-specta, etc?

willcrichton commented 11 months ago

I am using tauri-specta. This is the return type from a Tauri command.

Brendonovich commented 11 months ago

I'm not sure what other attributes would need phase-specific support, but i think this could go nicely with #76, where we use the union type by default but remove options from it if they're never going to be hit.