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}