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 fn check_max_length(
14 name: &str,
15 limit: usize,
16 kind: &str,
17 source: Option<Source>,
18) -> Result<(), Error> {
19 if name.len() > limit {
20 return Err(Error::resource_limit_exceeded(
21 format!("max_{kind}_name_length"),
22 format!("{limit} characters"),
23 format!("{} characters", name.len()),
24 format!("Shorten the {kind} name to at most {limit} characters"),
25 source,
26 None,
27 None,
28 ));
29 }
30 Ok(())
31}
32
33#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
38pub struct ResourceLimits {
39 pub max_source_size_bytes: usize,
42
43 pub max_expression_depth: usize,
46
47 pub max_expression_count: usize,
50
51 pub max_data_value_bytes: usize,
55
56 pub max_loaded_bytes: usize,
58
59 pub max_sources: usize,
61
62 pub max_normalized_expression_nodes: usize,
67
68 pub max_spec_dependency_depth: usize,
72
73 pub max_dag_specs: usize,
76
77 pub max_normal_form_depth: usize,
84}
85
86impl Default for ResourceLimits {
87 fn default() -> Self {
88 Self {
89 max_source_size_bytes: 5 * 1024 * 1024, max_expression_depth: 7,
91 max_expression_count: 65_536,
92 max_data_value_bytes: 1024, max_loaded_bytes: 50 * 1024 * 1024, max_sources: 4096,
95 max_normalized_expression_nodes: 30_000,
96 max_spec_dependency_depth: 32,
97 max_dag_specs: 4096,
98 max_normal_form_depth: 4096,
100 }
101 }
102}
103
104impl ResourceLimits {
105 pub fn apply(&mut self, key: &str, value: usize) -> Result<(), String> {
107 match key {
108 "max_source_size_bytes" => self.max_source_size_bytes = value,
109 "max_expression_depth" => self.max_expression_depth = value,
110 "max_expression_count" => self.max_expression_count = value,
111 "max_data_value_bytes" => self.max_data_value_bytes = value,
112 "max_loaded_bytes" => self.max_loaded_bytes = value,
113 "max_sources" => self.max_sources = value,
114 "max_normalized_expression_nodes" => self.max_normalized_expression_nodes = value,
115 "max_spec_dependency_depth" => self.max_spec_dependency_depth = value,
116 "max_dag_specs" => self.max_dag_specs = value,
117 "max_normal_form_depth" => self.max_normal_form_depth = value,
118 other => return Err(format!("unknown limits key: '{other}'")),
119 }
120 Ok(())
121 }
122}
123
124#[cfg(any(test, target_arch = "wasm32"))]
128pub(crate) fn usize_limit_from_f64(key: &str, value: f64) -> Result<usize, String> {
129 if !value.is_finite() || value < 0.0 || value.fract() != 0.0 {
130 return Err(format!(
131 "limits value for '{key}' must be a non-negative integer"
132 ));
133 }
134 let as_u64 = value as u64;
135 if value >= 2f64.powi(53) || as_u64 as f64 != value {
136 return Err(format!(
137 "limits value for '{key}' must be a non-negative integer within f64 safe range"
138 ));
139 }
140 if as_u64 > usize::MAX as u64 {
141 return Err(format!(
142 "limits value for '{key}' exceeds platform usize maximum ({})",
143 usize::MAX
144 ));
145 }
146 Ok(as_u64 as usize)
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152
153 #[test]
154 fn apply_sets_known_key() {
155 let mut limits = ResourceLimits::default();
156 limits.apply("max_sources", 7).expect("known key");
157 assert_eq!(limits.max_sources, 7);
158 }
159
160 #[test]
161 fn apply_sets_max_normal_form_depth() {
162 let mut limits = ResourceLimits::default();
163 limits
164 .apply("max_normal_form_depth", 99)
165 .expect("known key");
166 assert_eq!(limits.max_normal_form_depth, 99);
167 }
168
169 #[test]
170 fn apply_rejects_unknown_key() {
171 let mut limits = ResourceLimits::default();
172 let err = limits.apply("not_a_limit", 1).expect_err("unknown");
173 assert!(err.contains("unknown limits key"));
174 }
175
176 #[test]
177 fn usize_limit_from_f64_accepts_integer() {
178 assert_eq!(usize_limit_from_f64("max_sources", 7.0).unwrap(), 7);
179 }
180
181 #[test]
182 fn usize_limit_from_f64_rejects_fraction() {
183 let err = usize_limit_from_f64("max_sources", 1.5).expect_err("fraction");
184 assert!(err.contains("non-negative integer"));
185 }
186
187 #[test]
188 fn usize_limit_from_f64_rejects_above_safe_integer() {
189 let err = usize_limit_from_f64("max_sources", 2f64.powi(53)).expect_err("unsafe");
190 assert!(err.contains("f64 safe range"));
191 }
192
193 #[test]
194 fn usize_limit_from_f64_rejects_above_usize_max() {
195 if (usize::MAX as u64) < (1u64 << 53) {
198 let too_big = (usize::MAX as u64).saturating_add(1) as f64;
199 let err = usize_limit_from_f64("max_loaded_bytes", too_big).expect_err("overflow");
200 assert!(err.contains("exceeds platform usize maximum"), "got: {err}");
201 }
202 }
203}