Skip to main content

neptune_job_queue/
job_result_wrapper.rs

1//! This module provides type `JobResultWrapper` to enhance the ergonomics of
2//! working with job-specific result types which must implement the `JobResult`
3//! trait.
4//!
5//! It is useful for:
6//!
7//! 1. returning job results of type T as `Box<dyn JobResult>` when implementing
8//!    the `Job` trait.
9//!
10//! 2. converting the `Box<dyn JobResult>` from a completed `Job` back into `T`.
11//!
12//! See [module docs](super) for usage examples.
13use std::any::Any;
14use std::fmt::Display;
15use std::ops::Deref;
16use std::ops::DerefMut;
17
18use super::errors::JobHandleError;
19use super::traits::JobResult;
20use super::JobCompletion;
21
22/// A generic wrapper around a job-specific result type `T` that implements the
23/// [`JobResult`] trait.
24///
25/// This wrapper simplifies the process of:
26///
27/// * Returning concrete job results (`T`) as trait objects (`Box<dyn JobResult>`).
28/// * Attempting to convert a `Box<dyn JobResult>` back into the original concrete type `T`.
29///
30/// # Type Parameters
31///
32/// * `T`: The specific type of the job result being wrapped. This type must be
33///   `'static`, `Send`, and `Sync`.
34///
35/// `JobResultWrapper` also implements the following traits **if** T
36/// implements the trait:
37///   Debug, Clone, Copy, Display, PartialOrd, Ord, PartialEq, Eq,
38//
39// note: each derive only applies if T impl's the trait.
40#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq)]
41pub struct JobResultWrapper<T>(T);
42
43impl<T: 'static + Send + Sync> JobResult for JobResultWrapper<T> {
44    fn as_any(&self) -> &dyn Any {
45        self
46    }
47
48    fn into_any(self: Box<Self>) -> Box<dyn Any> {
49        self
50    }
51}
52
53impl<T> Deref for JobResultWrapper<T> {
54    type Target = T;
55
56    fn deref(&self) -> &Self::Target {
57        &self.0
58    }
59}
60
61impl<T> DerefMut for JobResultWrapper<T> {
62    fn deref_mut(&mut self) -> &mut Self::Target {
63        &mut self.0
64    }
65}
66
67impl<T: 'static> TryFrom<JobCompletion> for JobResultWrapper<T> {
68    type Error = JobHandleError;
69
70    fn try_from(job_completion: JobCompletion) -> Result<Self, Self::Error> {
71        JobResultWrapper::try_from_completion(job_completion)
72    }
73}
74
75impl<'a, T: 'static> TryFrom<&'a JobCompletion> for &'a JobResultWrapper<T> {
76    type Error = JobHandleError;
77
78    fn try_from(job_completion: &'a JobCompletion) -> Result<Self, Self::Error> {
79        JobResultWrapper::try_from_completion_ref(job_completion)
80    }
81}
82
83impl<T: 'static> TryFrom<Box<dyn JobResult>> for JobResultWrapper<T> {
84    type Error = JobHandleError;
85
86    fn try_from(job_result: Box<dyn JobResult>) -> Result<Self, Self::Error> {
87        JobResultWrapper::try_from_boxed_job_result(job_result)
88    }
89}
90
91impl<'a, T: 'static> TryFrom<&'a dyn JobResult> for &'a JobResultWrapper<T> {
92    type Error = JobHandleError;
93
94    fn try_from(job_result: &'a dyn JobResult) -> Result<Self, Self::Error> {
95        JobResultWrapper::try_from_boxed_job_result_ref(job_result)
96    }
97}
98
99impl<T: 'static + Send + Sync> From<JobResultWrapper<T>> for Box<dyn JobResult> {
100    fn from(wrapper: JobResultWrapper<T>) -> Self {
101        Box::new(wrapper) as Box<dyn JobResult>
102    }
103}
104
105impl<T: Display> Display for JobResultWrapper<T> {
106    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107        self.0.fmt(f)
108    }
109}
110
111impl<T> JobResultWrapper<T> {
112    /// convert into inner `T`
113    pub fn into_inner(self) -> T {
114        self.0
115    }
116}
117
118impl<T: 'static> JobResultWrapper<T> {
119    /// instantiate new wrapper from job results
120    pub fn new(job_result: T) -> Self {
121        Self(job_result)
122    }
123
124    /// fallibly convert a [JobCompletion] into a `JobResultWrapper<T>`.
125    fn try_from_completion(job_completion: JobCompletion) -> Result<Self, JobHandleError> {
126        Self::try_from_boxed_job_result(job_completion.result()?)
127    }
128
129    /// fallibly convert a [JobCompletion] into a `JobResultWrapper<T>`.
130    fn try_from_completion_ref(job_completion: &JobCompletion) -> Result<&Self, JobHandleError> {
131        Self::try_from_boxed_job_result_ref(job_completion.result_ref().unwrap())
132    }
133
134    /// fallibly convert a `Box<dyn JobResult>` into a `JobResultWrapper<T>`.
135    fn try_from_boxed_job_result(
136        boxed_trait_object: Box<dyn JobResult>,
137    ) -> Result<Self, JobHandleError> {
138        let any = boxed_trait_object.into_any(); // Convert Box<dyn JobResult> to Box<dyn Any>
139        if let Ok(concrete_wrapper) = any.downcast::<JobResultWrapper<T>>() {
140            Ok(*concrete_wrapper) // Dereference the Box to get JobResultWrapper<T>
141        } else {
142            Err(JobHandleError::JobResultWrapperError {
143                from: std::any::type_name::<dyn JobResult>(),
144                to: std::any::type_name::<JobResultWrapper<T>>(),
145            })
146        }
147    }
148
149    /// fallibly convert an `&dyn JobResult` reference into a `JobResultWrapper<T>`.
150    fn try_from_boxed_job_result_ref(
151        boxed_trait_object: &dyn JobResult,
152    ) -> Result<&Self, JobHandleError> {
153        let any = boxed_trait_object.as_any();
154        if let Some(concrete_wrapper) = any.downcast_ref::<JobResultWrapper<T>>() {
155            Ok(concrete_wrapper)
156        } else {
157            Err(JobHandleError::JobResultWrapperError {
158                from: std::any::type_name::<dyn JobResult>(),
159                to: std::any::type_name::<JobResultWrapper<T>>(),
160            })
161        }
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168
169    #[test]
170    pub fn basic_conversions() {
171        type MyJobResult = u64;
172        type MyJobResultWrapper = JobResultWrapper<MyJobResult>;
173
174        let result: MyJobResult = 5;
175
176        let wrapper = MyJobResultWrapper::new(result);
177
178        // JobResult for JobResultWrapper<T>
179        let _ = wrapper.as_any();
180        let _ = Box::new(MyJobResultWrapper::new(result)).into_any();
181
182        // Deref, DerefMut for JobResultWrapper<T>
183        assert_eq!(&*wrapper, &mut *MyJobResultWrapper::new(result));
184
185        let completion: JobCompletion = wrapper.into();
186
187        // TryFrom<&JobCompletion> for &JobResultWrapper<T>
188        let _ = <&MyJobResultWrapper>::try_from(&completion).unwrap();
189
190        // TryFrom<JobCompletion> for JobResultWrapper<T>
191        let _ = MyJobResultWrapper::try_from(completion).unwrap();
192
193        // From<JobResultWrapper<T>> for Box<dyn JobResult>
194        let boxed: Box<dyn JobResult> = MyJobResultWrapper::new(5).into();
195
196        // TryFrom<&dyn JobResult> for &JobResultWrapper<T>
197        let _ = <&JobResultWrapper<u64>>::try_from(boxed.as_ref()).unwrap();
198
199        // TryFrom<Box<dyn JobResult>> for JobResultWrapper<T>
200        let _ = MyJobResultWrapper::try_from(boxed).unwrap();
201    }
202
203    #[test]
204    pub fn optional_traits() {
205        type MyJobResult = u64;
206
207        let wrapper = JobResultWrapper::<MyJobResult>::new(5);
208
209        // impl PartialEq, Eq, Clone, Copy for JobResultWrapper<T>
210        let copy = wrapper;
211        assert_eq!(wrapper, copy);
212
213        // impl Debug for JobResultWrapper<T>
214        let _ = format!("{:?}", wrapper);
215
216        // impl Display for JobResultWrapper<T>
217        assert_eq!(wrapper.to_string(), 5.to_string());
218
219        // impl PartialOrd, Ord for JobResultWrapper<T>
220        assert!(wrapper > JobResultWrapper::<MyJobResult>::new(2));
221    }
222}