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    /// Bytes this value occupies on the wire (upper bound; used for budgets).
81    pub fn wire_size(&self) -> u64 {
82        match self {
83            Value::Null => 1,
84            Value::Bool(_) => 2,
85            Value::Int(_) | Value::Float(_) => 9,
86            Value::Text(text) | Value::Path(text) => 5 + text.len() as u64,
87            Value::Secret => 1,
88            Value::Module(bytes) => 5 + bytes.len() as u64,
89            Value::List(items) => 5 + items.iter().map(Value::wire_size).sum::<u64>(),
90            Value::Map(entries) => {
91                5 + entries
92                    .iter()
93                    .map(|(key, value)| 5 + key.len() as u64 + value.wire_size())
94                    .sum::<u64>()
95            }
96        }
97    }
98
99    /// True when this value or any descendant is a secret marker.
100    pub fn is_tainted(&self) -> bool {
101        match self {
102            Value::Secret => true,
103            Value::List(items) => items.iter().any(Value::is_tainted),
104            Value::Map(entries) => entries.values().any(Value::is_tainted),
105            _ => false,
106        }
107    }
108}
109
110/// One call argument.
111#[derive(Debug, Clone, PartialEq)]
112pub enum Arg {
113    /// A concrete value.
114    Value(Value),
115    /// A promise: the call is pipelined and starts as soon as it resolves.
116    Promise(PromiseId),
117    /// A capability handle (capability passing).
118    Cap(CapId),
119}
120
121impl Arg {
122    /// Build a value argument.
123    pub fn value(value: Value) -> Arg {
124        Arg::Value(value)
125    }
126
127    /// Build a text argument.
128    pub fn text(value: impl Into<String>) -> Arg {
129        Arg::Value(Value::Text(value.into()))
130    }
131
132    /// The promise this argument waits for, if any.
133    pub fn promise(&self) -> Option<PromiseId> {
134        match self {
135            Arg::Promise(id) => Some(*id),
136            _ => None,
137        }
138    }
139
140    /// Bytes this argument occupies on the wire (used for budget accounting).
141    pub fn wire_size(&self) -> u64 {
142        match self {
143            Arg::Value(value) => value.wire_size(),
144            Arg::Promise(_) => 8,
145            Arg::Cap(_) => 16,
146        }
147    }
148}
149
150/// A request as a provider sees it.
151#[derive(Debug, Clone, PartialEq)]
152pub struct Call {
153    /// Method name.
154    pub method: String,
155    /// Arguments in order.
156    pub args: Vec<Arg>,
157    /// Path restriction inherited from the capability, when present.
158    pub path_scope: Option<String>,
159    /// Whether the caller asked for redacted output.
160    pub redact: bool,
161    /// Whether the capability is read-only.
162    pub read_only: bool,
163    /// Whether the capability's tool policy grants process execution.
164    pub tool_allows_process: bool,
165    /// Effective budget (capability ∩ lane) for this call.
166    pub budget: crate::budget::Budget,
167}
168
169impl Call {
170    /// Build a call.
171    pub fn new(method: impl Into<String>, args: Vec<Arg>) -> Call {
172        Call {
173            method: method.into(),
174            args,
175            path_scope: None,
176            redact: false,
177            read_only: false,
178            tool_allows_process: false,
179            budget: crate::budget::Budget::default(),
180        }
181    }
182
183    /// The first argument, when present.
184    pub fn first(&self) -> Option<&Arg> {
185        self.args.first()
186    }
187}
188
189/// What a provider returns.
190#[derive(Debug, Clone, PartialEq)]
191pub enum Response {
192    /// A plain value.
193    Value(Value),
194    /// A serialized module (already canonical and validated).
195    Module(Vec<u8>),
196    /// An ordered list of labels (why/impact answers).
197    Labels(Vec<String>),
198}
199
200impl Response {
201    /// Convert into a value for uniform handling by the kernel.
202    pub fn into_value(self) -> Value {
203        match self {
204            Response::Value(value) => value,
205            Response::Module(bytes) => Value::Module(bytes),
206            Response::Labels(labels) => Value::List(labels.into_iter().map(Value::Text).collect()),
207        }
208    }
209}