main
Drew Owens 0.11.1 source a674891 5d ago
#![recursion_limit = "256"]
//! Adom SOLIDWORKS bridge (`swx`) — one binary, two modes.
//!
//! ```text
//! ab (Adom Bridge) ── HTTP /command ──> swx-bridge.exe            (server mode)
//!                                          │ JSON-lines over stdio
//!                                          └─> swx-bridge.exe --com-host
//!                                              (child process holding the live
//!                                               SldWorks.Application COM object)
//! ```
//!
//! WHY a child process rather than an in-process COM thread: a wedged COM call
//! (SOLIDWORKS parked on a modal dialog, a rebuild that never returns) cannot be
//! killed as a thread — it can be killed as a process. The server owns the
//! child's lifecycle: lazy spawn, one request in flight, per-request timeout,
//! kill + respawn after a crash or a hang, stderr tail surfaced in `detail`.
//!
//! Non-Windows (or `SWX_MOCK=1`) swaps the real COM backend for the mock
//! backend, so the whole verb surface — routing, hints, error shapes, BOM/CSV
//! plumbing, the stdio protocol itself — is testable with no SOLIDWORKS and no
//! Windows.
//!
//! Bridge SDK contracts honored here: `--port N` off argv; bind ADOM_BIND_HOST
//! (loopback, never 0.0.0.0) FIRST, before anything slow, as the single-instance
//! guard; `/status` + `/health` with led/summary/tooltip; a rich `_hint` on every
//! verb response; `swx_uninstall` removes only what this bridge created; the
//! scratch dir is pruned; caller identity is logged (never trusted for auth).

pub mod abwin;
pub mod bom;
pub mod caller;
pub mod compat;
pub mod cswa;
pub mod comhost;
pub mod detect;
pub mod findfiles;
pub mod httpc;
pub mod jobs;
pub mod launch;
pub mod launchos;
pub mod modeling;
pub mod ledger;
pub mod memory;
pub mod owned;
pub mod perflog;
pub mod presence;
pub mod quit;
pub mod readiness;
pub mod render;
pub mod dialogs;
pub mod disk;
pub mod selftest;
pub mod scratch;
pub mod signin;
pub mod sha256;
pub mod script;
pub mod server;
pub mod session_history;
pub mod verbs;
pub mod verbs3;
pub mod visualize;
pub mod writes;

/// The bridge version ab reports and every response carries.
///
/// Cargo.toml is the single source of truth; the BRIDGE_VERSION file (which the
/// packaging scripts and bridge.json track) is checked against it AT COMPILE
/// TIME below, so the two can never drift apart unnoticed.
pub const BRIDGE_VERSION: &str = env!("CARGO_PKG_VERSION");

const _: () = {
    const FILE_VERSION: &str = include_str!("../BRIDGE_VERSION");
    const fn eq(a: &str, b: &str) -> bool {
        let (a, b) = (a.as_bytes(), b.as_bytes());
        if a.len() != b.len() {
            return false;
        }
        let mut i = 0;
        while i < a.len() {
            if a[i] != b[i] {
                return false;
            }
            i += 1;
        }
        true
    }
    assert!(
        eq(BRIDGE_VERSION, FILE_VERSION.trim_ascii()),
        "BRIDGE_VERSION file does not match the Cargo.toml package version — bump both together"
    );
};

/// True when the bridge serves the mock backend instead of real COM: an
/// explicit `SWX_MOCK=1`, or any non-Windows host (where SOLIDWORKS cannot
/// exist). Server and com-host child agree because they read the same rule
/// from the same inherited environment.
pub fn is_mock() -> bool {
    std::env::var("SWX_MOCK").as_deref() == Ok("1") || !cfg!(windows)
}

/// Entry point for both modes. `swx-bridge --com-host` becomes the COM child;
/// anything else is the HTTP bridge server.
pub fn run() {
    let args: Vec<String> = std::env::args().skip(1).collect();
    if args.iter().any(|a| a == "--com-host") {
        comhost::host::run();
    } else {
        server::run(parse_port_arg(&args));
    }
}

/// `--port N` / `--port=N` off argv. ab always passes it; the fallback only
/// matters when a developer runs the binary by hand.
pub fn parse_port_arg(args: &[String]) -> Option<u16> {
    let mut it = args.iter();
    while let Some(a) = it.next() {
        if a == "--port" {
            return it.next().and_then(|s| s.parse().ok());
        }
        if let Some(rest) = a.strip_prefix("--port=") {
            return rest.parse().ok();
        }
    }
    None
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn port_arg_forms() {
        let s = |v: &[&str]| v.iter().map(|x| x.to_string()).collect::<Vec<_>>();
        assert_eq!(parse_port_arg(&s(&["--port", "1234"])), Some(1234));
        assert_eq!(parse_port_arg(&s(&["--port=99"])), Some(99));
        assert_eq!(parse_port_arg(&s(&["--com-host"])), None);
        assert_eq!(parse_port_arg(&s(&["--port", "not-a-port"])), None);
    }
}