Improve behavior authoring and typed trace history (untested checkpoint)
Rust checks / check (ubuntu-latest) (push) Canceled after 0s
Rust checks / check (windows-latest) (push) Canceled after 0s
Rust checks / check (ubuntu-latest) (push) Canceled after 0s
Rust checks / check (windows-latest) (push) Canceled after 0s
This commit is contained in:
@@ -16,6 +16,7 @@ use serde_json::{Value, json};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
mod context_fields;
|
||||
mod data_fields;
|
||||
mod history_fields;
|
||||
mod port_fields;
|
||||
mod view_fields;
|
||||
|
||||
@@ -50,6 +51,14 @@ pub(crate) fn set_shape_value(record: &mut Value, path: &str, value: Value) -> R
|
||||
*target = value;
|
||||
return Ok(());
|
||||
}
|
||||
if history_fields::is_path(path)
|
||||
&& history_fields::has_field(path)
|
||||
&& let Some((parent, field)) = path.rsplit_once('/')
|
||||
&& let Some(fields) = record.pointer_mut(parent).and_then(Value::as_object_mut)
|
||||
{
|
||||
fields.insert(field.into(), value);
|
||||
return Ok(());
|
||||
}
|
||||
// Ordinary messages omit operation=None in source/JSON. Authoring that one
|
||||
// optional field must not require changing the historic record encoding.
|
||||
if let Some(parent) = path.strip_suffix("/operation")
|
||||
@@ -75,6 +84,7 @@ pub struct Draft {
|
||||
port_contract_drafts: BTreeMap<String, Value>,
|
||||
pub value_browsers: BTreeMap<String, ValueBrowser>,
|
||||
pub reference_browsers: BTreeMap<String, ReferenceBrowser>,
|
||||
history: history_fields::State,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
@@ -156,6 +166,7 @@ impl Draft {
|
||||
})
|
||||
.collect();
|
||||
draft.port_contract_drafts = saved.ports;
|
||||
draft.history.open = draft.kind == RecordKind::Trace;
|
||||
Ok(draft)
|
||||
}
|
||||
pub fn from_project(project: &Project, selected: Id) -> Option<Self> {
|
||||
@@ -197,6 +208,7 @@ impl Draft {
|
||||
port_contract_drafts: BTreeMap::new(),
|
||||
value_browsers: BTreeMap::new(),
|
||||
reference_browsers: BTreeMap::new(),
|
||||
history: Default::default(),
|
||||
};
|
||||
draft.rebuild();
|
||||
Some(draft)
|
||||
@@ -311,9 +323,38 @@ impl Draft {
|
||||
.map_or(true, |value| value != self.baseline)
|
||||
}
|
||||
pub fn set_exact(&mut self, enabled: bool) -> Result<(), String> {
|
||||
self.value = self.materialize()?;
|
||||
let value = self.materialize()?;
|
||||
let mut history_inputs = Vec::new();
|
||||
if enabled != self.exact_strings {
|
||||
for (key, (_, inputs)) in &self.constructor_drafts {
|
||||
if self.kind != RecordKind::Trace || !history_fields::is_path(&key.0) {
|
||||
continue;
|
||||
}
|
||||
for (path, input) in inputs {
|
||||
if free_string(path) {
|
||||
let raw = if enabled {
|
||||
serde_json::to_string(&input.raw).expect("String serialization")
|
||||
} else {
|
||||
serde_json::from_str::<String>(&input.raw).map_err(|_| "Repair the quoted text in the retained history constructor before changing escaped-string mode.".to_owned())?
|
||||
};
|
||||
history_inputs.push((key.clone(), path.clone(), raw));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.value = value;
|
||||
self.exact_strings = enabled;
|
||||
self.rebuild();
|
||||
for (key, path, raw) in history_inputs {
|
||||
if let Some(input) = self
|
||||
.constructor_drafts
|
||||
.get_mut(&key)
|
||||
.and_then(|(_, inputs)| inputs.get_mut(&path))
|
||||
{
|
||||
input.editor = text_editor::Content::with_text(&raw);
|
||||
input.raw = raw;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn input(&mut self, path: &str, value: String) {
|
||||
@@ -329,6 +370,29 @@ impl Draft {
|
||||
}
|
||||
}
|
||||
pub fn shape(&mut self, path: &str, value: Value) -> Result<(), String> {
|
||||
if self.kind == RecordKind::Trace {
|
||||
if path == history_fields::TOGGLE {
|
||||
self.history.open = value
|
||||
.as_bool()
|
||||
.ok_or("Choose whether to show stored history.")?;
|
||||
return Ok(());
|
||||
}
|
||||
if let Some(path) = path.strip_prefix(history_fields::PAGE) {
|
||||
self.history.pages.insert(
|
||||
path.into(),
|
||||
value.as_u64().ok_or("Choose a history page.")? as usize,
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
if let Some(path) = path.strip_prefix(history_fields::VARIANT) {
|
||||
return self.variant_scoped(path, value);
|
||||
}
|
||||
if history_fields::is_path(path) {
|
||||
self.constructor_drafts
|
||||
.retain(|(root, _), _| !within(root, path));
|
||||
return self.shape_scoped(path, value);
|
||||
}
|
||||
}
|
||||
let mut candidate = self.materialize()?;
|
||||
let value = if path.ends_with("/Port/contract") {
|
||||
port_fields::switch_contract(
|
||||
@@ -369,7 +433,8 @@ impl Draft {
|
||||
|| path.ends_with("/Occurrence/body")
|
||||
|| path.ends_with("/Event/body")
|
||||
|| path.ends_with("/message_kind")
|
||||
|| path.ends_with("/ContextualLink/purpose");
|
||||
|| path.ends_with("/ContextualLink/purpose")
|
||||
|| (self.kind == RecordKind::Trace && history_fields::is_path(path));
|
||||
if !constructor {
|
||||
return self.shape_scoped(path, proposed);
|
||||
}
|
||||
@@ -448,7 +513,7 @@ impl Draft {
|
||||
.len();
|
||||
edit_array(&mut candidate, path, index, direction)?;
|
||||
self.value = candidate;
|
||||
self.rebuild_retaining(|old| {
|
||||
let relocate = |old: &str| {
|
||||
let Some(tail) = old.strip_prefix(&format!("{path}/")) else {
|
||||
return Some(old.to_owned());
|
||||
};
|
||||
@@ -474,7 +539,24 @@ impl Draft {
|
||||
} else {
|
||||
format!("{path}/{position}/{rest}")
|
||||
})
|
||||
});
|
||||
};
|
||||
self.rebuild_retaining(relocate);
|
||||
if self.kind == RecordKind::Trace && history_fields::is_path(path) {
|
||||
let previous = std::mem::take(&mut self.constructor_drafts);
|
||||
for ((root, tag), (value, inputs)) in previous {
|
||||
if !root.starts_with(&format!("{path}/")) {
|
||||
self.constructor_drafts.insert((root, tag), (value, inputs));
|
||||
continue;
|
||||
}
|
||||
if let Some(root) = relocate(&root) {
|
||||
let inputs = inputs
|
||||
.into_iter()
|
||||
.filter_map(|(path, input)| relocate(&path).map(|path| (path, input)))
|
||||
.collect();
|
||||
self.constructor_drafts.insert((root, tag), (value, inputs));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn append_scoped(&mut self, path: &str, value: Value) -> Result<(), String> {
|
||||
@@ -510,6 +592,9 @@ impl Draft {
|
||||
index: Option<usize>,
|
||||
direction: i32,
|
||||
) -> Result<(), String> {
|
||||
if self.kind == RecordKind::Trace && history_fields::is_path(path) {
|
||||
return self.array_scoped(path, index, direction);
|
||||
}
|
||||
let mut candidate = self.materialize()?;
|
||||
edit_array(&mut candidate, path, index, direction)?;
|
||||
self.value = candidate;
|
||||
@@ -517,6 +602,9 @@ impl Draft {
|
||||
Ok(())
|
||||
}
|
||||
pub fn append(&mut self, path: &str, value: Value) -> Result<(), String> {
|
||||
if self.kind == RecordKind::Trace && history_fields::is_path(path) {
|
||||
return self.append_scoped(path, value);
|
||||
}
|
||||
let mut candidate = self.materialize()?;
|
||||
candidate
|
||||
.pointer_mut(path)
|
||||
@@ -625,6 +713,9 @@ fn edit_array(
|
||||
index: Option<usize>,
|
||||
direction: i32,
|
||||
) -> Result<(), String> {
|
||||
if history_fields::is_path(path) && index.is_none() {
|
||||
return Err("Choose a typed stored fact or list value before adding it.".into());
|
||||
}
|
||||
let items = value
|
||||
.pointer_mut(path)
|
||||
.and_then(Value::as_array_mut)
|
||||
@@ -807,6 +898,9 @@ fn multiline(path: &str) -> bool {
|
||||
)
|
||||
}
|
||||
fn free_string(path: &str) -> bool {
|
||||
if history_fields::is_path(path) {
|
||||
return history_fields::free_string(path);
|
||||
}
|
||||
multiline(path)
|
||||
|| matches!(
|
||||
path.rsplit('/').next(),
|
||||
@@ -1728,6 +1822,9 @@ fn property<'a>(
|
||||
label: &str,
|
||||
skip_variant: bool,
|
||||
) -> View<'a, Message> {
|
||||
if draft.kind == RecordKind::Trace && path == "/baseline" {
|
||||
return history_fields::view(draft, project, value);
|
||||
}
|
||||
if let Some(view) = context_fields::property(draft, project, value, path) {
|
||||
return view;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user