1use super::{
2 caught_error, map_entries, protocol_deref, protocol_deref_timeout, thrown_error, ExceptionInfo,
3 PromiseRejection, PromiseState, Value,
4};
5
6fn native_equal(left: &Value, right: &Value) -> bool {
7 left == right
8}
9use crate::lang::data::{Keyword, Map as PMap};
10use crate::lang::hash::{self as jh, JavaHash};
11use crate::lang::protocol::HashType;
12use std::cell::RefCell;
13use std::cmp::Ordering;
14use std::panic::{catch_unwind, AssertUnwindSafe};
15use std::rc::Rc;
16use std::time::Duration;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
19pub enum ResultStatus {
20 Success,
21 Error,
22}
23
24impl ResultStatus {
25 pub fn keyword(self) -> &'static str {
26 match self {
27 Self::Success => "success",
28 Self::Error => "error",
29 }
30 }
31}
32
33#[derive(Debug, Clone)]
34pub struct ResultValue {
35 pub status: ResultStatus,
36 pub data: Value,
37 pub error: Option<Rc<ExceptionInfo>>,
38 pub context: Value,
39}
40
41impl ResultValue {
42 pub fn success(data: Value, context: Value) -> Result<Self, String> {
43 Ok(Self {
44 status: ResultStatus::Success,
45 data,
46 error: None,
47 context: validate_context(context)?,
48 })
49 }
50
51 pub fn error(error: Value, context: Value) -> Result<Self, String> {
52 Ok(Self {
53 status: ResultStatus::Error,
54 data: Value::Nil,
55 error: Some(normalize_error(error)),
56 context: validate_context(context)?,
57 })
58 }
59
60 pub fn status_value(&self) -> Value {
61 Value::Keyword(Keyword::from(self.status.keyword()))
62 }
63
64 pub fn error_value(&self) -> Value {
65 self.error
66 .as_ref()
67 .map(|error| Value::ExceptionInfo(error.clone()))
68 .unwrap_or(Value::Nil)
69 }
70
71 pub fn is_success(&self) -> bool {
72 self.status == ResultStatus::Success
73 }
74
75 pub fn is_error(&self) -> bool {
76 self.status == ResultStatus::Error
77 }
78
79 pub fn is_timeout(&self) -> bool {
80 if !self.is_error() {
81 return false;
82 }
83 let Some(error) = &self.error else {
84 return false;
85 };
86 let code_key = Value::Keyword(Keyword::from("code"));
87 map_entries(error.data.as_ref()).is_some_and(|entries| {
88 entries.into_iter().any(|(key, value)| {
89 key == code_key
90 && matches!(
91 value,
92 Value::Keyword(code) if code.as_str() == "result/timeout"
93 )
94 })
95 })
96 }
97
98 pub fn with_context(&self, additional: Value) -> Result<Self, String> {
99 let additional = validate_context(additional)?;
100 let mut merged = PMap::new();
101 for (key, value) in map_entries(&self.context)
102 .expect("validated Result context")
103 .into_iter()
104 .chain(
105 map_entries(&additional)
106 .expect("validated additional Result context")
107 .into_iter(),
108 )
109 {
110 merged = merged.assoc_value(key, value);
111 }
112 let mut updated = self.clone();
113 updated.context = Value::Map(merged);
114 Ok(updated)
115 }
116
117 pub(crate) fn transport_context(&self) -> Value {
118 let display = Value::Keyword(Keyword::from("display"));
119 Value::Map(PMap::from_iter(
120 map_entries(&self.context)
121 .expect("validated Result context")
122 .into_iter()
123 .filter(|(key, _)| key != &display),
124 ))
125 }
126
127 pub(crate) fn deref_value(&self) -> Result<Value, String> {
128 match self.status {
129 ResultStatus::Success => Ok(self.data.clone()),
130 ResultStatus::Error => self
131 .error
132 .as_ref()
133 .map(|error| Err(thrown_error(Value::ExceptionInfo(error.clone()))))
134 .unwrap_or_else(|| Err("invalid Result/error without a native Error".into())),
135 }
136 }
137
138 pub fn display(&self) -> String {
139 format!(
140 "#hara/Result[{} {} {} {}]",
141 self.status_value().display(),
142 self.data.display(),
143 self.error_value().display(),
144 self.context.display()
145 )
146 }
147
148 pub fn compare(&self, other: &Self) -> Ordering {
149 self.status
150 .cmp(&other.status)
151 .then_with(|| self.data.cmp(&other.data))
152 .then_with(|| compare_error(self.error.as_deref(), other.error.as_deref()))
153 }
154
155 pub fn java_hash(&self, hash_type: HashType) -> i64 {
156 jh::compose_ordered(
157 "RESULT",
158 [
159 match self.status {
160 ResultStatus::Success => 1,
161 ResultStatus::Error => 2,
162 },
163 self.data.java_hash(hash_type),
164 self.error
165 .as_deref()
166 .map_or(0, |error| error_hash(error, hash_type)),
167 ],
168 )
169 }
170}
171
172const DEREF_UNSUPPORTED: &str = "IDeref/deref has no implementation for this value";
173const DEREF_TIMEOUT_UNSUPPORTED: &str =
174 "IDerefTimeout/deref-timeout expects a dereferenceable value, milliseconds, and timeout value";
175
176pub(super) fn synchronize_value(
177 value: Value,
178 timeout: Option<u64>,
179 context: Value,
180) -> Result<Value, String> {
181 let context = validate_context(context)?;
182 if let Value::Result(result) = value {
183 if map_entries(&context)
184 .expect("validated Result context")
185 .is_empty()
186 {
187 return Ok(Value::Result(result));
188 }
189 return Ok(Value::Result(Rc::new(result.with_context(context)?)));
190 }
191
192 let result = match value {
193 Value::Promise(promise) => synchronize_promise(promise, timeout, context)?,
194 value => match timeout {
195 Some(milliseconds) => synchronize_timed(value, milliseconds, context)?,
196 None => synchronize_untimed(value, context)?,
197 },
198 };
199 Ok(Value::Result(Rc::new(result)))
200}
201
202fn synchronize_untimed(value: Value, context: Value) -> Result<ResultValue, String> {
203 match protocol_deref(std::slice::from_ref(&value)) {
204 Ok(data) => ResultValue::success(data, context),
205 Err(error) if error == DEREF_UNSUPPORTED => ResultValue::success(value, context),
206 Err(error) => ResultValue::error(caught_error(&error), context),
207 }
208}
209
210fn synchronize_timed(
211 value: Value,
212 milliseconds: u64,
213 context: Value,
214) -> Result<ResultValue, String> {
215 let marker = Value::Array(Rc::new(RefCell::new(Vec::new())));
216 let milliseconds_value = Value::Number(i64::try_from(milliseconds).unwrap_or(i64::MAX));
217 match protocol_deref_timeout(&[value.clone(), milliseconds_value, marker.clone()]) {
218 Ok(resolved) if same_marker(&resolved, &marker) => {
219 timeout_result(milliseconds, context, None)
220 }
221 Ok(data) => ResultValue::success(data, context),
222 Err(error) if error == DEREF_TIMEOUT_UNSUPPORTED => {
223 if matches!(value, Value::Pointer(_)) {
224 timeout_unsupported_result(milliseconds, context)
225 } else {
226 ResultValue::success(value, context)
227 }
228 }
229 Err(error) => ResultValue::error(caught_error(&error), context),
230 }
231}
232
233fn synchronize_promise(
234 promise: super::Promise,
235 timeout: Option<u64>,
236 context: Value,
237) -> Result<ResultValue, String> {
238 let state = match timeout {
239 Some(milliseconds) => promise.wait_state_timeout(Duration::from_millis(milliseconds)),
240 None => promise.wait_state(),
241 };
242 match state {
243 PromiseState::Fulfilled(data) => ResultValue::success(data, context),
244 PromiseState::Rejected(error) => {
245 ResultValue::error(promise_rejection_value(error), context)
246 }
247 PromiseState::Pending => timeout_result(
248 timeout.expect("only timed Promise synchronization can remain pending"),
249 context,
250 Some(promise),
251 ),
252 }
253}
254
255fn promise_rejection_value(error: PromiseRejection) -> Value {
256 error.value()
257}
258
259fn timeout_result(
260 milliseconds: u64,
261 context: Value,
262 promise: Option<super::Promise>,
263) -> Result<ResultValue, String> {
264 let mut details = vec![
265 (
266 Value::Keyword(Keyword::from("result/timeout")),
267 Value::Number(i64::try_from(milliseconds).unwrap_or(i64::MAX)),
268 ),
269 (
270 Value::Keyword(Keyword::from("result/cancellation-requested")),
271 Value::Bool(promise.is_some()),
272 ),
273 ];
274
275 if let Some(promise) = promise {
276 match catch_unwind(AssertUnwindSafe(|| promise.cancel())) {
277 Ok(cancelled) => details.push((
278 Value::Keyword(Keyword::from("result/cancelled")),
279 Value::Bool(cancelled),
280 )),
281 Err(payload) => {
282 details.push((
283 Value::Keyword(Keyword::from("result/cancelled")),
284 Value::Bool(false),
285 ));
286 details.push((
287 Value::Keyword(Keyword::from("result/cancellation-error")),
288 Value::String(panic_message(payload)),
289 ));
290 }
291 }
292 }
293
294 ResultValue::error(
295 result_error(
296 "result/timeout",
297 "Result synchronization timed out",
298 milliseconds,
299 ),
300 context_with(context, details),
301 )
302}
303
304fn timeout_unsupported_result(milliseconds: u64, context: Value) -> Result<ResultValue, String> {
305 ResultValue::error(
306 result_error(
307 "result/timeout-unsupported",
308 "Timed synchronization is unsupported for this dereferenceable value",
309 milliseconds,
310 ),
311 context_with(
312 context,
313 [(
314 Value::Keyword(Keyword::from("result/timeout")),
315 Value::Number(i64::try_from(milliseconds).unwrap_or(i64::MAX)),
316 )],
317 ),
318 )
319}
320
321fn result_error(code: &str, message: &str, milliseconds: u64) -> Value {
322 Value::ExceptionInfo(Rc::new(ExceptionInfo {
323 message: message.into(),
324 data: Box::new(Value::Map(PMap::from_iter([
325 (
326 Value::Keyword(Keyword::from("code")),
327 Value::Keyword(Keyword::from(code)),
328 ),
329 (
330 Value::Keyword(Keyword::from("message")),
331 Value::String(message.into()),
332 ),
333 (
334 Value::Keyword(Keyword::from("timeout")),
335 Value::Number(i64::try_from(milliseconds).unwrap_or(i64::MAX)),
336 ),
337 ]))),
338 cause: None,
339 provenance: Rc::new(RefCell::new(Default::default())),
340 }))
341}
342
343fn context_with(context: Value, entries: impl IntoIterator<Item = (Value, Value)>) -> Value {
344 let mut merged = PMap::new();
345 for (key, value) in map_entries(&context).expect("validated Result context") {
346 merged = merged.assoc_value(key, value);
347 }
348 for (key, value) in entries {
349 merged = merged.assoc_value(key, value);
350 }
351 Value::Map(merged)
352}
353
354fn same_marker(left: &Value, right: &Value) -> bool {
355 matches!(
356 (left, right),
357 (Value::Array(left), Value::Array(right)) if Rc::ptr_eq(left, right)
358 )
359}
360
361fn panic_message(payload: Box<dyn std::any::Any + Send>) -> String {
362 payload
363 .downcast_ref::<&str>()
364 .map(|message| (*message).to_owned())
365 .or_else(|| payload.downcast_ref::<String>().cloned())
366 .unwrap_or_else(|| "Promise cancellation panicked".into())
367}
368
369impl PartialEq for ResultValue {
370 fn eq(&self, other: &Self) -> bool {
371 self.status == other.status
372 && native_equal(&self.data, &other.data)
373 && error_equal(self.error.as_deref(), other.error.as_deref())
374 }
375}
376
377impl Eq for ResultValue {}
378
379fn validate_context(context: Value) -> Result<Value, String> {
380 map_entries(&context)
381 .is_some()
382 .then_some(context)
383 .ok_or_else(|| "Result context must be a map".into())
384}
385
386fn normalize_error(value: Value) -> Rc<ExceptionInfo> {
387 match value {
388 Value::ExceptionInfo(error) => error,
389 value => {
390 let message = match &value {
391 Value::String(text) => text.clone(),
392 _ => value.display(),
393 };
394 Rc::new(ExceptionInfo {
395 message,
396 data: Box::new(Value::Map(PMap::from_iter([(
397 Value::Keyword(Keyword::from("error/value")),
398 value,
399 )]))),
400 cause: None,
401 provenance: Rc::new(RefCell::new(Default::default())),
402 })
403 }
404 }
405}
406
407fn error_equal(left: Option<&ExceptionInfo>, right: Option<&ExceptionInfo>) -> bool {
408 match (left, right) {
409 (None, None) => true,
410 (Some(left), Some(right)) => {
411 left.message == right.message
412 && native_equal(&left.data, &right.data)
413 && match (&left.cause, &right.cause) {
414 (None, None) => true,
415 (Some(left), Some(right)) => native_equal(left, right),
416 _ => false,
417 }
418 }
419 _ => false,
420 }
421}
422
423fn compare_error(left: Option<&ExceptionInfo>, right: Option<&ExceptionInfo>) -> Ordering {
424 match (left, right) {
425 (None, None) => Ordering::Equal,
426 (None, Some(_)) => Ordering::Less,
427 (Some(_), None) => Ordering::Greater,
428 (Some(left), Some(right)) => left
429 .message
430 .cmp(&right.message)
431 .then_with(|| left.data.cmp(&right.data))
432 .then_with(|| left.cause.cmp(&right.cause)),
433 }
434}
435
436fn error_hash(error: &ExceptionInfo, hash_type: HashType) -> i64 {
437 jh::compose_ordered(
438 "RESULT_ERROR",
439 [
440 jh::java_string_hash("hara/Error") as i64,
441 jh::java_string_hash(&error.message) as i64,
442 error.data.java_hash(hash_type),
443 error
444 .cause
445 .as_deref()
446 .map_or(0, |cause| cause.java_hash(hash_type)),
447 ],
448 )
449}
450
451#[cfg(test)]
452mod tests {
453 use super::*;
454
455 fn context(key: &str, value: Value) -> Value {
456 Value::Map(PMap::from_iter([(
457 Value::Keyword(Keyword::from(key)),
458 value,
459 )]))
460 }
461
462 #[test]
463 fn native_result_equality_and_hash_ignore_context() {
464 let left = ResultValue::success(
465 Value::Number(42),
466 context("source", Value::String("left".into())),
467 )
468 .expect("left Result");
469 let right = ResultValue::success(
470 Value::Number(42),
471 context("source", Value::String("right".into())),
472 )
473 .expect("right Result");
474 assert_eq!(left, right);
475 assert_eq!(
476 left.java_hash(crate::lang::hash::DEFAULT_HASH),
477 right.java_hash(crate::lang::hash::DEFAULT_HASH)
478 );
479 assert_eq!(
480 left.deref_value().expect("success deref"),
481 Value::Number(42)
482 );
483 }
484
485 #[test]
486 fn native_result_context_merge_uses_supplied_keys() {
487 let result = ResultValue::success(
488 Value::Number(7),
489 Value::Map(PMap::from_iter([
490 (
491 Value::Keyword(Keyword::from("source")),
492 Value::String("left".into()),
493 ),
494 (Value::Keyword(Keyword::from("kept")), Value::Bool(true)),
495 ])),
496 )
497 .expect("Result");
498 let updated = result
499 .with_context(Value::Map(PMap::from_iter([
500 (
501 Value::Keyword(Keyword::from("source")),
502 Value::String("right".into()),
503 ),
504 (Value::Keyword(Keyword::from("added")), Value::Number(1)),
505 ])))
506 .expect("merged Result");
507 let source =
508 super::super::map_value(&updated.context, &Value::Keyword(Keyword::from("source")))
509 .expect("source context");
510 assert!(matches!(source, Value::String(value) if value.as_str() == "right"));
511 assert_eq!(result, updated);
512 }
513
514 #[test]
515 fn synchronize_raw_existing_and_nested_results() {
516 let raw = synchronize_value(Value::Number(42), None, Value::Map(PMap::new()))
517 .expect("raw synchronization");
518 let Value::Result(raw) = raw else {
519 panic!("expected Result");
520 };
521 assert!(raw.is_success());
522 assert_eq!(raw.data, Value::Number(42));
523
524 let existing = Rc::new(
525 ResultValue::success(
526 Value::Number(7),
527 context("source", Value::String("left".into())),
528 )
529 .expect("existing Result"),
530 );
531 let synchronized = synchronize_value(
532 Value::Result(existing.clone()),
533 None,
534 context("source", Value::String("right".into())),
535 )
536 .expect("existing synchronization");
537 let Value::Result(synchronized) = synchronized else {
538 panic!("expected Result");
539 };
540 assert_eq!(synchronized.as_ref(), existing.as_ref());
541 let source = super::super::map_value(
542 &synchronized.context,
543 &Value::Keyword(Keyword::from("source")),
544 )
545 .expect("source context");
546 assert!(matches!(source, Value::String(value) if value == "right"));
547
548 let promise = super::super::Promise::new();
549 promise.resolve(Value::Result(existing.clone()));
550 let wrapped = synchronize_value(Value::Promise(promise), None, Value::Map(PMap::new()))
551 .expect("nested synchronization");
552 let Value::Result(wrapped) = wrapped else {
553 panic!("expected Result");
554 };
555 assert!(matches!(
556 &wrapped.data,
557 Value::Result(value) if Rc::ptr_eq(value, &existing)
558 ));
559 }
560
561 #[test]
562 fn synchronize_captures_rejection_timeout_and_cancellation_failure() {
563 let error = Rc::new(ExceptionInfo {
564 message: "rejected".into(),
565 data: Box::new(context("code", Value::Keyword(Keyword::from("rejected")))),
566 cause: None,
567 provenance: Rc::new(RefCell::new(Default::default())),
568 });
569 let rejected = super::super::Promise::new();
570 rejected.reject_value(Value::ExceptionInfo(error.clone()));
571 let captured = synchronize_value(Value::Promise(rejected), None, Value::Map(PMap::new()))
572 .expect("rejection synchronization");
573 let Value::Result(captured) = captured else {
574 panic!("expected Result");
575 };
576 assert!(captured.is_error());
577 assert!(!captured.is_timeout());
578 assert!(matches!(
579 captured.error_value(),
580 Value::ExceptionInfo(value) if Rc::ptr_eq(&value, &error)
581 ));
582
583 let timed = super::super::Promise::new();
584 let timeout = synchronize_value(
585 Value::Promise(timed.clone()),
586 Some(0),
587 Value::Map(PMap::new()),
588 )
589 .expect("timeout synchronization");
590 let Value::Result(timeout) = timeout else {
591 panic!("expected Result");
592 };
593 assert!(timeout.is_error());
594 assert!(timeout.is_timeout());
595 let Value::ExceptionInfo(timeout_error) = timeout.error_value() else {
596 panic!("expected timeout Error");
597 };
598 let code = super::super::map_value(
599 timeout_error.data.as_ref(),
600 &Value::Keyword(Keyword::from("code")),
601 )
602 .expect("timeout code");
603 assert_eq!(code, &Value::Keyword(Keyword::from("result/timeout")));
604 assert!(matches!(timed.state(), PromiseState::Rejected(_)));
605
606 let cancellation_failure = super::super::Promise::new();
607 cancellation_failure.set_cancel_hook(Rc::new(|| panic!("cannot cancel")));
608 let timeout = synchronize_value(
609 Value::Promise(cancellation_failure),
610 Some(0),
611 Value::Map(PMap::new()),
612 )
613 .expect("cancellation failure synchronization");
614 let Value::Result(timeout) = timeout else {
615 panic!("expected Result");
616 };
617 assert!(super::super::map_value(
618 &timeout.context,
619 &Value::Keyword(Keyword::from("result/cancellation-error")),
620 )
621 .is_some());
622 }
623
624 #[test]
625 fn native_result_error_preserves_native_error_and_deref_throws() {
626 let error = Rc::new(ExceptionInfo {
627 message: "boom".into(),
628 data: Box::new(context("code", Value::Keyword(Keyword::from("boom")))),
629 cause: None,
630 provenance: Rc::new(RefCell::new(Default::default())),
631 });
632 let result =
633 ResultValue::error(Value::ExceptionInfo(error.clone()), Value::Map(PMap::new()))
634 .expect("error Result");
635 assert!(result.is_error());
636 let preserved = match result.error_value() {
637 Value::ExceptionInfo(preserved) => preserved,
638 other => panic!("expected native Error, got {}", other.display()),
639 };
640 assert_eq!(preserved.message, error.message);
641 assert_eq!(preserved.data.display(), error.data.display());
642 assert!(result.deref_value().is_err());
643 assert!(result.display().contains("#hara/Result[:error"));
644 }
645
646 #[test]
647 fn native_result_string_errors_keep_unquoted_messages() {
648 let result = ResultValue::error(Value::String("boom".into()), Value::Map(PMap::new()))
649 .expect("error Result");
650 let Value::ExceptionInfo(error) = result.error_value() else {
651 panic!("expected native Error");
652 };
653 assert_eq!(error.message, "boom");
654 }
655}