1use serde::{Deserialize, Serialize};
7use std::fmt;
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
11#[serde(rename_all = "snake_case")]
12pub enum CapabilityLevel {
13 Yes,
15 Partial,
17 Experimental,
19 No,
21}
22
23impl CapabilityLevel {
24 pub fn is_usable(self) -> bool {
26 matches!(self, Self::Yes | Self::Partial | Self::Experimental)
27 }
28
29 pub fn glyph(self) -> &'static str {
31 match self {
32 Self::Yes => "YES",
33 Self::Partial => "PARTIAL",
34 Self::Experimental => "EXPERIMENTAL",
35 Self::No => "NO",
36 }
37 }
38}
39
40impl fmt::Display for CapabilityLevel {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 write!(f, "{}", self.glyph())
43 }
44}
45
46#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
48pub struct RuntimeCapabilities {
49 pub values: CapabilityLevel,
51 pub globals: CapabilityLevel,
53 pub locals: CapabilityLevel,
55 pub functions: CapabilityLevel,
57 pub closures: CapabilityLevel,
59 pub stack_capture: CapabilityLevel,
61 #[serde(default = "default_no")]
63 pub stack_frames: CapabilityLevel,
64 #[serde(default = "default_no")]
66 pub control_position: CapabilityLevel,
67 #[serde(default = "default_no")]
69 pub exception_state: CapabilityLevel,
70 #[serde(default = "default_no")]
72 pub continuation_capture: CapabilityLevel,
73 pub continuation_restore: CapabilityLevel,
75 #[serde(default = "default_no")]
77 pub cross_runtime_resume: CapabilityLevel,
78 pub async_migration: CapabilityLevel,
80 pub execution: CapabilityLevel,
82 pub stdio_capture: CapabilityLevel,
84 pub timeouts: CapabilityLevel,
86 pub resource_limits: CapabilityLevel,
88 pub cancellation: CapabilityLevel,
90}
91
92fn default_no() -> CapabilityLevel {
93 CapabilityLevel::No
94}
95
96impl RuntimeCapabilities {
97 pub fn none() -> Self {
99 Self {
100 values: CapabilityLevel::No,
101 globals: CapabilityLevel::No,
102 locals: CapabilityLevel::No,
103 functions: CapabilityLevel::No,
104 closures: CapabilityLevel::No,
105 stack_capture: CapabilityLevel::No,
106 stack_frames: CapabilityLevel::No,
107 control_position: CapabilityLevel::No,
108 exception_state: CapabilityLevel::No,
109 continuation_capture: CapabilityLevel::No,
110 continuation_restore: CapabilityLevel::No,
111 cross_runtime_resume: CapabilityLevel::No,
112 async_migration: CapabilityLevel::No,
113 execution: CapabilityLevel::No,
114 stdio_capture: CapabilityLevel::No,
115 timeouts: CapabilityLevel::No,
116 resource_limits: CapabilityLevel::No,
117 cancellation: CapabilityLevel::No,
118 }
119 }
120
121 pub fn python() -> Self {
123 Self {
124 values: CapabilityLevel::Yes,
125 globals: CapabilityLevel::Yes,
126 locals: CapabilityLevel::Partial,
127 functions: CapabilityLevel::Partial,
128 closures: CapabilityLevel::Partial,
129 stack_capture: CapabilityLevel::Experimental,
130 stack_frames: CapabilityLevel::Experimental,
131 control_position: CapabilityLevel::Experimental,
132 exception_state: CapabilityLevel::No,
133 continuation_capture: CapabilityLevel::Experimental,
134 continuation_restore: CapabilityLevel::Experimental,
135 cross_runtime_resume: CapabilityLevel::No,
136 async_migration: CapabilityLevel::No,
137 execution: CapabilityLevel::Yes,
138 stdio_capture: CapabilityLevel::Yes,
139 timeouts: CapabilityLevel::Yes,
140 resource_limits: CapabilityLevel::Partial,
141 cancellation: CapabilityLevel::Yes,
142 }
143 }
144
145 pub fn javascript() -> Self {
147 Self {
148 values: CapabilityLevel::Yes,
149 globals: CapabilityLevel::Yes,
150 locals: CapabilityLevel::Partial,
151 functions: CapabilityLevel::Partial,
152 closures: CapabilityLevel::Partial,
153 stack_capture: CapabilityLevel::No,
154 stack_frames: CapabilityLevel::Experimental,
155 control_position: CapabilityLevel::Experimental,
156 exception_state: CapabilityLevel::No,
157 continuation_capture: CapabilityLevel::Experimental,
158 continuation_restore: CapabilityLevel::Experimental,
159 cross_runtime_resume: CapabilityLevel::No,
160 async_migration: CapabilityLevel::No,
161 execution: CapabilityLevel::Yes,
162 stdio_capture: CapabilityLevel::Yes,
163 timeouts: CapabilityLevel::Yes,
164 resource_limits: CapabilityLevel::Partial,
165 cancellation: CapabilityLevel::Yes,
166 }
167 }
168
169 pub fn wasm() -> Self {
171 Self {
172 values: CapabilityLevel::Partial,
173 globals: CapabilityLevel::No,
174 locals: CapabilityLevel::No,
175 functions: CapabilityLevel::Partial,
176 closures: CapabilityLevel::No,
177 stack_capture: CapabilityLevel::No,
178 stack_frames: CapabilityLevel::No,
179 control_position: CapabilityLevel::No,
180 exception_state: CapabilityLevel::No,
181 continuation_capture: CapabilityLevel::No,
182 continuation_restore: CapabilityLevel::No,
183 cross_runtime_resume: CapabilityLevel::No,
184 async_migration: CapabilityLevel::No,
185 execution: CapabilityLevel::Yes,
186 stdio_capture: CapabilityLevel::No,
187 timeouts: CapabilityLevel::Yes,
188 resource_limits: CapabilityLevel::Yes,
189 cancellation: CapabilityLevel::Yes,
190 }
191 }
192
193 pub fn entries(&self) -> Vec<(&'static str, CapabilityLevel)> {
195 vec![
196 ("Values", self.values),
197 ("Globals", self.globals),
198 ("Locals", self.locals),
199 ("Functions", self.functions),
200 ("Closures", self.closures),
201 ("Stack capture", self.stack_capture),
202 ("Stack frames", self.stack_frames),
203 ("Control position", self.control_position),
204 ("Exception state", self.exception_state),
205 ("Continuation capture", self.continuation_capture),
206 ("Continuation restore", self.continuation_restore),
207 ("Cross-runtime resume", self.cross_runtime_resume),
208 ("Async migration", self.async_migration),
209 ("Execution", self.execution),
210 ("Stdio capture", self.stdio_capture),
211 ("Timeouts", self.timeouts),
212 ("Resource limits", self.resource_limits),
213 ("Cancellation", self.cancellation),
214 ]
215 }
216
217 pub fn continuation_entries(&self) -> Vec<(&'static str, CapabilityLevel)> {
219 vec![
220 ("Stack frames", self.stack_frames),
221 ("Control position", self.control_position),
222 ("Exception state", self.exception_state),
223 ("Closures", self.closures),
224 ("Async migration", self.async_migration),
225 ("Continuation capture", self.continuation_capture),
226 ("Continuation restore", self.continuation_restore),
227 ("Cross-runtime resume", self.cross_runtime_resume),
228 ]
229 }
230
231 pub fn level_of(&self, name: &str) -> Option<CapabilityLevel> {
233 match name {
234 "values" => Some(self.values),
235 "globals" => Some(self.globals),
236 "locals" => Some(self.locals),
237 "functions" => Some(self.functions),
238 "closures" => Some(self.closures),
239 "stack_capture" => Some(self.stack_capture),
240 "stack_frames" => Some(self.stack_frames),
241 "control_position" => Some(self.control_position),
242 "exception_state" => Some(self.exception_state),
243 "continuation_capture" => Some(self.continuation_capture),
244 "continuation_restore" => Some(self.continuation_restore),
245 "cross_runtime_resume" => Some(self.cross_runtime_resume),
246 "async_migration" => Some(self.async_migration),
247 "execution" => Some(self.execution),
248 "stdio_capture" => Some(self.stdio_capture),
249 "timeouts" => Some(self.timeouts),
250 "resource_limits" => Some(self.resource_limits),
251 "cancellation" => Some(self.cancellation),
252 _ => None,
253 }
254 }
255
256 pub fn require(&self, name: &str, runtime: &crate::runtime::RuntimeKind) -> crate::Result<()> {
258 let level = self.level_of(name).unwrap_or(CapabilityLevel::No);
259 if level.is_usable() {
260 return Ok(());
261 }
262 Err(crate::error::ParallaxError::new(
263 crate::error::ErrorCode::CapabilityViolation,
264 format!("capability '{name}' is unsupported on {runtime}"),
265 )
266 .with_source("parallax-core")
267 .with_operation("require_capability")
268 .with_runtime(runtime.clone())
269 .context("capability", name.to_string())
270 .context("level", level.glyph())
271 .remediate(crate::error::Remediation::new(
272 "Choose a runtime that declares this capability, or omit the unsupported operation",
273 )))
274 }
275}
276
277#[cfg(test)]
278mod tests {
279 use super::*;
280
281 #[test]
282 fn python_can_execute() {
283 assert!(RuntimeCapabilities::python().execution.is_usable());
284 assert!(RuntimeCapabilities::python()
285 .continuation_capture
286 .is_usable());
287 assert!(!RuntimeCapabilities::python()
288 .cross_runtime_resume
289 .is_usable());
290 }
291
292 #[test]
293 fn wasm_has_no_continuations() {
294 let w = RuntimeCapabilities::wasm();
295 assert!(!w.continuation_capture.is_usable());
296 assert!(!w.continuation_restore.is_usable());
297 }
298}