runa/crates/runa-dap/src/variables.rs

276 lines
10 KiB
Rust
Raw Normal View History

use std::collections::BTreeMap;
use runa_protocol::{ActorSnapshotDto, StackFrame, Value};
use serde_json::{Value as Json, json};
#[derive(Default)]
pub(crate) struct Variables {
next: u64,
values: BTreeMap<u64, (Value, String)>,
references: BTreeMap<String, u64>,
root: Option<u64>,
}
impl Variables {
pub fn clear(&mut self) {
self.values.clear();
self.references.clear();
self.root = None;
}
fn reference(&mut self, value: Value, path: String) -> u64 {
if !path.is_empty() {
if let Some(reference) = self.references.get(&path) {
return *reference;
}
self.references.insert(path.clone(), self.next + 1);
}
self.next += 1;
self.values.insert(self.next, (value, path));
self.next
}
pub fn capture(&mut self, actor: &ActorSnapshotDto) {
self.clear();
let mut metadata = plain(json!({
"mailbox_id": actor.mailbox_id,
"implementation_id": actor.implementation_id,
"incarnation": actor.incarnation,
"behavior_path": actor.behavior_path,
"status": serde_json::to_value(&actor.status).unwrap()["status"],
}));
if let Value::Record(entries) = &mut metadata {
entries.insert(
"actor_ref".to_owned(),
runa_core::ActorRef::parse(&actor.actor_ref)
.map(Value::ActorRef)
.unwrap_or_else(|| Value::String(actor.actor_ref.clone())),
);
if let runa_protocol::ActorStatusDto::Failed(message) = &actor.status {
entries.insert("failure".to_owned(), Value::String(message.clone()));
}
}
let value = record([
("committed_state", actor.state.clone()),
(
"mailbox",
record([
(
"reserved",
Value::List(actor.reserved.clone().into_iter().collect()),
),
("queued", Value::List(actor.queued.clone())),
]),
),
("config", plain(json!({"log_level": actor.log_level}))),
("metadata", metadata),
]);
self.root = Some(self.reference(value, "actor".to_owned()));
}
pub fn scope(&self) -> Result<Json, String> {
let reference = self
.root
.ok_or("Actor snapshot unavailable while running")?;
Ok(json!({
"name": "Actor snapshot (committed; not frame locals)",
"variablesReference": reference, "expensive": false, "namedVariables": 4
}))
}
pub fn frame_scope(&mut self, frame: &StackFrame) -> Json {
let value = plain(serde_json::to_value(frame).unwrap());
let reference = self.reference(value, String::new());
json!({"name": "Frame metadata (not locals)", "variablesReference": reference, "expensive": false})
}
pub fn variables(&mut self, args: &Json) -> Result<Json, String> {
let reference = super::integer(args, "variablesReference", None)?;
let (value, path) = self
.values
.get(&reference)
.cloned()
.ok_or("Stale or unknown variables reference")?;
let indexed = matches!(value, Value::List(_));
match args.get("filter") {
Some(Json::String(filter))
if (filter == "named" && indexed) || (filter == "indexed" && !indexed) =>
{
return Ok(json!({"variables": []}));
}
None => {}
Some(Json::String(filter)) if matches!(filter.as_str(), "named" | "indexed") => {}
_ => return Err("Invalid variables filter".to_owned()),
}
let entries: Vec<_> = match value {
Value::Record(entries) => entries.into_iter().collect(),
Value::List(entries) => entries
.into_iter()
.enumerate()
.map(|(index, value)| (index.to_string(), value))
.collect(),
_ => return Err("Variable has no children".to_owned()),
};
let start = super::integer(args, "start", Some(0))? as usize;
let count = super::integer(args, "count", Some(0))? as usize;
let variables: Vec<_> = entries
.into_iter()
.skip(start)
.take(if count == 0 { usize::MAX } else { count })
.map(|(name, value)| {
let child_path = if path.is_empty() {
String::new()
} else {
format!("{path}/{}", name.replace('~', "~0").replace('/', "~1"))
};
self.variable(&name, value, child_path)
})
.collect();
Ok(json!({"variables": variables}))
}
pub fn evaluate(&mut self, args: &Json) -> Result<Json, String> {
if let Some(context) = args.get("context")
&& !matches!(context.as_str(), Some("watch" | "repl" | "variables"))
{
return Err(
"Snapshot paths are supported only in Watch, Debug Console and Variables"
.to_owned(),
);
}
let expression = args["expression"]
.as_str()
.ok_or("Expected a snapshot path")?;
let mut tokens = expression.split('/');
if expression.len() > 4096 || tokens.next() != Some("actor") || tokens.clone().count() > 64
{
return Err(
"Use actor or actor/<JSON Pointer path>; Rune expressions are unsupported"
.to_owned(),
);
}
let root = self
.root
.ok_or("Actor snapshot unavailable while running")?;
let mut value = self.values[&root].0.clone();
for token in tokens {
let mut chars = token.chars();
let mut key = String::new();
while let Some(ch) = chars.next() {
key.push(if ch == '~' {
match chars.next() {
Some('0') => '~',
Some('1') => '/',
_ => return Err("Invalid JSON Pointer escape; use ~0 or ~1".to_owned()),
}
} else {
ch
});
}
value = match value {
Value::Record(mut entries) => {
entries.remove(&key).ok_or("Snapshot field not found")?
}
Value::List(entries) => {
if key.is_empty()
|| (key.starts_with('0') && key.len() > 1)
|| !key.bytes().all(|byte| byte.is_ascii_digit())
{
return Err("Invalid snapshot list index".to_owned());
}
let index: usize = key.parse().map_err(|_| "Invalid snapshot list index")?;
entries
.get(index)
.cloned()
.ok_or("Snapshot list index out of range")?
}
_ => return Err("Snapshot path traverses a scalar value".to_owned()),
};
}
let mut variable = self.variable(expression, value, expression.to_owned());
variable["result"] = variable["value"].take();
let object = variable.as_object_mut().unwrap();
object.remove("name");
object.remove("value");
Ok(variable)
}
fn variable(&mut self, name: &str, value: Value, path: String) -> Json {
let (kind, preview, children, indexed) = match &value {
Value::Null => ("null", "null".to_owned(), 0, false),
Value::Bool(value) => ("bool", value.to_string(), 0, false),
Value::Integer(value) => ("integer", value.to_string(), 0, false),
Value::String(value) => {
let preview: String = value.chars().take(256).collect();
let preview = if preview.len() < value.len() {
format!("{}… ({} bytes)", json!(preview), value.len())
} else {
json!(preview).to_string()
};
("string", preview, 0, false)
}
Value::ActorRef(value) => ("actor_ref", format!("@{value}"), 0, false),
Value::List(entries) => (
"list",
format!("List({})", entries.len()),
entries.len(),
true,
),
Value::Record(entries) => (
"record",
format!("Record({})", entries.len()),
entries.len(),
false,
),
};
let reference = if children == 0 {
0
} else {
self.reference(value, path.clone())
};
let mut result = json!({
"name": name, "value": preview, "type": kind, "variablesReference": reference,
"presentationHint": {"attributes": ["readOnly"]}
});
if !path.is_empty() {
result["evaluateName"] = json!(path);
}
if matches!(kind, "list" | "record") {
result[if indexed {
"indexedVariables"
} else {
"namedVariables"
}] = json!(children);
}
result
}
}
fn record<const N: usize>(entries: [(&str, Value); N]) -> Value {
Value::Record(
entries
.into_iter()
.map(|(key, value)| (key.to_owned(), value))
.collect(),
)
}
fn plain(value: Json) -> Value {
match value {
Json::Null => Value::Null,
Json::Bool(value) => Value::Bool(value),
Json::Number(value) => value
.as_i64()
.map(Value::Integer)
.unwrap_or_else(|| Value::String(value.to_string())),
Json::String(value) => Value::String(value),
Json::Array(values) => Value::List(values.into_iter().map(plain).collect()),
Json::Object(values) => Value::Record(
values
.into_iter()
.map(|(key, value)| (key, plain(value)))
.collect(),
),
}
}