1use core::fmt;
8
9use crate::UnknownVariant;
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
15#[non_exhaustive]
16pub enum RefusalReason {
17 AnonymousActor,
19 ExpectActorMismatch,
21 GateRefusal,
23 StrictOpFailure,
25 ParseError,
27 VerbRefused,
29}
30
31impl RefusalReason {
32 pub const ALL: [Self; 6] = [
34 Self::AnonymousActor,
35 Self::ExpectActorMismatch,
36 Self::GateRefusal,
37 Self::StrictOpFailure,
38 Self::ParseError,
39 Self::VerbRefused,
40 ];
41
42 pub const fn as_str(self) -> &'static str {
44 match self {
45 Self::AnonymousActor => "anonymous-actor",
46 Self::ExpectActorMismatch => "expect-actor-mismatch",
47 Self::GateRefusal => "gate-refusal",
48 Self::StrictOpFailure => "strict-op-failure",
49 Self::ParseError => "parse-error",
50 Self::VerbRefused => "verb-refused",
51 }
52 }
53
54 pub fn from_token(token: &str) -> Option<Self> {
56 match token {
57 "anonymous-actor" => Some(Self::AnonymousActor),
58 "expect-actor-mismatch" => Some(Self::ExpectActorMismatch),
59 "gate-refusal" => Some(Self::GateRefusal),
60 "strict-op-failure" => Some(Self::StrictOpFailure),
61 "parse-error" => Some(Self::ParseError),
62 "verb-refused" => Some(Self::VerbRefused),
63 _ => None,
64 }
65 }
66}
67
68impl fmt::Display for RefusalReason {
69 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70 f.write_str(self.as_str())
71 }
72}
73
74impl core::str::FromStr for RefusalReason {
75 type Err = UnknownVariant;
76
77 fn from_str(value: &str) -> Result<Self, Self::Err> {
78 Self::from_token(value).ok_or_else(|| {
79 UnknownVariant::new(
80 "refusal_reason",
81 value,
82 &[
83 "anonymous-actor",
84 "expect-actor-mismatch",
85 "gate-refusal",
86 "strict-op-failure",
87 "parse-error",
88 "verb-refused",
89 ],
90 )
91 })
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 #[test]
100 fn token_vocabulary_is_exact_and_append_only() {
101 let tokens = RefusalReason::ALL.map(RefusalReason::as_str);
102 assert_eq!(
103 tokens,
104 [
105 "anonymous-actor",
106 "expect-actor-mismatch",
107 "gate-refusal",
108 "strict-op-failure",
109 "parse-error",
110 "verb-refused",
111 ]
112 );
113 for (reason, token) in RefusalReason::ALL.into_iter().zip(tokens) {
114 assert_eq!(RefusalReason::from_token(token), Some(reason));
115 assert_eq!(token.parse::<RefusalReason>().unwrap(), reason);
116 }
117 assert_eq!(RefusalReason::from_token("Gate-Refusal"), None);
118 assert_eq!(RefusalReason::from_token("gate_refusal"), None);
119 }
120
121 #[cfg(feature = "serde")]
122 #[test]
123 fn serde_uses_the_machine_token() {
124 let encoded = serde_json::to_string(&RefusalReason::GateRefusal).unwrap();
125 assert_eq!(encoded, "\"gate-refusal\"");
126 assert_eq!(
127 serde_json::from_str::<RefusalReason>(&encoded).unwrap(),
128 RefusalReason::GateRefusal
129 );
130 }
131}