1use serde::{Deserialize, Serialize};
4
5const RELAXED_MAX_STRING_LENGTH: usize = 104_857_600;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
10#[serde(rename_all = "lowercase")]
11pub enum Language {
12 Rhai,
14 Lua,
16 JavaScript,
18 Python,
20}
21
22impl Language {
23 pub fn as_str(&self) -> &'static str {
25 match self {
26 Language::Rhai => "rhai",
27 Language::Lua => "lua",
28 Language::JavaScript => "javascript",
29 Language::Python => "python",
30 }
31 }
32
33 pub fn parse(s: &str) -> Option<Self> {
35 match s.to_lowercase().as_str() {
36 "rhai" => Some(Language::Rhai),
37 "lua" => Some(Language::Lua),
38 "javascript" | "js" => Some(Language::JavaScript),
39 "python" | "py" => Some(Language::Python),
40 _ => None,
41 }
42 }
43
44 pub fn extension(&self) -> &'static str {
46 match self {
47 Language::Rhai => "rhai",
48 Language::Lua => "lua",
49 Language::JavaScript => "js",
50 Language::Python => "py",
51 }
52 }
53}
54
55impl std::fmt::Display for Language {
56 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 write!(f, "{}", self.as_str())
58 }
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct ExecutionRequest {
64 pub language: Language,
66
67 pub code: String,
69
70 #[serde(default)]
72 pub stdin: Option<String>,
73
74 #[serde(default = "default_timeout_ms")]
76 pub timeout_ms: u64,
77
78 #[serde(default = "default_memory_mb")]
80 pub memory_limit_mb: u32,
81
82 #[serde(default)]
84 pub context: Option<serde_json::Value>,
85
86 #[serde(default)]
88 pub limits: Option<ExecutionLimits>,
89}
90
91fn default_timeout_ms() -> u64 {
92 30_000
93}
94
95fn default_memory_mb() -> u32 {
96 256
97}
98
99impl Default for ExecutionRequest {
100 fn default() -> Self {
101 Self {
102 language: Language::Rhai,
103 code: String::new(),
104 stdin: None,
105 timeout_ms: default_timeout_ms(),
106 memory_limit_mb: default_memory_mb(),
107 context: None,
108 limits: None,
109 }
110 }
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct ExecutionResult {
116 pub success: bool,
118
119 pub stdout: String,
121
122 pub stderr: String,
124
125 #[serde(default)]
127 pub result: Option<serde_json::Value>,
128
129 #[serde(default)]
131 pub error: Option<String>,
132
133 pub timing_ms: u64,
135
136 #[serde(default)]
138 pub memory_used_bytes: Option<u64>,
139
140 #[serde(default)]
142 pub operations_count: Option<u64>,
143}
144
145impl ExecutionResult {
146 pub fn success(stdout: String, result: Option<serde_json::Value>, timing_ms: u64) -> Self {
148 Self {
149 success: true,
150 stdout,
151 stderr: String::new(),
152 result,
153 error: None,
154 timing_ms,
155 memory_used_bytes: None,
156 operations_count: None,
157 }
158 }
159
160 pub fn error(error: String, timing_ms: u64) -> Self {
162 Self {
163 success: false,
164 stdout: String::new(),
165 stderr: String::new(),
166 result: None,
167 error: Some(error),
168 timing_ms,
169 memory_used_bytes: None,
170 operations_count: None,
171 }
172 }
173
174 pub fn error_with_output(
176 error: String,
177 stdout: String,
178 stderr: String,
179 timing_ms: u64,
180 ) -> Self {
181 Self {
182 success: false,
183 stdout,
184 stderr,
185 result: None,
186 error: Some(error),
187 timing_ms,
188 memory_used_bytes: None,
189 operations_count: None,
190 }
191 }
192}
193
194impl Default for ExecutionResult {
195 fn default() -> Self {
196 Self::error("No execution performed".to_string(), 0)
197 }
198}
199
200#[derive(Debug, Clone, Serialize, Deserialize)]
202pub struct ExecutionLimits {
203 #[serde(default = "ExecutionLimits::default_timeout_ms")]
205 pub max_timeout_ms: u64,
206
207 #[serde(default = "ExecutionLimits::default_memory_mb")]
209 pub max_memory_mb: u32,
210
211 #[serde(default = "ExecutionLimits::default_output_bytes")]
213 pub max_output_bytes: usize,
214
215 #[serde(default = "ExecutionLimits::default_operations")]
217 pub max_operations: u64,
218
219 #[serde(default = "ExecutionLimits::default_call_depth")]
221 pub max_call_depth: u32,
222
223 #[serde(default = "ExecutionLimits::default_string_length")]
225 pub max_string_length: usize,
226
227 #[serde(default = "ExecutionLimits::default_array_length")]
229 pub max_array_length: usize,
230
231 #[serde(default = "ExecutionLimits::default_map_size")]
233 pub max_map_size: usize,
234}
235
236impl ExecutionLimits {
237 fn default_timeout_ms() -> u64 {
238 30_000
239 }
240 fn default_memory_mb() -> u32 {
241 256
242 }
243 fn default_output_bytes() -> usize {
244 1_048_576 }
246 fn default_operations() -> u64 {
247 1_000_000
248 }
249 fn default_call_depth() -> u32 {
250 64
251 }
252 fn default_string_length() -> usize {
253 10_485_760 }
255 fn default_array_length() -> usize {
256 100_000
257 }
258 fn default_map_size() -> usize {
259 10_000
260 }
261
262 pub fn strict() -> Self {
264 Self {
265 max_timeout_ms: 5_000,
266 max_memory_mb: 64,
267 max_output_bytes: 65_536, max_operations: 100_000,
269 max_call_depth: 32,
270 max_string_length: 1_048_576, max_array_length: 10_000,
272 max_map_size: 1_000,
273 }
274 }
275
276 pub fn relaxed() -> Self {
278 Self {
279 max_timeout_ms: 120_000, max_memory_mb: 512,
281 max_output_bytes: 10_485_760, max_operations: 10_000_000,
283 max_call_depth: 128,
284 max_string_length: RELAXED_MAX_STRING_LENGTH,
285 max_array_length: 1_000_000,
286 max_map_size: 100_000,
287 }
288 }
289}
290
291impl Default for ExecutionLimits {
292 fn default() -> Self {
293 Self {
294 max_timeout_ms: Self::default_timeout_ms(),
295 max_memory_mb: Self::default_memory_mb(),
296 max_output_bytes: Self::default_output_bytes(),
297 max_operations: Self::default_operations(),
298 max_call_depth: Self::default_call_depth(),
299 max_string_length: Self::default_string_length(),
300 max_array_length: Self::default_array_length(),
301 max_map_size: Self::default_map_size(),
302 }
303 }
304}
305
306#[derive(Debug, Clone, thiserror::Error, Serialize, Deserialize)]
308pub enum ExecutionError {
309 #[error("Language '{0}' is not supported or not enabled")]
311 UnsupportedLanguage(String),
312
313 #[error("Execution timed out after {0}ms")]
315 Timeout(u64),
316
317 #[error("Memory limit exceeded: {0}MB")]
319 MemoryLimitExceeded(u32),
320
321 #[error("Operation limit exceeded: {0} operations")]
323 OperationLimitExceeded(u64),
324
325 #[error("Output too large: {0} bytes")]
327 OutputTooLarge(usize),
328
329 #[error("Syntax error: {0}")]
331 SyntaxError(String),
332
333 #[error("Runtime error: {0}")]
335 RuntimeError(String),
336
337 #[error("Internal error: {0}")]
339 InternalError(String),
340}
341
342impl ExecutionError {
343 pub fn to_result(&self, timing_ms: u64) -> ExecutionResult {
345 ExecutionResult::error(self.to_string(), timing_ms)
346 }
347}
348
349#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
351#[derive(Default)]
352pub enum SandboxProfile {
353 Minimal,
355 #[default]
357 Standard,
358 Extended,
360}
361
362impl SandboxProfile {
363 pub fn allowed_modules(&self) -> Vec<&'static str> {
365 match self {
366 SandboxProfile::Minimal => vec!["math"],
367 SandboxProfile::Standard => vec!["math", "json", "string", "array", "print"],
368 SandboxProfile::Extended => vec![
369 "math", "json", "string", "array", "print", "datetime", "regex", "base64",
370 ],
371 }
372 }
373}
374
375
376#[cfg(test)]
377mod tests {
378 use super::*;
379
380 #[test]
381 fn test_language_parsing() {
382 assert_eq!(Language::parse("python"), Some(Language::Python));
383 assert_eq!(Language::parse("py"), Some(Language::Python));
384 assert_eq!(Language::parse("JAVASCRIPT"), Some(Language::JavaScript));
385 assert_eq!(Language::parse("js"), Some(Language::JavaScript));
386 assert_eq!(Language::parse("lua"), Some(Language::Lua));
387 assert_eq!(Language::parse("rhai"), Some(Language::Rhai));
388 assert_eq!(Language::parse("unknown"), None);
389 }
390
391 #[test]
392 fn test_execution_limits_profiles() {
393 let strict = ExecutionLimits::strict();
394 let relaxed = ExecutionLimits::relaxed();
395
396 assert!(strict.max_timeout_ms < relaxed.max_timeout_ms);
397 assert!(strict.max_memory_mb < relaxed.max_memory_mb);
398 assert!(strict.max_operations < relaxed.max_operations);
399 }
400
401 #[test]
402 fn test_execution_result_creation() {
403 let success = ExecutionResult::success(
404 "Hello".to_string(),
405 Some(serde_json::json!(42)),
406 100,
407 );
408 assert!(success.success);
409 assert_eq!(success.stdout, "Hello");
410
411 let error = ExecutionResult::error("Failed".to_string(), 50);
412 assert!(!error.success);
413 assert_eq!(error.error, Some("Failed".to_string()));
414 }
415}