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 with_snapshot_key(mut self, key: [u8; 32]) -> Self {
232 self.snapshot_key = Some(key);
233 self
234 }
235
236 pub(super) fn snapshot_key(&self) -> Option<&[u8; 32]> {
237 self.snapshot_key.as_ref()
238 }
239
240 pub(super) fn fingerprint(&self) -> [u8; 32] {
241 let mut hasher = blake3::Hasher::new();
242 for grant in &self.grants {
243 hasher.update(grant.as_bytes());
244 hasher.update(&[0]);
245 }
246 *hasher.finalize().as_bytes()
247 }
248
249 pub fn allows(&self, capability: &str, operation: &str) -> bool {
250 self.grants.contains(&format!("{capability}.{operation}"))
251 }
252}
253
254impl std::fmt::Debug for GrantSet {
255 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
256 formatter
257 .debug_struct("GrantSet")
258 .field("grants", &self.grants)
259 .field(
260 "snapshot_key",
261 &self.snapshot_key.is_some().then_some("<redacted>"),
262 )
263 .finish()
264 }
265}
266
267#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
268pub struct CapabilityRequest {
269 pub id: String,
270 pub capability: String,
271 pub operation: String,
272 pub arguments: DataValue,
273 pub expected: ValueShape,
274}
275
276#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
277#[serde(rename_all = "snake_case")]
278pub enum ValueShape {
279 Any,
280 Nil,
281 Bool,
282 Int,
283 Float,
284 String,
285 Bytes,
286 List,
287 Record,
288}
289
290impl ValueShape {
291 pub(super) fn from_type(ty: Ty) -> Self {
292 match ty {
293 Ty::Named("nil") | Ty::Never => Self::Nil,
294 Ty::Named("bool") => Self::Bool,
295 Ty::Named("int") | Ty::LitInt(_) => Self::Int,
296 Ty::Named("float") => Self::Float,
297 Ty::Named("string") | Ty::LitString(_) => Self::String,
298 Ty::Named("bytes") => Self::Bytes,
299 Ty::Named("list") | Ty::Apply("list" | "List", _) => Self::List,
300 Ty::Named("dict" | "record") | Ty::Shape(_) => Self::Record,
301 Ty::Optional(_)
302 | Ty::Any
303 | Ty::Generic(_)
304 | Ty::Named(_)
305 | Ty::Apply(_, _)
306 | Ty::Union(_)
307 | Ty::Fn(_, _)
308 | Ty::SchemaOf(_) => Self::Any,
309 }
310 }
311}
312
313#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
314#[serde(tag = "status", rename_all = "snake_case")]
315pub enum CapabilityResult {
316 Ok {
317 request_id: String,
318 value: DataValue,
319 },
320 Err {
321 request_id: String,
322 code: String,
323 message: String,
324 },
325}
326
327impl CapabilityResult {
328 pub(super) fn request_id(&self) -> &str {
329 match self {
330 Self::Ok { request_id, .. } | Self::Err { request_id, .. } => request_id,
331 }
332 }
333}
334
335#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
336#[serde(tag = "status", rename_all = "snake_case")]
337pub enum Execution {
338 Completed {
339 value: DataValue,
340 },
341 Suspended {
342 request: CapabilityRequest,
343 snapshot: Vec<u8>,
344 },
345 Failed {
346 diagnostic: Diagnostic,
347 },
348}
349
350pub(super) fn value_kind(value: &DataValue) -> &'static str {
351 match value {
352 DataValue::Nil => "nil",
353 DataValue::Bool(_) => "bool",
354 DataValue::Int(_) => "int",
355 DataValue::Float(_) => "float",
356 DataValue::String(_) => "string",
357 DataValue::Bytes(_) => "bytes",
358 DataValue::List(_) => "list",
359 DataValue::Record(_) => "record",
360 }
361}