use std::fmt;
macro_rules! scopes {
($count:literal: $( $(#[$meta:meta])* $variant:ident => $wire:literal, )+) => {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Scope {
$( $(#[$meta])* $variant, )+
}
impl Scope {
pub const ALL: [Scope; $count] = [ $( Scope::$variant, )+ ];
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
$( Scope::$variant => $wire, )+
}
}
}
};
}
scopes! { 23:
PreferenceRead => "preference:read",
PreferenceWrite => "preference:write",
EmailRead => "email:read",
EngineRead => "engine:read",
EngineWrite => "engine:write",
ChallengeRead => "challenge:read",
ChallengeWrite => "challenge:write",
ChallengeBulk => "challenge:bulk",
StudyRead => "study:read",
StudyWrite => "study:write",
TournamentWrite => "tournament:write",
RacerWrite => "racer:write",
PuzzleRead => "puzzle:read",
PuzzleWrite => "puzzle:write",
TeamRead => "team:read",
TeamWrite => "team:write",
TeamLead => "team:lead",
FollowRead => "follow:read",
FollowWrite => "follow:write",
MsgWrite => "msg:write",
BoardPlay => "board:play",
BotPlay => "bot:play",
WebMod => "web:mod",
}
impl Scope {
#[must_use]
pub fn parse(value: &str) -> Option<Self> {
Self::ALL.into_iter().find(|scope| scope.as_str() == value)
}
}
impl fmt::Display for Scope {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trips_every_scope() {
for scope in Scope::ALL {
assert_eq!(Scope::parse(scope.as_str()), Some(scope));
}
}
#[test]
fn unknown_scope_is_none() {
assert_eq!(Scope::parse("does:notexist"), None);
}
#[test]
fn uses_colon_separated_wire_format() {
assert_eq!(Scope::BoardPlay.as_str(), "board:play");
assert_eq!(Scope::WebMod.to_string(), "web:mod");
}
#[test]
fn wire_strings_are_unique() {
let mut wires: Vec<&str> = Scope::ALL.iter().map(|scope| scope.as_str()).collect();
wires.sort_unstable();
let total = wires.len();
wires.dedup();
assert_eq!(wires.len(), total, "duplicate wire strings in the table");
}
}