@@ -12,7 +12,7 @@ use rust_i18n::t;
1212use serde:: { Deserialize , Serialize } ;
1313use thiserror:: Error ;
1414
15- use crate :: types:: FullyQualifiedTypeName ;
15+ use crate :: types:: { FullyQualifiedTypeName , FullyQualifiedTypeNameError } ;
1616use crate :: util:: convert_wildcard_to_regex;
1717
1818/// Defines the wildcard type name for filtering DSC resources or extensions by name.
@@ -224,6 +224,16 @@ pub enum WildcardTypeNameError {
224224 #[ source]
225225 err : regex:: Error ,
226226 } ,
227+ #[ error( "{t}" , t = t!(
228+ "types.wildcard_type_name.invalidErrorConversion" ,
229+ "err" => err
230+ ) ) ]
231+ InvalidErrorConversion {
232+ /// The specific error that was encountered when attempting to convert the wildcard type
233+ /// name into a regex pattern for matching, but which is not a regex::Error and thus
234+ /// can't be included in the `InvalidRegex` error variant.
235+ err : FullyQualifiedTypeNameError ,
236+ }
227237}
228238
229239/// This static lazily defines the validating regex for [`WildcardTypeName`]. It enables the
@@ -409,73 +419,17 @@ impl WildcardTypeName {
409419 } ) ;
410420 }
411421
412- // We need to validate each segment of the type name separately to provide better error
413- // messages about which specific segment(s) contain invalid characters. We also need to
414- // allow for the presence of wildcard characters in any segment, which means we can't rely
415- // on the same regex pattern used for fully qualified type names and instead need a simpler
416- // pattern that just checks for valid characters within each segment.
417- //
418- // We also want to collect all segment errors rather than failing on the first invalid
419- // segment, to provide more comprehensive feedback to the user.
420422 let errors = & mut Vec :: < WildcardTypeNameError > :: new ( ) ;
421- let owner: String ;
422- let namespaces: Vec < String > ;
423- let require_name: bool ;
424- let name: String ;
425- let regex: Regex ;
426- let validating_segment_regex = Self :: init_validating_segment_regex ( ) ;
427-
428- // Split the input into owner/namespaces and name segments
429- if let Some ( ( owner_and_namespaces, name_segment) ) = text. rsplit_once ( '/' ) {
430- name = name_segment. to_string ( ) ;
431- let mut segments = owner_and_namespaces
432- . split ( '.' )
433- . map ( |s| s. to_string ( ) )
434- . collect :: < Vec < String > > ( ) ;
435- owner = segments. remove ( 0 ) ;
436- namespaces = segments;
437- } else if text. contains ( '.' ) {
438- let mut segments = text
439- . split ( '.' )
440- . map ( |s| s. to_string ( ) )
441- . collect :: < Vec < String > > ( ) ;
442- owner = segments. remove ( 0 ) ;
443- namespaces = segments;
444- name = String :: new ( ) ;
445- } else {
446- owner = text. to_string ( ) ;
447- namespaces = Vec :: new ( ) ;
448- name = String :: new ( ) ;
449- }
450-
451- // Validate the owner segment, which _must_ be defined.
452- if owner. is_empty ( ) {
453- errors. push ( WildcardTypeNameError :: EmptyOwnerSegment ) ;
454- } else if !validating_segment_regex. is_match ( & owner) {
455- errors. push ( WildcardTypeNameError :: InvalidOwnerSegment {
456- segment_text : owner. clone ( ) ,
457- } ) ;
458- }
459-
460- // Validate every defined namespace segment
461- for ( index, namespace) in ( & namespaces) . into_iter ( ) . enumerate ( ) {
462- if namespace. is_empty ( ) {
463- errors. push ( WildcardTypeNameError :: EmptyNamespaceSegment {
464- // Insert the index as 1-based for more user-friendly error messages
465- index : index + 1
466- } ) ;
467- } else if !validating_segment_regex. is_match ( & namespace) {
468- errors. push ( WildcardTypeNameError :: InvalidNamespaceSegment {
469- segment_text : namespace. clone ( ) ,
470- } ) ;
471- }
472- }
423+ let ( owner, namespaces, name) = Self :: parse_segments (
424+ text,
425+ errors
426+ ) ;
473427
474428 // The name segment is required if no wildcard is defined that can match the name
475429 // segment. For example, `Contoso.*.Example` would require a name segment because it
476430 // defines a wildcard in a prior namespace segment with a following literal namespace
477431 // segment.
478- require_name = if namespaces. is_empty ( ) {
432+ let require_name = if namespaces. is_empty ( ) {
479433 !owner. contains ( '*' )
480434 } else {
481435 !namespaces. last ( ) . unwrap ( ) . contains ( '*' )
@@ -485,6 +439,7 @@ impl WildcardTypeName {
485439 }
486440
487441 // Validate the name segment for invalid characters if it's defined.
442+ let validating_segment_regex = Self :: init_validating_segment_regex ( ) ;
488443 if !name. is_empty ( ) && !validating_segment_regex. is_match ( & name) {
489444 errors. push ( WildcardTypeNameError :: InvalidNameSegment {
490445 segment_text : name. clone ( ) ,
@@ -493,7 +448,7 @@ impl WildcardTypeName {
493448
494449 // Construct the regular expression.
495450 let pattern = convert_wildcard_to_regex ( text) ;
496- regex = match RegexBuilder :: new ( & pattern) . case_insensitive ( true ) . build ( ) {
451+ let regex = match RegexBuilder :: new ( & pattern) . case_insensitive ( true ) . build ( ) {
497452 Ok ( r) => r,
498453 Err ( err) => {
499454 errors. push ( WildcardTypeNameError :: InvalidRegex {
@@ -521,6 +476,49 @@ impl WildcardTypeName {
521476 }
522477 }
523478
479+ /// Private helper to parse the input into segments and collect validation errors for owner
480+ /// and namespace segments.
481+ ///
482+ /// This is used by the public `parse()` method to perform the initial parsing of the input
483+ /// string into its constituent segments. It reuses the implementation from
484+ /// [`FullyQualifiedTypeName::parse_segments()`], but converts the errors into the appropriate
485+ /// [`WildcardTypeNameError`] variants and collects them in the provided `errors` vector for
486+ /// bubbling up in the main error handling logic of the `parse()` method.
487+ ///
488+ /// # Arguments
489+ ///
490+ /// - `text` - The input string to parse into segments.
491+ /// - `errors`: A mutable reference to a vector for collecting any validation errors encountered
492+ /// while parsing the segments. This allows the method to accumulate multiple errors for
493+ /// different segments of the type name.
494+ ///
495+ /// # Returns
496+ ///
497+ /// The method returns the parsed segments (`owner`, `namespaces`, and `name`) as a tuple. Any
498+ /// validation errors encountered during parsing are added to the provided `errors` vector for
499+ /// the caller to handle.
500+ pub ( crate ) fn parse_segments ( text : & str , errors : & mut Vec < WildcardTypeNameError > ) -> ( String , Vec < String > , String ) {
501+ // Reuse the segment parsing logic from FullyQualifiedTypeName since the overall structure
502+ // is the same, but we need to provide a different validating regex that allows for
503+ // wildcard characters and the name segment may be optional depending on the presence of
504+ // wildcards in prior segments.
505+ let fqtn_errors = & mut Vec :: < FullyQualifiedTypeNameError > :: new ( ) ;
506+ let validating_segment_regex = Self :: init_validating_segment_regex ( ) ;
507+ let parsed = FullyQualifiedTypeName :: parse_segments (
508+ text,
509+ validating_segment_regex,
510+ fqtn_errors
511+ ) ;
512+ // Convert the FQTN errors and add into the WCTN error collection
513+ errors. extend ( fqtn_errors. into_iter ( ) . map ( |e| {
514+ match TryInto :: < WildcardTypeNameError > :: try_into ( e. clone ( ) ) {
515+ Ok ( wtne) => wtne,
516+ Err ( e) => e,
517+ }
518+ } ) ) ;
519+ // Return the parsed segments.
520+ parsed
521+ }
524522 /// Returns `true` if the wildcard type name has a length of zero and otherwise `false`.
525523 pub fn is_empty ( & self ) -> bool {
526524 self . text . is_empty ( )
@@ -714,3 +712,27 @@ impl Hash for WildcardTypeName {
714712 self . text . to_lowercase ( ) . hash ( state) ;
715713 }
716714}
715+
716+ impl TryFrom < FullyQualifiedTypeNameError > for WildcardTypeNameError {
717+ type Error = WildcardTypeNameError ;
718+ fn try_from ( value : FullyQualifiedTypeNameError ) -> Result < Self , Self :: Error > {
719+ match value {
720+ FullyQualifiedTypeNameError :: InvalidOwnerSegment {
721+ segment_text
722+ } => Ok ( WildcardTypeNameError :: InvalidOwnerSegment { segment_text } ) ,
723+ FullyQualifiedTypeNameError :: InvalidNamespaceSegment {
724+ segment_text
725+ } => Ok ( WildcardTypeNameError :: InvalidNamespaceSegment { segment_text } ) ,
726+ FullyQualifiedTypeNameError :: EmptyNamespaceSegment {
727+ index
728+ } => Ok ( WildcardTypeNameError :: EmptyNamespaceSegment { index } ) ,
729+ FullyQualifiedTypeNameError :: InvalidNameSegment {
730+ segment_text
731+ } => Ok ( WildcardTypeNameError :: InvalidNameSegment { segment_text } ) ,
732+ FullyQualifiedTypeNameError :: EmptyOwnerSegment => Ok ( WildcardTypeNameError :: EmptyOwnerSegment ) ,
733+ FullyQualifiedTypeNameError :: MissingNameSegment => Ok ( WildcardTypeNameError :: MissingNameSegment ) ,
734+ // No other errors should be converted
735+ _ => Err ( WildcardTypeNameError :: InvalidErrorConversion { err : value } )
736+ }
737+ }
738+ }
0 commit comments