diagnostics.kernel takes a typed contract, as main's other diagnostics commands do

Its choice (solidcore or smlib) and its reply ({choice, environment}) get
schemas; a bad choice is refused with the handler's message.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M41G8Nua7HvuFPZkmJ
This commit is contained in:
jyviko
2026-11-02 16:58:17 -05:00
parent 3483d1af01
commit 7ad1d3e125
3 changed files with 55 additions and 3 deletions
+13 -2
View File
@@ -60,7 +60,12 @@ pub static COMMANDS: &[CommandSpec] = &[
inside it; total counts its outermost run only. Recording also starts with SOLIDCRAFT_PROFILE=1, the CLI's \
--profile and the app's preference; off, it costs nothing measurable. Read-only",
),
CommandSpec::new("diagnostics.kernel", "Geometry Kernel", kernel).icon("bug_report").noundo().params(
CommandSpec::new("diagnostics.kernel", "Geometry Kernel", crate::checked!("diagnostics.kernel", kernel))
.icon("bug_report")
.noundo()
.contract(&KERNEL_CONTRACT)
.output(output_schema_for::<crate::schema::diagnostics::KernelReply>)
.params(
"choice?: solidcore | smlib (sets which kernel answers every operation: solidcore, the default = the Rust port; \
smlib = the C++ SMLib kernel, refused as not available on a build without it, such as the web app; bodies already \
computed keep their results). SOLIDCRAFT_KERNEL, when set, wins over it. Returns {choice, environment}: choice is \
@@ -96,7 +101,12 @@ fn kernel(_: &mut Session, p: &Value) -> Result<Value> {
if let Some(v) = p.get("choice").filter(|v| !v.is_null()) {
route::set_choice(kernel_asked(v, solidcraft_kernel::smlib_built())?);
}
Ok(json!({ "choice": route::choice().id(), "environment": route::overridden() }))
use crate::schema::diagnostics::{KernelChoice, KernelReply};
let choice = match route::choice() {
route::Backend::Solidcore => KernelChoice::Solidcore,
route::Backend::Smlib => KernelChoice::Smlib,
};
serde_json::to_value(KernelReply { choice, environment: route::overridden() }).map_err(|e| bad("diagnostics.kernel", e.to_string()))
}
/// The kernel `choice` asks for, on a build that has (`built`) the C++ kernel or doesn't. A choice of
@@ -172,6 +182,7 @@ fn crash_test(_: &mut Session, p: &Value) -> Result<Value> {
const PROFILE_CONTRACT: crate::schema::TypedContract<crate::schema::diagnostics::ProfileParams> = crate::schema::TypedContract::new();
const KERNEL_CONTRACT: crate::schema::TypedContract<crate::schema::diagnostics::KernelParams> = crate::schema::TypedContract::new();
const VALIDATION_CONTRACT: crate::schema::TypedContract<crate::schema::diagnostics::ValidationParams> = crate::schema::TypedContract::new();
fn validation(s: &mut Session, p: &Value) -> Result<Value> {
+38
View File
@@ -330,6 +330,44 @@ impl From<solidcraft_kernel::Validation> for ValidationMode {
}
}
/// Read, or set, which kernel answers every operation.
#[derive(Debug, Default, Deserialize, JsonSchema)]
#[schemars(deny_unknown_fields)]
pub struct KernelParams {
/// The kernel to use from now on. Default: leave it as it is. `smlib` is refused on a build
/// without the C++ kernel; `SOLIDCRAFT_KERNEL`, when set, wins over it.
#[serde(default, deserialize_with = "kernel_choice")]
pub choice: Option<KernelChoice>,
}
/// A geometry kernel.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum KernelChoice {
/// solidcore, the Rust port of SMLib (the default).
Solidcore,
/// The C++ SMLib kernel.
Smlib,
}
fn kernel_choice<'de, D: serde::Deserializer<'de>>(d: D) -> Result<Option<KernelChoice>, D::Error> {
match Value::deserialize(d)? {
Value::Null => Ok(None),
Value::String(s) if s == "solidcore" => Ok(Some(KernelChoice::Solidcore)),
Value::String(s) if s == "smlib" => Ok(Some(KernelChoice::Smlib)),
_ => Err(serde::de::Error::custom(super::check::whole("`choice` is solidcore or smlib"))),
}
}
/// The reply of `diagnostics.kernel`.
#[derive(Debug, Serialize, JsonSchema)]
pub struct KernelReply {
/// The kernel in force now.
pub choice: KernelChoice,
/// Whether `SOLIDCRAFT_KERNEL` chose it.
pub environment: bool,
}
/// Read, or set, when SMLib's full check runs on the features computed from now on.
#[derive(Debug, Default, Deserialize, JsonSchema)]
#[schemars(deny_unknown_fields)]
@@ -2,7 +2,7 @@ use serde_json::{Value, json};
use super::{
CrashReport, CrashReportList, CrashReportParams, CrashReportsClearParams, CrashReportsCleared, CrashReportsParams, CrashTestMode,
CrashTestParams, ProfileParams, ValidationParams,
CrashTestParams, KernelParams, ProfileParams, ValidationParams,
};
use crate::Session;
use crate::schema::tests::{assert_keys, assert_strict_keeps_todays_errors, conforms};
@@ -16,6 +16,7 @@ fn schemas_and_the_key_check_agree() {
assert_keys(input_schema_for::<CrashTestParams>(), &["mode"]);
assert_keys(input_schema_for::<ProfileParams>(), &["enabled", "reset", "top", "trace"]);
assert_keys(input_schema_for::<ValidationParams>(), &["mode"]);
assert_keys(input_schema_for::<KernelParams>(), &["choice"]);
}
#[test]
@@ -29,6 +30,8 @@ fn strict_keeps_todays_errors() {
("diagnostics.profile", json!({"trace": 5})),
("diagnostics.validation", json!({"mode": "each step"})),
("diagnostics.validation", json!({"mode": true})),
("diagnostics.kernel", json!({"choice": "cpp"})),
("diagnostics.kernel", json!({"choice": 1})),
]);
}