Skip to content

Commit e11cb19

Browse files
committed
models: Implement CrateScope struct
1 parent c20c096 commit e11cb19

File tree

1 file changed

+109
-0
lines changed

1 file changed

+109
-0
lines changed

src/models/token/scopes.rs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::models::Crate;
12
use diesel::deserialize::{self, FromSql};
23
use diesel::pg::Pg;
34
use diesel::serialize::{self, IsNull, Output, ToSql};
@@ -50,3 +51,111 @@ impl FromSql<Text, Pg> for EndpointScope {
5051
Ok(EndpointScope::try_from(not_none!(bytes))?)
5152
}
5253
}
54+
55+
#[derive(Clone, Debug, PartialEq, Eq)]
56+
pub struct CrateScope {
57+
pattern: String,
58+
}
59+
60+
impl TryFrom<&str> for CrateScope {
61+
type Error = String;
62+
63+
fn try_from(pattern: &str) -> Result<Self, Self::Error> {
64+
match CrateScope::is_valid_pattern(pattern) {
65+
true => Ok(CrateScope {
66+
pattern: pattern.to_string(),
67+
}),
68+
false => Err("Invalid crate scope pattern".to_string()),
69+
}
70+
}
71+
}
72+
73+
impl CrateScope {
74+
fn is_valid_pattern(pattern: &str) -> bool {
75+
if pattern.is_empty() {
76+
return false;
77+
}
78+
79+
if pattern == "*" {
80+
return true;
81+
}
82+
83+
let name_without_wildcard = pattern.strip_suffix('*').unwrap_or(pattern);
84+
Crate::valid_name(name_without_wildcard)
85+
}
86+
87+
pub fn matches(&self, crate_name: &str) -> bool {
88+
let canonicalize = |name: &str| name.replace('-', "_");
89+
90+
if self.pattern == "*" {
91+
return true;
92+
}
93+
94+
return match self.pattern.strip_suffix('*') {
95+
Some(prefix) => {
96+
crate_name.starts_with(prefix)
97+
|| canonicalize(crate_name).starts_with(&canonicalize(prefix))
98+
}
99+
None => {
100+
crate_name == self.pattern
101+
|| canonicalize(crate_name) == canonicalize(&self.pattern)
102+
}
103+
};
104+
}
105+
}
106+
107+
#[cfg(test)]
108+
mod tests {
109+
use super::*;
110+
111+
#[test]
112+
fn crate_scope_validation() {
113+
assert_ok!(CrateScope::try_from("foo"));
114+
115+
// wildcards
116+
assert_ok!(CrateScope::try_from("foo*"));
117+
assert_ok!(CrateScope::try_from("f*"));
118+
assert_ok!(CrateScope::try_from("*"));
119+
assert_err!(CrateScope::try_from("te*st"));
120+
121+
// hyphens and underscores
122+
assert_ok!(CrateScope::try_from("foo-bar"));
123+
assert_ok!(CrateScope::try_from("foo_bar"));
124+
125+
// empty string
126+
assert_err!(CrateScope::try_from(""));
127+
128+
// invalid characters
129+
assert_err!(CrateScope::try_from("test#"));
130+
}
131+
132+
#[test]
133+
fn crate_scope_matching() {
134+
let scope = |pattern: &str| CrateScope::try_from(pattern).unwrap();
135+
136+
assert!(scope("foo").matches("foo"));
137+
assert!(!scope("foo").matches("bar"));
138+
assert!(!scope("foo").matches("fo"));
139+
assert!(!scope("foo").matches("fooo"));
140+
141+
// wildcards
142+
assert!(scope("foo*").matches("foo"));
143+
assert!(!scope("foo*").matches("bar"));
144+
assert!(scope("foo*").matches("foo-bar"));
145+
assert!(scope("foo*").matches("foo_bar"));
146+
assert!(scope("f*").matches("foo"));
147+
assert!(scope("*").matches("foo"));
148+
149+
// hyphens and underscores
150+
assert!(!scope("foo").matches("foo-bar"));
151+
assert!(!scope("foo").matches("foo_bar"));
152+
assert!(scope("foo-bar").matches("foo-bar"));
153+
assert!(scope("foo-bar").matches("foo_bar"));
154+
assert!(scope("foo_bar").matches("foo-bar"));
155+
assert!(scope("foo_bar").matches("foo_bar"));
156+
assert!(scope("foo-*").matches("foo-bar"));
157+
assert!(scope("foo-*").matches("foo_bar"));
158+
assert!(scope("foo_*").matches("foo-bar"));
159+
assert!(scope("foo_*").matches("foo_bar"));
160+
}
161+
}

0 commit comments

Comments
 (0)