1use crate::error::Error;
2use crate::parsing::source::Source;
3
4pub const MAX_SPEC_NAME_LENGTH: usize = 128;
5pub const MAX_DATA_NAME_LENGTH: usize = 256;
6pub const MAX_RULE_NAME_LENGTH: usize = 256;
7
8pub const MAX_TEXT_VALUE_LENGTH: usize = 1024;
10
11pub const MAX_UNIT_EXPONENT: i32 = 64;
14
15pub const MAX_RATIONAL_BITS: u64 = 1 << 15;
19
20pub fn check_max_length(
23 name: &str,
24 limit: usize,
25 kind: &str,
26 source: Option<Source>,
27) -> Result<(), Error> {
28 if name.len() > limit {
29 return Err(Error::resource_limit_exceeded(
30 format!("max_{kind}_name_length"),
31 format!("{limit} characters"),
32 format!("{} characters", name.len()),
33 format!("Shorten the {kind} name to at most {limit} characters"),
34 source,
35 None,
36 None,
37 ));
38 }
39 Ok(())
40}
41
42#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
47pub struct ResourceLimits {
48 pub max_source_size_bytes: usize,
51
52 pub max_expression_depth: usize,
55
56 pub max_expression_count: usize,
59
60 pub max_data_value_bytes: usize,
65
66 pub max_loaded_bytes: usize,
68
69 pub max_sources: usize,
71
72 pub max_normalized_expression_nodes: usize,
77
78 pub max_spec_dependency_depth: usize,
82
83 pub max_dag_specs: usize,
86
87 pub max_list_rows: usize,
89 pub max_list_depth: usize,
91 pub max_list_nodes: usize,
93}
94
95impl Default for ResourceLimits {
96 fn default() -> Self {
97 Self {
98 max_source_size_bytes: 5 * 1024 * 1024, max_expression_depth: 7,
100 max_expression_count: 65_536,
101 max_data_value_bytes: 1024, max_loaded_bytes: 50 * 1024 * 1024, max_sources: 4096,
104 max_normalized_expression_nodes: 30_000,
105 max_spec_dependency_depth: 32,
106 max_dag_specs: 4096,
107 max_list_rows: 1024,
108 max_list_depth: 32,
109 max_list_nodes: 4096,
110 }
111 }
112}
113
114impl ResourceLimits {
115 pub fn apply(&mut self, key: &str, value: usize) -> Result<(), String> {
117 match key {
118 "max_source_size_bytes" => self.max_source_size_bytes = value,
119 "max_expression_depth" => self.max_expression_depth = value,
120 "max_expression_count" => self.max_expression_count = value,
121 "max_data_value_bytes" => self.max_data_value_bytes = value,
122 "max_loaded_bytes" => self.max_loaded_bytes = value,
123 "max_sources" => self.max_sources = value,
124 "max_normalized_expression_nodes" => self.max_normalized_expression_nodes = value,
125 "max_spec_dependency_depth" => self.max_spec_dependency_depth = value,
126 "max_dag_specs" => self.max_dag_specs = value,
127 "max_list_rows" => self.max_list_rows = value,
128 "max_list_depth" => self.max_list_depth = value,
129 "max_list_nodes" => self.max_list_nodes = value,
130 other => return Err(format!("unknown limits key: '{other}'")),
131 }
132 Ok(())
133 }
134}
135
136#[cfg(any(test, target_arch = "wasm32"))]
140pub(crate) fn usize_limit_from_f64(key: &str, value: f64) -> Result<usize, String> {
141 if !value.is_finite() || value < 0.0 || value.fract() != 0.0 {
142 return Err(format!(
143 "limits value for '{key}' must be a non-negative integer"
144 ));
145 }
146 let as_u64 = value as u64;
147 if value >= 2f64.powi(53) || as_u64 as f64 != value {
148 return Err(format!(
149 "limits value for '{key}' must be a non-negative integer within f64 safe range"
150 ));
151 }
152 if as_u64 > usize::MAX as u64 {
153 return Err(format!(
154 "limits value for '{key}' exceeds platform usize maximum ({})",
155 usize::MAX
156 ));
157 }
158 Ok(as_u64 as usize)
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164
165 #[test]
166 fn apply_sets_known_key() {
167 let mut limits = ResourceLimits::default();
168 limits.apply("max_sources", 7).expect("known key");
169 assert_eq!(limits.max_sources, 7);
170 }
171
172 #[test]
173 fn apply_rejects_removed_max_normal_form_depth() {
174 let mut limits = ResourceLimits::default();
175 assert!(limits.apply("max_normal_form_depth", 99).is_err());
176 }
177
178 #[test]
179 fn apply_rejects_unknown_key() {
180 let mut limits = ResourceLimits::default();
181 let err = limits.apply("not_a_limit", 1).expect_err("unknown");
182 assert!(err.contains("unknown limits key"));
183 }
184
185 #[test]
186 fn usize_limit_from_f64_accepts_integer() {
187 assert_eq!(usize_limit_from_f64("max_sources", 7.0).unwrap(), 7);
188 }
189
190 #[test]
191 fn usize_limit_from_f64_rejects_fraction() {
192 let err = usize_limit_from_f64("max_sources", 1.5).expect_err("fraction");
193 assert!(err.contains("non-negative integer"));
194 }
195
196 #[test]
197 fn usize_limit_from_f64_rejects_above_safe_integer() {
198 let err = usize_limit_from_f64("max_sources", 2f64.powi(53)).expect_err("unsafe");
199 assert!(err.contains("f64 safe range"));
200 }
201
202 #[test]
203 fn usize_limit_from_f64_rejects_above_usize_max() {
204 if (usize::MAX as u64) < (1u64 << 53) {
207 let too_big = (usize::MAX as u64).saturating_add(1) as f64;
208 let err = usize_limit_from_f64("max_loaded_bytes", too_big).expect_err("overflow");
209 assert!(err.contains("exceeds platform usize maximum"), "got: {err}");
210 }
211 }
212}