1use crate::error::{Error, ErrorKind};
2use crate::number::{Sign, check_sign};
3use crate::{Meta, Validator};
4use tanzim_value::{Value, ValueType};
5
6#[derive(Debug, Clone, Default)]
13pub struct Float {
14 meta: Meta,
15 min: Option<f64>,
16 max: Option<f64>,
17 sign: Option<Sign>,
18}
19
20impl Float {
21 pub fn with_meta(mut self, meta: Meta) -> Self {
23 self.meta = meta;
24 self
25 }
26
27 pub fn new() -> Self {
28 Self::default()
29 }
30
31 pub fn min(mut self, min: f64) -> Self {
32 self.min = Some(min);
33 self
34 }
35
36 pub fn max(mut self, max: f64) -> Self {
37 self.max = Some(max);
38 self
39 }
40
41 pub fn range(mut self, start: f64, end: f64) -> Self {
42 self.min = Some(start);
43 self.max = Some(end);
44 self
45 }
46
47 pub fn positive(mut self) -> Self {
49 self.sign = Some(Sign::Positive);
50 self
51 }
52
53 pub fn non_negative(mut self) -> Self {
55 self.sign = Some(Sign::NonNegative);
56 self
57 }
58
59 pub fn negative(mut self) -> Self {
61 self.sign = Some(Sign::Negative);
62 self
63 }
64
65 pub fn non_positive(mut self) -> Self {
67 self.sign = Some(Sign::NonPositive);
68 self
69 }
70}
71
72crate::impl_meta_methods!(Float);
73
74impl Validator for Float {
75 fn meta(&self) -> &Meta {
76 &self.meta
77 }
78
79 fn meta_mut(&mut self) -> &mut Meta {
80 &mut self.meta
81 }
82
83 fn check(&self, value: &mut Value) -> Result<(), Error> {
84 let coerced = match value {
85 Value::Float(number) => *number,
86 Value::Int(number) => *number as f64,
87 Value::String(text) => match text.parse::<f64>() {
88 Ok(number) => number,
89 Err(_) => {
90 return Err(Error::new(ErrorKind::NotConvertible {
91 target: ValueType::Float,
92 found: ValueType::String,
93 }));
94 }
95 },
96 other => {
97 return Err(Error::new(ErrorKind::Type {
98 expected: ValueType::Float,
99 found: other.type_name(),
100 }));
101 }
102 };
103
104 if let Some(min) = self.min
105 && coerced < min
106 {
107 return Err(Error::new(ErrorKind::BelowMin {
108 value: coerced.to_string(),
109 min: min.to_string(),
110 }));
111 }
112 if let Some(max) = self.max
113 && coerced > max
114 {
115 return Err(Error::new(ErrorKind::AboveMax {
116 value: coerced.to_string(),
117 max: max.to_string(),
118 }));
119 }
120
121 check_sign(self.sign, coerced)?;
122
123 *value = Value::Float(coerced);
124 Ok(())
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn accepts_float() {
134 let mut value = Value::Float(1.5);
135 assert!(Float::new().validate(&mut value).is_ok());
136 }
137
138 #[test]
139 fn coerces_integer() {
140 let mut value = Value::Int(7);
141 Float::new().validate(&mut value).unwrap();
142 assert_eq!(value, Value::Float(7.0));
143 }
144
145 #[test]
146 fn coerces_string() {
147 let mut value = Value::String("1.5".into());
148 Float::new().validate(&mut value).unwrap();
149 assert_eq!(value, Value::Float(1.5));
150 }
151
152 #[test]
153 fn enforces_range() {
154 let mut value = Value::Float(-0.1);
155 let error = Float::new()
156 .range(0.0, 1.0)
157 .validate(&mut value)
158 .unwrap_err();
159 assert!(matches!(error.kind, ErrorKind::BelowMin { .. }));
160
161 let mut high = Value::Float(2.0);
162 let error = Float::new()
163 .range(0.0, 1.0)
164 .validate(&mut high)
165 .unwrap_err();
166 assert!(matches!(error.kind, ErrorKind::AboveMax { .. }));
167 }
168
169 #[test]
170 fn enforces_sign_constraints() {
171 let mut zero = Value::Float(0.0);
172 assert!(Float::new().positive().validate(&mut zero).is_err());
173 let mut negative = Value::Float(-1.0);
174 assert!(Float::new().non_negative().validate(&mut negative).is_err());
175 let mut positive = Value::Float(1.0);
176 assert!(Float::new().negative().validate(&mut positive).is_err());
177 }
178
179 #[test]
180 fn rejects_wrong_type_and_unparseable_string() {
181 let mut list = Value::List(Vec::new());
182 let error = Float::new().validate(&mut list).unwrap_err();
183 assert!(matches!(error.kind, ErrorKind::Type { .. }));
184
185 let mut text = Value::String("not-a-number".into());
186 let error = Float::new().validate(&mut text).unwrap_err();
187 assert!(matches!(error.kind, ErrorKind::NotConvertible { .. }));
188 }
189}