1use std::collections::{BTreeMap, BTreeSet};
2
3use harn_builtin_meta::Ty;
4use serde::{Deserialize, Serialize};
5
6use super::diagnostic;
7use super::resource::{validate_data_value, validate_json_value};
8use crate::{type_contract::manifest_signature_is_portable, Diagnostic};
9
10const MAX_JSON_SAFE_INTEGER: i64 = 9_007_199_254_740_991;
11
12#[derive(Debug, Clone, PartialEq)]
13pub enum DataValue {
14 Nil,
15 Bool(bool),
16 Int(i64),
17 Float(f64),
18 String(String),
19 Bytes(Vec<u8>),
20 List(Vec<DataValue>),
21 Record(BTreeMap<String, DataValue>),
22}
23
24impl Serialize for DataValue {
29 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
30 where
31 S: serde::Serializer,
32 {
33 self.to_json().serialize(serializer)
34 }
35}
36
37impl<'de> Deserialize<'de> for DataValue {
38 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
39 where
40 D: serde::Deserializer<'de>,
41 {
42 let value = serde_json::Value::deserialize(deserializer)?;
43 Self::from_json(value).map_err(|diagnostic| {
44 serde::de::Error::custom(format!("{}: {}", diagnostic.code, diagnostic.message))
45 })
46 }
47}
48
49impl DataValue {
50 pub fn from_json(value: serde_json::Value) -> Result<Self, Diagnostic> {
51 validate_json_value(&value)?;
52 Self::from_json_validated(value)
53 }
54
55 fn from_json_validated(value: serde_json::Value) -> Result<Self, Diagnostic> {
56 Ok(match value {
57 serde_json::Value::Null => Self::Nil,
58 serde_json::Value::Bool(value) => Self::Bool(value),
59 serde_json::Value::Number(value) => {
60 if let Some(value) = value.as_i64() {
61 Self::Int(value)
62 } else {
63 Self::Float(value.as_f64().ok_or_else(|| {
64 diagnostic(
65 "input_number",
66 "JSON number is outside Harn's numeric range",
67 )
68 })?)
69 }
70 }
71 serde_json::Value::String(value) => Self::String(value),
72 serde_json::Value::Array(values) => Self::List(
73 values
74 .into_iter()
75 .map(Self::from_json_validated)
76 .collect::<Result<_, _>>()?,
77 ),
78 serde_json::Value::Object(entries) => {
79 if entries.len() == 1 {
80 let (tag, tagged) = entries.iter().next().expect("single entry");
81 match (tag.as_str(), tagged) {
82 ("$int", serde_json::Value::String(value)) => {
83 return value.parse::<i64>().map(Self::Int).map_err(|_| {
84 diagnostic(
85 "input_integer",
86 "tagged integer is outside the i64 range",
87 )
88 });
89 }
90 ("$int", _) => {
91 return Err(diagnostic(
92 "input_integer",
93 "tagged integer must contain a decimal string",
94 ));
95 }
96 ("$float", serde_json::Value::String(value)) => {
97 return match value.as_str() {
98 "nan" => Ok(Self::Float(f64::NAN)),
99 "infinity" => Ok(Self::Float(f64::INFINITY)),
100 "-infinity" => Ok(Self::Float(f64::NEG_INFINITY)),
101 _ => Err(diagnostic(
102 "input_float",
103 "tagged float must be nan, infinity, or -infinity",
104 )),
105 };
106 }
107 ("$float", _) => {
108 return Err(diagnostic(
109 "input_float",
110 "tagged float must contain a string",
111 ));
112 }
113 ("$bytes", serde_json::Value::Array(values)) => {
114 let mut bytes = Vec::with_capacity(values.len());
115 for value in values {
116 let Some(value) =
117 value.as_u64().and_then(|value| u8::try_from(value).ok())
118 else {
119 return Err(diagnostic(
120 "input_bytes",
121 "tagged bytes must contain integers from 0 through 255",
122 ));
123 };
124 bytes.push(value);
125 }
126 return Ok(Self::Bytes(bytes));
127 }
128 ("$bytes", _) => {
129 return Err(diagnostic(
130 "input_bytes",
131 "tagged bytes must contain an integer array",
132 ));
133 }
134 _ => {}
135 }
136 }
137 Self::Record(
138 entries
139 .into_iter()
140 .map(|(key, value)| Ok((key, Self::from_json_validated(value)?)))
141 .collect::<Result<_, Diagnostic>>()?,
142 )
143 }
144 })
145 }
146
147 pub fn to_json(&self) -> serde_json::Value {
148 match self {
149 Self::Nil => serde_json::Value::Null,
150 Self::Bool(value) => (*value).into(),
151 Self::Int(value) if value.unsigned_abs() <= MAX_JSON_SAFE_INTEGER as u64 => {
152 (*value).into()
153 }
154 Self::Int(value) => serde_json::json!({"$int": value.to_string()}),
155 Self::Float(value) if value.is_nan() => serde_json::json!({"$float": "nan"}),
156 Self::Float(value) if *value == f64::INFINITY => {
157 serde_json::json!({"$float": "infinity"})
158 }
159 Self::Float(value) if *value == f64::NEG_INFINITY => {
160 serde_json::json!({"$float": "-infinity"})
161 }
162 Self::Float(value) => serde_json::Number::from_f64(*value)
163 .map(serde_json::Value::Number)
164 .expect("finite floats are representable as JSON numbers"),
165 Self::String(value) => value.clone().into(),
166 Self::Bytes(value) => serde_json::json!({"$bytes": value}),
167 Self::List(values) => {
168 serde_json::Value::Array(values.iter().map(Self::to_json).collect())
169 }
170 Self::Record(entries) => serde_json::Value::Object(
171 entries
172 .iter()
173 .map(|(key, value)| (key.clone(), value.to_json()))
174 .collect(),
175 ),
176 }
177 }
178
179 pub(super) fn validate(&self) -> Result<(), Diagnostic> {
180 validate_data_value(self)
181 }
182}
183
184#[derive(Clone, Default, PartialEq, Eq)]
185pub struct GrantSet {
186 grants: BTreeSet<String>,
187 snapshot_key: Option<[u8; 32]>,
188}
189
190impl GrantSet {
191 pub fn pure() -> Self {
192 Self::default()
193 }
194
195 pub fn from_names(names: impl IntoIterator<Item = String>) -> Result<Self, Diagnostic> {
196 let grants = names.into_iter().collect::<BTreeSet<_>>();
197 for grant in &grants {
198 let Some((capability, operation)) = grant.split_once('.') else {
199 return Err(diagnostic(
200 "invalid_capability_grant",
201 format!("grant `{grant}` must name one exact capability operation"),
202 ));
203 };
204 let Some(contract) =
205 harn_capability_contracts::capability_method_entry(capability, operation)
206 else {
207 return Err(diagnostic(
208 "unknown_capability_grant",
209 format!("grant `{grant}` is not in the canonical capability registry"),
210 ));
211 };
212 if !manifest_signature_is_portable(contract.signature) {
213 return Err(diagnostic(
214 "unsupported_portable_capability_type",
215 format!(
216 "grant `{grant}` uses a capability type outside the portable value contract"
217 ),
218 ));
219 }
220 }
221 Ok(Self {
222 grants,
223 snapshot_key: None,
224 })
225 }
226
227 pub fn from_host_json(json: &str) -> Result<Self, Diagnostic> {
233 #[derive(Deserialize)]
234 #[serde(rename_all = "camelCase", deny_unknown_fields)]
235 struct HostGrants {
236 capabilities: Vec<String>,
237 snapshot_key: Option<Vec<u8>>,
238 }
239
240 let input: HostGrants = serde_json::from_str(json).map_err(|error| {
241 diagnostic(
242 "invalid_capability_grants",
243 format!("invalid grants JSON: {error}"),
244 )
245 })?;
246 let grants = Self::from_names(input.capabilities)?;
247 match input.snapshot_key {
248 Some(snapshot_key) => {
249 let key = decode_snapshot_key(&snapshot_key)?;
250 Ok(grants.with_snapshot_key(key))
251 }
252 None => Ok(grants),
253 }
254 }
255
256 pub fn with_snapshot_key(mut self, key: [u8; 32]) -> Self {
261 self.snapshot_key = Some(key);
262 self
263 }
264
265 pub(super) fn snapshot_key(&self) -> Option<&[u8; 32]> {
266 self.snapshot_key.as_ref()
267 }
268
269 pub(super) fn fingerprint(&self) -> [u8; 32] {
270 let mut hasher = blake3::Hasher::new();
271 for grant in &self.grants {
272 hasher.update(grant.as_bytes());
273 hasher.update(&[0]);
274 }
275 *hasher.finalize().as_bytes()
276 }
277
278 pub fn allows(&self, capability: &str, operation: &str) -> bool {
279 self.grants.contains(&format!("{capability}.{operation}"))
280 }
281}
282
283fn decode_snapshot_key(encoded: &[u8]) -> Result<[u8; 32], Diagnostic> {
284 if encoded.len() != 32 {
285 return Err(diagnostic(
286 "invalid_snapshot_key",
287 "snapshotKey must contain exactly 32 bytes",
288 ));
289 }
290 let mut key = [0_u8; 32];
291 key.copy_from_slice(encoded);
292 Ok(key)
293}
294
295impl std::fmt::Debug for GrantSet {
296 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
297 formatter
298 .debug_struct("GrantSet")
299 .field("grants", &self.grants)
300 .field(
301 "snapshot_key",
302 &self.snapshot_key.is_some().then_some("<redacted>"),
303 )
304 .finish()
305 }
306}
307
308#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
309pub struct CapabilityRequest {
310 pub id: String,
311 pub capability: String,
312 pub operation: String,
313 pub arguments: DataValue,
314 pub expected: ValueShape,
315}
316
317#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
318#[serde(rename_all = "snake_case")]
319pub enum ValueShape {
320 Any,
321 Nil,
322 Bool,
323 Int,
324 Float,
325 String,
326 Bytes,
327 List,
328 Record,
329}
330
331impl ValueShape {
332 pub(super) fn from_type(ty: Ty) -> Self {
333 match ty {
334 Ty::Named("nil") | Ty::Never => Self::Nil,
335 Ty::Named("bool") => Self::Bool,
336 Ty::Named("int") | Ty::LitInt(_) => Self::Int,
337 Ty::Named("float") => Self::Float,
338 Ty::Named("string") | Ty::LitString(_) => Self::String,
339 Ty::Named("bytes") => Self::Bytes,
340 Ty::Named("list") | Ty::Apply("list" | "List", _) => Self::List,
341 Ty::Named("dict" | "record") | Ty::Shape(_) => Self::Record,
342 Ty::Optional(_)
343 | Ty::Any
344 | Ty::Generic(_)
345 | Ty::Named(_)
346 | Ty::Apply(_, _)
347 | Ty::Union(_)
348 | Ty::Fn(_, _)
349 | Ty::SchemaOf(_) => Self::Any,
350 }
351 }
352}
353
354#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
355#[serde(tag = "status", rename_all = "snake_case")]
356pub enum CapabilityResult {
357 Ok {
358 request_id: String,
359 value: DataValue,
360 },
361 Err {
362 request_id: String,
363 code: String,
364 message: String,
365 },
366}
367
368impl CapabilityResult {
369 pub(super) fn request_id(&self) -> &str {
370 match self {
371 Self::Ok { request_id, .. } | Self::Err { request_id, .. } => request_id,
372 }
373 }
374}
375
376#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
377#[serde(tag = "status", rename_all = "snake_case")]
378pub enum Execution {
379 Completed {
380 value: DataValue,
381 },
382 Suspended {
383 request: CapabilityRequest,
384 snapshot: Vec<u8>,
385 },
386 Failed {
387 diagnostic: Diagnostic,
388 },
389}
390
391pub(super) fn value_kind(value: &DataValue) -> &'static str {
392 match value {
393 DataValue::Nil => "nil",
394 DataValue::Bool(_) => "bool",
395 DataValue::Int(_) => "int",
396 DataValue::Float(_) => "float",
397 DataValue::String(_) => "string",
398 DataValue::Bytes(_) => "bytes",
399 DataValue::List(_) => "list",
400 DataValue::Record(_) => "record",
401 }
402}