Skip to main content

vld/primitives/
literal.rs

1use serde_json::Value;
2
3use crate::error::{IssueCode, VldError};
4use crate::schema::VldSchema;
5
6/// Trait for types that can be used as literal values.
7pub trait IntoLiteral: Clone + 'static {
8    type Output: Clone + PartialEq + std::fmt::Debug;
9    fn to_json_value(&self) -> Value;
10    fn extract(value: &Value) -> Option<Self::Output>;
11    fn display(&self) -> String;
12}
13
14impl IntoLiteral for &'static str {
15    type Output = String;
16    fn to_json_value(&self) -> Value {
17        Value::String(self.to_string())
18    }
19    fn extract(value: &Value) -> Option<String> {
20        value.as_str().map(|s| s.to_string())
21    }
22    fn display(&self) -> String {
23        format!("\"{}\"", self)
24    }
25}
26
27impl IntoLiteral for String {
28    type Output = String;
29    fn to_json_value(&self) -> Value {
30        Value::String(self.clone())
31    }
32    fn extract(value: &Value) -> Option<String> {
33        value.as_str().map(|s| s.to_string())
34    }
35    fn display(&self) -> String {
36        format!("\"{}\"", self)
37    }
38}
39
40impl IntoLiteral for i64 {
41    type Output = i64;
42    fn to_json_value(&self) -> Value {
43        Value::Number((*self).into())
44    }
45    fn extract(value: &Value) -> Option<i64> {
46        value.as_i64()
47    }
48    fn display(&self) -> String {
49        self.to_string()
50    }
51}
52
53impl IntoLiteral for f64 {
54    type Output = f64;
55    fn to_json_value(&self) -> Value {
56        serde_json::Number::from_f64(*self)
57            .map(Value::Number)
58            .unwrap_or(Value::Null)
59    }
60    fn extract(value: &Value) -> Option<f64> {
61        value.as_f64()
62    }
63    fn display(&self) -> String {
64        self.to_string()
65    }
66}
67
68impl IntoLiteral for bool {
69    type Output = bool;
70    fn to_json_value(&self) -> Value {
71        Value::Bool(*self)
72    }
73    fn extract(value: &Value) -> Option<bool> {
74        value.as_bool()
75    }
76    fn display(&self) -> String {
77        self.to_string()
78    }
79}
80
81/// Schema for exact value matching. Created via [`vld::literal()`](crate::literal).
82pub struct ZLiteral<T: IntoLiteral> {
83    expected_value: Value,
84    display: String,
85    _phantom: std::marker::PhantomData<T>,
86}
87
88impl<T: IntoLiteral> ZLiteral<T> {
89    pub fn new(expected: T) -> Self {
90        let expected_value = expected.to_json_value();
91        let display = expected.display();
92        Self {
93            expected_value,
94            display,
95            _phantom: std::marker::PhantomData,
96        }
97    }
98}
99
100impl<T: IntoLiteral> VldSchema for ZLiteral<T> {
101    type Output = T::Output;
102
103    fn parse_value(&self, value: &Value) -> Result<T::Output, VldError> {
104        if *value == self.expected_value {
105            T::extract(value).ok_or_else(|| {
106                VldError::single(
107                    IssueCode::Custom {
108                        code: "invalid_literal".to_string(),
109                    },
110                    format!("Expected literal {}", self.display),
111                )
112            })
113        } else {
114            Err(VldError::single_with_value(
115                IssueCode::Custom {
116                    code: "invalid_literal".to_string(),
117                },
118                format!("Expected literal {}, received {:?}", self.display, value),
119                value,
120            ))
121        }
122    }
123}