modewise_pipeline/
operation.rs

1use std::collections::BTreeMap;
2use std::sync::Arc;
3
4use serde::Serialize;
5use serde::de::DeserializeOwned;
6use serde_json::Value;
7
8use crate::{Artifact, ArtifactKind};
9
10/// A named input or output. Inputs may accept several kinds. An output lists one kind, or
11/// several when `Operation::output_kind` picks one from the kinds of the inputs.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct Port {
14    pub name: &'static str,
15    pub kinds: &'static [ArtifactKind],
16}
17
18impl Port {
19    /// The kind an output port produces.
20    pub fn output_kind(&self) -> ArtifactKind {
21        self.kinds.first().copied().unwrap_or(ArtifactKind::Series)
22    }
23}
24
25#[derive(Debug, Clone, Copy)]
26pub struct OperationDescriptor {
27    pub name: &'static str,
28    /// Part of every cache key. Bump it when results of the operation change.
29    pub version: &'static str,
30    pub inputs: &'static [Port],
31    pub outputs: &'static [Port],
32}
33
34pub type Inputs = BTreeMap<String, Arc<Artifact>>;
35pub type Outputs = BTreeMap<String, Artifact>;
36
37/// One kind of pipeline step, for example a decomposition method.
38pub trait Operation: Send + Sync {
39    fn descriptor(&self) -> OperationDescriptor;
40
41    /// Checks parameters and fills in defaults. The result is what gets cached and recorded.
42    fn resolve(&self, params: Value) -> Result<Value, String>;
43
44    /// Runs on parameters that `resolve` returned.
45    fn run(&self, params: &Value, inputs: &Inputs) -> Result<Outputs, String>;
46
47    /// The kind an output will have, given the kinds bound to each input. By default the first
48    /// kind the port lists; operations whose output follows their input override this.
49    fn output_kind(&self, port: &Port, _inputs: &BTreeMap<String, ArtifactKind>) -> ArtifactKind {
50        port.output_kind()
51    }
52
53    /// One sentence on what the operation does, for `modewise ops` and the generated reference.
54    fn summary(&self) -> &'static str {
55        ""
56    }
57
58    /// JSON Schema of the parameters: types, descriptions from doc comments, and defaults. The
59    /// generated reference (docs/reference/operations.md) and tools read it.
60    fn params_schema(&self) -> Value {
61        Value::Object(Default::default())
62    }
63}
64
65/// The JSON Schema of a parameter struct.
66pub fn schema_of<T: schemars::JsonSchema>() -> Value {
67    schemars::schema_for!(T).to_value()
68}
69
70/// Round-trips raw parameters through a typed struct, which applies defaults and rejects unknown fields.
71pub fn resolve_params<T: DeserializeOwned + Serialize>(
72    params: Value,
73) -> Result<(T, Value), String> {
74    let typed: T = serde_json::from_value(params).map_err(|e| e.to_string())?;
75    let resolved = serde_json::to_value(&typed).map_err(|e| e.to_string())?;
76    Ok((typed, resolved))
77}