homestar_invocation/task/
result.rs1use 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#[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(T),
42 Error(T),
44 Just(T),
48}
49
50#[cfg(not(feature = "diesel"))]
54#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
55pub enum Result<T> {
56 Ok(T),
58 Error(T),
60 Just(T),
64}
65
66impl<T> Result<T> {
67 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 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#[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#[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}