use std::fmt;
use serde::de::Error as _;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Scope(String);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InvalidScopeToken(pub String);
impl fmt::Display for InvalidScopeToken {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "invalid scope token {:?}", self.0)
}
}
impl std::error::Error for InvalidScopeToken {}
fn scope_char_ok(b: u8) -> bool {
b == 0x21 || (0x23..=0x5B).contains(&b) || (0x5D..=0x7E).contains(&b)
}
impl Scope {
pub fn new(token: impl Into<String>) -> Result<Self, InvalidScopeToken> {
let token = token.into();
if token.is_empty() || !token.bytes().all(scope_char_ok) {
return Err(InvalidScopeToken(token));
}
Ok(Scope(token))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for Scope {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ScopeSet(Vec<Scope>);
impl ScopeSet {
pub fn empty() -> Self {
ScopeSet(Vec::new())
}
fn sorted(mut tokens: Vec<Scope>) -> Self {
tokens.sort_unstable();
tokens.dedup();
ScopeSet(tokens)
}
pub fn parse(s: &str) -> Result<Self, InvalidScopeToken> {
let count = s.split(' ').filter(|t| !t.is_empty()).count();
let mut tokens = Vec::with_capacity(count);
for tok in s.split(' ').filter(|t| !t.is_empty()) {
tokens.push(Scope::new(tok)?);
}
Ok(ScopeSet::sorted(tokens))
}
pub fn from_tokens<I, T>(tokens: I) -> Result<Self, InvalidScopeToken>
where
I: IntoIterator<Item = T>,
T: Into<String>,
{
let iter = tokens.into_iter();
let mut out = Vec::with_capacity(iter.size_hint().0);
for t in iter {
out.push(Scope::new(t)?);
}
Ok(ScopeSet::sorted(out))
}
pub fn is_subset(&self, other: &ScopeSet) -> bool {
let mut theirs = other.0.iter();
'mine: for mine in &self.0 {
for other in theirs.by_ref() {
match other.cmp(mine) {
std::cmp::Ordering::Less => continue,
std::cmp::Ordering::Equal => continue 'mine,
std::cmp::Ordering::Greater => return false,
}
}
return false;
}
true
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn contains(&self, token: &str) -> bool {
self.0.iter().any(|s| s.as_str() == token)
}
pub fn iter(&self) -> impl Iterator<Item = &Scope> {
self.0.iter()
}
}
impl fmt::Display for ScopeSet {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut first = true;
for s in &self.0 {
if !first {
f.write_str(" ")?;
}
first = false;
f.write_str(s.as_str())?;
}
Ok(())
}
}
impl Serialize for ScopeSet {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.to_string())
}
}
impl<'de> Deserialize<'de> for ScopeSet {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
ScopeSet::parse(&s).map_err(D::Error::custom)
}
}
#[cfg(test)]
#[path = "tests/scope.rs"]
mod tests;