1use std::fmt;
4
5use didbot_lexicon::nsid_syntax;
6
7use crate::ScopeParseError;
8
9#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
13pub enum NsidPattern {
14 Any,
16 Prefix(String),
18 Exact(String),
20}
21
22impl NsidPattern {
23 pub(crate) fn parse(s: &str) -> Result<Self, ScopeParseError> {
24 if s == "*" {
25 return Ok(NsidPattern::Any);
26 }
27 if let Some(prefix) = s.strip_suffix(".*") {
28 if prefix.is_empty() || !valid_nsid_prefix(prefix) {
29 return Err(ScopeParseError::MalformedNsid(s.to_owned()));
30 }
31 return Ok(NsidPattern::Prefix(prefix.to_owned()));
32 }
33 if nsid_syntax::validate(s).is_err() {
34 return Err(ScopeParseError::MalformedNsid(s.to_owned()));
35 }
36 Ok(NsidPattern::Exact(s.to_owned()))
37 }
38
39 pub(crate) fn contains(&self, other: &NsidPattern) -> bool {
41 match (self, other) {
42 (NsidPattern::Any, _) => true,
43 (_, NsidPattern::Any) => false,
44 (NsidPattern::Exact(a), NsidPattern::Exact(b)) => a == b,
45 (NsidPattern::Exact(_), NsidPattern::Prefix(_)) => false,
46 (NsidPattern::Prefix(p), NsidPattern::Exact(nsid)) => under_prefix(p, nsid),
47 (NsidPattern::Prefix(p), NsidPattern::Prefix(q)) => q == p || under_prefix(p, q),
48 }
49 }
50
51 pub(crate) fn intersect(&self, other: &NsidPattern) -> Option<NsidPattern> {
54 if self.contains(other) {
55 return Some(other.clone());
56 }
57 if other.contains(self) {
58 return Some(self.clone());
59 }
60 None
61 }
62}
63
64pub(crate) fn under_prefix(prefix: &str, candidate: &str) -> bool {
65 candidate == prefix
66 || candidate
67 .strip_prefix(prefix)
68 .is_some_and(|rest| rest.starts_with('.'))
69}
70
71fn valid_nsid_prefix(prefix: &str) -> bool {
81 nsid_syntax::validate(&format!("{prefix}.x")).is_ok()
82}
83
84impl fmt::Display for NsidPattern {
85 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86 match self {
87 NsidPattern::Any => write!(f, "*"),
88 NsidPattern::Prefix(p) => write!(f, "{p}.*"),
89 NsidPattern::Exact(nsid) => write!(f, "{nsid}"),
90 }
91 }
92}
93
94#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
96pub struct MimePattern {
97 ty: MimePart,
98 subtype: MimePart,
99}
100
101#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
102enum MimePart {
103 Any,
104 Exact(String),
105}
106
107impl MimePattern {
108 pub(crate) fn parse(s: &str) -> Result<Self, ScopeParseError> {
109 let (ty, subtype) = s
110 .split_once('/')
111 .ok_or_else(|| ScopeParseError::MalformedMime(s.to_owned()))?;
112 if ty.is_empty() || subtype.is_empty() {
113 return Err(ScopeParseError::MalformedMime(s.to_owned()));
114 }
115 let part = |p: &str| {
116 if p == "*" {
117 MimePart::Any
118 } else {
119 MimePart::Exact(p.to_owned())
120 }
121 };
122 Ok(MimePattern {
123 ty: part(ty),
124 subtype: part(subtype),
125 })
126 }
127
128 pub(crate) fn contains(&self, other: &MimePattern) -> bool {
129 fn part_contains(a: &MimePart, b: &MimePart) -> bool {
130 match (a, b) {
131 (MimePart::Any, _) => true,
132 (_, MimePart::Any) => false,
133 (MimePart::Exact(a), MimePart::Exact(b)) => a == b,
134 }
135 }
136 part_contains(&self.ty, &other.ty) && part_contains(&self.subtype, &other.subtype)
137 }
138
139 pub(crate) fn intersect(&self, other: &MimePattern) -> Option<MimePattern> {
140 if self.contains(other) {
141 return Some(other.clone());
142 }
143 if other.contains(self) {
144 return Some(self.clone());
145 }
146 None
147 }
148}
149
150impl fmt::Display for MimePattern {
151 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152 let part = |p: &MimePart| match p {
153 MimePart::Any => "*".to_owned(),
154 MimePart::Exact(s) => s.clone(),
155 };
156 write!(f, "{}/{}", part(&self.ty), part(&self.subtype))
157 }
158}