vld/primitives/
literal.rs1use serde_json::Value;
2
3use crate::error::{IssueCode, VldError};
4use crate::schema::VldSchema;
5
6pub 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().or_else(|| {
48 value
49 .as_f64()
50 .filter(|f| f.fract() == 0.0 && *f >= i64::MIN as f64 && *f < i64::MAX as f64)
51 .map(|f| f as i64)
52 })
53 }
54 fn display(&self) -> String {
55 self.to_string()
56 }
57}
58
59macro_rules! int_literals {
62 ($($t:ty),+) => {$(
63 impl IntoLiteral for $t {
64 type Output = $t;
65 fn to_json_value(&self) -> Value {
66 Value::Number((*self).into())
67 }
68 fn extract(value: &Value) -> Option<$t> {
69 let exact = value
70 .as_i64()
71 .and_then(|i| <$t>::try_from(i).ok())
72 .or_else(|| value.as_u64().and_then(|u| <$t>::try_from(u).ok()));
73 exact.or_else(|| i64::extract(value).and_then(|i| <$t>::try_from(i).ok()))
74 }
75 fn display(&self) -> String {
76 self.to_string()
77 }
78 }
79 )+};
80}
81
82int_literals!(i8, i16, i32, u8, u16, u32, u64, isize, usize);
83
84impl IntoLiteral for f32 {
85 type Output = f32;
86 fn to_json_value(&self) -> Value {
87 self.to_string()
89 .parse::<f64>()
90 .ok()
91 .and_then(serde_json::Number::from_f64)
92 .map(Value::Number)
93 .unwrap_or(Value::Null)
94 }
95 fn extract(value: &Value) -> Option<f32> {
96 value.as_f64().map(|f| f as f32)
97 }
98 fn display(&self) -> String {
99 self.to_string()
100 }
101}
102
103impl IntoLiteral for f64 {
104 type Output = f64;
105 fn to_json_value(&self) -> Value {
106 serde_json::Number::from_f64(*self)
107 .map(Value::Number)
108 .unwrap_or(Value::Null)
109 }
110 fn extract(value: &Value) -> Option<f64> {
111 value.as_f64()
112 }
113 fn display(&self) -> String {
114 self.to_string()
115 }
116}
117
118impl IntoLiteral for bool {
119 type Output = bool;
120 fn to_json_value(&self) -> Value {
121 Value::Bool(*self)
122 }
123 fn extract(value: &Value) -> Option<bool> {
124 value.as_bool()
125 }
126 fn display(&self) -> String {
127 self.to_string()
128 }
129}
130
131pub struct ZLiteral<T: IntoLiteral> {
135 expected_value: Value,
136 _phantom: std::marker::PhantomData<T>,
137}
138
139impl<T: IntoLiteral> ZLiteral<T> {
140 pub fn new(expected: T) -> Self {
141 Self {
142 expected_value: expected.to_json_value(),
143 _phantom: std::marker::PhantomData,
144 }
145 }
146
147 pub fn expected_value(&self) -> &Value {
149 &self.expected_value
150 }
151}
152
153impl<T: IntoLiteral> VldSchema for ZLiteral<T> {
154 type Output = T::Output;
155
156 fn parse_value(&self, value: &Value) -> Result<T::Output, VldError> {
157 super::json_eq(value, &self.expected_value)
158 .then(|| T::extract(value))
159 .flatten()
160 .ok_or_else(|| {
161 VldError::from_code_with_value(
162 IssueCode::InvalidLiteral {
163 expected: self.expected_value.clone(),
164 },
165 value,
166 )
167 })
168 }
169}
170
171#[cfg(feature = "openapi")]
173impl<T: IntoLiteral> crate::openapi::JsonSchema for ZLiteral<T> {
174 fn json_schema(&self) -> Value {
175 serde_json::json!({ "const": self.expected_value })
176 }
177}
178
179#[cfg(feature = "openapi")]
180impl<T: IntoLiteral> crate::openapi::CollectNestedSchemas for ZLiteral<T> {}