homestar_invocation/task/
result.rs

1//!  The output `Result` of an [Instruction], tagged as a `success` (`Ok`) or
2//!  `failure` (`Error`), or returned/inlined directly.
3//!
4//!  [Instruction]: crate::task::Instruction
5
6use crate::{Error, Unit};
7#[cfg(feature = "diesel")]
8use diesel::{
9    backend::Backend,
10    deserialize::{self, FromSql, FromSqlRow},
11    expression::AsExpression,
12    serialize::{self, IsNull, Output, ToSql},
13    sql_types::Binary,
14    sqlite::Sqlite,
15};
16use libipld::Ipld;
17#[cfg(feature = "diesel")]
18use libipld::{cbor::DagCborCodec, prelude::Codec};
19use schemars::{
20    gen::SchemaGenerator,
21    schema::{ArrayValidation, InstanceType, Metadata, Schema, SchemaObject, SingleOrVec},
22    JsonSchema,
23};
24use serde::{Deserialize, Serialize};
25use serde_json::json;
26use std::borrow::Cow;
27
28const OK: &str = "ok";
29const ERR: &str = "error";
30const JUST: &str = "just";
31
32/// Resultant output of an executed [Instruction].
33///
34/// [Instruction]: super::Instruction
35#[cfg(feature = "diesel")]
36#[cfg_attr(docsrs, doc(cfg(feature = "diesel")))]
37#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, AsExpression, FromSqlRow)]
38#[diesel(sql_type = Binary)]
39pub enum Result<T> {
40    /// `Ok` branch.
41    Ok(T),
42    /// `Error` branch.
43    Error(T),
44    /// `Just` branch, meaning `just the value`. Used for
45    /// not incorporating unwrapped ok/error into arg/param, where a
46    /// result may show up directly.
47    Just(T),
48}
49
50/// Output of an executed [Instruction].
51///
52/// [Instruction]: super::Instruction
53#[cfg(not(feature = "diesel"))]
54#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
55pub enum Result<T> {
56    /// `Ok` branch.
57    Ok(T),
58    /// `Error` branch.
59    Error(T),
60    /// `Just` branch, meaning `just the value`. Used for
61    /// not incorporating unwrapped ok/error into arg/param, where a
62    /// result may show up directly.
63    Just(T),
64}
65
66impl<T> Result<T> {
67    /// Owned, inner result of a [Task] invocation.
68    ///
69    /// [Task]: super::Task
70    pub fn into_inner(self) -> T {
71        match self {
72            Result::Ok(inner) => inner,
73            Result::Error(inner) => inner,
74            Result::Just(inner) => inner,
75        }
76    }
77
78    /// Referenced, inner result of a [Task] invocation.
79    ///
80    /// [Task]: super::Task
81    pub fn inner(&self) -> &T {
82        match self {
83            Result::Ok(inner) => inner,
84            Result::Error(inner) => inner,
85            Result::Just(inner) => inner,
86        }
87    }
88}
89
90impl<T> From<Result<T>> for Ipld
91where
92    Ipld: From<T>,
93{
94    fn from(result: Result<T>) -> Self {
95        match result {
96            Result::Ok(res) => Ipld::List(vec![OK.into(), res.into()]),
97            Result::Error(res) => Ipld::List(vec![ERR.into(), res.into()]),
98            Result::Just(res) => Ipld::List(vec![JUST.into(), res.into()]),
99        }
100    }
101}
102
103impl<T> TryFrom<Ipld> for Result<T>
104where
105    T: From<Ipld>,
106{
107    type Error = Error<Unit>;
108
109    fn try_from(ipld: Ipld) -> std::result::Result<Self, Error<Unit>> {
110        if let Ipld::List(v) = ipld {
111            match &v[..] {
112                [Ipld::String(result), res] if result == OK => {
113                    Ok(Result::Ok(res.to_owned().into()))
114                }
115                [Ipld::String(result), res] if result == ERR => {
116                    Ok(Result::Error(res.to_owned().into()))
117                }
118                [Ipld::String(result), res] if result == JUST => {
119                    Ok(Result::Just(res.to_owned().into()))
120                }
121                other_ipld => Err(Error::unexpected_ipld(other_ipld.to_owned().into())),
122            }
123        } else {
124            Err(Error::not_an_ipld_list())
125        }
126    }
127}
128
129impl<T> TryFrom<&Ipld> for Result<T>
130where
131    T: From<Ipld>,
132{
133    type Error = Error<Unit>;
134
135    fn try_from(ipld: &Ipld) -> std::result::Result<Self, Error<Unit>> {
136        TryFrom::try_from(ipld.to_owned())
137    }
138}
139
140/// Diesel, [Sqlite] [ToSql] implementation.
141#[cfg(feature = "diesel")]
142#[cfg_attr(docsrs, doc(cfg(feature = "diesel")))]
143impl ToSql<Binary, Sqlite> for Result<Ipld>
144where
145    [u8]: ToSql<Binary, Sqlite>,
146{
147    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> serialize::Result {
148        let ipld = Ipld::from(self.to_owned());
149        out.set_value(DagCborCodec.encode(&ipld)?);
150        Ok(IsNull::No)
151    }
152}
153
154/// Diesel, [Sqlite] [FromSql] implementation.
155#[cfg(feature = "diesel")]
156#[cfg_attr(docsrs, doc(cfg(feature = "diesel")))]
157impl<DB> FromSql<Binary, DB> for Result<Ipld>
158where
159    DB: Backend,
160    *const [u8]: FromSql<Binary, DB>,
161{
162    fn from_sql(bytes: DB::RawValue<'_>) -> deserialize::Result<Self> {
163        let raw_bytes = <*const [u8] as FromSql<Binary, DB>>::from_sql(bytes)?;
164        let raw_bytes: &[u8] = unsafe { &*raw_bytes };
165        let decoded: Ipld = DagCborCodec.decode(raw_bytes)?;
166        Ok(Result::try_from(decoded)?)
167    }
168}
169
170impl<T> JsonSchema for Result<T> {
171    fn schema_name() -> String {
172        "out".to_owned()
173    }
174
175    fn schema_id() -> Cow<'static, str> {
176        Cow::Borrowed("homestar-invocation::task::Result")
177    }
178
179    fn json_schema(gen: &mut SchemaGenerator) -> Schema {
180        let out_result = SchemaObject {
181            instance_type: Some(SingleOrVec::Single(InstanceType::Object.into())),
182            enum_values: Some(vec![json!(OK), json!(ERR), json!(JUST)]),
183            ..Default::default()
184        };
185
186        let schema = SchemaObject {
187            instance_type: Some(SingleOrVec::Single(InstanceType::Object.into())),
188            metadata: Some(Box::new(Metadata {
189                title: Some("Computation result".to_string()),
190                description: Some(
191                    "Result tuple with ok/err/just result and associated output".to_string(),
192                ),
193                ..Default::default()
194            })),
195            array: Some(Box::new(ArrayValidation {
196                items: Some(SingleOrVec::Vec(vec![
197                    Schema::Object(out_result),
198                    gen.subschema_for::<crate::ipld::schema::IpldStub>(),
199                ])),
200                min_items: Some(2),
201                max_items: Some(2),
202                ..Default::default()
203            })),
204            ..Default::default()
205        };
206
207        schema.into()
208    }
209}
210
211#[cfg(test)]
212mod test {
213    use super::*;
214
215    #[test]
216    fn ipld_roundtrip() {
217        let res1 = Result::Error(Ipld::String("bad stuff".to_string()));
218        let res2 = Result::Ok(Ipld::String("ok stuff".to_string()));
219        let res3 = Result::Just(Ipld::String("just the right stuff".to_string()));
220        let ipld1 = Ipld::from(res1.clone());
221        let ipld2 = Ipld::from(res2.clone());
222        let ipld3 = Ipld::from(res3.clone());
223
224        assert_eq!(ipld1, Ipld::List(vec!["error".into(), "bad stuff".into()]));
225        assert_eq!(ipld2, Ipld::List(vec!["ok".into(), "ok stuff".into()]));
226        assert_eq!(
227            ipld3,
228            Ipld::List(vec!["just".into(), "just the right stuff".into()])
229        );
230
231        assert_eq!(res1, ipld1.try_into().unwrap());
232        assert_eq!(res2, ipld2.try_into().unwrap());
233        assert_eq!(res3, ipld3.try_into().unwrap());
234    }
235
236    #[test]
237    fn ser_de() {
238        let res1 = Result::Error(Ipld::String("bad stuff".to_string()));
239        let res2 = Result::Ok(Ipld::String("ok stuff".to_string()));
240        let res3 = Result::Just(Ipld::String("just the right stuff".to_string()));
241
242        let ser = serde_json::to_string(&res1).unwrap();
243        let de = serde_json::from_str(&ser).unwrap();
244        assert_eq!(res1, de);
245
246        let ser = serde_json::to_string(&res2).unwrap();
247        let de = serde_json::from_str(&ser).unwrap();
248        assert_eq!(res2, de);
249
250        let ser = serde_json::to_string(&res3).unwrap();
251        let de = serde_json::from_str(&ser).unwrap();
252        assert_eq!(res3, de);
253    }
254}