Tonic annotate only enums at compile time - rust

Using tonic_build I can add .type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]”) to annotate my types with e.g. serde.
However is there a way to only annotate enums, that are in the same package as structs.
Example:
syntax = "proto3";
package foo.bar;
message A {
Enu b = 0;
}
enum Enu {
FOO = 0;
BAR = 1;
}
I want in the end, for the generated struct to have serde, which I can already but for the enum to have one more annotation (strum in this case):
#[derive(serde::Serialize, serde::Deserialize)]
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct A {
#[prost(enumeration = β€œEnu", tag = β€œ1")]
pub b: i32,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
#[derive(strum::EnumString)] // <β€” This is what I want added only here
pub enum Enu {
FOO = 0,
BAR = 1,
}
Note this was hand-crafted, so I might have missed a character somewhere

Related

Retrieve constants from inner in Newtype pattern

Is there a way to retrieve public constants from the inner struct in the Newtype pattern?
Say I am using a struct from another crate like this
#[derive(PartialEq)]
pub struct Version(u32);
impl Version {
pub const v0: Self = Self(0);
pub const v1: Self = Self(1);
}
Now, in my code I need to wrap it with a newtype pattern. So far so good.
#[derive(PartialEq)]
pub struct MyVersion(Version);
I want to get the inner constant using the wrapper type along the lines of MyVersion::v0. Is this doable?
Rust Playground link
Thanks to #Sven for suggesting a solution that works with stable Rust.
A way out of this is to implement a trait with an associated type. The end usage is awkward (due to E0223), but at least re-defining constants and associated methods is unnecessary.
// original crate
#[derive(Debug, PartialEq)]
pub struct Version(u32);
impl Version {
pub const V0: Self = Self(0);
pub const V1: Self = Self(1);
}
// another crate
use std::ops::Deref;
#[derive(Debug, PartialEq)]
pub struct MyVersion(Version);
impl Deref for MyVersion {
type Target = Version;
fn deref(&self) -> &Self::Target {
&self.0
}
}
trait Versioning {
type V;
}
impl Versioning for MyVersion {
type V = Version;
}
fn main() {
let myv0 = MyVersion(Version::V0);
assert_eq!(*myv0, <MyVersion as Versioning>::V::V0);
}
The custom trait will probably not be needed when inherent associated type lands in the stable channel.

How to use serde_json with a union type like enum?

I have two structs that I want to serialize/deserialize with the tag as a "type" field in JSON, like so.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
struct ThingA {
value: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
struct ThingB {
value: usize,
}
These serialize as expected. For example,
let a = ThingA { value: 0 };
println!("{}", serde_json::to_string(&a)?);
// This yields the expected result:
// {"type":"ThingA","value":0}
However, I'm running into trouble when I try to add an enum to stand in as a union type for the structs.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
enum Thing {
ThingA(ThingA),
ThingB(ThingB),
}
The definition above works fine for deserializing JSON, but adds an extra field during serialization.
let json = r#"{"type": "ThingB", "value": 0}"#;
let thing: Thing = serde_json::from_str(json)?;
// Correctly parses to:
// ThingB(ThingB { value: 0 })
println!("{}", serde_json::to_string(&thing)?);
// Incorrectly serializes with an extra "type" field:
// {"type":"ThingB","type":"ThingB","value":0}
Changing #[serde(tag = "type")] to #[serde(untagged)] on the Thing enum causes the opposite problem: Thing instances serialize properly, but don't get parsed correctly anymore.
My goal is to get JSON {"type": "ThingB", value: 0} to evaluate to Thing::ThingB(ThingB {value: 0}) during deserialization, and vice versa, but only if I'm deserializing to a Thing. If I have an unwrapped ThingB, like ThingB {value: 0}, I want it to serialize to {"type": "ThingB", value: 0} as well.
So my questions are: Is there any way to assign the serde tag or untagged attributes such that they only apply during serialization/deserialization (similar to serde's rename)? If not, any advice on how to implement Serialize and/or Deserialize to achieve my goal?
You can just use tag in your Thing enum, leaving the others clean:
use serde::{Serialize, Deserialize}; // 1.0.124
use serde_json; // 1.0.64
#[derive(Debug, Clone, Serialize, Deserialize)]
struct ThingA {
value: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct ThingB {
value: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
enum Thing {
ThingA(ThingA),
ThingB(ThingB),
}
fn main() {
let json = r#"{"type": "ThingB", "value": 0}"#;
let thing: Thing = serde_json::from_str(json).unwrap();
println!("{}", serde_json::to_string(&thing).unwrap());
}
Playground
As requested in the comments.
In case we would want to have both tagged (enum and structs) we would need to make some serde abracadabra playing with wrappers and with the with. More info can be found here

Serde tag = x, but keep the tag in the struct

I'm trying to de-serialise JSON to a Rust struct with enum variants using internally tagged JSON (https://serde.rs/enum-representations.html).
I want to store the tag of the variant inside the struct - currently serde stores this data in attributes.
Can this be done keeping the tag key inside the struct?
The methods I have tried:
A. #[serde(untagged)]
This works but I want to avoid it because of the performance hit of searching for a pattern match.
B. #[serde(tag = "f_tag")]
This does not work and results in "duplicate field `f_tag`".
Both the serde attribute and the enum variant rename try to use the same key.
I do not want to place #[serde(rename = "f_tag")] under Uni as this defines the tag key in the parent enum. (I want child structs to have the same tag key anywhere they are contained inside a parent enum).
Example:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=584c3f32488a1a27f47dac9a0e81d31c
use serde::{Deserialize, Serialize};
use serde_json::json;
use serde_json::{Result, Value};
#[derive(Serialize, Deserialize, Debug)]
struct S1 {
f1: String,
f2: Uni,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "f_tag")]
// #[serde(untagged)]
enum Uni {
String(String),
// #[serde(rename = "f_tag")]
S2(S2),
// #[serde(rename = "f_tag")]
S3(S3),
}
#[derive(Serialize, Deserialize, Debug)]
struct S2 {
f_tag: S2Tag,
f2_s2: bool,
}
#[derive(Serialize, Deserialize, Debug)]
struct S3 {
f_tag: S3Tag,
f2_s3: bool,
}
#[derive(Serialize, Deserialize, Debug)]
enum S2Tag {
#[serde(rename = "s2")]
S2,
}
#[derive(Serialize, Deserialize, Debug)]
enum S3Tag {
#[serde(rename = "s3")]
S3,
}
fn main() {
let s1 = S1 {
f1: "s1.f1".into(),
f2: Uni::S2(S2 {
f_tag: S2Tag::S2,
f2_s2: true,
}),
};
let j = serde_json::to_string(&s1).unwrap();
dbg!(&j);
let s1_2: S1 = serde_json::from_str(&j).unwrap();
dbg!(s1_2);
}
{
"f1": "s1.f1",
"f2": {
// Issue: serde(tag = x) uses the name of the enum here.
"f_tag": "S2",
"f_tag": "s2",
"f2_s2": true
}
}

How to make a public struct where all fields are public without repeating `pub` for every field?

How can I define a public struct in Rust where all the fields are public without having to repeat pub modifier in front of every field?
A pub_struct macro would be ideal:
pub_struct! Foo {
a: i32,
b: f64,
// ...
}
which would be equivalent to:
pub struct Foo {
pub a: i32,
pub b: f64,
//...
}
macro_rules! pub_struct {
($name:ident {$($field:ident: $t:ty,)*}) => {
#[derive(Debug, Clone, PartialEq)] // ewww
pub struct $name {
$(pub $field: $t),*
}
}
}
Unfortunately, derive may only be applied to structs, enums and unions, so I don't know how to hoist those to the caller.
Usage:
pub_struct!(Foo {
a: i32,
b: f64,
});
It would be nice if I didn't need the parentheses and semicolon, i.e. if Rust supported reader macros.

How do I create FFI bindings to C functions expecting OR-ed bytes?

I tried to create FFI bindings to libmodbus, written in C.
Here I stumble upon this function
modbus_set_error_recovery(ctx,
MODBUS_ERROR_RECOVERY_LINK |
MODBUS_ERROR_RECOVERY_PROTOCOL);
The second parameter is defined as
typedef enum
{
MODBUS_ERROR_RECOVERY_NONE = 0,
MODBUS_ERROR_RECOVERY_LINK = (1<<1),
MODBUS_ERROR_RECOVERY_PROTOCOL = (1<<2)
} modbus_error_recovery_mode;
My bindgen-generated bindings are these:
#[repr(u32)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum modbus_error_recovery_mode {
MODBUS_ERROR_RECOVERY_NONE = 0,
MODBUS_ERROR_RECOVERY_LINK = 2,
MODBUS_ERROR_RECOVERY_PROTOCOL = 4,
}
and
extern "C" {
pub fn modbus_set_error_recovery(ctx: *mut modbus_t,
error_recovery:
modbus_error_recovery_mode)
-> ::std::os::raw::c_int;
}
My safe interface looks like this, so far:
pub fn set_error_recovery(&mut self, error_recovery_mode: ErrorRecoveryMode) -> Result<()> {
unsafe {
match ffi::modbus_set_error_recovery(self.ctx, error_recovery_mode.to_c()) {
-1 => bail!(Error::last_os_error()),
0 => Ok(()),
_ => panic!("libmodbus API incompatible response"),
}
}
}
and
use std::ops::BitOr;
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum ErrorRecoveryMode {
NONE = 0,
Link = 2,
Protocol = 4,
}
impl ErrorRecoveryMode {
pub fn to_c(self) -> ffi::modbus_error_recovery_mode {
match self {
NONE => ffi::modbus_error_recovery_mode::MODBUS_ERROR_RECOVERY_NONE,
Link => ffi::modbus_error_recovery_mode::MODBUS_ERROR_RECOVERY_LINK,
Protocol => ffi::modbus_error_recovery_mode::MODBUS_ERROR_RECOVERY_PROTOCOL,
}
}
}
impl BitOr for ErrorRecoveryMode {
type Output = Self;
fn bitor(self, rhs: ErrorRecoveryMode) -> ErrorRecoveryMode {
self | rhs
}
}
This triggered a stack overflow if I call set_error_recovery like this
assert!(modbus.set_error_recovery(ErrorRecoveryMode::Link | ErrorRecoveryMode::Protocol).is_ok())
The error is
thread 'set_error_recovery' has overflowed its stack
fatal runtime error: stack overflow
As DK. mentioned:
C's enum and Rust's enum have different restrictions.
It's not valid to have a Rust enum that isn't one of the enum variants.
What you have is called "bitflags"
Luckily, Bindgen understands bitflags. If you generate your headers while passing the bitfield-enum flag or by using Builder::bitfield_enum:
bindgen --bitfield-enum modbus_error_recovery_mode fake-modbus.h
Bindgen will generate constants for each C enum value, a newtype wrapper, and implementations of the Bit* traits:
// Many implementation details removed
pub struct modbus_error_recovery_mode(pub ::std::os::raw::c_uint);
pub const modbus_error_recovery_mode_MODBUS_ERROR_RECOVERY_NONE: modbus_error_recovery_mode =
modbus_error_recovery_mode(0);
pub const modbus_error_recovery_mode_MODBUS_ERROR_RECOVERY_LINK: modbus_error_recovery_mode =
modbus_error_recovery_mode(2);
pub const modbus_error_recovery_mode_MODBUS_ERROR_RECOVERY_PROTOCOL: modbus_error_recovery_mode =
modbus_error_recovery_mode(4);
impl ::std::ops::BitOr<modbus_error_recovery_mode> for modbus_error_recovery_mode {}
impl ::std::ops::BitOrAssign for modbus_error_recovery_mode {}
impl ::std::ops::BitAnd<modbus_error_recovery_mode> for modbus_error_recovery_mode {}
impl ::std::ops::BitAndAssign for modbus_error_recovery_mode {}
extern "C" {
pub fn modbus_set_error_recovery(
ctx: *mut modbus_t,
error_recovery: modbus_error_recovery_mode,
) -> ::std::os::raw::c_int;
}
How do I expose the bindgen generated constants to the public
Of course, creating an idiomatic Rust API to non-Rust code is the hard part. I might try something like this:
#[derive(Debug)]
struct Modbus(*mut raw::modbus_t);
#[derive(Debug)]
struct Error;
#[derive(Debug, Copy, Clone)]
enum ErrorRecovery {
Link,
Protocol,
}
impl ErrorRecovery {
fn as_raw(&self) -> raw::modbus_error_recovery_mode {
use ErrorRecovery::*;
match *self {
Link => raw::modbus_error_recovery_mode_MODBUS_ERROR_RECOVERY_LINK,
Protocol => raw::modbus_error_recovery_mode_MODBUS_ERROR_RECOVERY_PROTOCOL,
}
}
}
impl Modbus {
fn set_error_recovery(&mut self, flags: Option<&[ErrorRecovery]>) -> Result<(), Error> {
let flag = flags.unwrap_or(&[]).iter().fold(
raw::modbus_error_recovery_mode_MODBUS_ERROR_RECOVERY_NONE,
|acc, v| acc | v.as_raw(),
);
let res = unsafe { raw::modbus_set_error_recovery(self.0, flag) };
Ok(()) // real error checking
}
}
The problem is that C's enum and Rust's enum are very different things. In particular, C allows an enum to have absolutely any value whatsoever, whether or not that value corresponds to a variant.
Rust does not. Rust relies on enums only ever having a single value of a defined variant, or you run the risk of undefined behaviour.
What you have is not an enumeration (in the Rust sense), you have are bit flags, for which you want the bitflags crate.
As for the stack overflow, that's just because you defined the BitOr implementations in terms of itself; that code is unconditionally recursive.

Resources