Failing for Internally/Adjacently tagged
I need to interact with an external API that uses Adjacentelly tagged enums, like the following:
#[derive(Serialize, Deserialize)]
#[tsync]
pub struct CameraControl {
pub camera_uuid: String,
#[serde(flatten)]
pub action: Action,
}
#[derive(Serialize, Deserialize)]
#[serde(tag = "action", content = "json")]
//#[serde(tag = "action")] // This also fails, same output
#[tsync]
pub enum Action {
GetVideoParameterSettings(VideoParameterSettings),
}
#[skip_serializing_none]
#[derive(Serialize, Deserialize)]
#[tsync]
pub struct VideoParameterSettings {
pub frame_rate: Option<u16>,
}
This is producing the following:
/* This file is generated and managed by tsync */
type CameraControl = Action & {
camera_uuid: string;
}
type Action =;
interface VideoParameterSettings {
frame_rate?: number;
}
If I define it as Externally Tagged (which is not what I need):
#[derive(Serialize, Deserialize)]
#[tsync]
pub struct CameraControl {
pub camera_uuid: String,
#[serde(flatten)]
pub action: Action,
}
#[derive(Serialize, Deserialize)]
//#[serde(tag = "action", content = "json")]
//#[serde(tag = "action")] // This also fails, same output
#[tsync]
pub enum Action {
GetVideoParameterSettings(VideoParameterSettings),
}
#[skip_serializing_none]
#[derive(Serialize, Deserialize)]
#[tsync]
pub struct VideoParameterSettings {
pub frame_rate: Option<u16>,
}
The result is:
/* This file is generated and managed by tsync */
type CameraControl = Action & {
camera_uuid: string;
}
type Action =
| { "GetVideoParameterSettings": VideoParameterSettings };
interface VideoParameterSettings {
frame_rate?: number;
}
using tsync = "2.2.1"
Am I missing something?
Thanks!
I have a bit of time rn so I might be able to take a crack at this, could you tell me more about the expected output in this case?
okay this seems to be a bug with how tuple variants are handled, probably related to #43 and #46
I have a bit of time rn so I might be able to take a crack at this, could you tell me more about the expected output in this case?
Nice! Thanks for the fast answer. I'm open to any output I can work with on the typescript side, as I'm still implementing the code.
One possibility would be something like:
interface GetVencConfAction {
action: "getVencConf";
json: VideoParameterSettings;
}
type CameraControl = {
camera_uuid: string;
} & Action;
type Action =
| GetVencConfAction;
interface VideoParameterSettings {
frame_rate?: number;
}
what about something like this?
/* This file is generated and managed by tsync */
type CameraControl = Action & {
camera_uuid: string;
}
type Action =
| Action__GetVideoParameterSettings;
type Action__GetVideoParameterSettings = {
"action": "GetVideoParameterSettings";
"json": VideoParameterSettings;
};
interface VideoParameterSettings {
frame_rate?: number;
}
I don't really use typescript, so I'm not 100% sure this is valid syntax
working on a PR that could resolve #58, #43, and #46.
so far, this input:
#[tsync]
#[derive(Serialize, Deserialize)]
pub struct CameraControl {
pub camera_uuid: String,
#[serde(flatten)]
pub action: Action,
}
#[tsync]
#[derive(Serialize, Deserialize)]
#[serde(tag = "action", content = "json")]
// #[serde(tag = "action")] // This also fails, same output
pub enum Action {
GetVideoParameterSettings(VideoParameterSettings),
SomeTupleVariant(String, u32),
SomeOtherAction {
some_parameters: SomeOtherActionParameters,
},
SomethingElseWithoutParameters,
}
#[tsync]
#[derive(Serialize, Deserialize)]
pub struct SomeTupleStruct(String, u32);
#[tsync]
#[derive(Serialize, Deserialize)]
pub struct VideoParameterSettings {
pub frame_rate: Option<u16>,
}
#[tsync]
#[derive(Serialize, Deserialize)]
pub struct SomeOtherActionParameters {
pub some_other_field: String,
}
produces this output:
/* This file is generated and managed by tsync */
type CameraControl = Action & {
camera_uuid: string;
}
type Action =
| Action__GetVideoParameterSettings
| Action__SomeTupleVariant
| Action__SomeOtherAction
| Action__SomethingElseWithoutParameters;
type Action__GetVideoParameterSettings = {
"action": "GetVideoParameterSettings";
"json": VideoParameterSettings;
};
type Action__SomeTupleVariant = {
"action": "SomeTupleVariant";
"json": [ string, number ];
};
type Action__SomeOtherAction = {
action: "SomeOtherAction";
some_parameters: SomeOtherActionParameters;
};
type Action__SomethingElseWithoutParameters = {
action: "SomethingElseWithoutParameters";
};
type SomeTupleStruct = [ string, number ]
interface VideoParameterSettings {
frame_rate?: number;
}
interface SomeOtherActionParameters {
some_other_field: string;
}
should type SomeTupleStruct = [ string, number ] end in a semi-colon?
That output looks great =)
should
type SomeTupleStruct = [ string, number ]end in a semi-colon?
it doesn't hurt to add, but if you prefer not to, the interpreter/engine will add it (ASI)
btw, I'll test your PR on my full code tomorrow, ty!
lmk how that goes
lmk how that goes
I've just tested it using your PR branch and the generated code is perfect for my use :)
For my final application, the only missing thing is the serde field rename (#42). Let me know if I can help!
I'll try to take a look at this on the weekend Anthony. Thanks for improving the community's rust tools! :)
Hi @AnthonyMichaelTDM, I was testing further your PR and noticed it is failing for the following case:
#[derive(Deserialize, Serialize)]
#[tsync]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum CaptureConfiguration {
Video(VideoCaptureConfiguration),
Redirect(RedirectCaptureConfiguration),
}
#[derive(Deserialize, Serialize)]
#[tsync]
pub struct VideoCaptureConfiguration {
pub height: u32,
pub width: u32,
}
#[derive(Deserialize, Serialize)]
#[tsync]
pub struct RedirectCaptureConfiguration {}
The expected would be:
type CaptureConfiguration =
| ({ type: "video" } & VideoCaptureConfiguration)
| ({ type: "redirect" } & RedirectCaptureConfiguration)
interface VideoCaptureConfiguration {
height: number;
width: number;
}
interface RedirectCaptureConfiguration {
[key: PropertyKey]: never // note: this is an empty object
}
But the current (from your PR) output is:
type CaptureConfiguration =;
export interface VideoCaptureConfiguration {
height: number;
width: number;
}
interface RedirectCaptureConfiguration = {
};
Two things: it's (1) failing to generate the internally tagged "type" for "CaptureConfiguration", and it's (2) failing to generate an empty object type. Note that: {} is not an empty object in typescript. I can easily overcome this empty-object thing using a regex after generation, but I think the (1) is very important to be fixed.
What can we do about it?
Thanks
yeah, so without specifying the content attribute, it'll try to do internal tagging, but fail (if you run it with the -d argument you'll see the errors)
to fix this, either don't specify a tag (external tagging):
#[derive(Deserialize, Serialize)]
#[tsync]
#[serde(rename_all = "lowercase")]
pub enum CaptureConfiguration {
Video(VideoCaptureConfiguration),
Redirect(RedirectCaptureConfiguration),
}
#[derive(Deserialize, Serialize)]
#[tsync]
pub struct VideoCaptureConfiguration {
pub height: u32,
pub width: u32,
}
#[derive(Deserialize, Serialize)]
#[tsync]
pub struct RedirectCaptureConfiguration {}
to get
/* This file is generated and managed by tsync */
type CaptureConfiguration =
| { "video": VideoCaptureConfiguration }
| { "redirect": RedirectCaptureConfiguration };
interface VideoCaptureConfiguration {
height: number;
width: number;
}
interface RedirectCaptureConfiguration {
}
or set the content attribute (adjacent tagging):
#[derive(Deserialize, Serialize)]
#[tsync]
#[serde(tag = "type", content = "data", rename_all = "lowercase")]
pub enum CaptureConfiguration {
Video(VideoCaptureConfiguration),
Redirect(RedirectCaptureConfiguration),
}
#[derive(Deserialize, Serialize)]
#[tsync]
pub struct VideoCaptureConfiguration {
pub height: u32,
pub width: u32,
}
#[derive(Deserialize, Serialize)]
#[tsync]
pub struct RedirectCaptureConfiguration {}
to get
/* This file is generated and managed by tsync */
type CaptureConfiguration =
| CaptureConfiguration__Video
| CaptureConfiguration__Redirect;
type CaptureConfiguration__Video = {
"type": "Video";
"data": VideoCaptureConfiguration;
};
type CaptureConfiguration__Redirect = {
"type": "Redirect";
"data": RedirectCaptureConfiguration;
};
interface VideoCaptureConfiguration {
height: number;
width: number;
}
I see, thanks for the ideas, but for this CaptureConfiguration case, I need it internally tagged, like:
type CaptureConfiguration =
| ({ type: "video" } & VideoCaptureConfiguration)
| ({ type: "redirect" } & RedirectCaptureConfiguration);
For now, I'm using ts-rs for that, and using this lib when I need numbered enums, which is something this crate does very well! :)
thanks!
https://serde.rs/enum-representations.html
https://serde.rs/enum-representations.html
It would be perfect to me if this lib could support all those enum representations, because I do need to use all of them: we don't always have control over the APIs we are working with. In my specific case, some use externally tagged, some internally tagged, and others adjacently tagged. On top of that, they use different casing styles (camelCase, PascalCase, snake_case, etc.), so I need to just be flexible.
Given the example from the serde docs, I would expect the following Rust enum:
#[derive(Serialize, Deserialize)]
#[serde(tag = "type")]
enum Message {
Request { id: String, method: String, params: Params },
Response { id: String, result: Value },
}
can represent the following two json objects:
{
"type": "Request",
"id": "123",
"method": "getData",
"params": {...}
}
{
"type": "Response",
"id": "123",
"result": {...}
}
which can be represented in typescript as:
interface Request {
id: string;
method: string;
params: Params;
}
interface Response {
id: string;
result: Value;
}
type Message =
| ({ type: "Request" } & Request)
| ({ type: "Response" } & Response);
Does this sound ok?
Also, just to clarify -- there's no rush or pressure. I truly appreciate the work you've already done; it has literally saved me this month with the numbered enum representation!
Thanks for taking the time and effort!
That makes sense, I'm just not sure that it's entirely correct if that makes sense?
Just sending the link wasn't very .. helpful of me, sorry about that. What I meant was that, according to that link, giving a tag for an enum that has tuple variants is a compile error.
That leaves me with two questions:
- what is the correct type for an empty object?
- is what you want functionally different from using internal tagging but with struct variants, like:
#[derive(Serialize, Deserialize)]
#[serde(tag = "type")]
enum Message {
Request { id: String, method: String, params: Params },
Response { id: String, result: Value },
}
or for your case, I suppose:
#[derive(Deserialize, Serialize)]
#[tsync]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum CaptureConfiguration {
Video {
height: u32,
width: u32,
},
Redirect,
}
which generates:
type CaptureConfiguration =
| CaptureConfiguration__Video
| CaptureConfiguration__Redirect;
type CaptureConfiguration__Video = {
type: "video";
height: number;
width: number;
};
type CaptureConfiguration__Redirect = {
type: "redirect";
};
Oh right, newtype variants!
It says they should be supported, and your example qualifies as one.
I'll take a crack at it but can't make any promises
what is the correct type for an empty object?
interface EmptyObjectExample {
[key: PropertyKey]: never
}
is what you want functionally different from using internal tagging but with struct variants, like: Oh right, newtype variants!
Yes, that's almost the same, except I need the internal structs (the newtype variants) to be exported as well :)
I'll take a crack at it but can't make any promises
awesome, let me know if you need anything from my side!
I'm actually almost done, it was easier than I thought. Just doing final cleanup before I commit the changes
Alright, pushed. Go ahead and take a look, I'll fix empty object typing next