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 PolicyRefusal,
31}
32
33impl RefusalReason {
34 pub const ALL: [Self; 7] = [
36 Self::AnonymousActor,
37 Self::ExpectActorMismatch,
38 Self::GateRefusal,
39 Self::StrictOpFailure,
40 Self::ParseError,
41 Self::VerbRefused,
42 Self::PolicyRefusal,
43 ];
44
45 pub const fn as_str(self) -> &'static str {
47 match self {
48 Self::AnonymousActor => "anonymous-actor",
49 Self::ExpectActorMismatch => "expect-actor-mismatch",
50 Self::GateRefusal => "gate-refusal",
51 Self::StrictOpFailure => "strict-op-failure",
52 Self::ParseError => "parse-error",
53 Self::VerbRefused => "verb-refused",
54 Self::PolicyRefusal => "policy-refusal",
55 }
56 }
57
58 pub fn from_token(token: &str) -> Option<Self> {
60 match token {
61 "anonymous-actor" => Some(Self::AnonymousActor),
62 "expect-actor-mismatch" => Some(Self::ExpectActorMismatch),
63 "gate-refusal" => Some(Self::GateRefusal),
64 "strict-op-failure" => Some(Self::StrictOpFailure),
65 "parse-error" => Some(Self::ParseError),
66 "verb-refused" => Some(Self::VerbRefused),
67 "policy-refusal" => Some(Self::PolicyRefusal),
68 _ => None,
69 }
70 }
71}
72
73impl fmt::Display for RefusalReason {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 f.write_str(self.as_str())
76 }
77}
78
79impl core::str::FromStr for RefusalReason {
80 type Err = UnknownVariant;
81
82 fn from_str(value: &str) -> Result<Self, Self::Err> {
83 Self::from_token(value).ok_or_else(|| {
84 UnknownVariant::new(
85 "refusal_reason",
86 value,
87 &[
88 "anonymous-actor",
89 "expect-actor-mismatch",
90 "gate-refusal",
91 "strict-op-failure",
92 "parse-error",
93 "verb-refused",
94 "policy-refusal",
95 ],
96 )
97 })
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn token_vocabulary_is_exact_and_append_only() {
107 let tokens = RefusalReason::ALL.map(RefusalReason::as_str);
108 assert_eq!(
109 tokens,
110 [
111 "anonymous-actor",
112 "expect-actor-mismatch",
113 "gate-refusal",
114 "strict-op-failure",
115 "parse-error",
116 "verb-refused",
117 "policy-refusal",
118 ]
119 );
120 for (reason, token) in RefusalReason::ALL.into_iter().zip(tokens) {
121 assert_eq!(RefusalReason::from_token(token), Some(reason));
122 assert_eq!(token.parse::<RefusalReason>().unwrap(), reason);
123 }
124 assert_eq!(RefusalReason::from_token("Gate-Refusal"), None);
125 assert_eq!(RefusalReason::from_token("gate_refusal"), None);
126 assert_eq!(RefusalReason::from_token("Policy-Refusal"), None);
127 assert_eq!(RefusalReason::from_token("policy_refusal"), None);
128 }
129
130 #[cfg(feature = "serde")]
131 #[test]
132 fn serde_uses_the_machine_token() {
133 for reason in RefusalReason::ALL {
134 let encoded = serde_json::to_string(&reason).unwrap();
135 assert_eq!(encoded, alloc::format!("\"{}\"", reason.as_str()));
136 assert_eq!(
137 serde_json::from_str::<RefusalReason>(&encoded).unwrap(),
138 reason
139 );
140 }
141 }
142}