Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Derive Serialize and Deserialize for most objects #412

Merged
merged 5 commits into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions changelog/@unreleased/pr-412.v2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
type: improvement
improvement:
description: Serialize and Deserialize implementations of objects, aliases, and
enums now use serde's derive macros.
links:
- https://github.com/palantir/conjure-rust/pull/412
29 changes: 7 additions & 22 deletions conjure-codegen/src/aliases.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,14 @@ pub fn generate(ctx: &Context, def: &AliasDefinition) -> TokenStream {
let result = ctx.result_ident(def.type_name());
let docs = ctx.docs(def.docs());

let mut type_attrs = vec![];
let mut type_attrs = vec![quote!(#[serde(crate = "conjure_object::serde", transparent)])];
let mut field_attrs = vec![];
let mut derives = vec!["Debug", "Clone"];
let mut derives = vec![
"Debug",
"Clone",
"conjure_object::serde::Deserialize",
"conjure_object::serde::Serialize",
];

if ctx.is_copy(def.alias()) {
derives.push("Copy");
Expand Down Expand Up @@ -107,8 +112,6 @@ pub fn generate(ctx: &Context, def: &AliasDefinition) -> TokenStream {
let dealiased_type = ctx.rust_type(def.type_name(), ctx.dealiased_type(def.alias()));

quote! {
use conjure_object::serde::{ser, de};

#docs
#(#type_attrs)*
pub struct #name(#(#field_attrs)* pub #alias);
Expand Down Expand Up @@ -141,23 +144,5 @@ pub fn generate(ctx: &Context, def: &AliasDefinition) -> TokenStream {
&mut self.0
}
}

impl ser::Serialize for #name {
fn serialize<S>(&self, s: S) -> #result<S::Ok, S::Error>
where
S: ser::Serializer
{
self.0.serialize(s)
}
}

impl<'de> de::Deserialize<'de> for #name {
fn deserialize<D>(d: D) -> #result<#name, D::Error>
where
D: de::Deserializer<'de>
{
de::Deserialize::deserialize(d).map(#name)
}
}
}
}
26 changes: 15 additions & 11 deletions conjure-codegen/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -606,21 +606,29 @@ impl Context {
}
}

pub fn is_empty_method(&self, def: &Type) -> Option<TokenStream> {
pub fn is_empty_method(&self, this_type: &TypeName, def: &Type) -> Option<TokenStream> {
match def {
Type::Primitive(_) => None,
Type::Optional(_) => Some(quote!(is_none)),
Type::List(_) | Type::Set(_) | Type::Map(_) => Some(quote!(is_empty)),
Type::Reference(def) => self.is_empty_method_ref(def),
Type::External(def) => self.is_empty_method(def.fallback()),
Type::Optional(_) => {
let option = self.option_ident(this_type);
Some(quote!(#option::is_none))
}
Type::List(_) => {
let vec = self.vec_ident(this_type);
Some(quote!(#vec::is_empty))
}
Type::Set(_) => Some(quote!(std::collections::BTreeSet::is_empty)),
Type::Map(_) => Some(quote!(std::collections::BTreeMap::is_empty)),
Type::Reference(def) => self.is_empty_method_ref(this_type, def),
Type::External(def) => self.is_empty_method(this_type, def.fallback()),
}
}

fn is_empty_method_ref(&self, name: &TypeName) -> Option<TokenStream> {
fn is_empty_method_ref(&self, this_type: &TypeName, name: &TypeName) -> Option<TokenStream> {
let ctx = &self.types[name];

match &ctx.def {
TypeDefinition::Alias(def) => self.is_empty_method(def.alias()),
TypeDefinition::Alias(def) => self.is_empty_method(this_type, def.alias()),
TypeDefinition::Enum(_) | TypeDefinition::Object(_) | TypeDefinition::Union(_) => None,
}
}
Expand Down Expand Up @@ -835,10 +843,6 @@ impl Context {
self.prelude_ident(name, "IntoIterator", "std::iter::IntoIterator")
}

pub fn default_ident(&self, name: &TypeName) -> TokenStream {
self.prelude_ident(name, "Default", "std::default::Default")
}

pub fn send_ident(&self, name: &TypeName) -> TokenStream {
self.prelude_ident(name, "Send", "std::marker::Send")
}
Expand Down
89 changes: 34 additions & 55 deletions conjure-codegen/src/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@ use proc_macro2::TokenStream;
use quote::quote;

use crate::context::Context;
use crate::types::{EnumDefinition, EnumValueDefinition};
use crate::types::EnumDefinition;

pub fn generate(ctx: &Context, def: &EnumDefinition) -> TokenStream {
let enum_ = generate_enum(ctx, def);
let unknown = generate_unknown(ctx, def);

quote! {
use conjure_object::serde::{ser, de};
// https://github.com/serde-rs/serde/issues/2195
#![allow(deprecated)]
use std::fmt;
use std::str;

Expand All @@ -42,10 +43,12 @@ fn generate_enum(ctx: &Context, def: &EnumDefinition) -> TokenStream {
let variants = def.values().iter().map(|v| {
let docs = ctx.docs(v.docs());
let deprecated = ctx.deprecated(v.deprecated());
let value = v.value();
let name = ctx.type_name(v.value());
quote! {
#docs
#deprecated
#[serde(rename = #value)]
#name,
}
});
Expand All @@ -55,7 +58,8 @@ fn generate_enum(ctx: &Context, def: &EnumDefinition) -> TokenStream {
} else {
quote! {
/// An unknown variant.
Unknown(#unknown)
#[serde(untagged)]
#unknown(#unknown)
}
};

Expand All @@ -72,7 +76,7 @@ fn generate_enum(ctx: &Context, def: &EnumDefinition) -> TokenStream {
let as_str_other = if ctx.exhaustive() {
quote!()
} else {
quote!(#name::Unknown(v) => &*v,)
quote!(#name::#unknown(v) => &*v,)
};

let from_str_arms = def.values().iter().map(|v| {
Expand All @@ -91,21 +95,24 @@ fn generate_enum(ctx: &Context, def: &EnumDefinition) -> TokenStream {
}
} else {
quote! {
v => {
if conjure_object::private::valid_enum_variant(v) {
#ok(#name::Unknown(#unknown(v.to_string().into_boxed_str())))
} else {
#err(conjure_object::plain::ParseEnumError::new())
}
}
v => v.parse().map(|v| #name::#unknown(#unknown(v))),
}
};

let values = def.values().iter().map(EnumValueDefinition::value);

quote! {
#root_docs
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[derive(
Debug,
Clone,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
conjure_object::serde::Deserialize,
conjure_object::serde::Serialize,
)]
#[serde(crate = "conjure_object::serde")]
pub enum #name {
#(#variants)*
#other_variant
Expand Down Expand Up @@ -154,44 +161,6 @@ fn generate_enum(ctx: &Context, def: &EnumDefinition) -> TokenStream {
v.parse()
}
}

impl ser::Serialize for #name {
fn serialize<S>(&self, s: S) -> #result<S::Ok, S::Error>
where
S: ser::Serializer,
{
s.serialize_str(self.as_str())
}
}

impl<'de> de::Deserialize<'de> for #name {
fn deserialize<D>(d: D) -> #result<#name, D::Error>
where
D: de::Deserializer<'de>
{
d.deserialize_str(Visitor_)
}
}

struct Visitor_;

impl<'de> de::Visitor<'de> for Visitor_ {
type Value = #name;

fn expecting(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str("a string")
}

fn visit_str<E>(self, v: &str) -> #result<#name, E>
where
E: de::Error,
{
match v.parse() {
#ok(e) => Ok(e),
#err(_) => #err(de::Error::unknown_variant(v, &[#(#values, )*])),
}
}
}
}
}

Expand All @@ -214,12 +183,22 @@ fn generate_unknown(ctx: &Context, def: &EnumDefinition) -> TokenStream {
);

let unknown = unknown(ctx, def);
let box_ = ctx.box_ident(def.type_name());

quote! {
#[doc = #doc]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct #unknown(#box_<str>);
#[derive(
Debug,
Clone,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
conjure_object::serde::Deserialize,
conjure_object::serde::Serialize,
)]
#[serde(crate = "conjure_object::serde", transparent)]
pub struct #unknown(conjure_object::private::Variant);

impl std::ops::Deref for #unknown {
type Target = str;
Expand Down
82 changes: 13 additions & 69 deletions conjure-codegen/src/example_types/another/different_package.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading