1use proc_macro2::TokenStream;
2use syn::Expr;
3
4use crate::idents::SCHEMA;
5
6use super::{
7 parse_meta::{
8 parse_contains, parse_length_or_range, parse_nested_meta, parse_pattern,
9 parse_schemars_regex, parse_validate_regex, require_path_only, LengthOrRange,
10 },
11 AttrCtxt, CustomMeta,
12};
13
14#[derive(Clone, Copy, PartialEq)]
15pub enum Format {
16 Email,
17 Uri,
18 Ip,
19 Ipv4,
20 Ipv6,
21}
22
23impl Format {
24 fn attr_str(self) -> &'static str {
25 match self {
26 Format::Email => "email",
27 Format::Uri => "url",
28 Format::Ip => "ip",
29 Format::Ipv4 => "ipv4",
30 Format::Ipv6 => "ipv6",
31 }
32 }
33
34 fn schema_str(self) -> &'static str {
35 match self {
36 Format::Email => "email",
37 Format::Uri => "uri",
38 Format::Ip => "ip",
39 Format::Ipv4 => "ipv4",
40 Format::Ipv6 => "ipv6",
41 }
42 }
43
44 fn from_attr_str(s: &str) -> Option<Self> {
45 Some(match s {
46 "email" => Format::Email,
47 "url" => Format::Uri,
48 "ip" => Format::Ip,
49 "ipv4" => Format::Ipv4,
50 "ipv6" => Format::Ipv6,
51 _ => return None,
52 })
53 }
54}
55
56#[derive(Default)]
57pub struct ValidationAttrs {
58 pub length: Option<LengthOrRange>,
59 pub range: Option<LengthOrRange>,
60 pub pattern: Option<Expr>,
61 pub regex: Option<Expr>,
62 pub contains: Option<Expr>,
63 pub required: bool,
64 pub format: Option<Format>,
65 pub inner: Option<Box<ValidationAttrs>>,
66}
67
68impl ValidationAttrs {
69 pub fn add_mutators(&self, mutators: &mut Vec<TokenStream>) {
70 self.add_mutators2(mutators, "e!(&mut #SCHEMA));
71 }
72
73 fn add_mutators2(&self, mutators: &mut Vec<TokenStream>, mut_ref_schema: &TokenStream) {
74 if let Some(length) = &self.length {
75 Self::add_length_or_range(length, mutators, "string", "Length", mut_ref_schema);
76 Self::add_length_or_range(length, mutators, "array", "Items", mut_ref_schema);
77 }
78
79 if let Some(range) = &self.range {
80 Self::add_length_or_range(range, mutators, "number", "imum", mut_ref_schema);
81 }
82
83 if let Some(regex) = self.regex.as_ref().or(self.pattern.as_ref()) {
84 mutators.push(quote! {
85 schemars::_private::insert_validation_property(#mut_ref_schema, "string", "pattern", (#regex).to_string());
86 });
87 }
88
89 if let Some(contains) = &self.contains {
90 mutators.push(quote! {
91 schemars::_private::must_contain(#mut_ref_schema, &#contains.to_string());
92 });
93 }
94
95 if let Some(format) = &self.format {
96 let f = format.schema_str();
97 mutators.push(quote! {
98 (#mut_ref_schema).insert("format".into(), #f.into());
99 });
100 }
101
102 if let Some(inner) = &self.inner {
103 let mut inner_mutators = Vec::new();
104 inner.add_mutators2(&mut inner_mutators, "e!(inner_schema));
105
106 if !inner_mutators.is_empty() {
107 mutators.push(quote! {
108 schemars::_private::apply_inner_validation(#mut_ref_schema, |inner_schema| { #(#inner_mutators)* });
109 });
110 }
111 }
112 }
113
114 fn add_length_or_range(
115 value: &LengthOrRange,
116 mutators: &mut Vec<TokenStream>,
117 required_format: &str,
118 key_suffix: &str,
119 mut_ref_schema: &TokenStream,
120 ) {
121 if let Some(min) = value.min.as_ref().or(value.equal.as_ref()) {
122 let key = format!("min{key_suffix}");
123 mutators.push(quote!{
124 schemars::_private::insert_validation_property(#mut_ref_schema, #required_format, #key, #min);
125 });
126 }
127
128 if let Some(max) = value.max.as_ref().or(value.equal.as_ref()) {
129 let key = format!("max{key_suffix}");
130 mutators.push(quote!{
131 schemars::_private::insert_validation_property(#mut_ref_schema, #required_format, #key, #max);
132 });
133 }
134 }
135
136 pub(super) fn populate(
137 &mut self,
138 schemars_cx: &mut AttrCtxt,
139 validate_cx: &mut AttrCtxt,
140 garde_cx: &mut AttrCtxt,
141 ) {
142 self.process_attr(schemars_cx);
143 self.process_attr(validate_cx);
144 self.process_attr(garde_cx);
145 }
146
147 fn process_attr(&mut self, cx: &mut AttrCtxt) {
148 cx.parse_meta(|m, n, c| self.process_meta(m, n, c));
149 }
150
151 fn process_meta(
152 &mut self,
153 meta: CustomMeta,
154 meta_name: &str,
155 cx: &AttrCtxt,
156 ) -> Result<(), CustomMeta> {
157 if let Some(format) = Format::from_attr_str(meta_name) {
158 self.handle_format(&meta, format, cx);
159 return Ok(());
160 }
161 match meta_name {
162 "length" => match self.length {
163 Some(_) => cx.duplicate_error(&meta),
164 None => self.length = parse_length_or_range(&meta, cx).ok(),
165 },
166
167 "range" => match self.range {
168 Some(_) => cx.duplicate_error(&meta),
169 None => self.range = parse_length_or_range(&meta, cx).ok(),
170 },
171
172 "required" => {
173 if self.required {
174 cx.duplicate_error(&meta);
175 } else if require_path_only(&meta, cx).is_ok() {
176 self.required = true;
177 }
178 }
179
180 "pattern" if cx.attr_type != "validate" => {
181 match (&self.pattern, &self.regex, &self.contains) {
182 (Some(_p), _, _) => cx.duplicate_error(&meta),
183 (_, Some(_r), _) => cx.mutual_exclusive_error(&meta, "regex"),
184 (_, _, Some(_c)) => cx.mutual_exclusive_error(&meta, "contains"),
185 (None, None, None) => self.pattern = parse_pattern(&meta, cx).ok(),
186 }
187 }
188 "regex" if cx.attr_type != "garde" => {
189 match (&self.pattern, &self.regex, &self.contains) {
190 (Some(_p), _, _) => cx.mutual_exclusive_error(&meta, "pattern"),
191 (_, Some(_r), _) => cx.duplicate_error(&meta),
192 (_, _, Some(_c)) => cx.mutual_exclusive_error(&meta, "contains"),
193 (None, None, None) => {
194 if cx.attr_type == "validate" {
195 self.regex = parse_validate_regex(&meta, cx).ok();
196 } else {
197 self.regex = parse_schemars_regex(&meta, cx).ok();
198 }
199 }
200 }
201 }
202 "contains" => match (&self.pattern, &self.regex, &self.contains) {
203 (Some(_p), _, _) => cx.mutual_exclusive_error(&meta, "pattern"),
204 (_, Some(_r), _) => cx.mutual_exclusive_error(&meta, "regex"),
205 (_, _, Some(_c)) => cx.duplicate_error(&meta),
206 (None, None, None) => self.contains = parse_contains(meta, cx).ok(),
207 },
208
209 "inner" if cx.attr_type != "validate" => {
210 if let Ok(nested_meta) = parse_nested_meta(&meta, cx) {
211 let inner = self
212 .inner
213 .get_or_insert_with(|| Box::new(ValidationAttrs::default()));
214 let mut inner_cx = cx.new_nested_meta(nested_meta.into_iter().collect());
215 inner.process_attr(&mut inner_cx);
216 }
217 }
218
219 _ => return Err(meta),
220 }
221
222 Ok(())
223 }
224
225 fn handle_format(&mut self, meta: &CustomMeta, format: Format, cx: &AttrCtxt) {
226 match self.format {
227 Some(current) if current == format => cx.duplicate_error(meta),
228 Some(current) => cx.mutual_exclusive_error(meta, current.attr_str()),
229 None => {
230 if cx.attr_type == "validate" || require_path_only(meta, cx).is_ok() {
233 self.format = Some(format);
234 }
235 }
236 }
237 }
238
239 pub(crate) fn is_default(&self) -> bool {
240 matches!(
241 self,
242 Self {
243 contains: None,
244 format: None,
245 length: None,
246 range: None,
247 pattern: None,
248 regex: None,
249 required: false,
250 inner: None,
251 }
252 )
253 }
254}