Add targeted coverage tests to maintain >80% code coverage minimum: - ExtractInput error paths (14 tests): Type mismatch errors for Result, Option, Vec, HashMap, Arc, and primitive types. Directly exercises error formatting code that was previously uncovered. - DagRunner::get() type mismatch (6 tests): Covers the type mismatch error path when attempting to retrieve task output with wrong type. Tests various type combinations to ensure robust error handling. - TaskBuilder conversions (9 tests): Covers the From<TaskBuilder> to TaskHandle conversion for unit-type tasks, ensuring ID preservation and correct handling of mixed task types. Also fixes timing test threshold from 10ms to 50ms to account for tarpaulin instrumentation overhead while still validating parallel execution. Coverage increased from ~78% to 82.04% (above 80% minimum requirement).
101 lines
2.9 KiB
Rust
101 lines
2.9 KiB
Rust
//! Core type definitions for DAG nodes and handles.
|
|
//!
|
|
//! This module defines the fundamental types used throughout the DAG system,
|
|
//! including node identifiers, task handles, and type-state markers.
|
|
|
|
use crate::builder::{IsUnitType, TaskBuilder};
|
|
use crate::task::Task;
|
|
|
|
/// Opaque node identifier (internal use)
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
|
pub(crate) struct NodeId(pub usize);
|
|
|
|
/// Opaque, typed token for a node's output.
|
|
///
|
|
/// A `TaskHandle<T>` provides compile-time type-safe access to a task's output.
|
|
/// You can:
|
|
/// 1. Pass it to [`crate::TaskBuilder::depends_on`] to wire up dependencies
|
|
/// 2. Use it with [`crate::DagRunner::get`] to retrieve the output after [`crate::DagRunner::run`]
|
|
///
|
|
/// Handles are cheap to clone and copy.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```no_run
|
|
/// # use dagx::{task, DagRunner, Task};
|
|
/// # struct LoadValue { value: i32 }
|
|
/// # impl LoadValue { pub fn new(v: i32) -> Self { Self { value: v } } }
|
|
/// # #[task]
|
|
/// # impl LoadValue {
|
|
/// # async fn run(&mut self) -> i32 { self.value }
|
|
/// # }
|
|
/// # async {
|
|
/// let dag = DagRunner::new();
|
|
/// let node = dag.add_task(LoadValue::new(42));
|
|
///
|
|
/// dag.run(|fut| { tokio::spawn(fut); }).await.unwrap();
|
|
///
|
|
/// assert_eq!(dag.get(node).unwrap(), 42);
|
|
/// # };
|
|
/// ```
|
|
pub struct TaskHandle<T> {
|
|
pub(crate) id: NodeId,
|
|
pub(crate) _phantom: std::marker::PhantomData<fn() -> T>,
|
|
}
|
|
|
|
impl<T> Clone for TaskHandle<T> {
|
|
fn clone(&self) -> Self {
|
|
*self
|
|
}
|
|
}
|
|
|
|
impl<T> Copy for TaskHandle<T> {}
|
|
|
|
/// Type-level marker for empty dependency list.
|
|
///
|
|
/// This type appears in the type signature of [`crate::TaskBuilder<Tk, Pending>`](crate::TaskBuilder) before
|
|
/// dependencies are specified with [`crate::TaskBuilder::depends_on`].
|
|
///
|
|
/// You don't interact with this type directly; it's part of the type-state
|
|
/// pattern that ensures dependencies are wired correctly at compile time.
|
|
pub struct Pending;
|
|
|
|
// TaskBuilder can be converted to a TaskHandle ONLY if Input is () (unit type)
|
|
// This enforces at compile time that tasks with non-unit inputs MUST call .depends_on()
|
|
impl<'a, Tk: Task, Deps> From<TaskBuilder<'a, Tk, Deps>> for TaskHandle<Tk::Output>
|
|
where
|
|
Tk::Input: IsUnitType,
|
|
{
|
|
fn from(node: TaskBuilder<'a, Tk, Deps>) -> Self {
|
|
TaskHandle {
|
|
id: node.id,
|
|
_phantom: std::marker::PhantomData,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a, Tk: Task, Deps> From<&TaskBuilder<'a, Tk, Deps>> for TaskHandle<Tk::Output>
|
|
where
|
|
Tk::Input: IsUnitType,
|
|
{
|
|
fn from(node: &TaskBuilder<'a, Tk, Deps>) -> Self {
|
|
TaskHandle {
|
|
id: node.id,
|
|
_phantom: std::marker::PhantomData,
|
|
}
|
|
}
|
|
}
|
|
|
|
// TaskHandle can be converted from &TaskHandle (for .get() calls)
|
|
impl<T> From<&TaskHandle<T>> for TaskHandle<T> {
|
|
fn from(handle: &TaskHandle<T>) -> Self {
|
|
*handle
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests;
|
|
|
|
#[cfg(test)]
|
|
mod coverage_tests;
|