graphrefly_operators/error.rs
1//! Operator factory errors (Slice H /qa F7, 2026-05-07).
2//!
3//! Factory functions in this crate enforce two kinds of invariants:
4//!
5//! 1. **Core-layer construction invariants** — already typed via
6//! [`graphrefly_core::RegisterError`] (unknown dep, terminal-non-
7//! resubscribable dep, operator without deps, sentinel seed, etc.).
8//! 2. **Factory-shape invariants** — pre-conditions on the factory
9//! arguments that Core can't see because the factory hasn't yet
10//! constructed the [`graphrefly_core::OperatorOp`] descriptor (or
11//! the Core check would fire with the wrong error message). Examples:
12//! - `combine(sources, packer)` requires `!sources.is_empty()` (Core
13//! would surface the same condition as `OperatorWithoutDeps`, but
14//! the factory error is named after the public API).
15//! - `last_with_default(source, default)` requires `default !=
16//! NO_HANDLE` — Core accepts `Last { default: NO_HANDLE }` as the
17//! no-default variant (used by `last()`), so the factory must
18//! enforce its tighter contract.
19//!
20//! Both kinds funnel through [`OperatorFactoryError`] so factory
21//! callers handle a single error enum. The Core variant is
22//! transparent-wrapped: `?` propagation from `register_operator(...)?`
23//! works directly via the auto-derived `From<RegisterError>` impl.
24
25use graphrefly_core::RegisterError;
26use thiserror::Error;
27
28/// Errors returnable by [`crate::combine`], [`crate::merge`],
29/// [`crate::merge_as_op`], [`crate::last_with_default`], and
30/// [`crate::last_with_default_with`].
31///
32/// Other operator factories ([`crate::map`], [`crate::filter`],
33/// [`crate::with_latest_from`], etc.) have no extra factory-shape
34/// invariants beyond Core's checks — they panic on Core-layer
35/// `RegisterError` via `.expect("invariant: caller has validated dep
36/// ids …")` because their public contract is "the caller supplies
37/// already-valid `NodeId`s." Slice H `# Errors` rustdoc on those
38/// factories documents the panic conditions.
39#[derive(Error, Debug, Clone, PartialEq, Eq)]
40pub enum OperatorFactoryError {
41 /// `combine` / `merge` / `merge_as_op` was called with empty
42 /// `sources`. At least one upstream is required — for fan-out
43 /// broadcast use a producer node with explicit subscriptions.
44 #[error("operator factory: at least one source is required")]
45 EmptySources,
46
47 /// `last_with_default` / `last_with_default_with` was called with
48 /// `NO_HANDLE` as the default. Use [`crate::last`] for the
49 /// no-default variant, which emits Complete without a Data on
50 /// empty streams.
51 #[error(
52 "operator factory: last_with_default requires a real default handle \
53 (use last() for no-default behavior)"
54 )]
55 ZeroDefault,
56
57 /// Underlying Core registration failed. Transparent-wrapped so
58 /// `register_operator(...)?` propagates correctly.
59 #[error(transparent)]
60 Register(#[from] RegisterError),
61}