1use crate::{
4 authority::UcanPrf,
5 ipld::{DagCbor, DagJson},
6 Error, Pointer, Unit,
7};
8use libipld::{cid::Cid, serde::from_ipld, Ipld};
9use schemars::JsonSchema;
10use serde::{Deserialize, Serialize};
11use std::collections::BTreeMap;
12
13pub mod config;
14pub mod instruction;
15mod result;
16
17pub use config::Resources;
18pub use instruction::Instruction;
19use instruction::RunInstruction;
20pub use result::Result;
21
22const RUN_KEY: &str = "run";
23const CAUSE_KEY: &str = "cause";
24const METADATA_KEY: &str = "meta";
25const PROOF_KEY: &str = "prf";
26
27#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
33#[schemars(
34 rename = "task",
35 description = "Contains a run instruction, configuration, optional reference to receipt that caused task to run, and authorization"
36)]
37pub struct Task<'a, T> {
38 #[schemars(with = "Instruction<'a, T>", rename = "run", title = "Run instruction")]
39 run: RunInstruction<'a, T>,
40 #[schemars(title = "Receipt reference")]
41 cause: Option<Pointer>,
42 #[schemars(with = "Resources", title = "Task Configuration")]
43 meta: Ipld,
44 #[schemars(title = "UCAN Authorization")]
45 prf: UcanPrf,
46}
47
48impl<'a, T> Task<'a, T>
49where
50 Ipld: From<T>,
51 T: Clone,
52{
53 pub fn new(run: RunInstruction<'a, T>, meta: Ipld, prf: UcanPrf) -> Self {
55 Self {
56 run,
57 cause: None,
58 meta,
59 prf,
60 }
61 }
62
63 pub fn new_with_cause(
66 run: RunInstruction<'a, T>,
67 meta: Ipld,
68 prf: UcanPrf,
69 cause: Option<Pointer>,
70 ) -> Self {
71 Self {
72 run,
73 cause,
74 meta,
75 prf,
76 }
77 }
78
79 pub fn run(&self) -> &RunInstruction<'_, T> {
83 &self.run
84 }
85
86 pub fn meta(&self) -> &Ipld {
88 &self.meta
89 }
90
91 pub fn into_instruction(self) -> RunInstruction<'a, T> {
93 self.run
94 }
95
96 pub fn instruction_cid(&self) -> std::result::Result<Cid, Error<Unit>> {
100 match &self.run {
101 RunInstruction::Expanded(instruction) => Ok(instruction.to_owned().to_cid()?),
102 RunInstruction::Ptr(instruction_ptr) => Ok(instruction_ptr.cid()),
103 }
104 }
105}
106
107impl<T> From<Task<'_, T>> for Ipld
108where
109 Ipld: From<T>,
110{
111 fn from(task: Task<'_, T>) -> Self {
112 Ipld::Map(BTreeMap::from([
113 (RUN_KEY.into(), task.run.into()),
114 (
115 CAUSE_KEY.into(),
116 task.cause.map_or(Ipld::Null, |cause| cause.into()),
117 ),
118 (METADATA_KEY.into(), task.meta),
119 (PROOF_KEY.into(), task.prf.into()),
120 ]))
121 }
122}
123
124impl<T> TryFrom<Ipld> for Task<'_, T>
125where
126 T: From<Ipld>,
127{
128 type Error = Error<Unit>;
129
130 fn try_from(ipld: Ipld) -> std::result::Result<Self, Self::Error> {
131 let map = from_ipld::<BTreeMap<String, Ipld>>(ipld)?;
132
133 Ok(Self {
134 run: RunInstruction::try_from(
135 map.get(RUN_KEY)
136 .ok_or_else(|| Error::<Unit>::MissingField(RUN_KEY.to_string()))?
137 .to_owned(),
138 )?,
139 cause: map
140 .get(CAUSE_KEY)
141 .and_then(|ipld| match ipld {
142 Ipld::Null => None,
143 ipld => Some(ipld),
144 })
145 .and_then(|ipld| ipld.to_owned().try_into().ok()),
146 meta: map
147 .get(METADATA_KEY)
148 .ok_or_else(|| Error::<Unit>::MissingField(METADATA_KEY.to_string()))?
149 .to_owned(),
150 prf: UcanPrf::try_from(
151 map.get(PROOF_KEY)
152 .ok_or_else(|| Error::<Unit>::MissingField(PROOF_KEY.to_string()))?
153 .to_owned(),
154 )?,
155 })
156 }
157}
158
159impl<T> TryFrom<&Ipld> for Task<'_, T>
160where
161 T: From<Ipld>,
162{
163 type Error = Error<Unit>;
164
165 fn try_from<'a>(ipld: &Ipld) -> std::result::Result<Self, Self::Error> {
166 TryFrom::try_from(ipld.to_owned())
167 }
168}
169
170impl<T> TryFrom<Task<'_, T>> for Pointer
171where
172 Ipld: From<T>,
173{
174 type Error = Error<Unit>;
175
176 fn try_from(task: Task<'_, T>) -> std::result::Result<Self, Self::Error> {
177 Ok(Pointer::new(task.to_cid()?))
178 }
179}
180
181impl<'a, T> DagCbor for Task<'a, T> where Ipld: From<T> {}
182
183impl<T> DagJson for Task<'_, T>
184where
185 T: From<Ipld> + Clone,
186 Ipld: From<T>,
187{
188}
189
190#[cfg(test)]
191mod test {
192 use super::*;
193 use crate::{consts::WASM_MAX_MEMORY, test_utils};
194
195 #[test]
196 fn ipld_roundtrip() {
197 let config = Resources::default();
198 let instruction = test_utils::instruction::<Unit>();
199 let task1 = Task::new(
200 RunInstruction::Expanded(instruction.clone()),
201 config.clone().into(),
202 UcanPrf::default(),
203 );
204
205 let ipld1 = Ipld::from(task1.clone());
206
207 let ipld_task = Ipld::Map(BTreeMap::from([
208 (
209 "rsc".into(),
210 Ipld::String(
211 "ipfs://bafybeia32q3oy6u47x624rmsmgrrlpn7ulruissmz5z2ap6alv7goe7h3q".into(),
212 ),
213 ),
214 ("op".into(), Ipld::String("ipld/fun".to_string())),
215 ("input".into(), Ipld::List(vec![Ipld::Bool(true)])),
216 ("nnc".into(), Ipld::String("".to_string())),
217 ]));
218
219 assert_eq!(
220 ipld1,
221 Ipld::Map(BTreeMap::from([
222 (RUN_KEY.into(), ipld_task),
223 (CAUSE_KEY.into(), Ipld::Null),
224 (
225 METADATA_KEY.into(),
226 Ipld::Map(BTreeMap::from([
227 ("fuel".into(), Ipld::Integer(u64::MAX.into())),
228 ("memory".into(), Ipld::Integer(WASM_MAX_MEMORY.into())),
229 ("time".into(), Ipld::Integer(100_000))
230 ]))
231 ),
232 (PROOF_KEY.into(), Ipld::List(vec![]))
233 ]))
234 );
235
236 assert_eq!(task1, ipld1.try_into().unwrap());
237
238 let receipt = test_utils::receipt();
239
240 let task2 = Task::new_with_cause(
241 RunInstruction::Ptr::<Ipld>(instruction.try_into().unwrap()),
242 config.into(),
243 UcanPrf::default(),
244 Some(receipt.clone().try_into().unwrap()),
245 );
246
247 let ipld2 = Ipld::from(task2.clone());
248
249 assert_eq!(
250 ipld2,
251 Ipld::Map(BTreeMap::from([
252 (RUN_KEY.into(), Ipld::Link(task2.instruction_cid().unwrap())),
253 (CAUSE_KEY.into(), Ipld::Link(receipt.to_cid().unwrap())),
254 (
255 METADATA_KEY.into(),
256 Ipld::Map(BTreeMap::from([
257 ("fuel".into(), Ipld::Integer(u64::MAX.into())),
258 ("memory".into(), Ipld::Integer(WASM_MAX_MEMORY.into())),
259 ("time".into(), Ipld::Integer(100_000))
260 ]))
261 ),
262 (PROOF_KEY.into(), Ipld::List(vec![]))
263 ]))
264 );
265
266 assert_eq!(task2, ipld2.try_into().unwrap());
267 }
268
269 #[test]
270 fn ser_de() {
271 let config = Resources::default();
272 let instruction = test_utils::instruction::<Unit>();
273 let task = Task::new(
274 RunInstruction::Expanded(instruction.clone()),
275 config.into(),
276 UcanPrf::default(),
277 );
278
279 let ser = serde_json::to_string(&task).unwrap();
280 let de = serde_json::from_str(&ser).unwrap();
281
282 assert_eq!(task, de);
283 }
284}