Skip to documentation

SDK and error contracts

The four runtimes

The four runtimes One catalogue, four languages. Each runtime is a generated codes module — written by codegen/generate.mjs , committed, never edited by hand — plus a hand written envelope module around it. All four carry zero dependencies

The four runtimes

One catalogue, four languages. Each runtime is a generated codes module — written by codegen/generate.mjs, committed, never edited by hand — plus a hand-written envelope module around it. All four carry zero dependencies on purpose: a package every product must adopt has to be cheap to adopt, and a serde or validator requirement is a reason to keep a local copy.

rust/    crate   wisent-errors    (root Cargo workspace member)
python/  package wisent_errors   (pip subdirectory=python)
js/      package @wisent/errors  (root package.json; .d.ts included)
swift/   library WisentErrors    (Package.swift at the root; macOS 13, iOS 13)

One API, four spellings

what JavaScript Python Rust Swift
build, strict failure({...}) failure(**kw) Failure::new(point, code, service)? try Failure(failurePoint:code:service:)
build, never fails failureOrFallback failure_or_fallback Failure::or_fallback Failure.orFallback
throw/raise raiseFailureError raise_failureFailureError Failure is std::error::Error Failure via the throwing init
one line + JSON render(envelope) render(envelope) failure.render() failure.render()
flatten causes chain(envelope) chain(envelope) failure.chain() failure.chain()
hard trim trimDetail(text, limit) trim_detail(text, limit) trim_detail(text, limit) trimDetail(_:limit:)
word-edge trim trimDetailAtWordEdge trim_detail_at_word_edge trim_detail_at_word_edge trimDetailAtWordEdge
coerce a code codeOrFallback code_or_fallback Code::or_fallback Code.orFallback
membership isCode, codeOrNull code_or_none Code::parseOption Code(rawValue:)
classify a status fromUpstreamStatus from_upstream_status Code::from_upstream_status Code.fromUpstream(status:)
edge HTTP status httpStatus(code) http_status(code) code.http_status() code.httpStatus
exit code exitCode(code, chosen) exit_code(code, chosen) code.exit_code(chosen) code.exitCode(chosen:)
retry exit RETRY_EXIT (69) RETRY_EXIT (69) Code::RETRY_EXIT (69) Code.retryExit (69)
package bound DETAIL_LIMIT (2000) DETAIL_LIMIT (2000) DETAIL_LIMIT (2000) detailLimit (2000)

JS and Python build envelopes as plain objects/dicts. Rust and Swift build a Failure value whose code is a typed enum (Code::RateLimit, .rateLimit), so an off-catalogue code is unrepresentable there; the salvage builders in JS and Python additionally coerce an unknown code to unknown and record it in context as wisent_errors.error_code.

The trim rule

trimDetail strips whitespace at both ends and cuts hard at the limit. The limit is an argument because the width is a product's own decision — stado and probierz keep 300, wisent-customer-support 400, wisent-tools 500 — while the rule for how to cut is the thing that was written six times. trimDetailAtWordEdge backs up to the last word edge within slack (default 24) characters of the bound and is opt-in, because it changes emitted bytes. The edge is measured in characters, not bytes: an earlier Rust version compared a byte offset from rfind against a character limit and discarded two thirds of a non-ASCII detail. Both rules are compared across runtimes by the conformance harness.

Membership goes through a real set

The JS predicates answer membership through a Set, never in or a bare property read: "toString" in MEANINGS is true, and __proto__ reads through the prototype chain, so the in form accepted toString, constructor, hasOwnProperty, and valueOf as codes — at precisely the wire boundary these predicates exist to guard. The harness probes exactly those names against every runtime.

Rust specifics

The crate carries no serde: Failure::to_json() hand-serializes with the schema's key order and minimal escaping, so a git dependency costs nothing downstream. context is a BTreeMap<String, String>, so context keys serialize in sorted order. Strict construction returns Result<Failure, Invalid>, where Invalid names why the envelope was refused — an envelope with an unparseable failure point is worse than none, because it looks like a report.

Swift specifics

The library exists because two native clients — oko-desktop and wisent-ios — held their own copies of the envelope. Beyond Failure and Code, it ships WisentFailureReporter, a Probierz intake transport for desktop components, built around one rule: reporting must never fail the caller. Configuration is the process environment, read at call time — PROBIERZ_INTAKE_URL (no path; /v1/failures is appended) and PROBIERZ_INTAKE_TOKEN (the bearer). With either absent it tries the loopback default, http://127.0.0.1:9790 with the token from ~/.probierz/intake-token, which is what a Finder-launched app can still reach; with neither available, report is a no-op. The POST runs off the caller's path — a detached task, a five-second timeout, the response body ignored, a non-2xx answer swallowed.

Generated files are committed

Every codes module starts with the same header: generated from catalogue/codes.json by codegen/generate.mjs, do not edit. Changing the vocabulary means changing the catalogue and running the generator; node codegen/generate.mjs --check (and ci/check.mjs, which runs it) fails on any drift between the two. What the vocabulary contains is catalogue; what the envelope looks like is envelope.