Skip to main content

onetaskgraph_core/engine/
narrow.rs

1//! A task's priority and a task's content, each set on its own.
2//!
3//! Both verbs are one call to one source, with no compare-and-set: what lands is what was
4//! asked for, whatever the task held a moment before. Neither moves the task's status, so no
5//! delivered task is re-evaluated after one of these writes.
6//!
7//! A priority is refused here, before the source is asked, when the source declares it holds
8//! none: a source that cannot hold a priority has nowhere to put one, and asking it anyway
9//! would leave the refusal to each plugin's own wording — or, for a plugin written before
10//! priorities existed, to a write that drops it in silence.
11
12use onetaskgraph_plugin_api::Priority;
13use schemars::JsonSchema;
14use serde::{Deserialize, Serialize};
15
16use super::delivery::source_failed;
17use super::{Engine, EngineError};
18use crate::GlobalId;
19use crate::resolve::ResolvedSource;
20
21/// What `task priority set` answers with.
22#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
23pub struct TaskPrioritySet {
24    /// The task whose priority was set.
25    pub id: GlobalId,
26    /// Its priority as its source reads it back after the write.
27    pub priority: Priority,
28}
29
30/// What `task content set` answers with.
31#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
32pub struct TaskContentSet {
33    /// The task whose content was replaced.
34    pub id: GlobalId,
35}
36
37impl Engine {
38    /// Set one task's priority, and nothing else about it.
39    ///
40    /// # Errors
41    ///
42    /// Returns [`EngineError::UnknownSource`] for a source nothing configures,
43    /// [`EngineError::PriorityNotWritable`] for one with no write side,
44    /// [`EngineError::NoPriority`] for one declaring it holds no priority — neither of which
45    /// is asked — [`EngineError::NoSuchTask`] when the task is not there, and
46    /// [`EngineError::SourceFailed`] when the source refuses, a board with no option for the
47    /// priority included.
48    pub async fn set_task_priority(
49        &self,
50        id: &GlobalId,
51        priority: Priority,
52    ) -> Result<TaskPrioritySet, EngineError> {
53        let source = self.built(&id.source)?;
54        if !source.source().writes().is_supported() {
55            return Err(EngineError::PriorityNotWritable {
56                name: source.name().to_string(),
57                kind: source.kind().to_owned(),
58            });
59        }
60        // Every value, `none` included: a source that holds no priority has none to clear,
61        // and the verb asks it for a write it has no field for.
62        if !source.source().capabilities().priority.is_native() {
63            return Err(no_priority(source, &id.to_string(), priority));
64        }
65        let read = source
66            .source()
67            .set_task_priority(&id.native, priority)
68            .await
69            .map_err(|error| source_failed(source, error))?
70            .ok_or_else(|| EngineError::NoSuchTask { id: id.to_string() })?;
71        Ok(TaskPrioritySet {
72            id: id.clone(),
73            priority: read,
74        })
75    }
76
77    /// Replace one task's content with `content`, byte for byte, and nothing else about it.
78    ///
79    /// # Errors
80    ///
81    /// Returns [`EngineError::UnknownSource`] for a source nothing configures,
82    /// [`EngineError::ContentNotWritable`] for one with no write side, which is not asked,
83    /// [`EngineError::NoSuchTask`] when the task is not there, and
84    /// [`EngineError::SourceFailed`] when the source refuses.
85    pub async fn set_task_content(
86        &self,
87        id: &GlobalId,
88        content: &str,
89    ) -> Result<TaskContentSet, EngineError> {
90        let source = self.built(&id.source)?;
91        if !source.source().writes().is_supported() {
92            return Err(EngineError::ContentNotWritable {
93                name: source.name().to_string(),
94                kind: source.kind().to_owned(),
95            });
96        }
97        source
98            .source()
99            .set_task_content(&id.native, content)
100            .await
101            .map_err(|error| source_failed(source, error))?
102            .ok_or_else(|| EngineError::NoSuchTask { id: id.to_string() })?;
103        Ok(TaskContentSet { id: id.clone() })
104    }
105}
106
107/// Refuse `priority` for `task` when `source` declares it holds no priority at all.
108///
109/// `none` always passes: it is what such a source already reports for every task, so a write
110/// carrying it asks the source for nothing it cannot hold. That is what lets a copy carrying
111/// `none` into such a source write exactly as it did before priorities existed.
112pub(super) fn holds_priority(
113    source: &ResolvedSource,
114    task: &str,
115    priority: Priority,
116) -> Result<(), EngineError> {
117    if priority == Priority::None || source.source().capabilities().priority.is_native() {
118        return Ok(());
119    }
120    Err(no_priority(source, task, priority))
121}
122
123/// The refusal of `priority` for `task` at a source that holds none.
124fn no_priority(source: &ResolvedSource, task: &str, priority: Priority) -> EngineError {
125    EngineError::NoPriority {
126        name: source.name().to_string(),
127        kind: source.kind().to_owned(),
128        task: task.to_owned(),
129        priority,
130    }
131}