tycho_simulation/snapshot_feed/errors.rs
1//! What a feed reports when a snapshot does not arrive.
2
3use thiserror::Error;
4use tycho_common::simulation::errors::SimulationError;
5
6/// Failures of the feed layer: fetching, decoding, and publishing a provider's snapshots.
7///
8/// Match on the variant: it says whether another attempt could answer differently, which is what
9/// a feed's retrying and the conversion to [`SimulationError`] both turn on. The message beside
10/// it is written for a log line, and its wording carries no promise across versions.
11#[derive(Clone, Debug, Error)]
12pub enum FeedError {
13 /// The provider could not be reached, or answered with something a later attempt may not
14 /// answer with: a transport failure, a timeout, a server-side error.
15 #[error("feed connection error: {0}")]
16 Connection(String),
17 /// The provider's answer could not be turned into a snapshot.
18 #[error("feed parsing error: {0}")]
19 Parsing(String),
20 /// No attempt can answer differently, so the feed gives up whatever its failure budget says.
21 /// A failure someone can fix while the feed keeps retrying is not one of these.
22 #[error("feed fatal error: {0}")]
23 Fatal(String),
24 /// The feed was configured with a value it cannot run with.
25 #[error("feed invalid input error: {0}")]
26 InvalidInput(String),
27}
28
29impl FeedError {
30 /// Whether another attempt is pointless. A feed stops on these, whatever failure budget it
31 /// was configured with.
32 pub fn is_fatal(&self) -> bool {
33 matches!(self, FeedError::Fatal(_))
34 }
35
36 /// The same failure with `context` in front of its message: a caller can say what it was
37 /// doing when the failure reached it without reclassifying what went wrong, which stays
38 /// the judgement of whoever found out.
39 pub(crate) fn in_context(self, context: impl std::fmt::Display) -> Self {
40 match self {
41 FeedError::Connection(message) => {
42 FeedError::Connection(format!("{context}: {message}"))
43 }
44 FeedError::Parsing(message) => FeedError::Parsing(format!("{context}: {message}")),
45 FeedError::Fatal(message) => FeedError::Fatal(format!("{context}: {message}")),
46 FeedError::InvalidInput(message) => {
47 FeedError::InvalidInput(format!("{context}: {message}"))
48 }
49 }
50 }
51}
52
53impl From<FeedError> for SimulationError {
54 /// A state that cannot answer from the book it holds reports why in the vocabulary its
55 /// caller acts on: a newer book may serve what this one cannot, so only a genuinely fatal
56 /// feed error marks the component as one to stop simulating. The message moves across
57 /// unchanged — the variant it lands in classifies it, and `SimulationError` says so in its
58 /// own `Display`.
59 fn from(err: FeedError) -> Self {
60 match err {
61 FeedError::Connection(message) | FeedError::Parsing(message) => {
62 SimulationError::RecoverableError(message)
63 }
64 FeedError::Fatal(message) => SimulationError::FatalError(message),
65 FeedError::InvalidInput(message) => SimulationError::InvalidInput(message, None),
66 }
67 }
68}