Skip to content

Commit d766578

Browse files
committed
feat: add regex file policy type (#168)
An example usage of the regex policy: ```toml [protobuf] url = "github.com/protocolbuffers/protobuf" protocol = "https" revision = "938d3bdf860dfc3c9a55bc5961ce1ecdb1a87e0f" allow_policies = ["google/protobuf/*"] deny_policies = ["re://compiler|bridge|sample|test"] content_roots = ["src"] ``` Closes #168
1 parent 2d49b8d commit d766578

File tree

2 files changed

+106
-4
lines changed

2 files changed

+106
-4
lines changed

src/model/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,6 @@ pub enum ParseError {
2424
UnsupportedLockFileVersion(toml::Value),
2525
#[error("Old lock file version {0}, consider running \"protofetch update\"")]
2626
OldLockFileVersion(i64),
27+
#[error("Regex error: {0}")]
28+
Regex(#[from] regex_lite::Error),
2729
}

src/model/protofetch/mod.rs

Lines changed: 104 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ pub struct AllowPolicies {
258258
policies: BTreeSet<FilePolicy>,
259259
}
260260

261+
#[allow(clippy::mutable_key_type)]
261262
impl AllowPolicies {
262263
pub fn new(policies: BTreeSet<FilePolicy>) -> Self {
263264
AllowPolicies { policies }
@@ -280,6 +281,7 @@ pub struct DenyPolicies {
280281
policies: BTreeSet<FilePolicy>,
281282
}
282283

284+
#[allow(clippy::mutable_key_type)]
283285
impl DenyPolicies {
284286
pub fn new(policies: BTreeSet<FilePolicy>) -> Self {
285287
DenyPolicies { policies }
@@ -306,12 +308,14 @@ impl Default for DenyPolicies {
306308
#[derive(Ord, PartialOrd, PartialEq, Eq, Hash, Debug, Clone)]
307309
pub enum FilePolicy {
308310
Path(FilePathPolicy),
311+
Regex(FileRegexPolicy),
309312
}
310313

311314
impl FilePolicy {
312315
pub fn contains_file(&self, path: &Path) -> bool {
313316
match self {
314317
FilePolicy::Path(policy) => policy.contains_file(path),
318+
FilePolicy::Regex(policy) => policy.contains_file(path),
315319
}
316320
}
317321
}
@@ -320,15 +324,19 @@ impl TryFrom<String> for FilePolicy {
320324
type Error = ParseError;
321325

322326
fn try_from(value: String) -> Result<Self, Self::Error> {
323-
Ok((&value).parse()?)
327+
value.parse()
324328
}
325329
}
326330

327331
impl FromStr for FilePolicy {
328332
type Err = ParseError;
329333

330334
fn from_str(s: &str) -> Result<Self, Self::Err> {
331-
Ok(FilePolicy::Path(s.parse()?))
335+
if let Some(re) = s.strip_prefix("re://") {
336+
Ok(Self::Regex(re.parse()?))
337+
} else {
338+
Ok(FilePolicy::Path(s.parse()?))
339+
}
332340
}
333341
}
334342

@@ -349,7 +357,7 @@ impl TryFrom<String> for FilePathPolicy {
349357
type Error = ParseError;
350358

351359
fn try_from(value: String) -> Result<Self, Self::Error> {
352-
(&value).parse()
360+
value.parse()
353361
}
354362
}
355363

@@ -412,6 +420,79 @@ pub enum FilePathPolicyKind {
412420
SubPath,
413421
}
414422

423+
#[derive(Debug, Clone)]
424+
pub struct FileRegexPolicy {
425+
value: String,
426+
// NOTE: regex impl not used in key type (clippy::mutable_key_type)
427+
regex: Regex,
428+
}
429+
430+
impl FileRegexPolicy {
431+
pub fn contains_file(&self, path: &Path) -> bool {
432+
self.regex.is_match(&path.to_string_lossy())
433+
}
434+
}
435+
436+
impl Hash for FileRegexPolicy {
437+
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
438+
self.value.hash(state);
439+
}
440+
}
441+
442+
impl Eq for FileRegexPolicy {}
443+
444+
impl PartialEq for FileRegexPolicy {
445+
fn eq(&self, other: &Self) -> bool {
446+
self.value == other.value
447+
}
448+
}
449+
450+
impl Ord for FileRegexPolicy {
451+
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
452+
self.value.cmp(&other.value)
453+
}
454+
}
455+
456+
impl PartialOrd for FileRegexPolicy {
457+
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
458+
Some(self.cmp(other))
459+
}
460+
}
461+
462+
impl TryFrom<String> for FileRegexPolicy {
463+
type Error = ParseError;
464+
465+
fn try_from(value: String) -> Result<Self, Self::Error> {
466+
Ok(Self {
467+
regex: value.parse()?,
468+
value,
469+
})
470+
}
471+
}
472+
473+
impl FromStr for FileRegexPolicy {
474+
type Err = ParseError;
475+
476+
fn from_str(s: &str) -> Result<Self, Self::Err> {
477+
Ok(Self {
478+
regex: s.parse()?,
479+
value: s.to_owned(),
480+
})
481+
}
482+
}
483+
484+
impl AsRef<str> for FileRegexPolicy {
485+
fn as_ref(&self) -> &str {
486+
&self.value
487+
}
488+
}
489+
490+
impl Display for FileRegexPolicy {
491+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
492+
f.write_str(&self.value)
493+
}
494+
}
495+
415496
#[derive(Clone, Hash, Deserialize, Serialize, Debug, PartialEq, Eq, Ord, PartialOrd)]
416497
pub struct ModuleName(String);
417498

@@ -704,7 +785,7 @@ mod tests {
704785
revision = "1.0.0"
705786
prune = true
706787
content_roots = ["src"]
707-
allow_policies = ["/foo/proto/file.proto", "/foo/other/*", "*/some/path/*"]
788+
allow_policies = ["/foo/proto/file.proto", "/foo/other/*", "*/some/path/*", "re://_(?:test|unittest)\\.proto"]
708789
"#;
709790
let expected = Descriptor {
710791
name: ModuleName::from("test_file"),
@@ -739,6 +820,7 @@ mod tests {
739820
FilePathPolicyKind::SubPath,
740821
PathBuf::from("/some/path"),
741822
)),
823+
FilePolicy::Regex(r"_(?:test|unittest)\.proto".parse().unwrap()),
742824
])),
743825
deny_policies: DenyPolicies::default(),
744826
},
@@ -1017,4 +1099,22 @@ mod tests {
10171099
let res = DenyPolicies::should_deny_file(&rules, &file);
10181100
assert!(res);
10191101
}
1102+
1103+
#[test]
1104+
fn test_file_policy_regex_include() {
1105+
let policy: FilePolicy = r"re://_(?:test|unittest)\.proto".parse().unwrap();
1106+
1107+
for path in [
1108+
"google/protobuf/any_test.proto",
1109+
"google/protobuf/compiler/java/message_serialization_unittest.proto",
1110+
] {
1111+
assert!(policy.contains_file(&PathBuf::from(path)), "{path}");
1112+
}
1113+
}
1114+
1115+
#[test]
1116+
#[should_panic]
1117+
fn test_file_policy_regex_parse_error() {
1118+
FilePolicy::from_str(r"re://_(?:test").unwrap();
1119+
}
10201120
}

0 commit comments

Comments
 (0)