Skip to main content

vld/primitives/
boolean.rs

1use serde_json::Value;
2
3use crate::error::{value_type_name, IssueCode, VldError};
4use crate::schema::VldSchema;
5
6/// Schema for boolean validation. Created via [`vld::boolean()`](crate::boolean).
7#[derive(Clone)]
8pub struct ZBoolean {
9    coerce: bool,
10}
11
12impl ZBoolean {
13    pub fn new() -> Self {
14        Self { coerce: false }
15    }
16
17    /// Coerce truthy/falsy values to booleans.
18    ///
19    /// Accepted coercions:
20    /// - Strings: `"true"`, `"1"` → `true`; `"false"`, `"0"` → `false`
21    /// - Numbers: `0` → `false`; anything else → `true`
22    pub fn coerce(mut self) -> Self {
23        self.coerce = true;
24        self
25    }
26}
27
28impl Default for ZBoolean {
29    fn default() -> Self {
30        Self::new()
31    }
32}
33
34impl ZBoolean {
35    /// Generate a JSON Schema representation.
36    ///
37    /// Requires the `openapi` feature.
38    #[cfg(feature = "openapi")]
39    pub fn to_json_schema(&self) -> serde_json::Value {
40        serde_json::json!({"type": "boolean"})
41    }
42}
43
44impl VldSchema for ZBoolean {
45    type Output = bool;
46
47    fn parse_value(&self, value: &Value) -> Result<bool, VldError> {
48        if let Some(b) = value.as_bool() {
49            return Ok(b);
50        }
51
52        if self.coerce {
53            match value {
54                Value::String(s) => match s.as_str() {
55                    "true" | "1" => Ok(true),
56                    "false" | "0" => Ok(false),
57                    _ => Err(VldError::single_with_value(
58                        IssueCode::InvalidType {
59                            expected: "boolean".into(),
60                            received: "string".into(),
61                        },
62                        format!("Cannot coerce \"{}\" to boolean", s),
63                        value,
64                    )),
65                },
66                Value::Number(n) => Ok(n.as_f64() != Some(0.0)),
67                _ => Err(VldError::single_with_value(
68                    IssueCode::InvalidType {
69                        expected: "boolean".into(),
70                        received: value_type_name(value),
71                    },
72                    format!("Expected boolean, received {}", value_type_name(value)),
73                    value,
74                )),
75            }
76        } else {
77            Err(VldError::single_with_value(
78                IssueCode::InvalidType {
79                    expected: "boolean".into(),
80                    received: value_type_name(value),
81                },
82                format!("Expected boolean, received {}", value_type_name(value)),
83                value,
84            ))
85        }
86    }
87}