schemars_derive-0.8.22/.cargo_vcs_info.json0000644000000001550000000000100142710ustar { "git": { "sha1": "104b0fd65055d4b46f8dcbe38cdd2ef2c4098fe2" }, "path_in_vcs": "schemars_derive" }schemars_derive-0.8.22/.gitignore000064400000000000000000000000361046102023000150470ustar 00000000000000/target **/*.rs.bk Cargo.lock schemars_derive-0.8.22/Cargo.lock0000644000000037760000000000100122600ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "diff" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" [[package]] name = "pretty_assertions" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" dependencies = [ "diff", "yansi", ] [[package]] name = "proc-macro2" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] [[package]] name = "schemars_derive" version = "0.8.22" dependencies = [ "pretty_assertions", "proc-macro2", "quote", "serde_derive_internals", "syn", ] [[package]] name = "serde_derive_internals" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "syn" version = "2.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "yansi" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" schemars_derive-0.8.22/Cargo.toml0000644000000024270000000000100122730ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.60" name = "schemars_derive" version = "0.8.22" authors = ["Graham Esau "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Macros for #[derive(JsonSchema)], for use with schemars" homepage = "https://graham.cool/schemars/" readme = "README.md" keywords = [ "rust", "json-schema", "serde", ] license = "MIT" repository = "https://github.com/GREsau/schemars" [lib] name = "schemars_derive" path = "src/lib.rs" proc-macro = true [dependencies.proc-macro2] version = "1.0" [dependencies.quote] version = "1.0" [dependencies.serde_derive_internals] version = "0.29" [dependencies.syn] version = "2.0" features = ["extra-traits"] [dev-dependencies.pretty_assertions] version = "1.2.1" schemars_derive-0.8.22/Cargo.toml.orig000064400000000000000000000011251046102023000157460ustar 00000000000000[package] name = "schemars_derive" description = "Macros for #[derive(JsonSchema)], for use with schemars" homepage = "https://graham.cool/schemars/" repository = "https://github.com/GREsau/schemars" version = "0.8.22" authors = ["Graham Esau "] edition = "2021" license = "MIT" readme = "README.md" keywords = ["rust", "json-schema", "serde"] rust-version = "1.60" [lib] proc-macro = true [dependencies] proc-macro2 = "1.0" quote = "1.0" syn = { version = "2.0", features = ["extra-traits"] } serde_derive_internals = "0.29" [dev-dependencies] pretty_assertions = "1.2.1" schemars_derive-0.8.22/LICENSE000064400000000000000000000020541046102023000140660ustar 00000000000000MIT License Copyright (c) 2019 Graham Esau Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. schemars_derive-0.8.22/README.md000064400000000000000000000204101046102023000143340ustar 00000000000000# Schemars > [!NOTE] > Schemars 1.0 is in development on [the v1 branch](https://github.com/GREsau/schemars/tree/v1), see [draft PR 290](https://github.com/GREsau/schemars/pull/290) for updates [![CI Build](https://img.shields.io/github/actions/workflow/status/GREsau/schemars/ci.yml?branch=master&logo=GitHub)](https://github.com/GREsau/schemars/actions) [![Crates.io](https://img.shields.io/crates/v/schemars)](https://crates.io/crates/schemars) [![Docs](https://docs.rs/schemars/badge.svg)](https://docs.rs/schemars) [![MSRV 1.60+](https://img.shields.io/badge/schemars-rustc_1.60+-lightgray.svg)](https://blog.rust-lang.org/2022/04/07/Rust-1.60.0.html) Generate JSON Schema documents from Rust code ## Basic Usage If you don't really care about the specifics, the easiest way to generate a JSON schema for your types is to `#[derive(JsonSchema)]` and use the `schema_for!` macro. All fields of the type must also implement `JsonSchema` - Schemars implements this for many standard library types. ```rust use schemars::{schema_for, JsonSchema}; #[derive(JsonSchema)] pub struct MyStruct { pub my_int: i32, pub my_bool: bool, pub my_nullable_enum: Option, } #[derive(JsonSchema)] pub enum MyEnum { StringNewType(String), StructVariant { floats: Vec }, } let schema = schema_for!(MyStruct); println!("{}", serde_json::to_string_pretty(&schema).unwrap()); ```
Click to see the output JSON schema... ```json { "$schema": "http://json-schema.org/draft-07/schema#", "title": "MyStruct", "type": "object", "required": ["my_bool", "my_int"], "properties": { "my_bool": { "type": "boolean" }, "my_int": { "type": "integer", "format": "int32" }, "my_nullable_enum": { "anyOf": [ { "$ref": "#/definitions/MyEnum" }, { "type": "null" } ] } }, "definitions": { "MyEnum": { "anyOf": [ { "type": "object", "required": ["StringNewType"], "properties": { "StringNewType": { "type": "string" } }, "additionalProperties": false }, { "type": "object", "required": ["StructVariant"], "properties": { "StructVariant": { "type": "object", "required": ["floats"], "properties": { "floats": { "type": "array", "items": { "type": "number", "format": "float" } } } } }, "additionalProperties": false } ] } } } ```
### Serde Compatibility One of the main aims of this library is compatibility with [Serde](https://github.com/serde-rs/serde). Any generated schema _should_ match how [serde_json](https://github.com/serde-rs/json) would serialize/deserialize to/from JSON. To support this, Schemars will check for any `#[serde(...)]` attributes on types that derive `JsonSchema`, and adjust the generated schema accordingly. ```rust use schemars::{schema_for, JsonSchema}; use serde::{Deserialize, Serialize}; #[derive(Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct MyStruct { #[serde(rename = "myNumber")] pub my_int: i32, pub my_bool: bool, #[serde(default)] pub my_nullable_enum: Option, } #[derive(Deserialize, Serialize, JsonSchema)] #[serde(untagged)] pub enum MyEnum { StringNewType(String), StructVariant { floats: Vec }, } let schema = schema_for!(MyStruct); println!("{}", serde_json::to_string_pretty(&schema).unwrap()); ```
Click to see the output JSON schema... ```json { "$schema": "http://json-schema.org/draft-07/schema#", "title": "MyStruct", "type": "object", "required": ["myBool", "myNumber"], "properties": { "myBool": { "type": "boolean" }, "myNullableEnum": { "default": null, "anyOf": [ { "$ref": "#/definitions/MyEnum" }, { "type": "null" } ] }, "myNumber": { "type": "integer", "format": "int32" } }, "additionalProperties": false, "definitions": { "MyEnum": { "anyOf": [ { "type": "string" }, { "type": "object", "required": ["floats"], "properties": { "floats": { "type": "array", "items": { "type": "number", "format": "float" } } } } ] } } } ```
`#[serde(...)]` attributes can be overriden using `#[schemars(...)]` attributes, which behave identically (e.g. `#[schemars(rename_all = "camelCase")]`). You may find this useful if you want to change the generated schema without affecting Serde's behaviour, or if you're just not using Serde. ### Schema from Example Value If you want a schema for a type that can't/doesn't implement `JsonSchema`, but does implement `serde::Serialize`, then you can generate a JSON schema from a value of that type. However, this schema will generally be less precise than if the type implemented `JsonSchema` - particularly when it involves enums, since schemars will not make any assumptions about the structure of an enum based on a single variant. ```rust use schemars::schema_for_value; use serde::Serialize; #[derive(Serialize)] pub struct MyStruct { pub my_int: i32, pub my_bool: bool, pub my_nullable_enum: Option, } #[derive(Serialize)] pub enum MyEnum { StringNewType(String), StructVariant { floats: Vec }, } let schema = schema_for_value!(MyStruct { my_int: 123, my_bool: true, my_nullable_enum: Some(MyEnum::StringNewType("foo".to_string())) }); println!("{}", serde_json::to_string_pretty(&schema).unwrap()); ```
Click to see the output JSON schema... ```json { "$schema": "http://json-schema.org/draft-07/schema#", "title": "MyStruct", "examples": [ { "my_bool": true, "my_int": 123, "my_nullable_enum": { "StringNewType": "foo" } } ], "type": "object", "properties": { "my_bool": { "type": "boolean" }, "my_int": { "type": "integer" }, "my_nullable_enum": true } } ```
## Feature Flags - `derive` (enabled by default) - provides `#[derive(JsonSchema)]` macro - `impl_json_schema` - implements `JsonSchema` for Schemars types themselves - `preserve_order` - keep the order of struct fields in `Schema` and `SchemaObject` - `raw_value` - implements `JsonSchema` for `serde_json::value::RawValue` (enables the serde_json `raw_value` feature) Schemars can implement `JsonSchema` on types from several popular crates, enabled via feature flags (dependency versions are shown in brackets): - `chrono` - [chrono](https://crates.io/crates/chrono) (^0.4) - `indexmap1` - [indexmap](https://crates.io/crates/indexmap) (^1.2) - `indexmap2` - [indexmap](https://crates.io/crates/indexmap) (^2.0) - `either` - [either](https://crates.io/crates/either) (^1.3) - `uuid08` - [uuid](https://crates.io/crates/uuid) (^0.8) - `uuid1` - [uuid](https://crates.io/crates/uuid) (^1.0) - `smallvec` - [smallvec](https://crates.io/crates/smallvec) (^1.0) - `arrayvec05` - [arrayvec](https://crates.io/crates/arrayvec) (^0.5) - `arrayvec07` - [arrayvec](https://crates.io/crates/arrayvec) (^0.7) - `url` - [url](https://crates.io/crates/url) (^2.0) - `bytes` - [bytes](https://crates.io/crates/bytes) (^1.0) - `enumset` - [enumset](https://crates.io/crates/enumset) (^1.0) - `rust_decimal` - [rust_decimal](https://crates.io/crates/rust_decimal) (^1.0) - `bigdecimal03` - [bigdecimal](https://crates.io/crates/bigdecimal) (^0.3) - `bigdecimal04` - [bigdecimal](https://crates.io/crates/bigdecimal) (^0.4) - `smol_str` - [smol_str](https://crates.io/crates/smol_str) (^0.1.17) - `semver` - [semver](https://crates.io/crates/semver) (^1.0.9) For example, to implement `JsonSchema` on types from `chrono`, enable it as a feature in the `schemars` dependency in your `Cargo.toml` like so: ```toml [dependencies] schemars = { version = "0.8", features = ["chrono"] } ``` schemars_derive-0.8.22/src/ast/from_serde.rs000064400000000000000000000046771046102023000171470ustar 00000000000000use super::*; use crate::attr::Attrs; use serde_derive_internals::ast as serde_ast; use serde_derive_internals::Ctxt; pub trait FromSerde: Sized { type SerdeType; fn from_serde(errors: &Ctxt, serde: Self::SerdeType) -> Result; fn vec_from_serde(errors: &Ctxt, serdes: Vec) -> Result, ()> { let mut result = Vec::with_capacity(serdes.len()); for s in serdes { result.push(Self::from_serde(errors, s)?) } Ok(result) } } impl<'a> FromSerde for Container<'a> { type SerdeType = serde_ast::Container<'a>; fn from_serde(errors: &Ctxt, serde: Self::SerdeType) -> Result { Ok(Self { ident: serde.ident, serde_attrs: serde.attrs, data: Data::from_serde(errors, serde.data)?, generics: serde.generics.clone(), original: serde.original, // FIXME this allows with/schema_with attribute on containers attrs: Attrs::new(&serde.original.attrs, errors), }) } } impl<'a> FromSerde for Data<'a> { type SerdeType = serde_ast::Data<'a>; fn from_serde(errors: &Ctxt, serde: Self::SerdeType) -> Result { Ok(match serde { serde_ast::Data::Enum(variants) => { Data::Enum(Variant::vec_from_serde(errors, variants)?) } serde_ast::Data::Struct(style, fields) => { Data::Struct(style, Field::vec_from_serde(errors, fields)?) } }) } } impl<'a> FromSerde for Variant<'a> { type SerdeType = serde_ast::Variant<'a>; fn from_serde(errors: &Ctxt, serde: Self::SerdeType) -> Result { Ok(Self { ident: serde.ident, serde_attrs: serde.attrs, style: serde.style, fields: Field::vec_from_serde(errors, serde.fields)?, original: serde.original, attrs: Attrs::new(&serde.original.attrs, errors), }) } } impl<'a> FromSerde for Field<'a> { type SerdeType = serde_ast::Field<'a>; fn from_serde(errors: &Ctxt, serde: Self::SerdeType) -> Result { Ok(Self { member: serde.member, serde_attrs: serde.attrs, ty: serde.ty, original: serde.original, attrs: Attrs::new(&serde.original.attrs, errors), validation_attrs: ValidationAttrs::new(&serde.original.attrs, errors), }) } } schemars_derive-0.8.22/src/ast/mod.rs000064400000000000000000000040561046102023000155700ustar 00000000000000mod from_serde; use crate::attr::{Attrs, ValidationAttrs}; use from_serde::FromSerde; use serde_derive_internals::ast as serde_ast; use serde_derive_internals::{Ctxt, Derive}; pub struct Container<'a> { pub ident: syn::Ident, pub serde_attrs: serde_derive_internals::attr::Container, pub data: Data<'a>, pub generics: syn::Generics, pub original: &'a syn::DeriveInput, pub attrs: Attrs, } pub enum Data<'a> { Enum(Vec>), Struct(serde_ast::Style, Vec>), } pub struct Variant<'a> { pub ident: syn::Ident, pub serde_attrs: serde_derive_internals::attr::Variant, pub style: serde_ast::Style, pub fields: Vec>, pub original: &'a syn::Variant, pub attrs: Attrs, } pub struct Field<'a> { pub member: syn::Member, pub serde_attrs: serde_derive_internals::attr::Field, pub ty: &'a syn::Type, pub original: &'a syn::Field, pub attrs: Attrs, pub validation_attrs: ValidationAttrs, } impl<'a> Container<'a> { pub fn from_ast(item: &'a syn::DeriveInput) -> syn::Result> { let ctxt = Ctxt::new(); let result = serde_ast::Container::from_ast(&ctxt, item, Derive::Deserialize) .ok_or(()) .and_then(|serde| Self::from_serde(&ctxt, serde)); ctxt.check() .map(|_| result.expect("from_ast set no errors on Ctxt, so should have returned Ok")) } pub fn name(&self) -> &str { self.serde_attrs.name().deserialize_name() } pub fn transparent_field(&'a self) -> Option<&'a Field> { if self.serde_attrs.transparent() { if let Data::Struct(_, fields) = &self.data { return Some(&fields[0]); } } None } } impl<'a> Variant<'a> { pub fn name(&self) -> &str { self.serde_attrs.name().deserialize_name() } pub fn is_unit(&self) -> bool { matches!(self.style, serde_ast::Style::Unit) } } impl<'a> Field<'a> { pub fn name(&self) -> &str { self.serde_attrs.name().deserialize_name() } } schemars_derive-0.8.22/src/attr/doc.rs000064400000000000000000000042311046102023000157340ustar 00000000000000use syn::Attribute; pub fn get_title_and_desc_from_doc(attrs: &[Attribute]) -> (Option, Option) { let doc = match get_doc(attrs) { None => return (None, None), Some(doc) => doc, }; if doc.starts_with('#') { let mut split = doc.splitn(2, '\n'); let title = split .next() .unwrap() .trim_start_matches('#') .trim() .to_owned(); let maybe_desc = split.next().and_then(merge_description_lines); (none_if_empty(title), maybe_desc) } else { (None, merge_description_lines(&doc)) } } fn merge_description_lines(doc: &str) -> Option { let desc = doc .trim() .split("\n\n") .filter_map(|line| none_if_empty(line.trim().replace('\n', " "))) .collect::>() .join("\n\n"); none_if_empty(desc) } fn get_doc(attrs: &[Attribute]) -> Option { let attrs = attrs .iter() .filter_map(|attr| { if !attr.path().is_ident("doc") { return None; } let meta = attr.meta.require_name_value().ok()?; if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(lit_str), .. }) = &meta.value { return Some(lit_str.value()); } None }) .collect::>(); let mut lines = attrs .iter() .flat_map(|a| a.split('\n')) .map(str::trim) .skip_while(|s| s.is_empty()) .collect::>(); if let Some(&"") = lines.last() { lines.pop(); } // Added for backward-compatibility, but perhaps we shouldn't do this // https://github.com/rust-lang/rust/issues/32088 if lines.iter().all(|l| l.starts_with('*')) { for line in lines.iter_mut() { *line = line[1..].trim() } while let Some(&"") = lines.first() { lines.remove(0); } }; none_if_empty(lines.join("\n")) } fn none_if_empty(s: String) -> Option { if s.is_empty() { None } else { Some(s) } } schemars_derive-0.8.22/src/attr/mod.rs000064400000000000000000000242641046102023000157560ustar 00000000000000mod doc; mod schemars_to_serde; mod validation; pub use schemars_to_serde::process_serde_attrs; pub use validation::ValidationAttrs; use crate::metadata::SchemaMetadata; use proc_macro2::{Group, Span, TokenStream, TokenTree}; use quote::ToTokens; use serde_derive_internals::Ctxt; use syn::parse::{self, Parse}; use syn::{Meta, MetaNameValue}; // FIXME using the same struct for containers+variants+fields means that // with/schema_with are accepted (but ignored) on containers, and // repr/crate_name are accepted (but ignored) on variants and fields etc. #[derive(Debug, Default)] pub struct Attrs { pub with: Option, pub title: Option, pub description: Option, pub deprecated: bool, pub examples: Vec, pub repr: Option, pub crate_name: Option, pub is_renamed: bool, } #[derive(Debug)] pub enum WithAttr { Type(syn::Type), Function(syn::Path), } impl Attrs { pub fn new(attrs: &[syn::Attribute], errors: &Ctxt) -> Self { let mut result = Attrs::default() .populate(attrs, "schemars", false, errors) .populate(attrs, "serde", true, errors); result.deprecated = attrs.iter().any(|a| a.path().is_ident("deprecated")); result.repr = attrs .iter() .find(|a| a.path().is_ident("repr")) .and_then(|a| a.parse_args().ok()); let (doc_title, doc_description) = doc::get_title_and_desc_from_doc(attrs); result.title = result.title.or(doc_title); result.description = result.description.or(doc_description); result } pub fn as_metadata(&self) -> SchemaMetadata<'_> { #[allow(clippy::ptr_arg)] fn none_if_empty(s: &String) -> Option<&str> { if s.is_empty() { None } else { Some(s) } } SchemaMetadata { title: self.title.as_ref().and_then(none_if_empty), description: self.description.as_ref().and_then(none_if_empty), deprecated: self.deprecated, examples: &self.examples, read_only: false, write_only: false, default: None, } } fn populate( mut self, attrs: &[syn::Attribute], attr_type: &'static str, ignore_errors: bool, errors: &Ctxt, ) -> Self { let duplicate_error = |meta: &MetaNameValue| { if !ignore_errors { let msg = format!( "duplicate schemars attribute `{}`", meta.path.get_ident().unwrap() ); errors.error_spanned_by(meta, msg) } }; let mutual_exclusive_error = |meta: &MetaNameValue, other: &str| { if !ignore_errors { let msg = format!( "schemars attribute cannot contain both `{}` and `{}`", meta.path.get_ident().unwrap(), other, ); errors.error_spanned_by(meta, msg) } }; for meta_item in get_meta_items(attrs, attr_type, errors, ignore_errors) { match &meta_item { Meta::NameValue(m) if m.path.is_ident("with") => { if let Ok(ty) = parse_lit_into_ty(errors, attr_type, "with", &m.value) { match self.with { Some(WithAttr::Type(_)) => duplicate_error(m), Some(WithAttr::Function(_)) => mutual_exclusive_error(m, "schema_with"), None => self.with = Some(WithAttr::Type(ty)), } } } Meta::NameValue(m) if m.path.is_ident("schema_with") => { if let Ok(fun) = parse_lit_into_path(errors, attr_type, "schema_with", &m.value) { match self.with { Some(WithAttr::Function(_)) => duplicate_error(m), Some(WithAttr::Type(_)) => mutual_exclusive_error(m, "with"), None => self.with = Some(WithAttr::Function(fun)), } } } Meta::NameValue(m) if m.path.is_ident("title") => { if let Ok(title) = expr_as_lit_str(errors, attr_type, "title", &m.value) { match self.title { Some(_) => duplicate_error(m), None => self.title = Some(title.value()), } } } Meta::NameValue(m) if m.path.is_ident("description") => { if let Ok(description) = expr_as_lit_str(errors, attr_type, "description", &m.value) { match self.description { Some(_) => duplicate_error(m), None => self.description = Some(description.value()), } } } Meta::NameValue(m) if m.path.is_ident("example") => { if let Ok(fun) = parse_lit_into_path(errors, attr_type, "example", &m.value) { self.examples.push(fun) } } Meta::NameValue(m) if m.path.is_ident("rename") => self.is_renamed = true, Meta::NameValue(m) if m.path.is_ident("crate") && attr_type == "schemars" => { if let Ok(p) = parse_lit_into_path(errors, attr_type, "crate", &m.value) { if self.crate_name.is_some() { duplicate_error(m) } else { self.crate_name = Some(p) } } } _ if ignore_errors => {} Meta::List(m) if m.path.is_ident("inner") && attr_type == "schemars" => { // This will be processed with the validation attributes. // It's allowed only for the schemars attribute because the // validator crate doesn't support it yet. } _ => { if !is_known_serde_or_validation_keyword(&meta_item) { let path = meta_item .path() .into_token_stream() .to_string() .replace(' ', ""); errors.error_spanned_by( meta_item.path(), format!("unknown schemars attribute `{}`", path), ); } } } } self } pub fn is_default(&self) -> bool { matches!(self, Self { with: None, title: None, description: None, deprecated: false, examples, repr: None, crate_name: None, is_renamed: _, } if examples.is_empty()) } } fn is_known_serde_or_validation_keyword(meta: &syn::Meta) -> bool { let mut known_keywords = schemars_to_serde::SERDE_KEYWORDS .iter() .chain(validation::VALIDATION_KEYWORDS); meta.path() .get_ident() .map(|i| known_keywords.any(|k| i == k)) .unwrap_or(false) } fn get_meta_items( attrs: &[syn::Attribute], attr_type: &'static str, errors: &Ctxt, ignore_errors: bool, ) -> Vec { let mut result = vec![]; for attr in attrs.iter().filter(|a| a.path().is_ident(attr_type)) { match attr.parse_args_with(syn::punctuated::Punctuated::::parse_terminated) { Ok(list) => result.extend(list), Err(err) if !ignore_errors => errors.syn_error(err), Err(_) => {} } } result } fn expr_as_lit_str<'a>( cx: &Ctxt, attr_type: &'static str, meta_item_name: &'static str, expr: &'a syn::Expr, ) -> Result<&'a syn::LitStr, ()> { if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(lit_str), .. }) = expr { Ok(lit_str) } else { cx.error_spanned_by( expr, format!( "expected {} {} attribute to be a string: `{} = \"...\"`", attr_type, meta_item_name, meta_item_name ), ); Err(()) } } fn parse_lit_into_ty( cx: &Ctxt, attr_type: &'static str, meta_item_name: &'static str, lit: &syn::Expr, ) -> Result { let string = expr_as_lit_str(cx, attr_type, meta_item_name, lit)?; parse_lit_str(string).map_err(|_| { cx.error_spanned_by( lit, format!( "failed to parse type: `{} = {:?}`", meta_item_name, string.value() ), ) }) } fn parse_lit_into_path( cx: &Ctxt, attr_type: &'static str, meta_item_name: &'static str, expr: &syn::Expr, ) -> Result { let lit_str = expr_as_lit_str(cx, attr_type, meta_item_name, expr)?; parse_lit_str(lit_str).map_err(|_| { cx.error_spanned_by( expr, format!( "failed to parse path: `{} = {:?}`", meta_item_name, lit_str.value() ), ) }) } fn parse_lit_str(s: &syn::LitStr) -> parse::Result where T: Parse, { let tokens = spanned_tokens(s)?; syn::parse2(tokens) } fn spanned_tokens(s: &syn::LitStr) -> parse::Result { let stream = syn::parse_str(&s.value())?; Ok(respan_token_stream(stream, s.span())) } fn respan_token_stream(stream: TokenStream, span: Span) -> TokenStream { stream .into_iter() .map(|token| respan_token_tree(token, span)) .collect() } fn respan_token_tree(mut token: TokenTree, span: Span) -> TokenTree { if let TokenTree::Group(g) = &mut token { *g = Group::new(g.delimiter(), respan_token_stream(g.stream(), span)); } token.set_span(span); token } schemars_derive-0.8.22/src/attr/schemars_to_serde.rs000064400000000000000000000137621046102023000206710ustar 00000000000000use quote::ToTokens; use serde_derive_internals::Ctxt; use std::collections::HashSet; use syn::parse::Parser; use syn::{Attribute, Data, Field, Meta, Variant}; use super::get_meta_items; // List of keywords that can appear in #[serde(...)]/#[schemars(...)] attributes which we want serde_derive_internals to parse for us. pub(crate) static SERDE_KEYWORDS: &[&str] = &[ "rename", "rename_all", "deny_unknown_fields", "tag", "content", "untagged", "default", "skip", "skip_serializing", "skip_serializing_if", "skip_deserializing", "flatten", "remote", "transparent", // Special case - `bound` is removed from serde attrs, so is only respected when present in schemars attr. "bound", // Special cases - `with`/`serialize_with` are passed to serde but not copied from schemars attrs to serde attrs. // This is because we want to preserve any serde attribute's `serialize_with` value to determine whether the field's // default value should be serialized. We also check the `with` value on schemars/serde attrs e.g. to support deriving // JsonSchema on remote types, but we parse that ourselves rather than using serde_derive_internals. "serialize_with", "with", ]; // If a struct/variant/field has any #[schemars] attributes, then create copies of them // as #[serde] attributes so that serde_derive_internals will parse them for us. pub fn process_serde_attrs(input: &mut syn::DeriveInput) -> syn::Result<()> { let ctxt = Ctxt::new(); process_attrs(&ctxt, &mut input.attrs); match input.data { Data::Struct(ref mut s) => process_serde_field_attrs(&ctxt, s.fields.iter_mut()), Data::Enum(ref mut e) => process_serde_variant_attrs(&ctxt, e.variants.iter_mut()), Data::Union(ref mut u) => process_serde_field_attrs(&ctxt, u.fields.named.iter_mut()), }; ctxt.check() } fn process_serde_variant_attrs<'a>(ctxt: &Ctxt, variants: impl Iterator) { for v in variants { process_attrs(ctxt, &mut v.attrs); process_serde_field_attrs(ctxt, v.fields.iter_mut()); } } fn process_serde_field_attrs<'a>(ctxt: &Ctxt, fields: impl Iterator) { for f in fields { process_attrs(ctxt, &mut f.attrs); } } fn process_attrs(ctxt: &Ctxt, attrs: &mut Vec) { // Remove #[serde(...)] attributes (some may be re-added later) let (serde_attrs, other_attrs): (Vec<_>, Vec<_>) = attrs.drain(..).partition(|at| at.path().is_ident("serde")); *attrs = other_attrs; // Copy appropriate #[schemars(...)] attributes to #[serde(...)] attributes let (mut serde_meta, mut schemars_meta_names): (Vec<_>, HashSet<_>) = get_meta_items(attrs, "schemars", ctxt, false) .into_iter() .filter_map(|meta| { let keyword = get_meta_ident(&meta)?; if SERDE_KEYWORDS.contains(&keyword.as_ref()) && !keyword.ends_with("with") { Some((meta, keyword)) } else { None } }) .unzip(); if schemars_meta_names.contains("skip") { schemars_meta_names.insert("skip_serializing".to_string()); schemars_meta_names.insert("skip_deserializing".to_string()); } // Re-add #[serde(...)] attributes that weren't overridden by #[schemars(...)] attributes for meta in get_meta_items(&serde_attrs, "serde", ctxt, false) { if let Some(i) = get_meta_ident(&meta) { if !schemars_meta_names.contains(&i) && SERDE_KEYWORDS.contains(&i.as_ref()) && i != "bound" { serde_meta.push(meta); } } } if !serde_meta.is_empty() { let new_serde_attr = quote! { #[serde(#(#serde_meta),*)] }; let parser = Attribute::parse_outer; match parser.parse2(new_serde_attr) { Ok(ref mut parsed) => attrs.append(parsed), Err(e) => ctxt.error_spanned_by(to_tokens(attrs), e), } } } fn to_tokens(attrs: &[Attribute]) -> impl ToTokens { let mut tokens = proc_macro2::TokenStream::new(); for attr in attrs { attr.to_tokens(&mut tokens); } tokens } fn get_meta_ident(meta: &Meta) -> Option { meta.path().get_ident().map(|i| i.to_string()) } #[cfg(test)] mod tests { use super::*; use pretty_assertions::assert_eq; use syn::DeriveInput; #[test] fn test_process_serde_attrs() { let mut input: DeriveInput = parse_quote! { #[serde(rename(serialize = "ser_name"), rename_all = "camelCase")] #[serde(default, unknown_word)] #[schemars(rename = "overriden", another_unknown_word)] #[misc] struct MyStruct { /// blah blah blah #[serde(skip_serializing_if = "some_fn", bound = "removed")] field1: i32, #[serde(serialize_with = "se", deserialize_with = "de")] #[schemars(with = "with", bound = "bound")] field2: i32, #[schemars(skip)] #[serde(skip_serializing)] field3: i32, } }; let expected: DeriveInput = parse_quote! { #[schemars(rename = "overriden", another_unknown_word)] #[misc] #[serde(rename = "overriden", rename_all = "camelCase", default)] struct MyStruct { #[doc = r" blah blah blah"] #[serde(skip_serializing_if = "some_fn")] field1: i32, #[schemars(with = "with", bound = "bound")] #[serde(bound = "bound", serialize_with = "se")] field2: i32, #[schemars(skip)] #[serde(skip)] field3: i32, } }; if let Err(e) = process_serde_attrs(&mut input) { panic!("process_serde_attrs returned error: {}", e) }; assert_eq!(input, expected); } } schemars_derive-0.8.22/src/attr/validation.rs000064400000000000000000000510741046102023000173300ustar 00000000000000use super::{expr_as_lit_str, get_meta_items, parse_lit_into_path, parse_lit_str}; use proc_macro2::TokenStream; use quote::ToTokens; use serde_derive_internals::Ctxt; use syn::{ parse::Parser, punctuated::Punctuated, Expr, ExprPath, Lit, Meta, MetaList, MetaNameValue, Path, }; pub(crate) static VALIDATION_KEYWORDS: &[&str] = &[ "range", "regex", "contains", "email", "phone", "url", "length", "required", ]; #[derive(Debug, Clone, Copy, PartialEq)] enum Format { Email, Uri, Phone, } impl Format { fn attr_str(self) -> &'static str { match self { Format::Email => "email", Format::Uri => "url", Format::Phone => "phone", } } fn schema_str(self) -> &'static str { match self { Format::Email => "email", Format::Uri => "uri", Format::Phone => "phone", } } } #[derive(Debug, Default)] pub struct ValidationAttrs { length_min: Option, length_max: Option, length_equal: Option, range_min: Option, range_max: Option, regex: Option, contains: Option, required: bool, format: Option, inner: Option>, } impl ValidationAttrs { pub fn new(attrs: &[syn::Attribute], errors: &Ctxt) -> Self { let schemars_items = get_meta_items(attrs, "schemars", errors, false); let validate_items = get_meta_items(attrs, "validate", errors, true); ValidationAttrs::default() .populate(schemars_items, "schemars", false, errors) .populate(validate_items, "validate", true, errors) } pub fn required(&self) -> bool { self.required } fn populate( mut self, meta_items: Vec, attr_type: &'static str, ignore_errors: bool, errors: &Ctxt, ) -> Self { let duplicate_error = |path: &Path| { if !ignore_errors { let msg = format!( "duplicate schemars attribute `{}`", path.get_ident().unwrap() ); errors.error_spanned_by(path, msg) } }; let mutual_exclusive_error = |path: &Path, other: &str| { if !ignore_errors { let msg = format!( "schemars attribute cannot contain both `{}` and `{}`", path.get_ident().unwrap(), other, ); errors.error_spanned_by(path, msg) } }; let duplicate_format_error = |existing: Format, new: Format, path: &syn::Path| { if !ignore_errors { let msg = if existing == new { format!("duplicate schemars attribute `{}`", existing.attr_str()) } else { format!( "schemars attribute cannot contain both `{}` and `{}`", existing.attr_str(), new.attr_str(), ) }; errors.error_spanned_by(path, msg) } }; let parse_nested_meta = |meta_list: MetaList| { let parser = Punctuated::::parse_terminated; match parser.parse2(meta_list.tokens) { Ok(p) => p, Err(e) => { if !ignore_errors { errors.syn_error(e); } Default::default() } } }; for meta_item in meta_items { match meta_item { Meta::List(meta_list) if meta_list.path.is_ident("length") => { for nested in parse_nested_meta(meta_list) { match nested { Meta::NameValue(nv) if nv.path.is_ident("min") => { if self.length_min.is_some() { duplicate_error(&nv.path) } else if self.length_equal.is_some() { mutual_exclusive_error(&nv.path, "equal") } else { self.length_min = str_or_num_to_expr(errors, "min", nv.value); } } Meta::NameValue(nv) if nv.path.is_ident("max") => { if self.length_max.is_some() { duplicate_error(&nv.path) } else if self.length_equal.is_some() { mutual_exclusive_error(&nv.path, "equal") } else { self.length_max = str_or_num_to_expr(errors, "max", nv.value); } } Meta::NameValue(nv) if nv.path.is_ident("equal") => { if self.length_equal.is_some() { duplicate_error(&nv.path) } else if self.length_min.is_some() { mutual_exclusive_error(&nv.path, "min") } else if self.length_max.is_some() { mutual_exclusive_error(&nv.path, "max") } else { self.length_equal = str_or_num_to_expr(errors, "equal", nv.value); } } meta => { if !ignore_errors { errors.error_spanned_by( meta, "unknown item in schemars length attribute".to_string(), ); } } } } } Meta::List(meta_list) if meta_list.path.is_ident("range") => { for nested in parse_nested_meta(meta_list) { match nested { Meta::NameValue(nv) if nv.path.is_ident("min") => { if self.range_min.is_some() { duplicate_error(&nv.path) } else { self.range_min = str_or_num_to_expr(errors, "min", nv.value); } } Meta::NameValue(nv) if nv.path.is_ident("max") => { if self.range_max.is_some() { duplicate_error(&nv.path) } else { self.range_max = str_or_num_to_expr(errors, "max", nv.value); } } meta => { if !ignore_errors { errors.error_spanned_by( meta, "unknown item in schemars range attribute".to_string(), ); } } } } } Meta::Path(m) if m.is_ident("required") || m.is_ident("required_nested") => { self.required = true; } Meta::Path(p) if p.is_ident(Format::Email.attr_str()) => match self.format { Some(f) => duplicate_format_error(f, Format::Email, &p), None => self.format = Some(Format::Email), }, Meta::Path(p) if p.is_ident(Format::Uri.attr_str()) => match self.format { Some(f) => duplicate_format_error(f, Format::Uri, &p), None => self.format = Some(Format::Uri), }, Meta::Path(p) if p.is_ident(Format::Phone.attr_str()) => match self.format { Some(f) => duplicate_format_error(f, Format::Phone, &p), None => self.format = Some(Format::Phone), }, Meta::NameValue(nv) if nv.path.is_ident("regex") => { match (&self.regex, &self.contains) { (Some(_), _) => duplicate_error(&nv.path), (None, Some(_)) => mutual_exclusive_error(&nv.path, "contains"), (None, None) => { self.regex = parse_lit_into_expr_path(errors, attr_type, "regex", &nv.value).ok() } } } Meta::List(meta_list) if meta_list.path.is_ident("regex") => { match (&self.regex, &self.contains) { (Some(_), _) => duplicate_error(&meta_list.path), (None, Some(_)) => mutual_exclusive_error(&meta_list.path, "contains"), (None, None) => { for x in parse_nested_meta(meta_list) { match x { Meta::NameValue(MetaNameValue { path, value, .. }) if path.is_ident("path") => { self.regex = parse_lit_into_expr_path( errors, attr_type, "path", &value, ) .ok() } Meta::NameValue(MetaNameValue { path, value, .. }) if path.is_ident("pattern") => { self.regex = expr_as_lit_str(errors, attr_type, "pattern", &value) .ok() .map(|litstr| { Expr::Lit(syn::ExprLit { attrs: Vec::new(), lit: Lit::Str(litstr.clone()), }) }) } meta => { if !ignore_errors { errors.error_spanned_by( meta, "unknown item in schemars regex attribute" .to_string(), ); } } } } } } } Meta::NameValue(MetaNameValue { path, value, .. }) if path.is_ident("contains") => { match (&self.contains, &self.regex) { (Some(_), _) => duplicate_error(&path), (None, Some(_)) => mutual_exclusive_error(&path, "regex"), (None, None) => { self.contains = expr_as_lit_str(errors, attr_type, "contains", &value) .map(|litstr| litstr.value()) .ok() } } } Meta::List(meta_list) if meta_list.path.is_ident("contains") => { match (&self.contains, &self.regex) { (Some(_), _) => duplicate_error(&meta_list.path), (None, Some(_)) => mutual_exclusive_error(&meta_list.path, "regex"), (None, None) => { for x in parse_nested_meta(meta_list) { match x { Meta::NameValue(MetaNameValue { path, value, .. }) if path.is_ident("pattern") => { self.contains = expr_as_lit_str(errors, attr_type, "contains", &value) .ok() .map(|litstr| litstr.value()) } meta => { if !ignore_errors { errors.error_spanned_by( meta, "unknown item in schemars contains attribute" .to_string(), ); } } } } } } } Meta::List(meta_list) if meta_list.path.is_ident("inner") => match self.inner { Some(_) => duplicate_error(&meta_list.path), None => { let inner_attrs = ValidationAttrs::default().populate( parse_nested_meta(meta_list).into_iter().collect(), attr_type, ignore_errors, errors, ); self.inner = Some(Box::new(inner_attrs)); } }, _ => {} } } self } pub fn apply_to_schema(&self, schema_expr: &mut TokenStream) { if let Some(apply_expr) = self.apply_to_schema_expr() { *schema_expr = quote! { { let mut schema = #schema_expr; #apply_expr schema } } } } fn apply_to_schema_expr(&self) -> Option { let mut array_validation = Vec::new(); let mut number_validation = Vec::new(); let mut object_validation = Vec::new(); let mut string_validation = Vec::new(); if let Some(length_min) = self.length_min.as_ref().or(self.length_equal.as_ref()) { string_validation.push(quote! { validation.min_length = Some(#length_min as u32); }); array_validation.push(quote! { validation.min_items = Some(#length_min as u32); }); } if let Some(length_max) = self.length_max.as_ref().or(self.length_equal.as_ref()) { string_validation.push(quote! { validation.max_length = Some(#length_max as u32); }); array_validation.push(quote! { validation.max_items = Some(#length_max as u32); }); } if let Some(range_min) = &self.range_min { number_validation.push(quote! { validation.minimum = Some(#range_min as f64); }); } if let Some(range_max) = &self.range_max { number_validation.push(quote! { validation.maximum = Some(#range_max as f64); }); } if let Some(regex) = &self.regex { string_validation.push(quote! { validation.pattern = Some(#regex.to_string()); }); } if let Some(contains) = &self.contains { object_validation.push(quote! { validation.required.insert(#contains.to_string()); }); if self.regex.is_none() { let pattern = crate::regex_syntax::escape(contains); string_validation.push(quote! { validation.pattern = Some(#pattern.to_string()); }); } } let format = self.format.as_ref().map(|f| { let f = f.schema_str(); quote! { schema_object.format = Some(#f.to_string()); } }); let inner_validation = self .inner .as_deref() .and_then(|inner| inner.apply_to_schema_expr()) .map(|apply_expr| { quote! { if schema_object.has_type(schemars::schema::InstanceType::Array) { if let Some(schemars::schema::SingleOrVec::Single(inner_schema)) = &mut schema_object.array().items { let mut schema = &mut **inner_schema; #apply_expr } } } }); let array_validation = wrap_array_validation(array_validation); let number_validation = wrap_number_validation(number_validation); let object_validation = wrap_object_validation(object_validation); let string_validation = wrap_string_validation(string_validation); if array_validation.is_some() || number_validation.is_some() || object_validation.is_some() || string_validation.is_some() || format.is_some() || inner_validation.is_some() { Some(quote! { if let schemars::schema::Schema::Object(schema_object) = &mut schema { #array_validation #number_validation #object_validation #string_validation #format #inner_validation } }) } else { None } } } fn parse_lit_into_expr_path( cx: &Ctxt, attr_type: &'static str, meta_item_name: &'static str, lit: &Expr, ) -> Result { parse_lit_into_path(cx, attr_type, meta_item_name, lit).map(|path| { Expr::Path(ExprPath { attrs: Vec::new(), qself: None, path, }) }) } fn wrap_array_validation(v: Vec) -> Option { if v.is_empty() { None } else { Some(quote! { if schema_object.has_type(schemars::schema::InstanceType::Array) { let validation = schema_object.array(); #(#v)* } }) } } fn wrap_number_validation(v: Vec) -> Option { if v.is_empty() { None } else { Some(quote! { if schema_object.has_type(schemars::schema::InstanceType::Integer) || schema_object.has_type(schemars::schema::InstanceType::Number) { let validation = schema_object.number(); #(#v)* } }) } } fn wrap_object_validation(v: Vec) -> Option { if v.is_empty() { None } else { Some(quote! { if schema_object.has_type(schemars::schema::InstanceType::Object) { let validation = schema_object.object(); #(#v)* } }) } } fn wrap_string_validation(v: Vec) -> Option { if v.is_empty() { None } else { Some(quote! { if schema_object.has_type(schemars::schema::InstanceType::String) { let validation = schema_object.string(); #(#v)* } }) } } fn str_or_num_to_expr(cx: &Ctxt, meta_item_name: &str, expr: Expr) -> Option { // this odd double-parsing is to make `-10` parsed as an Lit instead of an Expr::Unary let lit: Lit = match syn::parse2(expr.to_token_stream()) { Ok(l) => l, Err(err) => { cx.syn_error(err); return None; } }; match lit { Lit::Str(s) => parse_lit_str::(&s).ok().map(Expr::Path), Lit::Int(_) | Lit::Float(_) => Some(expr), _ => { cx.error_spanned_by( &expr, format!( "expected `{}` to be a string or number literal, not {:?}", meta_item_name, &expr ), ); None } } } schemars_derive-0.8.22/src/lib.rs000064400000000000000000000161001046102023000147610ustar 00000000000000#![forbid(unsafe_code)] #[macro_use] extern crate quote; #[macro_use] extern crate syn; extern crate proc_macro; mod ast; mod attr; mod metadata; mod regex_syntax; mod schema_exprs; use ast::*; use proc_macro2::TokenStream; use syn::spanned::Spanned; #[proc_macro_derive(JsonSchema, attributes(schemars, serde, validate))] pub fn derive_json_schema_wrapper(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as syn::DeriveInput); derive_json_schema(input, false) .unwrap_or_else(syn::Error::into_compile_error) .into() } #[proc_macro_derive(JsonSchema_repr, attributes(schemars, serde))] pub fn derive_json_schema_repr_wrapper(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as syn::DeriveInput); derive_json_schema(input, true) .unwrap_or_else(syn::Error::into_compile_error) .into() } fn derive_json_schema(mut input: syn::DeriveInput, repr: bool) -> syn::Result { attr::process_serde_attrs(&mut input)?; let mut cont = Container::from_ast(&input)?; add_trait_bounds(&mut cont); let crate_alias = cont.attrs.crate_name.as_ref().map(|path| { quote_spanned! {path.span()=> use #path as schemars; } }); let type_name = &cont.ident; let (impl_generics, ty_generics, where_clause) = cont.generics.split_for_impl(); if let Some(transparent_field) = cont.transparent_field() { let (ty, type_def) = schema_exprs::type_for_field_schema(transparent_field); return Ok(quote! { const _: () = { #crate_alias #type_def #[automatically_derived] impl #impl_generics schemars::JsonSchema for #type_name #ty_generics #where_clause { fn is_referenceable() -> bool { <#ty as schemars::JsonSchema>::is_referenceable() } fn schema_name() -> std::string::String { <#ty as schemars::JsonSchema>::schema_name() } fn schema_id() -> std::borrow::Cow<'static, str> { <#ty as schemars::JsonSchema>::schema_id() } fn json_schema(generator: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema { <#ty as schemars::JsonSchema>::json_schema(generator) } fn _schemars_private_non_optional_json_schema(generator: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema { <#ty as schemars::JsonSchema>::_schemars_private_non_optional_json_schema(generator) } fn _schemars_private_is_option() -> bool { <#ty as schemars::JsonSchema>::_schemars_private_is_option() } }; }; }); } let mut schema_base_name = cont.name().to_string(); if !cont.attrs.is_renamed { if let Some(path) = cont.serde_attrs.remote() { if let Some(segment) = path.segments.last() { schema_base_name = segment.ident.to_string(); } } } // FIXME improve handling of generic type params which may not implement JsonSchema let type_params: Vec<_> = cont.generics.type_params().map(|ty| &ty.ident).collect(); let const_params: Vec<_> = cont.generics.const_params().map(|c| &c.ident).collect(); let params: Vec<_> = type_params.iter().chain(const_params.iter()).collect(); let (schema_name, schema_id) = if params.is_empty() || (cont.attrs.is_renamed && !schema_base_name.contains('{')) { ( quote! { #schema_base_name.to_owned() }, quote! { std::borrow::Cow::Borrowed(std::concat!( std::module_path!(), "::", #schema_base_name )) }, ) } else if cont.attrs.is_renamed { let mut schema_name_fmt = schema_base_name; for tp in ¶ms { schema_name_fmt.push_str(&format!("{{{}:.0}}", tp)); } ( quote! { format!(#schema_name_fmt #(,#type_params=#type_params::schema_name())* #(,#const_params=#const_params)*) }, quote! { std::borrow::Cow::Owned( format!( std::concat!( std::module_path!(), "::", #schema_name_fmt ) #(,#type_params=#type_params::schema_id())* #(,#const_params=#const_params)* ) ) }, ) } else { let mut schema_name_fmt = schema_base_name; schema_name_fmt.push_str("_for_{}"); schema_name_fmt.push_str(&"_and_{}".repeat(params.len() - 1)); ( quote! { format!(#schema_name_fmt #(,#type_params::schema_name())* #(,#const_params)*) }, quote! { std::borrow::Cow::Owned( format!( std::concat!( std::module_path!(), "::", #schema_name_fmt ) #(,#type_params::schema_id())* #(,#const_params)* ) ) }, ) }; let schema_expr = if repr { schema_exprs::expr_for_repr(&cont)? } else { schema_exprs::expr_for_container(&cont) }; Ok(quote! { const _: () = { #crate_alias #[automatically_derived] #[allow(unused_braces)] impl #impl_generics schemars::JsonSchema for #type_name #ty_generics #where_clause { fn schema_name() -> std::string::String { #schema_name } fn schema_id() -> std::borrow::Cow<'static, str> { #schema_id } fn json_schema(generator: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema { #schema_expr } }; }; }) } fn add_trait_bounds(cont: &mut Container) { if let Some(bounds) = cont.serde_attrs.ser_bound() { let where_clause = cont.generics.make_where_clause(); where_clause.predicates.extend(bounds.iter().cloned()); } else { // No explicit trait bounds specified, assume the Rust convention of adding the trait to each type parameter // TODO consider also adding trait bound to associated types when used as fields - I think Serde does this? for param in &mut cont.generics.params { if let syn::GenericParam::Type(ref mut type_param) = *param { type_param.bounds.push(parse_quote!(schemars::JsonSchema)); } } } } schemars_derive-0.8.22/src/metadata.rs000064400000000000000000000036501046102023000160010ustar 00000000000000use proc_macro2::TokenStream; #[derive(Debug, Clone)] pub struct SchemaMetadata<'a> { pub title: Option<&'a str>, pub description: Option<&'a str>, pub deprecated: bool, pub read_only: bool, pub write_only: bool, pub examples: &'a [syn::Path], pub default: Option, } impl<'a> SchemaMetadata<'a> { pub fn apply_to_schema(&self, schema_expr: &mut TokenStream) { if let Some(title) = &self.title { *schema_expr = quote! { schemars::_private::metadata::add_title(#schema_expr, #title) }; } if let Some(description) = &self.description { *schema_expr = quote! { schemars::_private::metadata::add_description(#schema_expr, #description) }; } if self.deprecated { *schema_expr = quote! { schemars::_private::metadata::add_deprecated(#schema_expr, true) }; } if self.read_only { *schema_expr = quote! { schemars::_private::metadata::add_read_only(#schema_expr, true) }; } if self.write_only { *schema_expr = quote! { schemars::_private::metadata::add_write_only(#schema_expr, true) }; } if !self.examples.is_empty() { let examples = self.examples.iter().map(|eg| { quote! { schemars::_serde_json::value::to_value(#eg()) } }); *schema_expr = quote! { schemars::_private::metadata::add_examples(#schema_expr, [#(#examples),*].into_iter().flatten()) }; } if let Some(default) = &self.default { *schema_expr = quote! { schemars::_private::metadata::add_default(#schema_expr, #default.and_then(|d| schemars::_schemars_maybe_to_value!(d))) }; } } } schemars_derive-0.8.22/src/regex_syntax.rs000064400000000000000000000036001046102023000167340ustar 00000000000000#![allow(clippy::all)] // Copied from regex_syntax crate to avoid pulling in the whole crate just for a utility function // https://github.com/rust-lang/regex/blob/431c4e4867e1eb33eb39b23ed47c9934b2672f8f/regex-syntax/src/lib.rs // // Copyright (c) 2014 The Rust Project Developers // // Permission is hereby granted, free of charge, to any // person obtaining a copy of this software and associated // documentation files (the "Software"), to deal in the // Software without restriction, including without // limitation the rights to use, copy, modify, merge, // publish, distribute, sublicense, and/or sell copies of // the Software, and to permit persons to whom the Software // is furnished to do so, subject to the following // conditions: // // The above copyright notice and this permission notice // shall be included in all copies or substantial portions // of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. pub fn escape(text: &str) -> String { let mut quoted = String::new(); escape_into(text, &mut quoted); quoted } fn escape_into(text: &str, buf: &mut String) { buf.reserve(text.len()); for c in text.chars() { if is_meta_character(c) { buf.push('\\'); } buf.push(c); } } fn is_meta_character(c: char) -> bool { match c { '\\' | '.' | '+' | '*' | '?' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$' | '#' | '&' | '-' | '~' => true, _ => false, } } schemars_derive-0.8.22/src/schema_exprs.rs000064400000000000000000000471671046102023000167150ustar 00000000000000use std::collections::HashSet; use crate::{ast::*, attr::WithAttr, metadata::SchemaMetadata}; use proc_macro2::{Span, TokenStream}; use serde_derive_internals::ast::Style; use serde_derive_internals::attr::{self as serde_attr, Default as SerdeDefault, TagType}; use syn::spanned::Spanned; pub fn expr_for_container(cont: &Container) -> TokenStream { let mut schema_expr = match &cont.data { Data::Struct(Style::Unit, _) => expr_for_unit_struct(), Data::Struct(Style::Newtype, fields) => expr_for_newtype_struct(&fields[0]), Data::Struct(Style::Tuple, fields) => expr_for_tuple_struct(fields), Data::Struct(Style::Struct, fields) => expr_for_struct( fields, cont.serde_attrs.default(), cont.serde_attrs.deny_unknown_fields(), ), Data::Enum(variants) => expr_for_enum(variants, &cont.serde_attrs), }; cont.attrs.as_metadata().apply_to_schema(&mut schema_expr); schema_expr } pub fn expr_for_repr(cont: &Container) -> Result { let repr_type = cont.attrs.repr.as_ref().ok_or_else(|| { syn::Error::new( Span::call_site(), "JsonSchema_repr: missing #[repr(...)] attribute", ) })?; let variants = match &cont.data { Data::Enum(variants) => variants, _ => return Err(syn::Error::new(Span::call_site(), "oh no!")), }; if let Some(non_unit_error) = variants.iter().find_map(|v| match v.style { Style::Unit => None, _ => Some(syn::Error::new( v.original.span(), "JsonSchema_repr: must be a unit variant", )), }) { return Err(non_unit_error); }; let enum_ident = &cont.ident; let variant_idents = variants.iter().map(|v| &v.ident); let mut schema_expr = schema_object(quote! { instance_type: Some(schemars::schema::InstanceType::Integer.into()), enum_values: Some(vec![#((#enum_ident::#variant_idents as #repr_type).into()),*]), }); cont.attrs.as_metadata().apply_to_schema(&mut schema_expr); Ok(schema_expr) } fn expr_for_field(field: &Field, allow_ref: bool) -> TokenStream { let (ty, type_def) = type_for_field_schema(field); let span = field.original.span(); let generator = quote!(generator); let mut schema_expr = if field.validation_attrs.required() { quote_spanned! {span=> <#ty as schemars::JsonSchema>::_schemars_private_non_optional_json_schema(#generator) } } else if allow_ref { quote_spanned! {span=> #generator.subschema_for::<#ty>() } } else { quote_spanned! {span=> <#ty as schemars::JsonSchema>::json_schema(#generator) } }; prepend_type_def(type_def, &mut schema_expr); field.validation_attrs.apply_to_schema(&mut schema_expr); schema_expr } pub fn type_for_field_schema(field: &Field) -> (syn::Type, Option) { match &field.attrs.with { None => (field.ty.to_owned(), None), Some(with_attr) => type_for_schema(with_attr), } } fn type_for_schema(with_attr: &WithAttr) -> (syn::Type, Option) { match with_attr { WithAttr::Type(ty) => (ty.to_owned(), None), WithAttr::Function(fun) => { let ty_name = syn::Ident::new("_SchemarsSchemaWithFunction", Span::call_site()); let fn_name = fun.segments.last().unwrap().ident.to_string(); let type_def = quote_spanned! {fun.span()=> struct #ty_name; impl schemars::JsonSchema for #ty_name { fn is_referenceable() -> bool { false } fn schema_name() -> std::string::String { #fn_name.to_string() } fn schema_id() -> std::borrow::Cow<'static, str> { std::borrow::Cow::Borrowed(std::concat!( "_SchemarsSchemaWithFunction/", std::module_path!(), "/", #fn_name )) } fn json_schema(generator: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema { #fun(generator) } } }; (parse_quote!(#ty_name), Some(type_def)) } } } fn expr_for_enum(variants: &[Variant], cattrs: &serde_attr::Container) -> TokenStream { let deny_unknown_fields = cattrs.deny_unknown_fields(); let variants = variants .iter() .filter(|v| !v.serde_attrs.skip_deserializing()); match cattrs.tag() { TagType::External => expr_for_external_tagged_enum(variants, deny_unknown_fields), TagType::None => expr_for_untagged_enum(variants, deny_unknown_fields), TagType::Internal { tag } => { expr_for_internal_tagged_enum(variants, tag, deny_unknown_fields) } TagType::Adjacent { tag, content } => { expr_for_adjacent_tagged_enum(variants, tag, content, deny_unknown_fields) } } } fn expr_for_external_tagged_enum<'a>( variants: impl Iterator>, deny_unknown_fields: bool, ) -> TokenStream { let mut unique_names = HashSet::<&str>::new(); let mut count = 0; let (unit_variants, complex_variants): (Vec<_>, Vec<_>) = variants .inspect(|v| { unique_names.insert(v.name()); count += 1; }) .partition(|v| v.is_unit() && v.attrs.is_default()); let unit_names = unit_variants.iter().map(|v| v.name()); let unit_schema = schema_object(quote! { instance_type: Some(schemars::schema::InstanceType::String.into()), enum_values: Some(vec![#(#unit_names.into()),*]), }); if complex_variants.is_empty() { return unit_schema; } let mut schemas = Vec::new(); if !unit_variants.is_empty() { schemas.push(unit_schema); } schemas.extend(complex_variants.into_iter().map(|variant| { let name = variant.name(); let mut schema_expr = if variant.is_unit() && variant.attrs.with.is_none() { quote! { schemars::_private::new_unit_enum(#name) } } else { let sub_schema = expr_for_untagged_enum_variant(variant, deny_unknown_fields); quote! { schemars::_private::new_externally_tagged_enum(#name, #sub_schema) } }; variant .attrs .as_metadata() .apply_to_schema(&mut schema_expr); schema_expr })); variant_subschemas(unique_names.len() == count, schemas) } fn expr_for_internal_tagged_enum<'a>( variants: impl Iterator>, tag_name: &str, deny_unknown_fields: bool, ) -> TokenStream { let mut unique_names = HashSet::new(); let mut count = 0; let variant_schemas = variants .map(|variant| { unique_names.insert(variant.name()); count += 1; let name = variant.name(); let mut tag_schema = quote! { schemars::_private::new_internally_tagged_enum(#tag_name, #name, #deny_unknown_fields) }; variant.attrs.as_metadata().apply_to_schema(&mut tag_schema); if let Some(variant_schema) = expr_for_untagged_enum_variant_for_flatten(variant, deny_unknown_fields) { tag_schema.extend(quote!(.flatten(#variant_schema))) } tag_schema }) .collect(); variant_subschemas(unique_names.len() == count, variant_schemas) } fn expr_for_untagged_enum<'a>( variants: impl Iterator>, deny_unknown_fields: bool, ) -> TokenStream { let schemas = variants .map(|variant| { let mut schema_expr = expr_for_untagged_enum_variant(variant, deny_unknown_fields); variant .attrs .as_metadata() .apply_to_schema(&mut schema_expr); schema_expr }) .collect(); // Untagged enums can easily have variants whose schemas overlap; rather // that checking the exclusivity of each subschema we simply us `any_of`. variant_subschemas(false, schemas) } fn expr_for_adjacent_tagged_enum<'a>( variants: impl Iterator>, tag_name: &str, content_name: &str, deny_unknown_fields: bool, ) -> TokenStream { let mut unique_names = HashSet::new(); let mut count = 0; let schemas = variants .map(|variant| { unique_names.insert(variant.name()); count += 1; let content_schema = if variant.is_unit() && variant.attrs.with.is_none() { None } else { Some(expr_for_untagged_enum_variant(variant, deny_unknown_fields)) }; let (add_content_to_props, add_content_to_required) = content_schema .map(|content_schema| { ( quote!(props.insert(#content_name.to_owned(), #content_schema);), quote!(required.insert(#content_name.to_owned());), ) }) .unwrap_or_default(); let name = variant.name(); let tag_schema = schema_object(quote! { instance_type: Some(schemars::schema::InstanceType::String.into()), enum_values: Some(vec![#name.into()]), }); let set_additional_properties = if deny_unknown_fields { quote! { additional_properties: Some(Box::new(false.into())), } } else { TokenStream::new() }; let mut outer_schema = schema_object(quote! { instance_type: Some(schemars::schema::InstanceType::Object.into()), object: Some(Box::new(schemars::schema::ObjectValidation { properties: { let mut props = schemars::Map::new(); props.insert(#tag_name.to_owned(), #tag_schema); #add_content_to_props props }, required: { let mut required = schemars::Set::new(); required.insert(#tag_name.to_owned()); #add_content_to_required required }, // As we're creating a "wrapper" object, we can honor the // disposition of deny_unknown_fields. #set_additional_properties ..Default::default() })), }); variant .attrs .as_metadata() .apply_to_schema(&mut outer_schema); outer_schema }) .collect(); variant_subschemas(unique_names.len() == count, schemas) } /// Callers must determine if all subschemas are mutually exclusive. This can /// be done for most tagging regimes by checking that all tag names are unique. fn variant_subschemas(unique: bool, schemas: Vec) -> TokenStream { if unique { schema_object(quote! { subschemas: Some(Box::new(schemars::schema::SubschemaValidation { one_of: Some(vec![#(#schemas),*]), ..Default::default() })), }) } else { schema_object(quote! { subschemas: Some(Box::new(schemars::schema::SubschemaValidation { any_of: Some(vec![#(#schemas),*]), ..Default::default() })), }) } } fn expr_for_untagged_enum_variant(variant: &Variant, deny_unknown_fields: bool) -> TokenStream { if let Some(with_attr) = &variant.attrs.with { let (ty, type_def) = type_for_schema(with_attr); let generator = quote!(generator); let mut schema_expr = quote_spanned! {variant.original.span()=> #generator.subschema_for::<#ty>() }; prepend_type_def(type_def, &mut schema_expr); return schema_expr; } match variant.style { Style::Unit => expr_for_unit_struct(), Style::Newtype => expr_for_field(&variant.fields[0], true), Style::Tuple => expr_for_tuple_struct(&variant.fields), Style::Struct => expr_for_struct(&variant.fields, &SerdeDefault::None, deny_unknown_fields), } } fn expr_for_untagged_enum_variant_for_flatten( variant: &Variant, deny_unknown_fields: bool, ) -> Option { if let Some(with_attr) = &variant.attrs.with { let (ty, type_def) = type_for_schema(with_attr); let generator = quote!(generator); let mut schema_expr = quote_spanned! {variant.original.span()=> <#ty as schemars::JsonSchema>::json_schema(#generator) }; prepend_type_def(type_def, &mut schema_expr); return Some(schema_expr); } Some(match variant.style { Style::Unit => return None, Style::Newtype => expr_for_field(&variant.fields[0], false), Style::Tuple => expr_for_tuple_struct(&variant.fields), Style::Struct => expr_for_struct(&variant.fields, &SerdeDefault::None, deny_unknown_fields), }) } fn expr_for_unit_struct() -> TokenStream { quote! { generator.subschema_for::<()>() } } fn expr_for_newtype_struct(field: &Field) -> TokenStream { expr_for_field(field, true) } fn expr_for_tuple_struct(fields: &[Field]) -> TokenStream { let fields: Vec<_> = fields .iter() .filter(|f| !f.serde_attrs.skip_deserializing()) .map(|f| expr_for_field(f, true)) .collect(); let len = fields.len() as u32; quote! { schemars::schema::Schema::Object( schemars::schema::SchemaObject { instance_type: Some(schemars::schema::InstanceType::Array.into()), array: Some(Box::new(schemars::schema::ArrayValidation { items: Some(vec![#(#fields),*].into()), max_items: Some(#len), min_items: Some(#len), ..Default::default() })), ..Default::default() }) } } fn expr_for_struct( fields: &[Field], default: &SerdeDefault, deny_unknown_fields: bool, ) -> TokenStream { let (flattened_fields, property_fields): (Vec<_>, Vec<_>) = fields .iter() .filter(|f| !f.serde_attrs.skip_deserializing() || !f.serde_attrs.skip_serializing()) .partition(|f| f.serde_attrs.flatten()); let set_container_default = match default { SerdeDefault::None => None, SerdeDefault::Default => Some(quote!(let container_default = Self::default();)), SerdeDefault::Path(path) => Some(quote!(let container_default = #path();)), }; let properties: Vec<_> = property_fields .into_iter() .map(|field| { let name = field.name(); let default = field_default_expr(field, set_container_default.is_some()); let (ty, type_def) = type_for_field_schema(field); let has_default = default.is_some(); let required = field.validation_attrs.required(); let metadata = SchemaMetadata { read_only: field.serde_attrs.skip_deserializing(), write_only: field.serde_attrs.skip_serializing(), default, ..field.attrs.as_metadata() }; let generator = quote!(generator); let mut schema_expr = if field.validation_attrs.required() { quote_spanned! {ty.span()=> <#ty as schemars::JsonSchema>::_schemars_private_non_optional_json_schema(#generator) } } else { quote_spanned! {ty.span()=> #generator.subschema_for::<#ty>() } }; metadata.apply_to_schema(&mut schema_expr); field.validation_attrs.apply_to_schema(&mut schema_expr); quote! { { #type_def schemars::_private::insert_object_property::<#ty>(object_validation, #name, #has_default, #required, #schema_expr); } } }) .collect(); let flattens: Vec<_> = flattened_fields .into_iter() .map(|field| { let (ty, type_def) = type_for_field_schema(field); let required = field.validation_attrs.required(); let args = quote!(generator, #required); let mut schema_expr = quote_spanned! {ty.span()=> schemars::_private::json_schema_for_flatten::<#ty>(#args) }; prepend_type_def(type_def, &mut schema_expr); schema_expr }) .collect(); let set_additional_properties = if deny_unknown_fields { quote! { object_validation.additional_properties = Some(Box::new(false.into())); } } else { TokenStream::new() }; quote! { { #set_container_default let mut schema_object = schemars::schema::SchemaObject { instance_type: Some(schemars::schema::InstanceType::Object.into()), ..Default::default() }; let object_validation = schema_object.object(); #set_additional_properties #(#properties)* schemars::schema::Schema::Object(schema_object) #(.flatten(#flattens))* } } } fn field_default_expr(field: &Field, container_has_default: bool) -> Option { let field_default = field.serde_attrs.default(); if field.serde_attrs.skip_serializing() || (field_default.is_none() && !container_has_default) { return None; } let ty = field.ty; let default_expr = match field_default { SerdeDefault::None => { let member = &field.member; quote!(container_default.#member) } SerdeDefault::Default => quote!(<#ty>::default()), SerdeDefault::Path(path) => quote!(#path()), }; let default_expr = match field.serde_attrs.skip_serializing_if() { Some(skip_if) => { quote! { { let default = #default_expr; if #skip_if(&default) { None } else { Some(default) } } } } None => quote!(Some(#default_expr)), }; Some(if let Some(ser_with) = field.serde_attrs.serialize_with() { quote! { { struct _SchemarsDefaultSerialize(T); impl serde::Serialize for _SchemarsDefaultSerialize<#ty> { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer { #ser_with(&self.0, serializer) } } #default_expr.map(|d| _SchemarsDefaultSerialize(d)) } } } else { default_expr }) } fn schema_object(properties: TokenStream) -> TokenStream { quote! { schemars::schema::Schema::Object( schemars::schema::SchemaObject { #properties ..Default::default() }) } } fn prepend_type_def(type_def: Option, schema_expr: &mut TokenStream) { if let Some(type_def) = type_def { *schema_expr = quote! { { #type_def #schema_expr } } } }