Conditional Configuration of Struct Member and Attribute

Posted by Kary Bruening on Wednesday, April 17, 2024

I have a feature called backend for my crate called shared. I have a struct in shared/cart_items.rs as follows:

//-------------------------------------------------------------------------- use serde::{Serialize, Deserialize}; use bigdecimal::BigDecimal; use chrono::Duration; use serde_with::serde_as; use serde_with::DurationSeconds; use uuid::Uuid; #[cfg(feature = "backend")] use scylla::{FromRow, ValueList}; #[cfg(feature = "backend")] use scylla::frame::value::Timestamp; #[cfg(feature = "backend")] use crate::extensions::scylla::frame::value::timestamp::TimestampDef; //Code I wrote to handle Timestamps for Scylla //------------------------------------------------------------------------------ #[serde_as] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[cfg_attr(feature = "backend", derive(FromRow, ValueList))] pub struct CartItem { pub id : Uuid, pub service_name : String, //#[cfg_attr(feature = "backend", serde(with = "TimestampDef"))] #[cfg(feature = "backend")] #[cfg_attr(feature="backend", serde(with = "TimestampDef"))] pub delivery_date : Timestamp, #[cfg(not(feature = "backend"))] #[cfg_attr(not(feature="backend"), serde_as(as = "DurationSeconds<i64>"))] pub delivery_date : Duration, pub additional_instructions : String, } 

The problem is that on compilation with the backend feature DISABLED I get:

error[E0277]: the trait bound `chrono::Duration: Deserialize<'_>` is not satisfied --> shared/cart_items.rs:40:25 | 40 | pub delivery_date : Duration, | ^^^^^^^^ the trait `Deserialize<'_>` is not implemented for `chrono::Duration` | 

Even though upon macro expansion I would expect the following is what rust has:

//-------------------------------------------------------------------------- use serde::{Serialize, Deserialize}; use bigdecimal::BigDecimal; use chrono::Duration; use serde_with::serde_as; use serde_with::DurationSeconds; use uuid::Uuid; //------------------------------------------------------------------------------ #[serde_as] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub struct CartItem { pub id : Uuid, pub service_name : String, #[serde_as(as = "DurationSeconds<i64>"))] pub delivery_date : Duration, pub additional_instructions : String, } 

How do I get this to work as intended. For context, this is a shared library between the frontend and backend and scylla CANNOT be imported for the frontend and for the backend it can which is why the struct needs to use TimestampDef and Timestamp as well as the Duration from chrono.

3

2 Answers

Here is your code minified:

use chrono::Duration; use serde::{Deserialize, Serialize}; use serde_with::{serde_as, DurationSeconds}; #[serde_as] #[derive(Deserialize, Serialize)] pub struct CartItem { #[cfg(not(feature = "backend"))] #[cfg_attr(not(feature="backend"), serde_as(as = "DurationSeconds<i64>"))] pub delivery_date: Duration, } 

Now the problem is, that the outer #[serde_as] is evaluated before the inner #[cfg_attr(..., serde_as(...))] is (can be seen by expanding the macros, observe the outermost #[serde_as] is already gone, while the field still has #[serde_as(...)]).

You can work around that by having 2 separate definitions of your struct:

#[cfg(not(feature = "backend"))] #[serde_as] #[derive(Deserialize, Serialize)] pub struct CartItem { pub service_name: String, #[serde_as(as = "DurationSeconds<i64>")] pub delivery_date: Duration, } #[cfg(feature = "backend")] #[derive(Deserialize, Serialize)] pub struct CartItem { #[serde(with = "TimestampDef")] pub delivery_date: Timestamp, } 

The following works as intended:

//-------------------------------------------------------------------------- use serde::{Serialize, Deserialize}; use bigdecimal::BigDecimal; use chrono::Duration; use serde_with::serde_as; use serde_with::DurationSeconds; use uuid::Uuid; #[cfg(feature = "backend")] use scylla::{FromRow, ValueList}; #[cfg(feature = "backend")] use scylla::frame::value::Timestamp; #[cfg(feature = "backend")] use crate::extensions::scylla::frame::value::timestamp::TimestampDef; //Code I wrote to handle Timestamps for Scylla //------------------------------------------------------------------------------ #[serde_as] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[cfg_attr(feature = "backend", derive(FromRow, ValueList))] pub struct CartItem { pub id : Uuid, pub service_name : String, #[cfg(feature = "backend")] #[serde(with = "TimestampDef")] pub delivery_date : Timestamp, #[cfg(not(feature = "backend"))] #[serde_as(as = "DurationSeconds<i64>")] pub delivery_date : Duration, pub additional_instructions : String, } 

ncG1vNJzZmirpJawrLvVnqmfpJ%2Bse6S7zGiorp2jqbawutJobnBrZ2yBc3yOnKannJmptrC6wKVknKeem7aowdGaq6KnnmK8p3nSramum6Riuqa5wZ6pZpmemXqiwNOroJutpJo%3D