Skip to main content

krabka_metadata/
error.rs

1use thiserror::Error;
2use uuid::Uuid;
3
4#[derive(Debug, Error, Clone, PartialEq, Eq)]
5#[non_exhaustive]
6pub enum MetadataError {
7    #[error("topic '{0}' already exists")]
8    TopicExists(String),
9
10    #[error("unknown topic '{0}'")]
11    UnknownTopic(String),
12
13    #[error("invalid partition {partition} on topic '{topic}'")]
14    InvalidPartition { topic: String, partition: i32 },
15
16    #[error("invalid record: {0}")]
17    InvalidRecord(&'static str),
18
19    /// A `V1TopicFreeze` record carried an empty scope. A literal scope names
20    /// one topic and a prefix scope names a namespace, so neither can be
21    /// empty. An empty prefix would cover every topic in the cluster.
22    #[error("topic freeze scope is empty")]
23    EmptyFreezeScope,
24
25    /// A `V1BreakGlassProposal` record replaced, reordered, or dropped an
26    /// approval already stored for the proposal. Two approvers who read the
27    /// same image each submit the list they read plus their own entry, so
28    /// only an append keeps both.
29    #[error("break-glass proposal {0} does not extend the stored approvals")]
30    BreakGlassApprovalsNotAnExtension(Uuid),
31
32    /// A `V1BreakGlassProposal` record changed a proposal whose approval is
33    /// already spent. One approval authorizes one transition.
34    #[error("break-glass proposal {0} is already consumed")]
35    BreakGlassProposalConsumed(Uuid),
36
37    /// A `V1BreakGlassProposal` record changed a proposal that the proposer
38    /// withdrew.
39    #[error("break-glass proposal {0} is withdrawn")]
40    BreakGlassProposalWithdrawn(Uuid),
41}
42
43#[cfg(test)]
44mod tests {
45
46    use super::*;
47
48    #[test]
49    fn display_topic_exists() {
50        let e = MetadataError::TopicExists("my-topic".into());
51        assert2::assert!(e.to_string() == "topic 'my-topic' already exists");
52    }
53
54    #[test]
55    fn display_invalid_partition() {
56        let e = MetadataError::InvalidPartition {
57            topic: "t".into(),
58            partition: 7,
59        };
60        assert2::assert!(e.to_string().contains("partition 7"));
61    }
62
63    #[test]
64    fn display_freeze_and_break_glass_rejections() {
65        let id = Uuid::from_u128(0xB1);
66        for (label, error, want) in [
67            (
68                "empty freeze scope",
69                MetadataError::EmptyFreezeScope,
70                "topic freeze scope is empty".to_string(),
71            ),
72            (
73                "approvals that do not extend",
74                MetadataError::BreakGlassApprovalsNotAnExtension(id),
75                format!("break-glass proposal {id} does not extend the stored approvals"),
76            ),
77            (
78                "consumed proposal",
79                MetadataError::BreakGlassProposalConsumed(id),
80                format!("break-glass proposal {id} is already consumed"),
81            ),
82            (
83                "withdrawn proposal",
84                MetadataError::BreakGlassProposalWithdrawn(id),
85                format!("break-glass proposal {id} is withdrawn"),
86            ),
87        ] {
88            assert2::check!(error.to_string() == want, "{label}");
89        }
90    }
91}