1use crate::capabilities::RuntimeCapabilities;
4use crate::ids::{ExecutionId, ObjectId};
5use crate::runtime::RuntimeKind;
6use crate::semantic::ConversionPolicy;
7use indexmap::IndexMap;
8use serde::{Deserialize, Serialize};
9use std::time::Duration;
10
11#[derive(Clone, Debug, Serialize, Deserialize)]
13pub struct ExecutionLimits {
14 #[serde(with = "duration_millis")]
16 pub timeout: Duration,
17 pub max_output_bytes: u64,
19 pub max_message_bytes: u64,
21 pub max_memory_bytes: Option<u64>,
23 pub max_fuel: Option<u64>,
25}
26
27impl Default for ExecutionLimits {
28 fn default() -> Self {
29 Self {
30 timeout: Duration::from_secs(30),
31 max_output_bytes: 1_048_576,
32 max_message_bytes: 16_777_216,
33 max_memory_bytes: Some(256 * 1024 * 1024),
34 max_fuel: Some(10_000_000),
35 }
36 }
37}
38
39mod duration_millis {
40 use serde::{Deserialize, Deserializer, Serializer};
41 use std::time::Duration;
42
43 pub fn serialize<S>(d: &Duration, serializer: S) -> Result<S::Ok, S::Error>
44 where
45 S: Serializer,
46 {
47 let ms = u64::try_from(d.as_millis()).unwrap_or(u64::MAX);
48 serializer.serialize_u64(ms)
49 }
50
51 pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
52 where
53 D: Deserializer<'de>,
54 {
55 let ms = u64::deserialize(deserializer)?;
56 Ok(Duration::from_millis(ms))
57 }
58}
59
60#[derive(Clone, Debug, Serialize, Deserialize)]
62#[serde(tag = "kind", rename_all = "snake_case")]
63pub enum ProgramSource {
64 File {
66 path: String,
68 },
69 Inline {
71 source: String,
73 filename: String,
75 },
76 Bytes {
78 #[serde(with = "serde_bytes_b64")]
80 data: Vec<u8>,
81 filename: String,
83 },
84 CaptureBindings {
86 source: String,
88 names: Vec<String>,
90 filename: String,
92 },
93}
94
95mod serde_bytes_b64 {
96 use serde::{Deserialize, Deserializer, Serializer};
97
98 pub fn serialize<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
99 where
100 S: Serializer,
101 {
102 use serde::ser::Error;
103 let encoded = base64_encode(bytes).map_err(S::Error::custom)?;
104 serializer.serialize_str(&encoded)
105 }
106
107 pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
108 where
109 D: Deserializer<'de>,
110 {
111 use serde::de::Error;
112 let s = String::deserialize(deserializer)?;
113 base64_decode(&s).map_err(D::Error::custom)
114 }
115
116 fn base64_encode(bytes: &[u8]) -> Result<String, String> {
117 const TABLE: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
119 let mut out = String::new();
120 for chunk in bytes.chunks(3) {
121 let b0 = chunk[0] as u32;
122 let b1 = chunk.get(1).copied().unwrap_or(0) as u32;
123 let b2 = chunk.get(2).copied().unwrap_or(0) as u32;
124 let n = (b0 << 16) | (b1 << 8) | b2;
125 out.push(TABLE[((n >> 18) & 63) as usize] as char);
126 out.push(TABLE[((n >> 12) & 63) as usize] as char);
127 out.push(if chunk.len() > 1 {
128 TABLE[((n >> 6) & 63) as usize] as char
129 } else {
130 '='
131 });
132 out.push(if chunk.len() > 2 {
133 TABLE[(n & 63) as usize] as char
134 } else {
135 '='
136 });
137 }
138 Ok(out)
139 }
140
141 fn base64_decode(input: &str) -> Result<Vec<u8>, String> {
142 fn val(c: u8) -> Result<u8, String> {
143 match c {
144 b'A'..=b'Z' => Ok(c - b'A'),
145 b'a'..=b'z' => Ok(c - b'a' + 26),
146 b'0'..=b'9' => Ok(c - b'0' + 52),
147 b'+' => Ok(62),
148 b'/' => Ok(63),
149 _ => Err(format!("invalid base64 byte: {}", c)),
150 }
151 }
152 let bytes = input.as_bytes();
153 if bytes.len() % 4 != 0 {
154 return Err("invalid base64 length".into());
155 }
156 let mut out = Vec::with_capacity(bytes.len() / 4 * 3);
157 for chunk in bytes.chunks(4) {
158 let n = ((val(chunk[0])? as u32) << 18)
159 | ((val(chunk[1])? as u32) << 12)
160 | ((if chunk[2] == b'=' {
161 0
162 } else {
163 val(chunk[2])? as u32
164 }) << 6)
165 | (if chunk[3] == b'=' {
166 0
167 } else {
168 val(chunk[3])? as u32
169 });
170 out.push(((n >> 16) & 0xff) as u8);
171 if chunk[2] != b'=' {
172 out.push(((n >> 8) & 0xff) as u8);
173 }
174 if chunk[3] != b'=' {
175 out.push((n & 0xff) as u8);
176 }
177 }
178 Ok(out)
179 }
180}
181
182#[derive(Clone, Debug, Serialize, Deserialize)]
184pub struct ExecutionRequest {
185 pub execution_id: ExecutionId,
187 pub runtime: RuntimeKind,
189 pub program: ProgramSource,
191 pub limits: ExecutionLimits,
193 pub entry: Option<String>,
195 pub args: Vec<serde_json::Value>,
197 pub capture_state: bool,
199 #[serde(default)]
201 pub continuum: bool,
202}
203
204impl ExecutionRequest {
205 pub fn file(runtime: RuntimeKind, path: impl Into<String>) -> Self {
207 Self {
208 execution_id: ExecutionId::new(),
209 runtime,
210 program: ProgramSource::File { path: path.into() },
211 limits: ExecutionLimits::default(),
212 entry: None,
213 args: Vec::new(),
214 capture_state: false,
215 continuum: false,
216 }
217 }
218}
219
220#[derive(Clone, Debug, Serialize, Deserialize)]
222pub struct ExecutionResult {
223 pub execution_id: ExecutionId,
225 pub runtime: RuntimeKind,
227 pub success: bool,
229 pub value: Option<serde_json::Value>,
231 pub stdout: String,
233 pub stderr: String,
235 pub duration_us: u64,
237 pub exception: Option<ExceptionInfo>,
239 pub state: Option<ExecutionState>,
241 #[serde(default)]
243 pub suspended: bool,
244 #[serde(default, skip_serializing_if = "Option::is_none")]
246 pub ues: Option<serde_json::Value>,
247 #[serde(default, skip_serializing_if = "Option::is_none")]
249 pub safepoint: Option<serde_json::Value>,
250 pub stats: IndexMap<String, serde_json::Value>,
252}
253
254#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
256pub struct ExceptionInfo {
257 pub type_name: String,
259 pub message: String,
261 pub stack: Option<String>,
263}
264
265#[derive(Clone, Debug, Serialize, Deserialize)]
269pub struct ExecutionState {
270 pub version: u32,
272 pub runtime: RuntimeKind,
274 pub capabilities: RuntimeCapabilities,
276 pub program: Option<String>,
278 pub instruction_position: Option<String>,
280 pub stack_frames: Vec<StackFrame>,
282 pub globals_root: Option<ObjectId>,
284 pub locals_root: Option<ObjectId>,
286 pub heap: serde_json::Value,
289 pub pending_exception: Option<ExceptionInfo>,
291 pub async_state: Option<serde_json::Value>,
293 pub granted_capabilities: Vec<String>,
295 pub runtime_metadata: IndexMap<String, serde_json::Value>,
297 pub migration_metadata: IndexMap<String, serde_json::Value>,
299 pub conversion_policy: ConversionPolicy,
301}
302
303impl ExecutionState {
304 pub fn empty(runtime: RuntimeKind, capabilities: RuntimeCapabilities) -> Self {
306 Self {
307 version: crate::PIR_SCHEMA_VERSION,
308 runtime,
309 capabilities,
310 program: None,
311 instruction_position: None,
312 stack_frames: Vec::new(),
313 globals_root: None,
314 locals_root: None,
315 heap: serde_json::json!({}),
316 pending_exception: None,
317 async_state: None,
318 granted_capabilities: Vec::new(),
319 runtime_metadata: IndexMap::new(),
320 migration_metadata: IndexMap::new(),
321 conversion_policy: ConversionPolicy::default(),
322 }
323 }
324}
325
326#[derive(Clone, Debug, Serialize, Deserialize)]
328pub struct StackFrame {
329 pub function: Option<String>,
331 pub location: Option<String>,
333 pub locals: Option<ObjectId>,
335}
336
337#[derive(Clone, Debug, Serialize, Deserialize)]
339pub struct RestoreResult {
340 pub success: bool,
342 pub runtime: RuntimeKind,
344 pub warnings: Vec<String>,
346 pub restored_bindings: IndexMap<String, String>,
348 pub duration_us: u64,
350}
351
352#[cfg(test)]
353mod tests {
354 use super::*;
355
356 #[test]
357 fn default_limits_are_bounded() {
358 let limits = ExecutionLimits::default();
359 assert!(limits.timeout.as_secs() > 0);
360 assert!(limits.max_output_bytes > 0);
361 }
362}