1use std::{sync::Arc, time::SystemTime};
4
5use async_trait::async_trait;
6use eyre::{bail, Context};
7use indexmap::IndexSet;
8use parking_lot::RwLock;
9use uuid::Uuid;
10
11use ora_storage::{
12 CancelledJob, CancelledSchedule, ExecutionDetails, IndexMap, JobExecutionStatus,
13 JobQueryFilters, JobQueryOrder, JobQueryResult, JobRetryPolicy, JobTimeoutPolicy, JobType,
14 NewExecution, NewJob, NewSchedule, PendingExecution, PendingJob, PendingSchedule,
15 ReadyExecution, ScheduleJobCreationPolicy, ScheduleJobTimingPolicy, ScheduleQueryFilters,
16 ScheduleQueryOrder, ScheduleQueryResult, ScheduleTimeRange, Storage,
17};
18
19mod job_query;
20mod schedule_query;
21mod snapshot;
22
23#[derive(Debug, Default, Clone)]
40#[must_use]
41pub struct MemoryStorage {
42 job_types: Arc<RwLock<IndexMap<String, JobType>>>,
43
44 schedulable_jobs: Arc<RwLock<IndexMap<Uuid, Job>>>,
46 unschedulable_jobs: Arc<RwLock<IndexMap<Uuid, Job>>>,
47
48 pending_executions: Arc<RwLock<IndexMap<Uuid, Execution>>>,
50 ready_executions: Arc<RwLock<IndexMap<Uuid, Execution>>>,
51 assigned_executions: Arc<RwLock<IndexMap<Uuid, Execution>>>,
52 started_executions: Arc<RwLock<IndexMap<Uuid, Execution>>>,
53 succeeded_executions: Arc<RwLock<IndexMap<Uuid, Execution>>>,
54 failed_executions: Arc<RwLock<IndexMap<Uuid, Execution>>>,
55
56 schedulable_schedules: Arc<RwLock<IndexMap<Uuid, Schedule>>>,
58 unschedulable_schedules: Arc<RwLock<IndexMap<Uuid, Schedule>>>,
59}
60
61impl MemoryStorage {
62 pub fn new() -> Self {
64 Self::default()
65 }
66}
67
68#[async_trait]
69impl Storage for MemoryStorage {
70 async fn job_types_added(&self, job_types: Vec<JobType>) -> eyre::Result<()> {
71 self.job_types.write().extend(
72 job_types
73 .iter()
74 .map(|job_type| (job_type.id.clone(), job_type.clone())),
75 );
76
77 Ok(())
78 }
79
80 async fn jobs_added(&self, new_jobs: Vec<NewJob>) -> eyre::Result<()> {
81 for new_job in new_jobs {
82 let mut jobs = self.schedulable_jobs.write();
83
84 if jobs.contains_key(&new_job.id) {
85 bail!("job with ID {} already exists", new_job.id);
86 }
87
88 jobs.insert(new_job.id, Job::from(new_job));
89 }
90
91 Ok(())
92 }
93
94 async fn jobs_cancelled(
95 &self,
96 job_ids: &[Uuid],
97 timestamp: SystemTime,
98 ) -> eyre::Result<Vec<CancelledJob>> {
99 let mut cancelled_jobs = Vec::with_capacity(job_ids.len());
100
101 for job_id in job_ids {
102 let active_job = self.schedulable_jobs.write().swap_remove(job_id);
103
104 if let Some(mut job) = active_job {
105 debug_assert!(job.cancelled_at.is_none());
106 job.cancelled_at = Some(timestamp);
107
108 let active_execution = self
109 .pending_executions
110 .read()
111 .iter()
112 .find_map(|(_, execution)| {
113 if &execution.job_id == job_id {
114 Some(execution.id)
115 } else {
116 None
117 }
118 })
119 .or_else(|| {
120 self.ready_executions
121 .read()
122 .iter()
123 .find_map(|(_, execution)| {
124 if &execution.job_id == job_id {
125 Some(execution.id)
126 } else {
127 None
128 }
129 })
130 })
131 .or_else(|| {
132 self.assigned_executions
133 .read()
134 .iter()
135 .find_map(|(_, execution)| {
136 if &execution.job_id == job_id {
137 Some(execution.id)
138 } else {
139 None
140 }
141 })
142 })
143 .or_else(|| {
144 self.started_executions
145 .read()
146 .iter()
147 .find_map(|(_, execution)| {
148 if &execution.job_id == job_id {
149 Some(execution.id)
150 } else {
151 None
152 }
153 })
154 });
155
156 if active_execution.is_some() {
157 job.marked_unschedulable_at = Some(timestamp);
158 self.unschedulable_jobs.write().insert(*job_id, job);
159 } else {
160 self.schedulable_jobs.write().insert(*job_id, job);
161 }
162
163 cancelled_jobs.push(CancelledJob {
164 id: *job_id,
165 active_execution,
166 });
167 }
168 }
169
170 Ok(cancelled_jobs)
171 }
172
173 async fn executions_added(
174 &self,
175 executions: Vec<NewExecution>,
176 timestamp: SystemTime,
177 ) -> eyre::Result<()> {
178 for execution in executions {
179 let mut pending_executions = self.pending_executions.write();
180 if pending_executions.contains_key(&execution.id) {
181 bail!("execution with ID {} already exists", execution.id);
182 }
183
184 pending_executions.insert(
185 execution.id,
186 Execution {
187 id: execution.id,
188 job_id: execution.job_id,
189 target_execution_time: execution.target_execution_time,
190 created_at: timestamp,
191 executor_id: None,
192 ready_at: None,
193 assigned_at: None,
194 started_at: None,
195 succeeded_at: None,
196 failed_at: None,
197 output_payload_json: None,
198 failure_reason: None,
199 },
200 );
201 }
202
203 Ok(())
204 }
205
206 async fn executions_ready(
207 &self,
208 execution_ids: &[Uuid],
209 timestamp: SystemTime,
210 ) -> eyre::Result<()> {
211 for execution_id in execution_ids {
212 let execution = self.pending_executions.write().swap_remove(execution_id);
213
214 if let Some(mut execution) = execution {
215 debug_assert!(execution.ready_at.is_none());
216 execution.ready_at = Some(timestamp);
217 self.ready_executions
218 .write()
219 .insert(*execution_id, execution);
220 }
221 }
222
223 Ok(())
224 }
225
226 async fn execution_assigned(
227 &self,
228 execution_id: Uuid,
229 executor_id: Uuid,
230 timestamp: SystemTime,
231 ) -> eyre::Result<()> {
232 let execution = self.ready_executions.write().swap_remove(&execution_id);
233
234 if let Some(mut execution) = execution {
235 debug_assert!(execution.assigned_at.is_none());
236 execution.assigned_at = Some(timestamp);
237 execution.executor_id = Some(executor_id);
238
239 self.assigned_executions
240 .write()
241 .insert(execution_id, execution);
242 }
243
244 Ok(())
245 }
246
247 async fn execution_started(
248 &self,
249 execution_id: Uuid,
250 timestamp: SystemTime,
251 ) -> eyre::Result<()> {
252 let execution = self.assigned_executions.write().swap_remove(&execution_id);
253 if let Some(mut execution) = execution {
254 debug_assert!(execution.started_at.is_none());
255 execution.started_at = Some(timestamp);
256
257 self.started_executions
258 .write()
259 .insert(execution_id, execution);
260 }
261
262 Ok(())
263 }
264
265 async fn execution_succeeded(
266 &self,
267 execution_id: Uuid,
268 timestamp: SystemTime,
269 output_payload_json: String,
270 ) -> eyre::Result<()> {
271 let mut execution = if let Some(execution) =
273 self.pending_executions.write().swap_remove(&execution_id)
274 {
275 execution
276 } else if let Some(execution) = self.ready_executions.write().swap_remove(&execution_id) {
277 execution
278 } else if let Some(execution) = self.assigned_executions.write().swap_remove(&execution_id)
279 {
280 execution
281 } else if let Some(execution) = self.started_executions.write().swap_remove(&execution_id) {
282 execution
283 } else {
284 return Ok(());
285 };
286
287 debug_assert!(execution.succeeded_at.is_none());
288 debug_assert!(execution.failed_at.is_none());
289
290 execution.succeeded_at = Some(timestamp);
291 execution.output_payload_json = Some(output_payload_json);
292
293 let job = self.schedulable_jobs.write().swap_remove(&execution.job_id);
294 if let Some(mut job) = job {
295 debug_assert!(job.marked_unschedulable_at.is_none());
296 job.marked_unschedulable_at = Some(timestamp);
297
298 self.unschedulable_jobs
299 .write()
300 .insert(execution.job_id, job);
301 }
302
303 self.succeeded_executions
304 .write()
305 .insert(execution_id, execution);
306
307 Ok(())
308 }
309
310 async fn executions_failed(
311 &self,
312 execution_ids: &[Uuid],
313 timestamp: SystemTime,
314 reason: String,
315 mark_job_inactive: bool,
316 ) -> eyre::Result<()> {
317 for execution_id in execution_ids {
319 let mut execution = if let Some(execution) =
320 self.pending_executions.write().swap_remove(execution_id)
321 {
322 execution
323 } else if let Some(execution) = self.ready_executions.write().swap_remove(execution_id)
324 {
325 execution
326 } else if let Some(execution) =
327 self.assigned_executions.write().swap_remove(execution_id)
328 {
329 execution
330 } else if let Some(execution) =
331 self.started_executions.write().swap_remove(execution_id)
332 {
333 execution
334 } else {
335 return Ok(());
336 };
337
338 debug_assert!(execution.succeeded_at.is_none());
339 debug_assert!(execution.failed_at.is_none());
340
341 execution.failed_at = Some(timestamp);
342 execution.failure_reason = Some(reason.clone());
343
344 if mark_job_inactive {
345 let job = self.schedulable_jobs.write().swap_remove(&execution.job_id);
346 if let Some(mut job) = job {
347 debug_assert!(job.marked_unschedulable_at.is_none());
348 job.marked_unschedulable_at = Some(timestamp);
349
350 self.unschedulable_jobs
351 .write()
352 .insert(execution.job_id, job);
353 }
354 }
355
356 self.failed_executions
357 .write()
358 .insert(*execution_id, execution);
359 }
360
361 Ok(())
362 }
363
364 async fn orphan_execution_ids(&self, executor_ids: &[Uuid]) -> eyre::Result<Vec<Uuid>> {
365 Ok(self
366 .assigned_executions
367 .read()
368 .iter()
369 .filter_map(|(id, execution)| {
370 if executor_ids.contains(&execution.executor_id.unwrap()) {
371 None
372 } else {
373 Some(*id)
374 }
375 })
376 .chain(
377 self.started_executions
378 .read()
379 .iter()
380 .filter_map(|(id, execution)| {
381 if executor_ids.contains(&execution.executor_id.unwrap()) {
382 None
383 } else {
384 Some(*id)
385 }
386 }),
387 )
388 .collect())
389 }
390
391 async fn jobs_unschedulable(
392 &self,
393 job_ids: &[Uuid],
394 timestamp: SystemTime,
395 ) -> eyre::Result<()> {
396 for job_id in job_ids {
397 let job = self.schedulable_jobs.write().swap_remove(job_id);
398 if let Some(mut job) = job {
399 debug_assert!(job.marked_unschedulable_at.is_none());
400 job.marked_unschedulable_at = Some(timestamp);
401
402 self.unschedulable_jobs.write().insert(*job_id, job);
403 } else {
404 debug_assert!(false, "active job with ID {job_id} not found");
405 }
406 }
407
408 Ok(())
409 }
410
411 async fn pending_executions(&self, after: Option<Uuid>) -> eyre::Result<Vec<PendingExecution>> {
412 Ok(self
413 .pending_executions
414 .read()
415 .iter()
416 .filter_map(|(id, execution)| {
417 if let Some(after) = after {
418 if *id <= after {
419 return None;
420 }
421 }
422
423 Some(PendingExecution {
424 id: *id,
425 target_execution_time: execution.target_execution_time,
426 })
427 })
428 .collect())
429 }
430
431 async fn ready_executions(&self, after: Option<Uuid>) -> eyre::Result<Vec<ReadyExecution>> {
432 Ok({
433 let mut executions = self
434 .ready_executions
435 .read()
436 .iter()
437 .filter_map(|(id, execution)| {
438 let jobs = self.schedulable_jobs.read();
439 let Some(job) = jobs.get(&execution.job_id) else {
440 debug_assert!(false, "active job with ID {} not found", execution.job_id);
441 return None;
442 };
443
444 Some(ReadyExecution {
445 id: *id,
446 target_execution_time: execution.target_execution_time,
447 job_id: execution.job_id,
448 input_payload_json: job.input_payload_json.clone(),
449 attempt_number: 0,
450 job_type_id: job.job_type_id.clone(),
451 timeout_policy: job.timeout_policy,
452 })
453 })
454 .collect::<Vec<_>>();
455
456 executions.sort_by_key(|execution| execution.id);
457
458 if let Some(after) = after {
459 executions.retain(|execution| execution.id > after);
460 }
461
462 for execution in &mut executions {
463 execution.attempt_number = u64::try_from(
464 self.executions_by_job_id(execution.job_id)
465 .position(|id| id == execution.id)
466 .unwrap(),
467 )
468 .unwrap()
469 + 1;
470 }
471
472 executions
473 })
474 }
475
476 async fn pending_jobs(&self, after: Option<Uuid>) -> eyre::Result<Vec<PendingJob>> {
477 let mut jobs: Vec<PendingJob> = {
478 let pending_executions = self.pending_executions.read();
479 let ready_executions = self.ready_executions.read();
480 let assigned_executions = self.assigned_executions.read();
481 let started_executions = self.started_executions.read();
482
483 self.schedulable_jobs
484 .read()
485 .iter()
486 .filter_map(|(job_id, job)| {
487 if pending_executions
488 .values()
489 .any(|execution| execution.job_id == *job_id)
490 {
491 return None;
492 }
493
494 if ready_executions
495 .values()
496 .any(|execution| execution.job_id == *job_id)
497 {
498 return None;
499 }
500
501 if assigned_executions
502 .values()
503 .any(|execution| execution.job_id == *job_id)
504 {
505 return None;
506 }
507
508 if started_executions
509 .values()
510 .any(|execution| execution.job_id == *job_id)
511 {
512 return None;
513 }
514
515 if let Some(after) = after {
516 if job.id <= after {
517 return None;
518 }
519 }
520
521 Some(PendingJob {
522 id: job.id,
523 target_execution_time: job.target_execution_time,
524 execution_count: 0,
525 retry_policy: job.retry_policy,
526 timeout_policy: job.timeout_policy,
527 })
528 })
529 .collect::<Vec<_>>()
530 };
531
532 for job in &mut jobs {
533 job.execution_count = u64::try_from(self.executions_by_job_id(job.id).len()).unwrap();
534 }
535
536 Ok(jobs)
537 }
538
539 async fn query_jobs(
540 &self,
541 cursor: Option<String>,
542 limit: usize,
543 order: JobQueryOrder,
544 filters: JobQueryFilters,
545 ) -> eyre::Result<JobQueryResult> {
546 let cursor: Option<job_query::Cursor> = match cursor {
547 Some(cursor) => serde_json::from_str(&cursor).wrap_err("invalid cursor")?,
548 None => None,
549 };
550
551 Ok(self.query_jobs_impl(cursor, limit, order, filters))
552 }
553
554 async fn query_job_ids(&self, filters: JobQueryFilters) -> eyre::Result<Vec<Uuid>> {
555 Ok(self.query_job_ids_impl(filters))
556 }
557
558 async fn count_jobs(&self, filters: JobQueryFilters) -> eyre::Result<u64> {
559 Ok(self.count_jobs_impl(filters))
560 }
561
562 async fn query_job_types(&self) -> eyre::Result<Vec<JobType>> {
563 Ok(self.job_types.read().values().cloned().collect())
564 }
565
566 async fn delete_jobs(&self, filters: JobQueryFilters) -> eyre::Result<Vec<Uuid>> {
567 let job_ids = self.query_job_ids_impl(filters);
568
569 let mut executions_to_remove = Vec::new();
570
571 for job_id in &job_ids {
572 self.unschedulable_jobs
573 .write()
574 .swap_remove(job_id)
575 .or_else(|| self.schedulable_jobs.write().swap_remove(job_id));
576
577 executions_to_remove.extend(self.executions_by_job_id(*job_id));
578 }
579
580 self.pending_executions
581 .write()
582 .retain(|id, _| !executions_to_remove.contains(id));
583 self.ready_executions
584 .write()
585 .retain(|id, _| !executions_to_remove.contains(id));
586 self.assigned_executions
587 .write()
588 .retain(|id, _| !executions_to_remove.contains(id));
589 self.started_executions
590 .write()
591 .retain(|id, _| !executions_to_remove.contains(id));
592 self.succeeded_executions
593 .write()
594 .retain(|id, _| !executions_to_remove.contains(id));
595 self.failed_executions
596 .write()
597 .retain(|id, _| !executions_to_remove.contains(id));
598
599 Ok(job_ids)
600 }
601
602 async fn schedules_added(&self, schedules: Vec<NewSchedule>) -> eyre::Result<()> {
603 for schedule in schedules {
604 let mut active_schedules = self.schedulable_schedules.write();
605
606 if active_schedules.contains_key(&schedule.id) {
607 bail!("schedule with ID {} already exists", schedule.id);
608 }
609
610 active_schedules.insert(schedule.id, Schedule::from(schedule));
611 }
612
613 Ok(())
614 }
615
616 async fn schedules_cancelled(
617 &self,
618 schedule_ids: &[Uuid],
619 timestamp: SystemTime,
620 ) -> eyre::Result<Vec<CancelledSchedule>> {
621 let mut cancelled_schedules = Vec::with_capacity(schedule_ids.len());
622
623 for schedule_id in schedule_ids {
624 let schedule = self.schedulable_schedules.write().swap_remove(schedule_id);
625
626 if let Some(mut schedule) = schedule {
627 debug_assert!(schedule.cancelled_at.is_none());
628 schedule.cancelled_at = Some(timestamp);
629
630 debug_assert!(schedule.marked_unschedulable_at.is_none());
631 schedule.marked_unschedulable_at = Some(timestamp);
632
633 let schedule_id = schedule.id;
634
635 self.unschedulable_schedules
636 .write()
637 .insert(schedule_id, schedule);
638
639 cancelled_schedules.push(CancelledSchedule { id: schedule_id });
640 }
641 }
642
643 Ok(cancelled_schedules)
644 }
645
646 async fn pending_schedules(&self, after: Option<Uuid>) -> eyre::Result<Vec<PendingSchedule>> {
647 Ok(self
648 .schedulable_schedules
649 .read()
650 .iter()
651 .filter_map(|(id, schedule)| {
652 if self
653 .schedulable_jobs
654 .read()
655 .values()
656 .any(|job| job.schedule_id == Some(schedule.id))
657 {
658 return None;
659 }
660
661 if let Some(after) = after {
662 if *id <= after {
663 return None;
664 }
665 }
666
667 Some(PendingSchedule {
668 id: *id,
669 job_timing_policy: schedule.job_timing_policy.clone(),
670 job_creation_policy: schedule.job_creation_policy.clone(),
671 last_target_execution_time: self.last_target_execution_time(schedule.id),
672 time_range: schedule.time_range,
673 })
674 })
675 .collect::<Vec<_>>())
676 }
677
678 async fn query_schedules(
679 &self,
680 cursor: Option<String>,
681 limit: usize,
682 filters: ScheduleQueryFilters,
683 order: ScheduleQueryOrder,
684 ) -> eyre::Result<ScheduleQueryResult> {
685 let cursor: Option<schedule_query::Cursor> = match cursor {
686 Some(cursor) => serde_json::from_str(&cursor).wrap_err("invalid cursor")?,
687 None => None,
688 };
689
690 Ok(self.query_schedules_impl(cursor, limit, order, filters))
691 }
692
693 async fn query_schedule_ids(&self, filters: ScheduleQueryFilters) -> eyre::Result<Vec<Uuid>> {
694 Ok(self.query_schedule_ids_impl(filters))
695 }
696
697 async fn count_schedules(&self, filters: ScheduleQueryFilters) -> eyre::Result<u64> {
698 Ok(self.count_schedules_impl(filters))
699 }
700
701 async fn schedules_unschedulable(
702 &self,
703 schedule_ids: &[Uuid],
704 timestamp: SystemTime,
705 ) -> eyre::Result<()> {
706 for schedule_id in schedule_ids {
707 let schedule = self.schedulable_schedules.write().swap_remove(schedule_id);
708
709 if let Some(mut schedule) = schedule {
710 debug_assert!(schedule.marked_unschedulable_at.is_none());
711 schedule.marked_unschedulable_at = Some(timestamp);
712
713 let schedule_id = schedule.id;
714
715 self.unschedulable_schedules
716 .write()
717 .insert(schedule_id, schedule);
718 }
719 }
720
721 Ok(())
722 }
723
724 async fn delete_schedules(&self, filters: ScheduleQueryFilters) -> eyre::Result<Vec<Uuid>> {
725 let schedule_ids = self
726 .query_schedule_ids_impl(filters)
727 .into_iter()
728 .collect::<IndexSet<_>>();
729
730 self.schedulable_schedules
732 .write()
733 .retain(|id, _| !schedule_ids.contains(id));
734 self.unschedulable_schedules
735 .write()
736 .retain(|id, _| !schedule_ids.contains(id));
737
738 Ok(schedule_ids.into_iter().collect())
739 }
740}
741
742impl MemoryStorage {
743 fn executions_by_job_id(&self, job_id: Uuid) -> impl ExactSizeIterator<Item = Uuid> {
745 let mut execution_ids = self
746 .pending_executions
747 .read()
748 .values()
749 .chain(self.ready_executions.read().values())
750 .chain(self.assigned_executions.read().values())
751 .chain(self.started_executions.read().values())
752 .chain(self.succeeded_executions.read().values())
753 .chain(self.failed_executions.read().values())
754 .filter(move |execution| execution.job_id == job_id)
755 .map(|execution| execution.id)
756 .collect::<Vec<_>>();
757
758 execution_ids.sort_unstable();
759
760 execution_ids.into_iter()
761 }
762
763 fn last_target_execution_time(&self, schedule_id: Uuid) -> Option<SystemTime> {
765 self.schedulable_jobs
766 .read()
767 .values()
768 .filter(|job| job.schedule_id == Some(schedule_id))
769 .map(|job| job.target_execution_time)
770 .max()
771 .max(
772 self.unschedulable_jobs
773 .read()
774 .values()
775 .filter(|job| job.schedule_id == Some(schedule_id))
776 .map(|job| job.target_execution_time)
777 .max(),
778 )
779 }
780}
781
782#[derive(Debug, Clone)]
783struct Job {
784 id: Uuid,
785 schedule_id: Option<Uuid>,
786 created_at: SystemTime,
787 job_type_id: String,
788 target_execution_time: SystemTime,
789 retry_policy: JobRetryPolicy,
790 timeout_policy: JobTimeoutPolicy,
791 labels: IndexMap<String, String>,
792 marked_unschedulable_at: Option<SystemTime>,
793 cancelled_at: Option<SystemTime>,
794 input_payload_json: String,
795 metadata_json: Option<String>,
796}
797
798impl From<NewJob> for Job {
799 fn from(job: NewJob) -> Self {
800 Self {
801 id: job.id,
802 schedule_id: job.schedule_id,
803 created_at: job.created_at,
804 job_type_id: job.job_type_id,
805 target_execution_time: job.target_execution_time,
806 retry_policy: job.retry_policy,
807 timeout_policy: job.timeout_policy,
808 labels: job.labels,
809 input_payload_json: job.input_payload_json,
810 marked_unschedulable_at: None,
811 cancelled_at: None,
812 metadata_json: job.metadata_json,
813 }
814 }
815}
816
817#[derive(Debug, Clone)]
818struct Execution {
819 id: Uuid,
820 job_id: Uuid,
821 target_execution_time: SystemTime,
822 executor_id: Option<Uuid>,
823 created_at: SystemTime,
824 ready_at: Option<SystemTime>,
825 assigned_at: Option<SystemTime>,
826 started_at: Option<SystemTime>,
827 succeeded_at: Option<SystemTime>,
828 failed_at: Option<SystemTime>,
829 output_payload_json: Option<String>,
830 failure_reason: Option<String>,
831}
832
833impl From<&Execution> for ExecutionDetails {
834 fn from(value: &Execution) -> Self {
835 Self {
836 id: value.id,
837 job_id: value.job_id,
838 executor_id: value.executor_id,
839 status: if value.succeeded_at.is_some() {
840 JobExecutionStatus::Succeeded
841 } else if value.failed_at.is_some() {
842 JobExecutionStatus::Failed
843 } else if value.started_at.is_some() {
844 JobExecutionStatus::Running
845 } else if value.assigned_at.is_some() {
846 JobExecutionStatus::Assigned
847 } else if value.ready_at.is_some() {
848 JobExecutionStatus::Ready
849 } else {
850 JobExecutionStatus::Pending
851 },
852 created_at: value.created_at,
853 ready_at: value.ready_at,
854 assigned_at: value.assigned_at,
855 started_at: value.started_at,
856 succeeded_at: value.succeeded_at,
857 failed_at: value.failed_at,
858 output_payload_json: value.output_payload_json.clone(),
859 failure_reason: value.failure_reason.clone(),
860 }
861 }
862}
863
864#[derive(Debug, Clone)]
865struct Schedule {
866 id: Uuid,
867 created_at: SystemTime,
868 job_type_id: Option<String>,
869 labels: IndexMap<String, String>,
870 marked_unschedulable_at: Option<SystemTime>,
871 cancelled_at: Option<SystemTime>,
872 job_timing_policy: ScheduleJobTimingPolicy,
873 job_creation_policy: ScheduleJobCreationPolicy,
874 time_range: Option<ScheduleTimeRange>,
875 metadata_json: Option<String>,
876}
877
878impl From<NewSchedule> for Schedule {
879 fn from(schedule: NewSchedule) -> Self {
880 Self {
881 id: schedule.id,
882 created_at: schedule.created_at,
883 job_type_id: match &schedule.job_creation_policy {
884 ScheduleJobCreationPolicy::JobDefinition(schedule_new_job_definition) => {
885 Some(schedule_new_job_definition.job_type_id.clone())
886 }
887 },
888 labels: schedule.labels,
889 marked_unschedulable_at: None,
890 cancelled_at: None,
891 job_timing_policy: schedule.job_timing_policy,
892 job_creation_policy: schedule.job_creation_policy,
893 time_range: schedule.time_range,
894 metadata_json: schedule.metadata_json,
895 }
896 }
897}