@@ -258,6 +258,7 @@ pub struct AllowPolicies {
258258 policies : BTreeSet < FilePolicy > ,
259259}
260260
261+ #[ allow( clippy:: mutable_key_type) ]
261262impl 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) ]
283285impl 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 ) ]
307309pub enum FilePolicy {
308310 Path ( FilePathPolicy ) ,
311+ Regex ( FileRegexPolicy ) ,
309312}
310313
311314impl 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
327331impl 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 ) ]
416497pub 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