1mod custom_meta;
2mod doc;
3mod parse_meta;
4mod schemars_to_serde;
5mod validation;
6
7use parse_meta::{
8 parse_extensions, parse_name_value_expr, parse_name_value_lit_str, require_name_value_lit_str,
9 require_path_only,
10};
11use proc_macro2::TokenStream;
12use quote::ToTokens;
13use serde_derive_internals::Ctxt;
14use syn::punctuated::Punctuated;
15use syn::spanned::Spanned;
16use syn::{Attribute, Expr, ExprLit, Ident, Lit, LitStr, Path, Type};
17use validation::ValidationAttrs;
18
19use crate::ast::Data;
20use crate::idents::SCHEMA;
21
22pub use custom_meta::*;
23pub use schemars_to_serde::process_serde_attrs;
24
25#[derive(Default)]
26pub struct CommonAttrs {
27 pub doc: Option<Expr>,
28 pub deprecated: bool,
29 pub title: Option<Expr>,
30 pub description: Option<Expr>,
31 pub examples: Vec<Expr>,
32 pub extensions: Vec<(String, TokenStream)>,
33 pub transforms: Vec<Expr>,
34}
35
36#[derive(Default)]
37pub struct FieldAttrs {
38 pub common: CommonAttrs,
39 pub with: Option<WithAttr>,
40 pub validation: ValidationAttrs,
41}
42
43#[derive(Default)]
44pub struct ContainerAttrs {
45 pub common: CommonAttrs,
46 pub repr: Option<Type>,
47 pub crate_name: Option<Path>,
48 pub rename_format_string: Option<LitStr>,
51 pub inline: bool,
52 pub ref_variants: bool,
53 pub with: Option<WithAttr>,
54}
55
56#[derive(Default)]
57pub struct VariantAttrs {
58 pub common: CommonAttrs,
59 pub with: Option<WithAttr>,
60}
61
62pub enum WithAttr {
63 Type(Type),
64 Function(Path),
65}
66
67impl CommonAttrs {
68 fn populate(
69 &mut self,
70 attrs: &[Attribute],
71 schemars_cx: &mut AttrCtxt,
72 serde_cx: &mut AttrCtxt,
73 ) {
74 self.process_attr(schemars_cx);
75 self.process_attr(serde_cx);
76
77 self.doc = doc::get_doc(attrs);
78 self.deprecated = attrs.iter().any(|a| a.path().is_ident("deprecated"));
79 }
80
81 fn process_attr(&mut self, cx: &mut AttrCtxt) {
82 cx.parse_meta(|m, n, c| self.process_meta(m, n, c));
83 }
84
85 fn process_meta(
86 &mut self,
87 meta: CustomMeta,
88 meta_name: &str,
89 cx: &AttrCtxt,
90 ) -> Result<(), CustomMeta> {
91 match meta_name {
92 "title" => match self.title {
93 Some(_) => cx.duplicate_error(&meta),
94 None => self.title = parse_name_value_expr(meta, cx).ok(),
95 },
96
97 "description" => match self.description {
98 Some(_) => cx.duplicate_error(&meta),
99 None => self.description = parse_name_value_expr(meta, cx).ok(),
100 },
101
102 "example" => {
103 if let Ok(expr) = parse_name_value_expr(meta, cx) {
104 if let Expr::Lit(ExprLit {
105 lit: Lit::Str(lit_str),
106 ..
107 }) = &expr
108 {
109 if lit_str.parse::<Path>().is_ok() {
110 let lit_str_value = lit_str.value();
111 cx.error_spanned_by(&expr, format_args!(
112 "`example` value must be an expression, and string literals that may be interpreted as function paths are currently disallowed to avoid migration errors \
113 (this restriction may be relaxed in a future version of schemars).\n\
114 If you want to use the result of a function, use `#[schemars(example = {lit_str_value}())]`.\n\
115 Or to use the string literal value, use `#[schemars(example = &\"{lit_str_value}\")]`."));
116 }
117 }
118
119 self.examples.push(expr);
120 }
121 }
122
123 "extend" => {
124 for ex in parse_extensions(&meta, cx).into_iter().flatten() {
125 if self.extensions.iter().any(|e| e.0 == ex.key_str) {
129 cx.error_spanned_by(
130 ex.key_lit,
131 format_args!("Duplicate extension key '{}'", ex.key_str),
132 );
133 } else {
134 self.extensions.push((ex.key_str, ex.value));
135 }
136 }
137 }
138
139 "transform" => {
140 if let Ok(expr) = parse_name_value_expr(meta, cx) {
141 if let Expr::Lit(ExprLit {
142 lit: Lit::Str(lit_str),
143 ..
144 }) = &expr
145 {
146 if lit_str.parse::<Expr>().is_ok() {
147 cx.error_spanned_by(
148 &expr,
149 format_args!(
150 "Expected a `fn(&mut Schema)` or other value implementing `schemars::transform::Transform`, found `&str`.\nDid you mean `#[schemars(transform = {})]`?",
151 lit_str.value()
152 ),
153 );
154 }
155 } else {
156 self.transforms.push(expr);
157 }
158 }
159 }
160
161 _ => return Err(meta),
162 }
163
164 Ok(())
165 }
166
167 pub fn is_default(&self) -> bool {
168 matches!(
169 self,
170 Self {
171 title: None,
172 description: None,
173 doc: None,
174 deprecated: false,
175 examples,
176 extensions,
177 transforms,
178 } if examples.is_empty() && extensions.is_empty() && transforms.is_empty()
179 )
180 }
181
182 pub fn add_mutators(&self, mutators: &mut Vec<TokenStream>) {
183 let mut title = self.title.as_ref().map(ToTokens::to_token_stream);
184 let mut description = self.description.as_ref().map(ToTokens::to_token_stream);
185 if let Some(doc) = &self.doc {
186 title.get_or_insert_with(|| {
187 quote!({
188 const TITLE: &str = schemars::_private::get_title_and_description(#doc).0;
189 TITLE
190 })
191 });
192 description.get_or_insert_with(|| {
193 quote!({
194 const DESCRIPTION: &str = schemars::_private::get_title_and_description(#doc).1;
195 DESCRIPTION
196 })
197 });
198 }
199 if let Some(title) = title {
200 mutators.push(quote! {
201 schemars::_private::insert_metadata_property_if_nonempty(&mut #SCHEMA, "title", #title);
202 });
203 }
204 if let Some(description) = description {
205 mutators.push(quote! {
206 schemars::_private::insert_metadata_property_if_nonempty(&mut #SCHEMA, "description", #description);
207 });
208 }
209
210 if self.deprecated {
211 mutators.push(quote! {
212 #SCHEMA.insert("deprecated".into(), true.into());
213 });
214 }
215
216 if !self.examples.is_empty() {
217 let examples = self.examples.iter().map(|eg| {
218 quote! {
219 schemars::_private::serde_json::value::to_value(#eg)
220 }
221 });
222 mutators.push(quote! {
223 #SCHEMA.insert("examples".into(), schemars::_private::serde_json::Value::Array([#(#examples),*].into_iter().flatten().collect()));
224 });
225 }
226
227 for (k, v) in &self.extensions {
228 mutators.push(quote! {
229 #SCHEMA.insert(#k.into(), schemars::_private::serde_json::json!(#v));
230 });
231 }
232
233 for transform in &self.transforms {
234 mutators.push(quote_spanned! {transform.span()=>
235 schemars::transform::Transform::transform(&mut #transform, &mut #SCHEMA);
236 });
237 }
238 }
239}
240
241impl FieldAttrs {
242 pub fn new(attrs: &[Attribute], cx: &Ctxt) -> Self {
243 let mut result = Self::default();
244 result.populate(attrs, cx);
245 result
246 }
247
248 fn populate(&mut self, attrs: &[Attribute], cx: &Ctxt) {
249 let schemars_cx = &mut AttrCtxt::new(cx, attrs, "schemars");
250 let serde_cx = &mut AttrCtxt::new(cx, attrs, "serde");
251 let validate_cx = &mut AttrCtxt::new(cx, attrs, "validate");
252 let garde_cx = &mut AttrCtxt::new(cx, attrs, "garde");
253
254 self.common.populate(attrs, schemars_cx, serde_cx);
255 self.validation.populate(schemars_cx, validate_cx, garde_cx);
256 self.process_attr(schemars_cx);
257 self.process_attr(serde_cx);
258 }
259
260 fn process_attr(&mut self, cx: &mut AttrCtxt) {
261 cx.parse_meta(|m, n, c| self.process_meta(m, n, c));
262 }
263
264 fn process_meta(
265 &mut self,
266 meta: CustomMeta,
267 meta_name: &str,
268 cx: &AttrCtxt,
269 ) -> Result<(), CustomMeta> {
270 match meta_name {
271 "with" => match self.with {
272 Some(WithAttr::Type(_)) => cx.duplicate_error(&meta),
273 Some(WithAttr::Function(_)) => cx.mutual_exclusive_error(&meta, "schema_with"),
274 None => self.with = parse_name_value_lit_str(meta, cx).ok().map(WithAttr::Type),
275 },
276 "schema_with" if cx.attr_type == "schemars" => match self.with {
277 Some(WithAttr::Function(_)) => cx.duplicate_error(&meta),
278 Some(WithAttr::Type(_)) => cx.mutual_exclusive_error(&meta, "with"),
279 None => {
280 self.with = parse_name_value_lit_str(meta, cx)
281 .ok()
282 .map(WithAttr::Function);
283 }
284 },
285
286 _ => return Err(meta),
287 }
288
289 Ok(())
290 }
291
292 pub fn is_default(&self) -> bool {
293 matches!(
294 self,
295 Self {
296 common,
297 validation,
298 with: None,
299 } if common.is_default() && validation.is_default())
300 }
301}
302
303impl ContainerAttrs {
304 pub fn new(attrs: &[Attribute], data: &Data, cx: &Ctxt) -> Self {
305 let mut result = Self::default();
306 result.populate(attrs, data, cx);
307 result
308 }
309
310 fn populate(&mut self, attrs: &[Attribute], data: &Data, cx: &Ctxt) {
311 let schemars_cx = &mut AttrCtxt::new(cx, attrs, "schemars");
312 let serde_cx = &mut AttrCtxt::new(cx, attrs, "serde");
313
314 self.common.populate(attrs, schemars_cx, serde_cx);
315 self.process_attr(data, schemars_cx);
316 self.process_attr(data, serde_cx);
317
318 self.repr = attrs
319 .iter()
320 .find(|a| a.path().is_ident("repr"))
321 .and_then(|a| a.parse_args().ok());
322 }
323
324 fn process_attr(&mut self, data: &Data, cx: &mut AttrCtxt) {
325 cx.parse_meta(|m, n, c| self.process_meta(m, n, data, c));
326 }
327
328 fn process_meta(
329 &mut self,
330 meta: CustomMeta,
331 meta_name: &str,
332 data: &Data,
333 cx: &AttrCtxt,
334 ) -> Result<(), CustomMeta> {
335 match meta_name {
336 "crate" => match self.crate_name {
337 Some(_) => cx.duplicate_error(&meta),
338 None => self.crate_name = parse_name_value_lit_str(meta, cx).ok(),
339 },
340
341 "rename" if cx.attr_type == "schemars" => match self.rename_format_string {
342 Some(_) => cx.duplicate_error(&meta),
343 None => self.rename_format_string = require_name_value_lit_str(meta, cx).ok(),
344 },
345
346 "inline" => {
347 if self.inline {
348 cx.duplicate_error(&meta);
349 } else if require_path_only(&meta, cx).is_ok() {
350 self.inline = true;
351 }
352 }
353
354 "with" if cx.attr_type == "schemars" => match self.with {
355 Some(WithAttr::Type(_)) => cx.duplicate_error(&meta),
356 Some(WithAttr::Function(_)) => cx.mutual_exclusive_error(&meta, "schema_with"),
357 None => self.with = parse_name_value_lit_str(meta, cx).ok().map(WithAttr::Type),
358 },
359 "schema_with" if cx.attr_type == "schemars" => match self.with {
360 Some(WithAttr::Function(_)) => cx.duplicate_error(&meta),
361 Some(WithAttr::Type(_)) => cx.mutual_exclusive_error(&meta, "with"),
362 None => {
363 self.with = parse_name_value_lit_str(meta, cx)
364 .ok()
365 .map(WithAttr::Function);
366 }
367 },
368
369 "_unstable_ref_variants" if cx.attr_type == "schemars" => {
370 if !matches!(data, Data::Enum(_)) {
371 cx.error_spanned_by(
372 meta.path(),
373 "`_unstable_ref_variants` can only be used on enums",
374 );
375 } else if self.ref_variants {
376 cx.duplicate_error(&meta);
377 } else if require_path_only(&meta, cx).is_ok() {
378 self.ref_variants = true;
379 }
380 }
381
382 _ => return Err(meta),
383 }
384
385 Ok(())
386 }
387}
388
389impl VariantAttrs {
390 pub fn new(attrs: &[Attribute], cx: &Ctxt) -> Self {
391 let mut result = Self::default();
392 result.populate(attrs, cx);
393 result
394 }
395
396 fn populate(&mut self, attrs: &[Attribute], cx: &Ctxt) {
397 let schemars_cx = &mut AttrCtxt::new(cx, attrs, "schemars");
398 let serde_cx = &mut AttrCtxt::new(cx, attrs, "serde");
399
400 self.common.populate(attrs, schemars_cx, serde_cx);
401 self.process_attr(schemars_cx);
402 self.process_attr(serde_cx);
403 }
404
405 fn process_attr(&mut self, cx: &mut AttrCtxt) {
406 cx.parse_meta(|m, n, c| self.process_meta(m, n, c));
407 }
408
409 fn process_meta(
410 &mut self,
411 meta: CustomMeta,
412 meta_name: &str,
413 cx: &AttrCtxt,
414 ) -> Result<(), CustomMeta> {
415 match meta_name {
416 "with" => match self.with {
417 Some(WithAttr::Type(_)) => cx.duplicate_error(&meta),
418 Some(WithAttr::Function(_)) => cx.mutual_exclusive_error(&meta, "schema_with"),
419 None => self.with = parse_name_value_lit_str(meta, cx).ok().map(WithAttr::Type),
420 },
421 "schema_with" if cx.attr_type == "schemars" => match self.with {
422 Some(WithAttr::Function(_)) => cx.duplicate_error(&meta),
423 Some(WithAttr::Type(_)) => cx.mutual_exclusive_error(&meta, "with"),
424 None => {
425 self.with = parse_name_value_lit_str(meta, cx)
426 .ok()
427 .map(WithAttr::Function);
428 }
429 },
430
431 _ => return Err(meta),
432 }
433
434 Ok(())
435 }
436
437 pub fn is_default(&self) -> bool {
438 matches!(
439 self,
440 Self {
441 common,
442 with: None,
443 } if common.is_default()
444 )
445 }
446}
447
448fn get_meta_items(attrs: &[Attribute], attr_type: &'static str, cx: &Ctxt) -> Vec<CustomMeta> {
449 let mut result = vec![];
450
451 for attr in attrs.iter().filter(|a| a.path().is_ident(attr_type)) {
452 match attr.parse_args_with(Punctuated::<CustomMeta, Token![,]>::parse_terminated) {
453 Ok(list) => result.extend(list),
454 Err(err) => {
455 if attr_type == "schemars" {
456 cx.syn_error(err);
457 }
458 }
459 }
460 }
461
462 result
463}
464
465fn path_str(path: &Path) -> String {
466 path.get_ident().map_or_else(
467 || path.into_token_stream().to_string().replace(' ', ""),
468 Ident::to_string,
469 )
470}
471
472pub struct AttrCtxt<'a> {
473 inner: &'a Ctxt,
474 attr_type: &'static str,
475 metas: Vec<CustomMeta>,
476}
477
478impl<'a> AttrCtxt<'a> {
479 pub fn new(inner: &'a Ctxt, attrs: &'a [Attribute], attr_type: &'static str) -> Self {
480 Self {
481 inner,
482 attr_type,
483 metas: get_meta_items(attrs, attr_type, inner),
484 }
485 }
486
487 pub fn new_nested_meta(&self, metas: Vec<CustomMeta>) -> Self {
488 Self { metas, ..*self }
489 }
490
491 pub fn parse_meta(
492 &mut self,
493 mut handle: impl FnMut(CustomMeta, &str, &Self) -> Result<(), CustomMeta>,
494 ) {
495 let metas = std::mem::take(&mut self.metas);
496 self.metas = metas
497 .into_iter()
498 .filter_map(|meta| match meta.path().get_ident().map(Ident::to_string) {
499 Some(ident) => handle(meta, &ident, self).err(),
500 _ => Some(meta),
501 })
502 .collect();
503 }
504
505 pub fn error_spanned_by<A: ToTokens, T: std::fmt::Display>(&self, obj: A, msg: T) {
506 self.inner.error_spanned_by(obj, msg);
507 }
508
509 pub fn syn_error(&self, err: syn::Error) {
510 self.inner.syn_error(err);
511 }
512
513 pub fn mutual_exclusive_error(&self, meta: &CustomMeta, other_attr: &str) {
514 if self.attr_type == "schemars" {
515 self.error_spanned_by(
516 meta,
517 format_args!(
518 "schemars attribute cannot contain both `{}` and `{}`",
519 path_str(meta.path()),
520 other_attr,
521 ),
522 );
523 }
524 }
525
526 pub fn duplicate_error(&self, meta: &CustomMeta) {
527 if self.attr_type == "schemars" {
528 self.error_spanned_by(
529 meta,
530 format_args!(
531 "duplicate schemars attribute item `{}`",
532 path_str(meta.path())
533 ),
534 );
535 }
536 }
537}
538
539impl Drop for AttrCtxt<'_> {
540 fn drop(&mut self) {
541 if self.attr_type == "schemars" {
542 for unhandled_meta in self.metas.iter().filter(|m| !is_schemars_serde_keyword(m)) {
543 self.error_spanned_by(
544 unhandled_meta.path(),
545 format_args!(
546 "unknown schemars attribute `{}`",
547 path_str(unhandled_meta.path())
548 ),
549 );
550 }
551 }
552 }
553}
554
555fn is_schemars_serde_keyword(meta: &CustomMeta) -> bool {
556 let known_keywords = schemars_to_serde::SCHEMARS_KEYWORDS_PARSED_BY_SERDE;
557 meta.path()
558 .get_ident()
559 .is_some_and(|i| known_keywords.contains(&i.to_string().as_str()))
560}