Skip to content

Commit 1b2fb75

Browse files
committed
Add support for default and skip_serializing_if by annotating them as Option<T>, mute warning attribute instead of env var
1 parent 8cd9a37 commit 1b2fb75

File tree

2 files changed

+77
-37
lines changed

2 files changed

+77
-37
lines changed

packages/cw-schema-derive/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ proc-macro = true
1010
heck = "0.5.0"
1111
itertools = { version = "0.13.0", default-features = false }
1212
owo-colors = { version = "4.0.0", features = ["supports-colors"] }
13-
proc-macro2 = "1.0.86"
13+
proc-macro2 = { version = "1.0.86", features = ["span-locations"] }
1414
quote = "1.0.36"
1515
syn = { version = "2.0.72", features = ["full"] }

packages/cw-schema-derive/src/expand.rs

Lines changed: 76 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,15 @@ use std::{
66
borrow::Cow,
77
env,
88
fmt::Display,
9-
io::{self, Write as _},
9+
io::{self, Write},
1010
};
11-
use syn::{DataEnum, DataStruct, DataUnion, DeriveInput, Lit};
12-
13-
const DISABLE_WARNINGS_VAR: &str = "SHUT_UP_CW_SCHEMA_DERIVE";
14-
15-
fn print_warning(title: impl Display, content: impl Display) -> io::Result<()> {
16-
if let Ok("1") = env::var(DISABLE_WARNINGS_VAR).as_deref() {
17-
return Ok(());
18-
}
11+
use syn::{spanned::Spanned, DataEnum, DataStruct, DataUnion, DeriveInput, Lit};
1912

13+
fn print_warning(
14+
span: proc_macro2::Span,
15+
title: impl Display,
16+
content: impl Display,
17+
) -> io::Result<()> {
2018
let mut sink = io::stderr();
2119

2220
let bold_yellow = Style::new().bold().yellow();
@@ -34,14 +32,17 @@ fn print_warning(title: impl Display, content: impl Display) -> io::Result<()> {
3432
write!(sink, "{}", " | ".style(blue))?;
3533
writeln!(sink, "{content}")?;
3634

37-
writeln!(sink, "{}", " | ".style(blue))?;
35+
let span = span.start();
36+
write!(sink, "{}", " | ".style(blue))?;
37+
writeln!(sink, "Location: {}:{}", span.line, span.column)?;
38+
3839
writeln!(sink, "{}", " | ".style(blue))?;
3940

4041
write!(sink, "{}", " = ".style(blue))?;
4142
write!(sink, "{}", "note: ".style(bold))?;
4243
writeln!(
4344
sink,
44-
"set `{DISABLE_WARNINGS_VAR}=1` to silence this warning"
45+
"annotate the container with #[schemaifier(mute_warnings)] to disable these warnings"
4546
)?;
4647

4748
Ok(())
@@ -90,7 +91,7 @@ struct SerdeContainerOptions {
9091
}
9192

9293
impl SerdeContainerOptions {
93-
fn parse(attributes: &[syn::Attribute]) -> syn::Result<Self> {
94+
fn parse(attributes: &[syn::Attribute], muted_warnings: bool) -> syn::Result<Self> {
9495
let mut options = SerdeContainerOptions {
9596
rename_all: None,
9697
untagged: false,
@@ -106,7 +107,8 @@ impl SerdeContainerOptions {
106107
} else if meta.path.is_ident("untagged") {
107108
options.untagged = true;
108109
} else {
109-
/*print_warning(
110+
print_warning(
111+
meta.path.span(),
110112
"unknown serde attribute",
111113
format!(
112114
"unknown attribute \"{}\"",
@@ -116,7 +118,7 @@ impl SerdeContainerOptions {
116118
.unwrap_or_else(|| "[error]".into())
117119
),
118120
)
119-
.unwrap();*/
121+
.unwrap();
120122

121123
// TODO: support other serde attributes
122124
//
@@ -127,8 +129,10 @@ impl SerdeContainerOptions {
127129
.unwrap_or_else(|_| meta.input.cursor().token_stream());
128130
}
129131

130-
if meta.path.is_ident("untagged") || meta.path.is_ident("tag") {
132+
if (meta.path.is_ident("untagged") || meta.path.is_ident("tag")) && !muted_warnings
133+
{
131134
print_warning(
135+
meta.path.span(),
132136
"unsupported tag type",
133137
meta.error("unsupported tag type").to_string(),
134138
)
@@ -147,6 +151,7 @@ struct ContainerOptions {
147151
r#as: Option<syn::Ident>,
148152
r#type: Option<syn::Expr>,
149153
crate_path: syn::Path,
154+
muted_warnings: bool,
150155
}
151156

152157
impl ContainerOptions {
@@ -155,6 +160,7 @@ impl ContainerOptions {
155160
r#as: None,
156161
r#type: None,
157162
crate_path: syn::parse_str("::cw_schema")?,
163+
muted_warnings: false,
158164
};
159165

160166
for attribute in attributes
@@ -169,6 +175,8 @@ impl ContainerOptions {
169175
options.r#as = Some(meta.value()?.parse()?);
170176
} else if meta.path.is_ident("type") {
171177
options.r#type = Some(meta.value()?.parse()?);
178+
} else if meta.path.is_ident("mute_warnings") {
179+
options.muted_warnings = true;
172180
} else {
173181
bail!(meta.path, "unknown attribute");
174182
}
@@ -182,12 +190,18 @@ impl ContainerOptions {
182190
}
183191

184192
struct SerdeFieldOptions {
193+
default: bool,
185194
rename: Option<syn::LitStr>,
195+
skip_serializing_if: Option<syn::Expr>,
186196
}
187197

188198
impl SerdeFieldOptions {
189-
fn parse(attributes: &[syn::Attribute]) -> syn::Result<Self> {
190-
let mut options = SerdeFieldOptions { rename: None };
199+
fn parse(attributes: &[syn::Attribute], muted_warnings: bool) -> syn::Result<Self> {
200+
let mut options = SerdeFieldOptions {
201+
default: false,
202+
rename: None,
203+
skip_serializing_if: None,
204+
};
191205

192206
for attribute in attributes
193207
.iter()
@@ -196,18 +210,28 @@ impl SerdeFieldOptions {
196210
attribute.parse_nested_meta(|meta| {
197211
if meta.path.is_ident("rename") {
198212
options.rename = Some(meta.value()?.parse()?);
213+
} else if meta.path.is_ident("default") {
214+
options.default = true;
215+
// just ignore the rest. it's not relevant.
216+
// but without this code, we'd sometimes hit compile errors.
217+
let _ = meta.value().and_then(|val| val.parse::<syn::Expr>());
218+
} else if meta.path.is_ident("skip_serializing_if") {
219+
options.skip_serializing_if = Some(meta.value()?.parse()?);
199220
} else {
200-
print_warning(
201-
"unknown serde attribute",
202-
format!(
203-
"unknown attribute \"{}\"",
204-
meta.path
205-
.get_ident()
206-
.map(|ident| ident.to_string())
207-
.unwrap_or_else(|| "[error]".into())
208-
),
209-
)
210-
.unwrap();
221+
if !muted_warnings {
222+
print_warning(
223+
meta.path.span(),
224+
"unknown serde attribute",
225+
format!(
226+
"unknown attribute \"{}\"",
227+
meta.path
228+
.get_ident()
229+
.map(|ident| ident.to_string())
230+
.unwrap_or_else(|| "[error]".into())
231+
),
232+
)
233+
.unwrap();
234+
}
211235

212236
// TODO: support other serde attributes
213237
//
@@ -287,19 +311,25 @@ fn collect_struct_fields<'a, C>(
287311
converter: &'a C,
288312
crate_path: &'a syn::Path,
289313
fields: &'a syn::FieldsNamed,
314+
muted_warnings: bool,
290315
) -> impl Iterator<Item = syn::Result<TokenStream>> + 'a
291316
where
292317
C: Fn(&syn::Ident) -> syn::Ident,
293318
{
294319
fields.named.iter().map(move |field| {
295-
let field_options = SerdeFieldOptions::parse(&field.attrs)?;
320+
let field_options = SerdeFieldOptions::parse(&field.attrs, muted_warnings)?;
296321

297322
let name = field_options
298323
.rename
299324
.map(|lit_str| format_ident!("{}", lit_str.value()))
300325
.unwrap_or_else(|| converter(field.ident.as_ref().unwrap()));
301326
let description = normalize_option(extract_documentation(&field.attrs)?);
302-
let field_ty = &field.ty;
327+
let field_ty = if field_options.default || field_options.skip_serializing_if.is_some() {
328+
let ty = &field.ty;
329+
syn::parse_quote!(::core::option::Option<#ty>)
330+
} else {
331+
field.ty.clone()
332+
};
303333

304334
let expanded = quote! {
305335
(
@@ -325,8 +355,13 @@ fn expand_enum(mut meta: ContainerMeta, input: DataEnum) -> syn::Result<TokenStr
325355
for variant in input.variants.iter() {
326356
let value = match variant.fields {
327357
syn::Fields::Named(ref fields) => {
328-
let items = collect_struct_fields(&converter, crate_path, fields)
329-
.collect::<syn::Result<Vec<_>>>()?;
358+
let items = collect_struct_fields(
359+
&converter,
360+
crate_path,
361+
fields,
362+
meta.options.muted_warnings,
363+
)
364+
.collect::<syn::Result<Vec<_>>>()?;
330365

331366
quote! {
332367
#crate_path::EnumValue::Named {
@@ -350,7 +385,7 @@ fn expand_enum(mut meta: ContainerMeta, input: DataEnum) -> syn::Result<TokenStr
350385
syn::Fields::Unit => quote! { #crate_path::EnumValue::Unit },
351386
};
352387

353-
let field_options = SerdeFieldOptions::parse(&variant.attrs)?;
388+
let field_options = SerdeFieldOptions::parse(&variant.attrs, meta.options.muted_warnings)?;
354389

355390
let variant_name = field_options
356391
.rename
@@ -432,8 +467,13 @@ fn expand_struct(mut meta: ContainerMeta, input: DataStruct) -> syn::Result<Toke
432467
} else {
433468
let node_ty = match input.fields {
434469
syn::Fields::Named(ref named) => {
435-
let items = collect_struct_fields(&converter, crate_path, named)
436-
.collect::<syn::Result<Vec<_>>>()?;
470+
let items = collect_struct_fields(
471+
&converter,
472+
crate_path,
473+
named,
474+
meta.options.muted_warnings,
475+
)
476+
.collect::<syn::Result<Vec<_>>>()?;
437477

438478
quote! {
439479
#crate_path::StructType::Named {
@@ -508,7 +548,7 @@ fn expand_union(_meta: ContainerMeta, input: DataUnion) -> syn::Result<TokenStre
508548

509549
pub fn expand(input: DeriveInput) -> syn::Result<TokenStream> {
510550
let options = ContainerOptions::parse(&input.attrs)?;
511-
let serde_options = SerdeContainerOptions::parse(&input.attrs)?;
551+
let serde_options = SerdeContainerOptions::parse(&input.attrs, options.muted_warnings)?;
512552
let description = extract_documentation(&input.attrs)?;
513553

514554
let meta = ContainerMeta {

0 commit comments

Comments
 (0)