mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-24 10:46:50 -06:00
Report an error when file commands get no file in MCP and CLI (#135)
This commit is contained in:
@@ -145,8 +145,7 @@ impl Args {
|
||||
// ---------------------------------------------------------------- helpers
|
||||
|
||||
fn exec(s: &mut Session, id: &str, params: Value) -> Result<Value, String> {
|
||||
let r = s.execute(id, params).map_err(|e| e.to_string());
|
||||
s.take_ui_requests();
|
||||
let r = s.execute_headless(id, params).map_err(|e| e.to_string());
|
||||
if id == "file.open"
|
||||
&& let Ok(value) = &r
|
||||
&& let Some(warnings) = value.get("warnings").and_then(Value::as_array)
|
||||
|
||||
@@ -311,6 +311,25 @@ impl Session {
|
||||
pub fn take_ui_requests(&mut self) -> Vec<UiRequest> {
|
||||
std::mem::take(&mut self.ui_requests)
|
||||
}
|
||||
|
||||
/// Runs a command for a caller with no UI (MCP, CLI): queued UI requests are dropped, and a command that only
|
||||
/// queued a file dialog because it got no file (`file.open`, `file.saveAs`, `file.save` on a workbook with no
|
||||
/// file yet, `data.getData`) is an error instead of a silent success.
|
||||
pub fn execute_headless(&mut self, id: &str, params: Json) -> Result<Json> {
|
||||
let r = self.execute(id, params);
|
||||
let requests = self.take_ui_requests();
|
||||
let asked_for_file = requests.iter().any(|q| matches!(q, UiRequest::Dialog(name, _) if name == "open" || name == "saveAs"));
|
||||
if r.is_ok() && asked_for_file {
|
||||
let what = match id {
|
||||
"file.open" => "`path` or `base64`",
|
||||
"data.getData" | "data.fromTextCsv" => "`path` or `text`",
|
||||
_ => "`path`",
|
||||
};
|
||||
let msg = format!("missing {what}: programmatic calls never open dialogs");
|
||||
return Err(EngineError::BadParams { cmd: id.to_string(), msg });
|
||||
}
|
||||
r
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience: the active workbook's active sheet.
|
||||
|
||||
@@ -34,12 +34,8 @@ impl Headless {
|
||||
}
|
||||
|
||||
fn execute(&mut self, id: &str, params: Value) -> Result<Value, String> {
|
||||
let r = self.session.execute(id, params).map_err(|e| e.to_string());
|
||||
// Programmatic calls never open dialogs; drop anything a command queued for a UI.
|
||||
for req in self.session.take_ui_requests() {
|
||||
log::debug!("headless: ignoring UI request {req:?}");
|
||||
}
|
||||
r
|
||||
// Programmatic calls never open dialogs; anything a command queued for a UI is dropped.
|
||||
self.session.execute_headless(id, params).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn commands(&self, params: &Value) -> Value {
|
||||
|
||||
@@ -381,3 +381,14 @@ fn remote_backend_over_tcp() {
|
||||
handle.join().unwrap();
|
||||
assert!(Remote::connect("127.0.0.1:1").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn file_commands_without_a_file_fail() {
|
||||
let mut s = server();
|
||||
// `file.save` on Book1, which has no file yet, would ask for one too.
|
||||
for command in ["file.open", "file.saveAs", "file.save", "data.getData", "data.fromTextCsv"] {
|
||||
let msg = fails(&mut s, "execute_command", json!({"command": command}));
|
||||
assert!(msg.contains("missing") && msg.contains("never open dialogs"), "{command}: {msg}");
|
||||
fails(&mut s, "execute_command", json!({"command": command, "params": {}}));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user