engine: refuse unknown command parameters, suggest a valid name, document heat loads and thermal materials

Every command with a schema now refuses a top-level key it does not take,
naming the key and the accepted ones (#316). A bad definition name's error
suggests the nearest valid one (#320). The load and material params docs list
the heat loads and the thermal block.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
jyvikoandClaude Opus 5.5 authored and Irakli Kourtis committed 2026-10-15 09:14:34 -04:00
1 parent 35ee88aa18
commit 7dd4043afe
4 files changed
+107 -4

No files matched your search

+15 -1
View File
@@ -171,6 +171,16 @@ pub fn find_command(id: &str) -> Option<&'static CommandSpec> {
commands().iter().find(|c| c.id == id)
}
/// Refuse a parameter the command does not take, naming the ones it does: a script that sets
/// `deformed` on `results.contour` learns it belongs elsewhere instead of being ignored.
fn check_keys(spec: &CommandSpec, params: &Value) -> Result<()> {
match spec.form.and_then(|form| schema::unknown_key(form, params)) {
Some((key, accepted)) if accepted.is_empty() => Err(EngineError::Params(format!("{} takes no parameters (got `{key}`)", spec.id))),
Some((key, accepted)) => Err(EngineError::Params(format!("{} has no parameter `{key}`; it takes {}", spec.id, accepted.join(", ")))),
None => Ok(()),
}
}
/// A new revision number, unique in the process: every state of every session has its own.
pub(crate) fn next_revision() -> u64 {
static NEXT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
@@ -269,7 +279,11 @@ impl Session {
let spec = find_command(id).ok_or_else(|| EngineError::UnknownCommand(id.chars().take(100).collect()))?;
let timer = usage::Timer::start(self.origin);
let elements = self.model.elements().len();
let result = self.availability(spec).map_err(EngineError::Unavailable).and_then(|()| self.run_command(spec, params));
let result = self
.availability(spec)
.map_err(EngineError::Unavailable)
.and_then(|()| check_keys(spec, params))
.and_then(|()| self.run_command(spec, params));
if timer.counting() {
let milestone = usage::milestone(spec.id, (elements, self.model.elements().len()));
let sample =