qualia_core_db/sparql_library/immersive/
value.rs1#[repr(u8)]
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
20pub enum ImmersiveValueKind {
21 Boolean = 0,
23 Scalar = 1,
25 Quantity = 2,
27 AssetRef = 3,
29 GeometryRef = 4,
31 TensorRef = 5,
33 Instant = 6,
35 Interval = 7,
37}
38
39#[repr(u8)]
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
46pub enum ExecutionClass {
47 HotZeroHeap = 0,
49 ColdBoundedSync = 1,
51 AsyncRequired = 2,
53}
54
55#[repr(u8)]
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
59pub enum ExactnessClass {
60 Exact = 0,
62 DeterministicApproximate = 1,
65 InteractiveApproximate = 2,
68}
69
70impl ExactnessClass {
71 pub const fn iri(&self) -> &'static str {
76 match self {
77 ExactnessClass::Exact => "https://webizen.org/immersive/0.1#Exact",
78 ExactnessClass::DeterministicApproximate => {
79 "https://webizen.org/immersive/0.1#DeterministicApproximate"
80 }
81 ExactnessClass::InteractiveApproximate => {
82 "https://webizen.org/immersive/0.1#InteractiveApproximate"
83 }
84 }
85 }
86}
87
88#[repr(C)]
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97pub struct ImmersiveFunctionDescriptor {
98 pub iri_hash: u64,
100 pub args: [ImmersiveValueKind; 4],
102 pub arg_count: u8,
104 pub result: ImmersiveValueKind,
106 pub execution: ExecutionClass,
108 pub deterministic: bool,
111 pub exactness: ExactnessClass,
113 pub max_input_bytes: u32,
115 pub max_output_bytes: u32,
117}
118
119impl ImmersiveFunctionDescriptor {
120 #[allow(clippy::too_many_arguments)]
123 pub const fn new(
124 iri_hash: u64,
125 args: [ImmersiveValueKind; 4],
126 arg_count: u8,
127 result: ImmersiveValueKind,
128 execution: ExecutionClass,
129 deterministic: bool,
130 exactness: ExactnessClass,
131 max_input_bytes: u32,
132 max_output_bytes: u32,
133 ) -> Self {
134 let arg_count = if arg_count > 4 { 4 } else { arg_count };
135 Self {
136 iri_hash,
137 args,
138 arg_count,
139 result,
140 execution,
141 deterministic,
142 exactness,
143 max_input_bytes,
144 max_output_bytes,
145 }
146 }
147
148 pub fn arg_kinds(&self) -> &[ImmersiveValueKind] {
150 &self.args[..(self.arg_count as usize)]
151 }
152
153 pub const fn is_async(&self) -> bool {
155 matches!(self.execution, ExecutionClass::AsyncRequired)
156 }
157
158 pub const fn legal_in_filter(&self) -> bool {
163 self.deterministic && !self.is_async()
164 }
165
166 pub const fn legal_in_bind(&self) -> bool {
169 self.legal_in_filter()
170 }
171}
172
173#[repr(u8)]
178#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
179pub enum QispError {
180 UnknownAsset = 0,
182 StaleAsset = 1,
184 UnsupportedCrsOrProfile = 2,
186 InvalidGeometry = 3,
188 ProfileMismatch = 4,
190 BudgetExceeded = 5,
192 ExactnessUnavailable = 6,
194 AuthorizationDenied = 7,
196 CancelledOrExpired = 8,
198 NonDeterministicDisallowed = 9,
200}
201
202impl QispError {
203 pub const fn code(&self) -> &'static str {
207 match self {
208 QispError::UnknownAsset => "unknown-asset",
209 QispError::StaleAsset => "stale-asset",
210 QispError::UnsupportedCrsOrProfile => "unsupported-crs-or-profile",
211 QispError::InvalidGeometry => "invalid-geometry",
212 QispError::ProfileMismatch => "profile-mismatch",
213 QispError::BudgetExceeded => "budget-exceeded",
214 QispError::ExactnessUnavailable => "exactness-unavailable",
215 QispError::AuthorizationDenied => "authorization-denied",
216 QispError::CancelledOrExpired => "cancelled-or-expired",
217 QispError::NonDeterministicDisallowed => "non-deterministic-disallowed",
218 }
219 }
220
221 pub const fn message(&self) -> &'static str {
224 match self {
225 QispError::UnknownAsset => "unknown or invalid dense asset reference",
226 QispError::StaleAsset => "stale dense asset reference (generation mismatch)",
227 QispError::UnsupportedCrsOrProfile => "unsupported CRS or profile conversion",
228 QispError::InvalidGeometry => "invalid or non-manifold geometry",
229 QispError::ProfileMismatch => "dimension or profile mismatch",
230 QispError::BudgetExceeded => "output or workspace budget exceeded",
231 QispError::ExactnessUnavailable => "requested exactness profile is unavailable",
232 QispError::AuthorizationDenied => "authorization denied",
233 QispError::CancelledOrExpired => "computation cancelled or expired",
234 QispError::NonDeterministicDisallowed => "non-deterministic backend disallowed here",
235 }
236 }
237}
238
239impl core::fmt::Display for QispError {
240 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
241 write!(f, "qisp[{}]: {}", self.code(), self.message())
242 }
243}
244
245impl std::error::Error for QispError {}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250 use std::mem::size_of;
251
252 #[test]
253 fn descriptor_is_a_small_fixed_repr_c_record() {
254 assert_eq!(size_of::<ImmersiveFunctionDescriptor>(), 32);
257 assert_eq!(size_of::<ImmersiveValueKind>(), 1);
258 assert_eq!(size_of::<ExecutionClass>(), 1);
259 assert_eq!(size_of::<ExactnessClass>(), 1);
260 fn assert_copy<T: Copy>() {}
262 assert_copy::<ImmersiveFunctionDescriptor>();
263 }
264
265 #[test]
266 fn descriptor_round_trips_by_value() {
267 let d = ImmersiveFunctionDescriptor::new(
268 crate::q_hash("https://webizen.org/immersive/function/0.1#intersects"),
269 [
270 ImmersiveValueKind::AssetRef,
271 ImmersiveValueKind::AssetRef,
272 ImmersiveValueKind::Boolean,
273 ImmersiveValueKind::Boolean,
274 ],
275 2,
276 ImmersiveValueKind::Boolean,
277 ExecutionClass::HotZeroHeap,
278 true,
279 ExactnessClass::Exact,
280 1 << 20,
281 0,
282 );
283 let copy = d; assert_eq!(d, copy);
285 assert_eq!(d.arg_count, 2);
286 assert_eq!(
287 d.arg_kinds(),
288 &[ImmersiveValueKind::AssetRef, ImmersiveValueKind::AssetRef]
289 );
290 assert_eq!(d.result, ImmersiveValueKind::Boolean);
291 }
292
293 #[test]
294 fn arg_count_is_clamped() {
295 let d = ImmersiveFunctionDescriptor::new(
296 0,
297 [ImmersiveValueKind::Scalar; 4],
298 9,
299 ImmersiveValueKind::Scalar,
300 ExecutionClass::HotZeroHeap,
301 true,
302 ExactnessClass::Exact,
303 0,
304 0,
305 );
306 assert_eq!(d.arg_count, 4);
307 assert_eq!(d.arg_kinds().len(), 4);
308 }
309
310 #[test]
311 fn filter_bind_legality() {
312 let hot = ImmersiveFunctionDescriptor::new(
313 0,
314 [ImmersiveValueKind::AssetRef; 4],
315 1,
316 ImmersiveValueKind::Boolean,
317 ExecutionClass::HotZeroHeap,
318 true,
319 ExactnessClass::Exact,
320 0,
321 0,
322 );
323 assert!(hot.legal_in_filter());
324 assert!(hot.legal_in_bind());
325
326 let cold = ImmersiveFunctionDescriptor {
327 execution: ExecutionClass::ColdBoundedSync,
328 ..hot
329 };
330 assert!(cold.legal_in_filter());
331
332 let async_fn = ImmersiveFunctionDescriptor {
334 execution: ExecutionClass::AsyncRequired,
335 ..hot
336 };
337 assert!(!async_fn.legal_in_filter());
338 assert!(!async_fn.legal_in_bind());
339 assert!(async_fn.is_async());
340
341 let nondet = ImmersiveFunctionDescriptor {
343 deterministic: false,
344 ..hot
345 };
346 assert!(!nondet.legal_in_filter());
347 }
348
349 #[test]
350 fn exactness_iris_are_stable_and_namespaced() {
351 use super::super::QISP_NS;
352 assert!(ExactnessClass::Exact.iri().starts_with(QISP_NS));
353 assert!(ExactnessClass::DeterministicApproximate
354 .iri()
355 .starts_with(QISP_NS));
356 assert!(ExactnessClass::InteractiveApproximate
357 .iri()
358 .starts_with(QISP_NS));
359 assert_eq!(
360 ExactnessClass::Exact.iri(),
361 "https://webizen.org/immersive/0.1#Exact"
362 );
363 assert_eq!(
364 ExactnessClass::DeterministicApproximate.iri(),
365 "https://webizen.org/immersive/0.1#DeterministicApproximate"
366 );
367 assert_eq!(
368 ExactnessClass::InteractiveApproximate.iri(),
369 "https://webizen.org/immersive/0.1#InteractiveApproximate"
370 );
371 }
372
373 #[test]
374 fn error_codes_are_stable() {
375 assert_eq!(QispError::UnknownAsset.code(), "unknown-asset");
376 assert_eq!(QispError::StaleAsset.code(), "stale-asset");
377 assert_eq!(
378 QispError::UnsupportedCrsOrProfile.code(),
379 "unsupported-crs-or-profile"
380 );
381 assert_eq!(QispError::InvalidGeometry.code(), "invalid-geometry");
382 assert_eq!(QispError::ProfileMismatch.code(), "profile-mismatch");
383 assert_eq!(QispError::BudgetExceeded.code(), "budget-exceeded");
384 assert_eq!(
385 QispError::ExactnessUnavailable.code(),
386 "exactness-unavailable"
387 );
388 assert_eq!(
389 QispError::AuthorizationDenied.code(),
390 "authorization-denied"
391 );
392 assert_eq!(QispError::CancelledOrExpired.code(), "cancelled-or-expired");
393 assert_eq!(
394 QispError::NonDeterministicDisallowed.code(),
395 "non-deterministic-disallowed"
396 );
397 }
398
399 #[test]
400 fn error_display_and_std_error() {
401 let e = QispError::StaleAsset;
402 let s = format!("{e}");
403 assert!(s.contains("stale-asset"));
404 let _boxed: Box<dyn std::error::Error> = Box::new(e);
406 }
407}