use core::fmt;
use crate::UnknownVariant;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
#[non_exhaustive]
pub enum RefusalReason {
AnonymousActor,
ExpectActorMismatch,
GateRefusal,
StrictOpFailure,
ParseError,
VerbRefused,
PolicyRefusal,
}
impl RefusalReason {
pub const ALL: [Self; 7] = [
Self::AnonymousActor,
Self::ExpectActorMismatch,
Self::GateRefusal,
Self::StrictOpFailure,
Self::ParseError,
Self::VerbRefused,
Self::PolicyRefusal,
];
pub const fn as_str(self) -> &'static str {
match self {
Self::AnonymousActor => "anonymous-actor",
Self::ExpectActorMismatch => "expect-actor-mismatch",
Self::GateRefusal => "gate-refusal",
Self::StrictOpFailure => "strict-op-failure",
Self::ParseError => "parse-error",
Self::VerbRefused => "verb-refused",
Self::PolicyRefusal => "policy-refusal",
}
}
pub fn from_token(token: &str) -> Option<Self> {
match token {
"anonymous-actor" => Some(Self::AnonymousActor),
"expect-actor-mismatch" => Some(Self::ExpectActorMismatch),
"gate-refusal" => Some(Self::GateRefusal),
"strict-op-failure" => Some(Self::StrictOpFailure),
"parse-error" => Some(Self::ParseError),
"verb-refused" => Some(Self::VerbRefused),
"policy-refusal" => Some(Self::PolicyRefusal),
_ => None,
}
}
}
impl fmt::Display for RefusalReason {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl core::str::FromStr for RefusalReason {
type Err = UnknownVariant;
fn from_str(value: &str) -> Result<Self, Self::Err> {
Self::from_token(value).ok_or_else(|| {
UnknownVariant::new(
"refusal_reason",
value,
&[
"anonymous-actor",
"expect-actor-mismatch",
"gate-refusal",
"strict-op-failure",
"parse-error",
"verb-refused",
"policy-refusal",
],
)
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn token_vocabulary_is_exact_and_append_only() {
let tokens = RefusalReason::ALL.map(RefusalReason::as_str);
assert_eq!(
tokens,
[
"anonymous-actor",
"expect-actor-mismatch",
"gate-refusal",
"strict-op-failure",
"parse-error",
"verb-refused",
"policy-refusal",
]
);
for (reason, token) in RefusalReason::ALL.into_iter().zip(tokens) {
assert_eq!(RefusalReason::from_token(token), Some(reason));
assert_eq!(token.parse::<RefusalReason>().unwrap(), reason);
}
assert_eq!(RefusalReason::from_token("Gate-Refusal"), None);
assert_eq!(RefusalReason::from_token("gate_refusal"), None);
assert_eq!(RefusalReason::from_token("Policy-Refusal"), None);
assert_eq!(RefusalReason::from_token("policy_refusal"), None);
}
#[cfg(feature = "serde")]
#[test]
fn serde_uses_the_machine_token() {
for reason in RefusalReason::ALL {
let encoded = serde_json::to_string(&reason).unwrap();
assert_eq!(encoded, alloc::format!("\"{}\"", reason.as_str()));
assert_eq!(
serde_json::from_str::<RefusalReason>(&encoded).unwrap(),
reason
);
}
}
}