smix-error 1.0.5

smix-error — ExpectationFailure + FailureCode + AI-readable to_prompt + buildSuggestions (stone, cold path).
Documentation

smix-error

Crates.io docs.rs License

AI-readable structured failure type for iOS-Simulator automation: ExpectationFailure carries the selector that failed, the top-10 visible elements at the failure moment, Levenshtein-based "Did you mean …?" suggestions, an optional hint, and an optional device-log tail — and renders all of it via to_prompt() as a multi-line block a coding agent can paste back as a user message and act on without further context.

This crate exists because every iOS-automation library has its own flavor of assertion failed, and most of them throw away the failure context that a downstream LLM needs to fix the test. smix-error is designed so the failure object itself can be the prompt.

Quickstart

use smix_error::{ExpectationFailure, FailureCode, FailureInit, build_suggestions};
use smix_screen::{ElementSummary, Rect, Role};

let visible_elements = vec![
    ElementSummary {
        role: Some(Role::Button), name: Some("Log in".into()),
        id: Some("btn-login".into()), text: None,
        bounds: Rect { x: 0.0, y: 0.0, w: 100.0, h: 40.0 }, enabled: true,
    },
    // ...
];

let suggestions = build_suggestions(Some("Login"), &visible_elements);

let failure = ExpectationFailure::new(FailureInit {
    code: Some(FailureCode::ElementNotFound),
    message: "element not found: { text: \"Login\" }".into(),
    visible_elements,
    suggestions,
    hint: Some(
        "matched 0 nodes in the current a11y tree; check selector or wait for the screen to settle".into(),
    ),
    ..Default::default()
});

println!("{}", failure.to_prompt());
// FAIL [ELEMENT_NOT_FOUND]: element not found: { text: "Login" }
//   suggestions:
//     - Did you mean "Log in"? (similarity 0.83, field name)
//   visible elements (top 1):
//     - button name="Log in" id="btn-login"
//   hint: matched 0 nodes in the current a11y tree; check selector or wait for the screen to settle

Why "AI-readable" matters

When a test fails and an LLM is the test author, the failure object arrives back at the LLM as a user message. If the failure says only "ELEMENT_NOT_FOUND" the LLM has to send another tool call to fetch the screen state — costing latency, tokens, and often the user's patience.

ExpectationFailure::to_prompt() packs all the context — selector that failed, visible elements, suggestions from edit-distance matching, optional hint, optional device-log tail — into one self-contained block. Coding agents can iterate on it immediately.

Components

Symbol Role
FailureCode 9-variant enum: ElementNotFound / NotVisible / NotEnabled / Ambiguous / Timeout / AssertionFailed / AppNotRunning / SimulatorNotBooted / DriverError
ExpectationFailure Structured failure with 8 fields, implements Display + std::error::Error
FailureInit Ergonomic builder; default-derived
False ok: false discriminator newtype (serde wire shape)
to_prompt() AI-readable rendering — caps visible elements at 10, device log at 200 lines
build_suggestions(target, visible) Top-3 "Did you mean …?" suggestions, Levenshtein-based, score > 0.5
similarity(a, b) [0, 1] normalized string similarity
edit_distance(a, b) Wagner-Fischer Levenshtein distance

When to reach for this

Use case Pick
Need a failure type that LLM agents can act on directly smix-error
Want "Did you mean …?" suggestions from a visible-element list smix-error
Need a fast Levenshtein distance strsim or smix-error (free with the rest)
Generic std::error::Error for a non-automation domain use thiserror directly

Scope

  • ✅ Implements std::error::Error + Display (Display = to_prompt)
  • ✅ JSON wire shape: {ok: false, code, message, selector, suggestions, visibleElements, hint, screenshot, deviceLog}
  • result_large_err clippy lint workspace-allowed (the rich context is deliberate; boxing the error would defeat the AI-readable design)
  • ❌ No I/O, no async
  • ❌ No assertions / matchers (use smix-sdk for assert_visible / assert_enabled / etc.)

License

Dual-licensed under either:

at your option.