Skip to main content

ff_sdk/
engine_error.rs

1//! Compatibility shim preserving the `ff_sdk::engine_error` surface.
2//!
3//! **RFC-012 Stage 1a:** the `EngineError` enum and its sub-kinds
4//! moved to `ff_core::engine_error`; the `From<ScriptError>` +
5//! ScriptError-downcast helpers moved to
6//! `ff_script::engine_error_ext`. This module re-exports both so
7//! every `ff_sdk::EngineError` / `ff_sdk::engine_error::*` path used
8//! by existing consumers (cairn-fabric, the SDK's own call sites)
9//! continues to compile unchanged.
10//!
11//! The `enrich_dependency_conflict` helper is kept here (not moved)
12//! because it performs a live `ferriskey::Client` round trip; that
13//! dependency is ff-sdk–scoped.
14
15pub use ff_core::engine_error::{
16    BugKind, ConflictKind, ContentionKind, EngineError, StateKind, ValidationKind,
17};
18// `class` is backend-agnostic (returns `ErrorClass` which classifies
19// any Transport whose source is a `ScriptError`; returns `Terminal`
20// for non-ScriptError Transport). Re-exported unconditionally.
21pub use ff_script::engine_error_ext::class;
22// The three helpers below are Valkey-specific: `transport_script` /
23// `transport_script_ref` name `ScriptError` (a Valkey-transport
24// error), and `valkey_kind` returns `ferriskey::ErrorKind`. Gating
25// them behind `valkey-default` keeps the `--no-default-features`
26// public surface free of Valkey transport types, honouring the
27// RFC-012 §1.3 agnosticism contract.
28#[cfg(feature = "valkey-default")]
29pub use ff_script::engine_error_ext::{transport_script, transport_script_ref, valkey_kind};
30
31#[cfg(feature = "valkey-default")]
32use std::collections::HashMap;
33
34#[cfg(feature = "valkey-default")]
35use ff_core::keys::FlowKeyContext;
36#[cfg(feature = "valkey-default")]
37use ff_core::partition::flow_partition;
38#[cfg(feature = "valkey-default")]
39use ff_core::types::{EdgeId, FlowId};
40#[cfg(feature = "valkey-default")]
41use ff_script::error::ScriptError;
42
43/// Upgrade a bare `From<ScriptError>` translation of
44/// `dependency_already_exists` into a fully-typed
45/// [`ConflictKind::DependencyAlreadyExists`] by performing the
46/// follow-up `HGETALL` on the edge hash.
47///
48/// Call sites that stage a dependency and receive
49/// [`EngineError::Transport`] whose inner is
50/// `ScriptError::DependencyAlreadyExists` use this helper to
51/// upgrade the error before surfacing. On follow-up failure the
52/// original `Transport` error is returned unchanged — the
53/// strict-parse posture means a half-populated `Conflict` is
54/// never constructed.
55///
56/// **RFC-012 Stage 1a:** converted from an inherent method on
57/// `EngineError` to a free function because `EngineError` now lives in
58/// ff-core (Rust disallows inherent impls on foreign types). The
59/// signature and semantics are otherwise unchanged.
60#[cfg(feature = "valkey-default")]
61pub async fn enrich_dependency_conflict(
62    client: &ferriskey::Client,
63    partition_config: &ff_core::partition::PartitionConfig,
64    flow_id: &FlowId,
65    edge_id: &EdgeId,
66) -> Result<EngineError, EngineError> {
67    let partition = flow_partition(flow_id, partition_config);
68    let ctx = FlowKeyContext::new(&partition, flow_id);
69    let edge_key = ctx.edge(edge_id);
70
71    let raw: HashMap<String, String> = match client
72        .cmd("HGETALL")
73        .arg(&edge_key)
74        .execute()
75        .await
76    {
77        Ok(raw) => raw,
78        Err(transport) => {
79            return Err(transport_script(ScriptError::Valkey(transport)));
80        }
81    };
82    if raw.is_empty() {
83        return Err(transport_script(ScriptError::DependencyAlreadyExists));
84    }
85    match ff_core::contracts::decode::build_edge_snapshot(flow_id, edge_id, &raw) {
86        Ok(existing) => Ok(EngineError::Conflict(ConflictKind::DependencyAlreadyExists {
87            existing,
88        })),
89        Err(_e) => Err(transport_script(ScriptError::DependencyAlreadyExists)),
90    }
91}