reifydb_type/value/constraint/
mod.rs1use serde::{Deserialize, Serialize};
5
6use crate::{
7 Error, OwnedFragment, Type, Value,
8 value::constraint::{bytes::MaxBytes, precision::Precision, scale::Scale},
9};
10
11pub mod bytes;
12pub mod precision;
13pub mod scale;
14
15#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
17pub struct TypeConstraint {
18 base_type: Type,
19 constraint: Option<Constraint>,
20}
21
22#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
24pub enum Constraint {
25 MaxBytes(MaxBytes),
27 PrecisionScale(Precision, Scale),
29}
30
31impl TypeConstraint {
32 pub fn unconstrained(ty: Type) -> Self {
34 Self {
35 base_type: ty,
36 constraint: None,
37 }
38 }
39
40 pub fn with_constraint(ty: Type, constraint: Constraint) -> Self {
42 Self {
43 base_type: ty,
44 constraint: Some(constraint),
45 }
46 }
47
48 pub fn get_type(&self) -> Type {
50 self.base_type
51 }
52
53 pub fn constraint(&self) -> &Option<Constraint> {
55 &self.constraint
56 }
57
58 pub fn validate(&self, value: &Value) -> Result<(), Error> {
60 let value_type = value.get_type();
62 if value_type != self.base_type && value_type != Type::Undefined {
63 return Err(crate::error!(crate::error::diagnostic::internal::internal(format!(
66 "Type mismatch: expected {}, got {}",
67 self.base_type, value_type
68 ))));
69 }
70
71 if matches!(value, Value::Undefined) {
73 return Ok(());
74 }
75
76 match (&self.base_type, &self.constraint) {
78 (Type::Utf8, Some(Constraint::MaxBytes(max))) => {
79 if let Value::Utf8(s) = value {
80 let byte_len = s.as_bytes().len();
81 let max_value: usize = (*max).into();
82 if byte_len > max_value {
83 return Err(crate::error!(
84 crate::error::diagnostic::constraint::utf8_exceeds_max_bytes(
85 OwnedFragment::None,
86 byte_len,
87 max_value
88 )
89 ));
90 }
91 }
92 }
93 (Type::Blob, Some(Constraint::MaxBytes(max))) => {
94 if let Value::Blob(blob) = value {
95 let byte_len = blob.len();
96 let max_value: usize = (*max).into();
97 if byte_len > max_value {
98 return Err(crate::error!(
99 crate::error::diagnostic::constraint::blob_exceeds_max_bytes(
100 OwnedFragment::None,
101 byte_len,
102 max_value
103 )
104 ));
105 }
106 }
107 }
108 (Type::Int, Some(Constraint::MaxBytes(max))) => {
109 if let Value::Int(vi) = value {
110 let str_len = vi.to_string().len();
116 let byte_len = (str_len * 415 / 1000) + 1; let max_value: usize = (*max).into();
118 if byte_len > max_value {
119 return Err(crate::error!(
120 crate::error::diagnostic::constraint::int_exceeds_max_bytes(
121 OwnedFragment::None,
122 byte_len,
123 max_value
124 )
125 ));
126 }
127 }
128 }
129 (Type::Uint, Some(Constraint::MaxBytes(max))) => {
130 if let Value::Uint(vu) = value {
131 let str_len = vu.to_string().len();
137 let byte_len = (str_len * 415 / 1000) + 1; let max_value: usize = (*max).into();
139 if byte_len > max_value {
140 return Err(crate::error!(
141 crate::error::diagnostic::constraint::uint_exceeds_max_bytes(
142 OwnedFragment::None,
143 byte_len,
144 max_value
145 )
146 ));
147 }
148 }
149 }
150 (Type::Decimal, Some(Constraint::PrecisionScale(precision, scale))) => {
151 if let Value::Decimal(decimal) = value {
152 let decimal_str = decimal.to_string();
155
156 let decimal_scale: u8 = if let Some(dot_pos) = decimal_str.find('.') {
159 let after_dot = &decimal_str[dot_pos + 1..];
160 after_dot.len().min(255) as u8
161 } else {
162 0
163 };
164
165 let decimal_precision: u8 =
168 decimal_str.chars().filter(|c| c.is_ascii_digit()).count().min(255)
169 as u8;
170
171 let scale_value: u8 = (*scale).into();
172 let precision_value: u8 = (*precision).into();
173
174 if decimal_scale > scale_value {
175 return Err(crate::error!(
176 crate::error::diagnostic::constraint::decimal_exceeds_scale(
177 OwnedFragment::None,
178 decimal_scale,
179 scale_value
180 )
181 ));
182 }
183 if decimal_precision > precision_value {
184 return Err(crate::error!(
185 crate::error::diagnostic::constraint::decimal_exceeds_precision(
186 OwnedFragment::None,
187 decimal_precision,
188 precision_value
189 )
190 ));
191 }
192 }
193 }
194 _ => {}
196 }
197
198 Ok(())
199 }
200
201 pub fn is_unconstrained(&self) -> bool {
203 self.constraint.is_none()
204 }
205
206 pub fn to_string(&self) -> String {
208 match &self.constraint {
209 None => format!("{}", self.base_type),
210 Some(Constraint::MaxBytes(max)) => {
211 format!("{}({})", self.base_type, max)
212 }
213 Some(Constraint::PrecisionScale(p, s)) => {
214 format!("{}({},{})", self.base_type, p, s)
215 }
216 }
217 }
218}
219
220#[cfg(test)]
221mod tests {
222 use super::*;
223
224 #[test]
225 fn test_unconstrained_type() {
226 let tc = TypeConstraint::unconstrained(Type::Utf8);
227 assert_eq!(tc.base_type, Type::Utf8);
228 assert_eq!(tc.constraint, None);
229 assert!(tc.is_unconstrained());
230 }
231
232 #[test]
233 fn test_constrained_utf8() {
234 let tc = TypeConstraint::with_constraint(Type::Utf8, Constraint::MaxBytes(MaxBytes::new(50)));
235 assert_eq!(tc.base_type, Type::Utf8);
236 assert_eq!(tc.constraint, Some(Constraint::MaxBytes(MaxBytes::new(50))));
237 assert!(!tc.is_unconstrained());
238 }
239
240 #[test]
241 fn test_constrained_decimal() {
242 let tc = TypeConstraint::with_constraint(
243 Type::Decimal,
244 Constraint::PrecisionScale(Precision::new(10), Scale::new(2)),
245 );
246 assert_eq!(tc.base_type, Type::Decimal);
247 assert_eq!(tc.constraint, Some(Constraint::PrecisionScale(Precision::new(10), Scale::new(2))));
248 }
249
250 #[test]
251 fn test_validate_utf8_within_limit() {
252 let tc = TypeConstraint::with_constraint(Type::Utf8, Constraint::MaxBytes(MaxBytes::new(10)));
253 let value = Value::Utf8("hello".to_string());
254 assert!(tc.validate(&value).is_ok());
255 }
256
257 #[test]
258 fn test_validate_utf8_exceeds_limit() {
259 let tc = TypeConstraint::with_constraint(Type::Utf8, Constraint::MaxBytes(MaxBytes::new(5)));
260 let value = Value::Utf8("hello world".to_string());
261 assert!(tc.validate(&value).is_err());
262 }
263
264 #[test]
265 fn test_validate_unconstrained() {
266 let tc = TypeConstraint::unconstrained(Type::Utf8);
267 let value = Value::Utf8("any length string is fine here".to_string());
268 assert!(tc.validate(&value).is_ok());
269 }
270
271 #[test]
272 fn test_validate_undefined() {
273 let tc = TypeConstraint::with_constraint(Type::Utf8, Constraint::MaxBytes(MaxBytes::new(5)));
274 let value = Value::Undefined;
275 assert!(tc.validate(&value).is_ok());
276 }
277
278 #[test]
279 fn test_to_string() {
280 let tc1 = TypeConstraint::unconstrained(Type::Utf8);
281 assert_eq!(tc1.to_string(), "Utf8");
282
283 let tc2 = TypeConstraint::with_constraint(Type::Utf8, Constraint::MaxBytes(MaxBytes::new(50)));
284 assert_eq!(tc2.to_string(), "Utf8(50)");
285
286 let tc3 = TypeConstraint::with_constraint(
287 Type::Decimal,
288 Constraint::PrecisionScale(Precision::new(10), Scale::new(2)),
289 );
290 assert_eq!(tc3.to_string(), "Decimal(10,2)");
291 }
292}