Skip to main content

reifydb_value/error/
util.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::borrow::Cow;
5
6use crate::value::value_type::ValueType;
7
8pub fn value_max(value: ValueType) -> Cow<'static, str> {
9	match value {
10		ValueType::Decimal {
11			precision,
12			scale,
13		} => Cow::Owned(nines(precision.value(), scale.value())),
14		ValueType::Option(inner) => value_max(*inner),
15		other => Cow::Borrowed(fixed_max(other)),
16	}
17}
18
19pub fn value_range(value: ValueType) -> Cow<'static, str> {
20	match value {
21		ValueType::Decimal {
22			precision,
23			scale,
24		} => {
25			let max = nines(precision.value(), scale.value());
26			Cow::Owned(format!("-{max} to {max}"))
27		}
28		ValueType::Option(inner) => value_range(*inner),
29		other => Cow::Borrowed(fixed_range(other)),
30	}
31}
32
33fn nines(precision: u8, scale: u8) -> String {
34	let integer_digits = precision.saturating_sub(scale) as usize;
35	let integer = if integer_digits == 0 {
36		"0".to_string()
37	} else {
38		let digits = "9".repeat(integer_digits);
39		let mut grouped = String::with_capacity(digits.len() + digits.len() / 3);
40		for (index, digit) in digits.chars().enumerate() {
41			if index > 0 && (digits.len() - index).is_multiple_of(3) {
42				grouped.push('_');
43			}
44			grouped.push(digit);
45		}
46		grouped
47	};
48	if scale == 0 {
49		integer
50	} else {
51		format!("{integer}.{}", "9".repeat(scale as usize))
52	}
53}
54
55fn fixed_max(value: ValueType) -> &'static str {
56	match value {
57		ValueType::Boolean => unreachable!(),
58		ValueType::Float4 => "+3.4e38",
59		ValueType::Float8 => "+1.8e308",
60		ValueType::Int1 => "127",
61		ValueType::Int2 => "32_767",
62		ValueType::Int4 => "2_147_483_647",
63		ValueType::Int8 => "9_223_372_036_854_775_807",
64		ValueType::Int16 => "170_141_183_460_469_231_731_687_303_715_884_105_727",
65		ValueType::Utf8 => unreachable!(),
66		ValueType::Uint1 => "255",
67		ValueType::Uint2 => "65_535",
68		ValueType::Uint4 => "4_294_967_295",
69		ValueType::Uint8 => "18_446_744_073_709_551_615",
70		ValueType::Uint16 => "340_282_366_920_938_463_463_374_607_431_768_211_455",
71		ValueType::Date => unreachable!(),
72		ValueType::DateTime => unreachable!(),
73		ValueType::Time => unreachable!(),
74		ValueType::Duration => unreachable!(),
75		ValueType::IdentityId => unreachable!(),
76		ValueType::Uuid4 => unreachable!(),
77		ValueType::Uuid7 => unreachable!(),
78		ValueType::Blob => unreachable!(),
79		ValueType::Decimal {
80			..
81		} => unreachable!(),
82		ValueType::Option(_) => unreachable!(),
83		ValueType::Any => unreachable!(),
84		ValueType::DictionaryId => unreachable!(),
85		ValueType::List(_) => unreachable!(),
86		ValueType::Record(_) => unreachable!(),
87		ValueType::Tuple(_) => unreachable!(),
88		ValueType::Digest {
89			..
90		} => unreachable!(),
91	}
92}
93
94fn fixed_range(value: ValueType) -> &'static str {
95	match value {
96		ValueType::Boolean => unreachable!(),
97		ValueType::Float4 => "-3.4e38 to +3.4e38",
98		ValueType::Float8 => "-1.8e308 to +1.8e308",
99		ValueType::Int1 => "-128 to 127",
100		ValueType::Int2 => "-32_768 to 32_767",
101		ValueType::Int4 => "-2_147_483_648 to 2_147_483_647",
102		ValueType::Int8 => "-9_223_372_036_854_775_808 to 9_223_372_036_854_775_807",
103		ValueType::Int16 => {
104			"-170_141_183_460_469_231_731_687_303_715_884_105_728 to 170_141_183_460_469_231_731_687_303_715_884_105_727"
105		}
106		ValueType::Utf8 => unreachable!(),
107		ValueType::Uint1 => "0 to 255",
108		ValueType::Uint2 => "0 to 65_535",
109		ValueType::Uint4 => "0 to 4_294_967_295",
110		ValueType::Uint8 => "0 to 18_446_744_073_709_551_615",
111		ValueType::Uint16 => "0 to 340_282_366_920_938_463_463_374_607_431_768_211_455",
112		ValueType::Date => unreachable!(),
113		ValueType::DateTime => unreachable!(),
114		ValueType::Time => unreachable!(),
115		ValueType::Duration => unreachable!(),
116		ValueType::IdentityId => unreachable!(),
117		ValueType::Uuid4 => unreachable!(),
118		ValueType::Uuid7 => unreachable!(),
119		ValueType::Blob => unreachable!(),
120		ValueType::Decimal {
121			..
122		} => unreachable!(),
123		ValueType::Option(_) => unreachable!(),
124		ValueType::Any => unreachable!(),
125		ValueType::DictionaryId => unreachable!(),
126		ValueType::List(_) => unreachable!(),
127		ValueType::Record(_) => unreachable!(),
128		ValueType::Tuple(_) => unreachable!(),
129		ValueType::Digest {
130			..
131		} => unreachable!(),
132	}
133}
134
135#[cfg(test)]
136pub mod tests {
137
138	mod value_max {
139		use crate::{error::util::value_max, value::value_type::ValueType};
140
141		#[test]
142		fn test_signed_ints() {
143			assert_eq!(value_max(ValueType::Int1), "127");
144			assert_eq!(value_max(ValueType::Int2), "32_767");
145			assert_eq!(value_max(ValueType::Int4), "2_147_483_647");
146			assert_eq!(value_max(ValueType::Int8), "9_223_372_036_854_775_807");
147			assert_eq!(value_max(ValueType::Int16), "170_141_183_460_469_231_731_687_303_715_884_105_727");
148		}
149
150		#[test]
151		fn test_unsigned_ints() {
152			assert_eq!(value_max(ValueType::Uint1), "255");
153			assert_eq!(value_max(ValueType::Uint2), "65_535");
154			assert_eq!(value_max(ValueType::Uint4), "4_294_967_295");
155			assert_eq!(value_max(ValueType::Uint8), "18_446_744_073_709_551_615");
156			assert_eq!(value_max(ValueType::Uint16), "340_282_366_920_938_463_463_374_607_431_768_211_455");
157		}
158
159		#[test]
160		fn test_floats() {
161			assert_eq!(value_max(ValueType::Float4), "+3.4e38");
162			assert_eq!(value_max(ValueType::Float8), "+1.8e308");
163		}
164	}
165
166	mod value_range {
167		use crate::{error::util::value_range, value::value_type::ValueType};
168
169		#[test]
170		fn test_signed_ints() {
171			assert_eq!(value_range(ValueType::Int1), "-128 to 127");
172			assert_eq!(value_range(ValueType::Int2), "-32_768 to 32_767");
173			assert_eq!(value_range(ValueType::Int4), "-2_147_483_648 to 2_147_483_647");
174			assert_eq!(
175				value_range(ValueType::Int8),
176				"-9_223_372_036_854_775_808 to 9_223_372_036_854_775_807"
177			);
178			assert_eq!(
179				value_range(ValueType::Int16),
180				"-170_141_183_460_469_231_731_687_303_715_884_105_728 to 170_141_183_460_469_231_731_687_303_715_884_105_727"
181			);
182		}
183
184		#[test]
185		fn test_unsigned_ints() {
186			assert_eq!(value_range(ValueType::Uint1), "0 to 255");
187			assert_eq!(value_range(ValueType::Uint2), "0 to 65_535");
188			assert_eq!(value_range(ValueType::Uint4), "0 to 4_294_967_295");
189			assert_eq!(value_range(ValueType::Uint8), "0 to 18_446_744_073_709_551_615");
190			assert_eq!(
191				value_range(ValueType::Uint16),
192				"0 to 340_282_366_920_938_463_463_374_607_431_768_211_455"
193			);
194		}
195
196		#[test]
197		fn test_floats() {
198			assert_eq!(value_range(ValueType::Float4), "-3.4e38 to +3.4e38");
199			assert_eq!(value_range(ValueType::Float8), "-1.8e308 to +1.8e308");
200		}
201	}
202
203	mod parameterized {
204		use crate::{
205			error::util::{value_max, value_range},
206			value::{
207				constraint::{precision::Precision, scale::Scale},
208				value_type::ValueType,
209			},
210		};
211
212		#[test]
213		fn range_follows_the_declared_precision_and_scale() {
214			// The error text must name the bound the column enforces, not the widest one.
215			let decimal = ValueType::decimal(Precision::new(10), Scale::new(2));
216			assert_eq!(value_max(decimal.clone()), "99_999_999.99");
217			assert_eq!(value_range(decimal), "-99_999_999.99 to 99_999_999.99");
218			assert_eq!(value_max(ValueType::decimal(Precision::new(2), Scale::new(2))), "0.99");
219		}
220	}
221}