dtolnay / request-for-implementation

Crates that don't exist, but should
610 stars 6 forks source link

Attribute macro to apply prefix to name of serialized fields or variants #19

Closed dtolnay closed 5 years ago

dtolnay commented 5 years ago

Some JSON protocols represent variant types using a common prefix. For example a network result may be transmitted as "NetworkResult_Success" / "NetworkResult_Failure". In Rust we would like to represent such values as enum variants NetworkResult::Success and NetworkResult::Failure. This is possible with Serde rename attributes but somewhat verbose.

#[derive(Deserialize)]
enum NetworkResult {
    #[serde(rename = "NetworkResult_Success")]
    Success,
    #[serde(rename = "NetworkResult_Failure")]
    Failure,
}

It would be nicer to have an attribute macro that inserts a given prefix as a rename attribute on each field, generating the code above.

#[prefix_all("NetworkResult_")]
#[derive(Deserialize)]
enum NetworkResult {
    Success,
    Failure,
}
TedDriggs commented 5 years ago

This feels like it'd be better as a PR to serde; having it in the docs at serde.rs would make it more discoverable and protecting it under serde's duplicate field and other validations seems to be a friendlier API. would you be opposed to this coming in a PR there?

Mingun commented 5 years ago

Also then it is good to have prefix and suffix attribute for flatten:

struct X {
  #[serde(flatten(prefix = "Embed", suffix = "Field"))]
  a,
}
dtolnay commented 5 years ago

I would not accept this as a PR to Serde. I don't think this pattern is sufficiently ubiquitous to justify a new attribute in serde_derive.

An attribute macro that expands to rename attributes would have just as much validation.

jonathan-s commented 5 years ago

@dtolnay I'm currently looking at this. I was thinking of creating the #[serde(rename = "prefix_fieldname")] attributes using quote! {} and then inserting those in the AST tree. Right now I'm a bit stuck as it doesn't seem possible to create a new Attribute which I would then insert.

Do you have any suggestions?

dtolnay commented 5 years ago

I would recommend using parse_quote to make attributes: https://docs.rs/syn/0.15/syn/macro.parse_quote.html

jonathan-s commented 5 years ago

Thanks! That got me on the right track!

On Wed 19. Jun 2019 at 20:06, David Tolnay notifications@github.com wrote:

I would recommend using parse_quote to make attributes: https://docs.rs/syn/0.15/syn/macro.parse_quote.html

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/dtolnay/request-for-implementation/issues/19?email_source=notifications&email_token=AAQGYEVYAVJQP7ZDFBOGSLDP3JYRHA5CNFSM4GRPAGM2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODYCWBRQ#issuecomment-503668934, or mute the thread https://github.com/notifications/unsubscribe-auth/AAQGYESNWV5NDZY3YXJHVW3P3JYRHANCNFSM4GRPAGMQ .

-- Sent from Gmail Mobile

jonathan-s commented 5 years ago

@dtolnay I've got a repo up now https://github.com/jonathan-s/serde_prefix I would be very grateful if you had a look at it. I still need to add some documentation for how it should be used.

But in short #[prefix_all("test_")] can be used for structs and enums to rename attributes. Inside the box it uses #[serde(rename = "test_attributeName")].

dtolnay commented 5 years ago

Fantastic!

I would be interested in someone experimenting with what serde_derive could do to integrate these sorts of extensions (skip_serializing_none is another one) more nicely. For example you could imagine that serde_derive could apply this transformation:

use serde_prefix::prefix_all;

// written by caller:
#[derive(Serialize)]
#[serde(apply = "prefix_all", prefix = "test_")]
struct Struct {
    /* ... */
}

// expands to:
#[prefix_all(prefix = "test_")]
#[derive(Serialize)]
struct Struct {
    /* ... */
}

Or somewhat more cleverly:

// no `use` needed

// written by caller:
#[derive(Serialize)]
#[serde(prefix::all = "test_")] // anything with colons is recognized as extension
struct Struct {
    /* ... */
}

// expands to:
#[serde_prefix::all = "test_"] // prepend `serde_` on original extension
#[derive(Serialize)]
struct Struct {
    /* ... */
}
jonathan-s commented 5 years ago

@dtolnay Would the #[serde(prefix-macro-here)] macro need to be defined in the serde crate first? Or is there a functionality like that already?

jonathan-s commented 5 years ago

And to answer my own question, it looks like it would need to be defined here: https://github.com/serde-rs/serde/blob/master/serde_derive/src/internals/attr.rs when implementing the container struct in the from_ast method, if I am correct.

jonathan-s commented 5 years ago

Another question, should prefix::all be considered meta? If that's the case we would need to change the syn crate as well.

RReverser commented 3 years ago

I'm not sure this issue is supposed to be closed, as the suffix part is not yet implemented anywhere (AFAIK).