app
SOLIDWORKS Bridge
Public Made by Adomby adom
Launch, model and quit SOLIDWORKS from any Adom container. Nothing is ever lost.
main
Drew Owens
0.9.0: bridge data moved out of the install cache (recovery survives updates), closed-in-place records, gates route thr…
5d02ca9
20d ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
//! WHEN AN IMPORT FAILS, THE TRANSLATOR'S OWN ANSWER MUST REACH THE CALLER.
//!
//! Live, 2026-09-17, on a bridge-launched session: `LoadFile4` began raising a
//! COM exception on EVERY STEP file — a real McMaster part and the gate's own
//! fixture alike — while native `OpenDoc6` opens on the same session worked and
//! the safe close worked. So it is translator state, and the only thing that
//! could say WHICH translator state is the HRESULT.
//!
//! What the caller actually saw was:
//!
//! ```text
//! error: SOLIDWORKS could not import … (LoadFile4 opened nothing, errors=0;
//! LoadFile2 refused)
//! detail: SOLIDWORKS rejected 'LoadFile4'
//! ```
//!
//! The HRESULT, the EXCEPINFO source and description and the failing argument
//! index were all already computed — `variant.rs` puts them on the inner error —
//! and the import kept only `e.message`. A failure diagnosed from prose that
//! omits the one number the translator returned is not diagnosed at all, and
//! `swx_com_eval` is read-only by design, so there is no way to probe around it
//! (and no reason to loosen it).
mod common;
use common::{Bridge, TempDir};
use serde_json::{json, Value};
fn s(v: &Value) -> String {
serde_json::to_string_pretty(v).unwrap_or_default()
}
#[test]
fn a_failed_import_carries_the_com_failure_the_caller_needs() {
let scratch = TempDir::new("import-diagnosis");
let b = Bridge::start_in(TempDir::borrowed(&scratch.0), &[]);
b.call("swx_start", json!({}));
std::fs::write(b.scratch.join("mock-import-load4-throws"), "").unwrap();
let step = scratch.join("F-W10.step");
std::fs::write(&step, "ISO-10303-21;\n").unwrap();
let r = b.call("swx_import", json!({"path": step.to_string_lossy()}));
check!(b, "the import fails", r["success"] == false, s(&r));
// THE PROSE IS STILL THERE — it is what a person reads first.
check!(
b,
"it still says what happened in words",
r["error"].as_str().unwrap_or("").contains("could not import"),
s(&r)
);
// AND THE DETAIL NOW CARRIES THE INNER FAILURE, not just its first sentence.
let detail = r["detail"].as_str().unwrap_or("");
check!(
b,
"the detail carries the COM failure, not only 'SOLIDWORKS rejected LoadFile4'",
detail.contains("0x") && detail.len() > "SOLIDWORKS rejected 'LoadFile4'".len(),
s(&r)
);
// AND THE STRUCTURED REMAINS REACH THE CALLER, so a translator can be
// diagnosed by its HRESULT rather than by matching prose.
let host = r["hostError"].clone();
check!(b, "the structured failure survives the trip to the caller", host.is_object(), s(&r));
let l4 = host["loadFile4"].clone();
check!(b, "...with LoadFile4's own failure as a sub-object", l4.is_object(), s(&host));
check!(
b,
"...carrying the HRESULT",
l4["hresult"].as_str().unwrap_or("").starts_with("0x")
|| l4["detail"].as_str().unwrap_or("").contains("0x"),
s(&l4)
);
check!(
b,
"...and LoadFile2's own answer, so 'refused' and 'threw' are distinguishable",
host["loadFile2"]["returned"] == false,
s(&host)
);
check!(b, "...and the path it was asked for", host["path"].is_string(), s(&host));
}
/// A SUCCESSFUL VERB CARRIES NO SUCH FIELD — the diagnosis rides on the failure,
/// not on every reply.
#[test]
fn a_working_import_reports_no_host_failure() {
let scratch = TempDir::new("import-diagnosis-ok");
let b = Bridge::start_in(TempDir::borrowed(&scratch.0), &[]);
b.call("swx_start", json!({}));
let step = scratch.join("smoke.step");
std::fs::write(&step, "ISO-10303-21;\n").unwrap();
let r = b.call("swx_import", json!({"path": step.to_string_lossy()}));
check!(b, "the import succeeds", r["success"] == true, s(&r));
check!(b, "and carries no failure object", r["hostError"].is_null(), s(&r));
}
/// THE ACTUAL LIVE FAILURE, and what a caller can now do with it.
///
/// 2026-09-17: `hostError.loadFile4.detail = "Type mismatch. (0x80020005)
/// (argument index 1)"`. `rgvarg` is packed REVERSED, so argument index 1 is the
/// THIRD parameter of `LoadFile4(path, "r", importData, byref errors)` —
/// `importData` — which this bridge had passed as `VT_EMPTY` since v0.2.0. The
/// fix is one variant encoding; this test is about the other half, the half that
/// made the fix findable: a caller who can read the HRESULT and the argument
/// index can locate a bug in an API they cannot see the source of.
#[test]
fn the_type_mismatch_that_named_the_argument_reaches_the_caller() {
let scratch = TempDir::new("import-typemismatch");
let b = Bridge::start_in(TempDir::borrowed(&scratch.0), &[]);
b.call("swx_start", json!({}));
std::fs::write(b.scratch.join("mock-import-typemismatch"), "").unwrap();
let step = scratch.join("F-W10.step");
std::fs::write(&step, "ISO-10303-21;\n").unwrap();
let r = b.call("swx_import", json!({"path": step.to_string_lossy()}));
check!(b, "the import fails", r["success"] == false, s(&r));
let l4 = r["hostError"]["loadFile4"].clone();
check!(
b,
"the caller is told WHICH failure it was, by number",
l4["hresult"] == "0x80020005" || l4["detail"].as_str().unwrap_or("").contains("0x80020005"),
s(&r)
);
check!(
b,
"...and WHICH argument SOLIDWORKS objected to",
l4["argumentIndex"] == 1 || l4["detail"].as_str().unwrap_or("").contains("argument index 1"),
s(&l4)
);
check!(
b,
"...and that LoadFile2 answered rather than threw, so the two are distinguishable",
r["hostError"]["loadFile2"]["returned"] == false
&& r["hostError"]["loadFile2"]["failure"].is_null(),
s(&r["hostError"])
);
}