Skip to main content

active_rpc/
value.rs

1//! Values, arguments and results crossing an Active RPC boundary.
2//!
3//! The value model is small on purpose: it carries what domain operations and
4//! tools need, and it never carries secret material. A secret is represented as
5//! [`Value::Secret`] — a marker — so no code path in this crate can put secret
6//! bytes on the wire.
7
8use std::collections::BTreeMap;
9
10use crate::cap::CapId;
11use crate::promise::PromiseId;
12
13/// A value crossing the RPC boundary.
14#[derive(Debug, Clone, PartialEq)]
15pub enum Value {
16    /// Absent value.
17    Null,
18    /// Boolean.
19    Bool(bool),
20    /// Integer.
21    Int(i64),
22    /// Floating-point number.
23    Float(f64),
24    /// UTF-8 text.
25    Text(String),
26    /// Ordered list.
27    List(Vec<Value>),
28    /// Key-sorted map (deterministic wire order).
29    Map(BTreeMap<String, Value>),
30    /// Redaction marker: a secret existed here. Never carries content.
31    Secret,
32    /// A serialized Active Infinity module.
33    Module(Vec<u8>),
34    /// A dotted path inside a module.
35    Path(String),
36}
37
38impl Value {
39    /// Convenience constructor for a text value.
40    pub fn text(value: impl Into<String>) -> Value {
41        Value::Text(value.into())
42    }
43
44    /// Convenience constructor for a path value.
45    pub fn path(value: impl Into<String>) -> Value {
46        Value::Path(value.into())
47    }
48
49    /// Convenience constructor for a list.
50    pub fn list(items: impl IntoIterator<Item = Value>) -> Value {
51        Value::List(items.into_iter().collect())
52    }
53
54    /// Convenience constructor for a map (keys sorted on construction).
55    pub fn map<K: Into<String>, I: IntoIterator<Item = (K, Value)>>(entries: I) -> Value {
56        Value::Map(
57            entries
58                .into_iter()
59                .map(|(key, value)| (key.into(), value))
60                .collect(),
61        )
62    }
63
64    /// Read as text, if it is text.
65    pub fn as_text(&self) -> Option<&str> {
66        match self {
67            Value::Text(text) => Some(text),
68            _ => None,
69        }
70    }
71
72    /// Read as a path, if it is a path.
73    pub fn as_path(&self) -> Option<&str> {
74        match self {
75            Value::Path(path) => Some(path),
76            _ => None,
77        }
78    }
79
80    /// True when this value or any descendant is a secret marker.
81    pub fn is_tainted(&self) -> bool {
82        match self {
83            Value::Secret => true,
84            Value::List(items) => items.iter().any(Value::is_tainted),
85            Value::Map(entries) => entries.values().any(Value::is_tainted),
86            _ => false,
87        }
88    }
89}
90
91/// One call argument.
92#[derive(Debug, Clone, PartialEq)]
93pub enum Arg {
94    /// A concrete value.
95    Value(Value),
96    /// A promise: the call is pipelined and starts as soon as it resolves.
97    Promise(PromiseId),
98    /// A capability handle (capability passing).
99    Cap(CapId),
100}
101
102impl Arg {
103    /// Build a value argument.
104    pub fn value(value: Value) -> Arg {
105        Arg::Value(value)
106    }
107
108    /// Build a text argument.
109    pub fn text(value: impl Into<String>) -> Arg {
110        Arg::Value(Value::Text(value.into()))
111    }
112
113    /// The promise this argument waits for, if any.
114    pub fn promise(&self) -> Option<PromiseId> {
115        match self {
116            Arg::Promise(id) => Some(*id),
117            _ => None,
118        }
119    }
120}
121
122/// A request as a provider sees it.
123#[derive(Debug, Clone, PartialEq)]
124pub struct Call {
125    /// Method name.
126    pub method: String,
127    /// Arguments in order.
128    pub args: Vec<Arg>,
129    /// Path restriction inherited from the capability, when present.
130    pub path_scope: Option<String>,
131    /// Whether the caller asked for redacted output.
132    pub redact: bool,
133    /// Whether the capability is read-only.
134    pub read_only: bool,
135    /// Whether the capability's tool policy grants process execution.
136    pub tool_allows_process: bool,
137    /// Effective budget (capability ∩ lane) for this call.
138    pub budget: crate::budget::Budget,
139}
140
141impl Call {
142    /// Build a call.
143    pub fn new(method: impl Into<String>, args: Vec<Arg>) -> Call {
144        Call {
145            method: method.into(),
146            args,
147            path_scope: None,
148            redact: false,
149            read_only: false,
150            tool_allows_process: false,
151            budget: crate::budget::Budget::default(),
152        }
153    }
154
155    /// The first argument, when present.
156    pub fn first(&self) -> Option<&Arg> {
157        self.args.first()
158    }
159}
160
161/// What a provider returns.
162#[derive(Debug, Clone, PartialEq)]
163pub enum Response {
164    /// A plain value.
165    Value(Value),
166    /// A serialized module (already canonical and validated).
167    Module(Vec<u8>),
168    /// An ordered list of labels (why/impact answers).
169    Labels(Vec<String>),
170}
171
172impl Response {
173    /// Convert into a value for uniform handling by the kernel.
174    pub fn into_value(self) -> Value {
175        match self {
176            Response::Value(value) => value,
177            Response::Module(bytes) => Value::Module(bytes),
178            Response::Labels(labels) => Value::List(labels.into_iter().map(Value::Text).collect()),
179        }
180    }
181}