vld/primitives/number/
float.rs1use serde_json::Value;
2#[cfg(feature = "openapi")]
3use std::cmp::Ordering;
4
5use crate::error::{IssueCode, ValidationIssue, VldError, issue_error};
6use crate::schema::VldSchema;
7
8use super::{Kind, Num, NumberCheck, ZInt};
9
10#[derive(Clone)]
25pub struct ZNumber {
26 checks: Vec<NumberCheck>,
27 pub(super) coerce: bool,
28 custom_type_error: Option<String>,
29 messages: crate::primitives::Messages,
30}
31
32impl ZNumber {
33 pub fn new() -> Self {
35 Self {
36 checks: vec![],
37 coerce: false,
38 custom_type_error: None,
39 messages: None,
40 }
41 }
42
43 fn push(mut self, kind: Kind) -> Self {
44 self.checks.push(NumberCheck { kind, msg: None });
45 self
46 }
47
48 pub fn type_error(mut self, msg: impl Into<String>) -> Self {
58 self.custom_type_error = Some(msg.into());
59 self
60 }
61
62 pub fn with_messages<F>(mut self, f: F) -> Self
83 where
84 F: Fn(&str) -> Option<String> + Send + Sync + 'static,
85 {
86 self.messages = Some(std::sync::Arc::new(f));
87 self
88 }
89
90 pub(super) fn min_n(self, v: Num) -> Self {
91 self.push(Kind::Min(v))
92 }
93
94 pub(super) fn max_n(self, v: Num) -> Self {
95 self.push(Kind::Max(v))
96 }
97
98 pub(super) fn gt_n(self, v: Num) -> Self {
99 self.push(Kind::Gt(v))
100 }
101
102 pub(super) fn lt_n(self, v: Num) -> Self {
103 self.push(Kind::Lt(v))
104 }
105
106 pub(super) fn multiple_of_n(self, v: Num) -> Self {
107 self.push(Kind::MultipleOf(v))
108 }
109
110 pub fn min(self, val: f64) -> Self {
112 self.min_n(Num::F(val))
113 }
114
115 pub fn max(self, val: f64) -> Self {
117 self.max_n(Num::F(val))
118 }
119
120 pub fn gt(self, val: f64) -> Self {
122 self.gt_n(Num::F(val))
123 }
124
125 pub fn gte(self, val: f64) -> Self {
127 self.min(val)
128 }
129
130 pub fn lt(self, val: f64) -> Self {
132 self.lt_n(Num::F(val))
133 }
134
135 pub fn lte(self, val: f64) -> Self {
137 self.max(val)
138 }
139
140 pub fn positive(self) -> Self {
142 self.push(Kind::Positive)
143 }
144
145 pub fn negative(self) -> Self {
147 self.push(Kind::Negative)
148 }
149
150 pub fn non_negative(self) -> Self {
152 self.push(Kind::NonNegative)
153 }
154
155 pub fn non_positive(self) -> Self {
157 self.push(Kind::NonPositive)
158 }
159
160 pub fn multiple_of(self, val: f64) -> Self {
165 self.multiple_of_n(Num::F(val))
166 }
167
168 pub fn safe(self) -> Self {
170 self.push(Kind::Safe)
171 }
172
173 pub fn int(self) -> ZInt {
175 ZInt::from_number(self)
176 }
177
178 pub fn coerce(mut self) -> Self {
182 self.coerce = true;
183 self
184 }
185
186 fn type_err(&self, value: &Value) -> VldError {
187 crate::primitives::type_error(&self.custom_type_error, "number", value)
188 }
189
190 fn coerce_err(&self, value: &Value) -> VldError {
191 issue_error(
192 IssueCode::InvalidType {
193 expected: "number".to_string(),
194 received: "string".to_string(),
195 },
196 self.custom_type_error.as_ref(),
197 value,
198 )
199 }
200
201 pub(super) fn extract_number(&self, value: &Value) -> Result<f64, VldError> {
202 match value {
203 Value::Number(n) => n.as_f64().ok_or_else(|| self.type_err(value)),
204 Value::String(s) if self.coerce => s
205 .parse::<f64>()
206 .ok()
207 .filter(|f| f.is_finite())
208 .ok_or_else(|| self.coerce_err(value)),
209 Value::Bool(b) if self.coerce => Ok(if *b { 1.0 } else { 0.0 }),
210 _ => Err(self.type_err(value)),
211 }
212 }
213
214 pub(super) fn validate(&self, n: Num, value: &Value) -> Result<(), VldError> {
215 let mut errors = VldError::new();
216 for check in &self.checks {
217 if let Some(code) = check.kind.failure(n) {
218 let bulk = || self.messages.as_ref().and_then(|f| f(check.kind.key()));
219 let msg = check.msg.clone().or_else(bulk);
220 errors
221 .issues
222 .push(ValidationIssue::with_override(code, msg.as_ref()).with_received(value));
223 }
224 }
225 if errors.is_empty() {
226 Ok(())
227 } else {
228 Err(errors)
229 }
230 }
231}
232
233impl Default for ZNumber {
234 fn default() -> Self {
235 Self::new()
236 }
237}
238
239impl VldSchema for ZNumber {
240 type Output = f64;
241
242 fn parse_value(&self, value: &Value) -> Result<f64, VldError> {
243 let n = self.extract_number(value)?;
244 self.validate(Num::F(n), value)?;
245 Ok(n)
246 }
247}
248
249#[cfg(feature = "openapi")]
256impl crate::openapi::JsonSchema for ZNumber {
257 fn json_schema(&self) -> serde_json::Value {
258 self.schema("number", None)
259 }
260}
261
262impl ZNumber {
263 #[cfg(feature = "openapi")]
266 pub(super) fn schema(&self, ty: &str, range: Option<(Num, Num)>) -> Value {
267 let mut schema = serde_json::json!({ "type": ty });
268 let pick = |a: (Num, bool), b: (Num, bool), want: Ordering| match b.0.cmp(a.0) {
270 Some(o) if o == want => b,
271 Some(Ordering::Equal) if b.1 => b,
272 _ => a,
273 };
274 let lower = range
275 .map(|(lo, _)| (lo, false))
276 .into_iter()
277 .chain(self.checks.iter().filter_map(|c| c.kind.lower()))
278 .reduce(|a, b| pick(a, b, Ordering::Greater));
279 let upper = range
280 .map(|(_, hi)| (hi, false))
281 .into_iter()
282 .chain(self.checks.iter().filter_map(|c| c.kind.upper()))
283 .reduce(|a, b| pick(a, b, Ordering::Less));
284 if let Some((v, excl)) = lower {
285 let key = if excl { "exclusiveMinimum" } else { "minimum" };
286 schema[key] = v.json();
287 }
288 if let Some((v, excl)) = upper {
289 let key = if excl { "exclusiveMaximum" } else { "maximum" };
290 schema[key] = v.json();
291 }
292 let mut multiples = self.checks.iter().filter_map(|c| match c.kind {
294 Kind::MultipleOf(v) if v.as_f64() != 0.0 => Some(v.json()),
295 _ => None,
296 });
297 if let Some(m) = multiples.next() {
298 schema["multipleOf"] = m;
299 }
300 let rest: Vec<Value> = multiples
301 .map(|m| serde_json::json!({ "multipleOf": m }))
302 .collect();
303 if !rest.is_empty() {
304 schema["allOf"] = Value::Array(rest);
305 }
306 schema
307 }
308}
309
310#[cfg(feature = "openapi")]
311impl crate::openapi::CollectNestedSchemas for ZNumber {}