Skip to main content

kernel/error/
provider.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4// Copyright (c) 2026 2youg1 and the sprawling contributors
5
6//! What kind of provider failure an `E_PROVIDER` error reports, and
7//! whether the account that met a failure can still take the request.
8//! The part `crates/kernel/spec/Error.lean` specifies this module with the
9//! rest of `kernel::error`.
10
11use serde::{Deserialize, Serialize};
12
13/// The kind of one provider failure, as the call site that saw it named
14/// it. It travels on the wire so a page can say it in the reader's own
15/// language; the city's recovery sentence stays beside it for the fold.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
17#[serde(tag = "kind", rename_all = "snake_case")]
18#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
19pub enum ProviderFailureKind {
20    /// The exchange never completed: the request did not reach the
21    /// provider, the answer stopped before its end, or the transport's
22    /// deadline passed.
23    Exchange,
24    /// A streamed body could not be read further off the socket.
25    Cut,
26    /// A stream stayed open and silent past the bound set for silence.
27    Silence,
28    /// The provider answered, and its status refuses the request. Held
29    /// as `u32` because the wire's schema subset has no upper bound to
30    /// spell a `u16` with.
31    Refused { status: u32 },
32    /// The provider refused the request because this account's quota is
33    /// used up (gateway D32). Asked again it answers the same; another
34    /// account, or credit added to this one, is the way out.
35    Quota { status: u32 },
36    /// The provider refused the request because it no longer fits the
37    /// model's context window.
38    Overflow { status: u32 },
39    /// The provider answered 2xx with a body this city cannot read.
40    Unreadable,
41    /// A stream that already answered 2xx carried an error frame. The
42    /// provider's type for it stays in the error's subject.
43    Reported,
44    /// The request could not be built on this side.
45    Unbuilt,
46}
47
48/// Whether the account a request went out on can still take it
49/// (`crates/kernel/spec/Error.lean` D53).
50///
51/// A second answer beside `Retry` rather than a reading of it:
52/// a rejected key and a request the provider calls malformed are both
53/// `Retry::No`, and only the first is mended by another account. On the
54/// wire `"keep"` or `"advance"`; `Keep` is left out, so a record written
55/// before this field existed reads as `Keep` and keeps its bytes.
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
57#[serde(rename_all = "lowercase")]
58#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
59pub enum AccountDisposition {
60    /// This failure says nothing about the account: whether the request
61    /// goes out again is `retry`'s answer alone.
62    #[default]
63    Keep,
64    /// This account cannot take the request - its key was rejected, its
65    /// quota is used up, or its credential is missing - and the next
66    /// account may.
67    Advance,
68}
69
70impl AccountDisposition {
71    /// The serde skip test: `Keep` is the absence of the field.
72    pub(crate) fn is_keep(&self) -> bool {
73        *self == AccountDisposition::Keep
74    }
75}