@@ -17,7 +17,13 @@ pub(super) struct Project {
1717 pub ( super ) name : String ,
1818 pub ( super ) contexts : Vec < String > ,
1919 #[ serde( default ) ]
20- pub ( super ) custom_namespaces : Vec < String > ,
20+ pub ( super ) custom_namespaces_by_context : Vec < ContextNamespaces > ,
21+ }
22+
23+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
24+ pub ( super ) struct ContextNamespaces {
25+ pub ( super ) context : String ,
26+ pub ( super ) namespaces : Vec < String > ,
2127}
2228
2329#[ derive( Debug , Clone ) ]
@@ -241,7 +247,7 @@ impl Default for ProjectStore {
241247 projects : vec ! [ Project {
242248 name: String :: from( DEFAULT_PROJECT_NAME ) ,
243249 contexts: Vec :: new( ) ,
244- custom_namespaces : Vec :: new( ) ,
250+ custom_namespaces_by_context : Vec :: new( ) ,
245251 } ] ,
246252 selected_project : Some ( String :: from ( DEFAULT_PROJECT_NAME ) ) ,
247253 visible_object_columns : default_object_columns ( ) ,
@@ -431,7 +437,7 @@ impl ProjectStore {
431437 self . projects . push ( Project {
432438 name : String :: from ( DEFAULT_PROJECT_NAME ) ,
433439 contexts : Vec :: new ( ) ,
434- custom_namespaces : Vec :: new ( ) ,
440+ custom_namespaces_by_context : Vec :: new ( ) ,
435441 } ) ;
436442 self . selected_project = Some ( String :: from ( DEFAULT_PROJECT_NAME ) ) ;
437443 }
@@ -447,6 +453,14 @@ impl ProjectStore {
447453 project
448454 . contexts
449455 . retain ( |name| live_names. contains ( name. as_str ( ) ) ) ;
456+ let project_contexts = project
457+ . contexts
458+ . iter ( )
459+ . map ( String :: as_str)
460+ . collect :: < BTreeSet < _ > > ( ) ;
461+ project
462+ . custom_namespaces_by_context
463+ . retain ( |entry| project_contexts. contains ( entry. context . as_str ( ) ) ) ;
450464 }
451465
452466 let selected_exists = self
@@ -460,8 +474,7 @@ impl ProjectStore {
460474 for project in & mut self . projects {
461475 project. contexts . sort ( ) ;
462476 project. contexts . dedup ( ) ;
463- project. custom_namespaces . sort ( ) ;
464- project. custom_namespaces . dedup ( ) ;
477+ project. normalize_custom_namespaces ( ) ;
465478 }
466479 }
467480
@@ -487,6 +500,9 @@ impl ProjectStore {
487500 return ;
488501 } ;
489502 project. contexts . retain ( |candidate| candidate != context) ;
503+ project
504+ . custom_namespaces_by_context
505+ . retain ( |entry| entry. context != context) ;
490506 }
491507
492508 pub ( super ) fn selected_project_mut ( & mut self ) -> Option < & mut Project > {
@@ -497,6 +513,81 @@ impl ProjectStore {
497513 }
498514}
499515
516+ impl Project {
517+ pub ( super ) fn custom_namespaces_for_context ( & self , context : Option < & str > ) -> Vec < String > {
518+ let Some ( context) = context else {
519+ return Vec :: new ( ) ;
520+ } ;
521+ self . custom_namespaces_by_context
522+ . iter ( )
523+ . find ( |entry| entry. context == context)
524+ . map ( |entry| entry. namespaces . clone ( ) )
525+ . unwrap_or_default ( )
526+ }
527+
528+ pub ( super ) fn has_custom_namespace ( & self , context : Option < & str > , namespace : & str ) -> bool {
529+ self . custom_namespaces_for_context ( context)
530+ . iter ( )
531+ . any ( |known| known == namespace)
532+ }
533+
534+ pub ( super ) fn add_custom_namespace ( & mut self , context : & str , namespace : & str ) -> bool {
535+ if context. is_empty ( ) || namespace. is_empty ( ) {
536+ return false ;
537+ }
538+
539+ if let Some ( entry) = self
540+ . custom_namespaces_by_context
541+ . iter_mut ( )
542+ . find ( |entry| entry. context == context)
543+ {
544+ if entry. namespaces . iter ( ) . any ( |known| known == namespace) {
545+ return false ;
546+ }
547+ entry. namespaces . push ( namespace. to_owned ( ) ) ;
548+ entry. namespaces . sort ( ) ;
549+ entry. namespaces . dedup ( ) ;
550+ return true ;
551+ }
552+
553+ self . custom_namespaces_by_context . push ( ContextNamespaces {
554+ context : context. to_owned ( ) ,
555+ namespaces : vec ! [ namespace. to_owned( ) ] ,
556+ } ) ;
557+ self . normalize_custom_namespaces ( ) ;
558+ true
559+ }
560+
561+ fn normalize_custom_namespaces ( & mut self ) {
562+ for entry in & mut self . custom_namespaces_by_context {
563+ entry. context = entry. context . trim ( ) . to_owned ( ) ;
564+ entry. namespaces . retain ( |namespace| {
565+ let namespace = namespace. trim ( ) ;
566+ !namespace. is_empty ( ) && namespace != "all"
567+ } ) ;
568+ for namespace in & mut entry. namespaces {
569+ * namespace = namespace. trim ( ) . to_owned ( ) ;
570+ }
571+ entry. namespaces . sort ( ) ;
572+ entry. namespaces . dedup ( ) ;
573+ }
574+ self . custom_namespaces_by_context
575+ . retain ( |entry| !entry. context . is_empty ( ) && !entry. namespaces . is_empty ( ) ) ;
576+ self . custom_namespaces_by_context
577+ . sort_by ( |left, right| left. context . cmp ( & right. context ) ) ;
578+ self . custom_namespaces_by_context . dedup_by ( |left, right| {
579+ if left. context == right. context {
580+ left. namespaces . extend ( right. namespaces . clone ( ) ) ;
581+ left. namespaces . sort ( ) ;
582+ left. namespaces . dedup ( ) ;
583+ true
584+ } else {
585+ false
586+ }
587+ } ) ;
588+ }
589+ }
590+
500591#[ cfg( test) ]
501592mod tests {
502593 use super :: * ;
@@ -518,7 +609,7 @@ mod tests {
518609 projects : vec ! [ Project {
519610 name: String :: from( "Work" ) ,
520611 contexts: vec![ String :: from( "local" ) ] ,
521- custom_namespaces : Vec :: new( ) ,
612+ custom_namespaces_by_context : Vec :: new( ) ,
522613 } ] ,
523614 selected_project : Some ( String :: from ( "Work" ) ) ,
524615 visible_object_columns : default_object_columns ( ) ,
@@ -531,4 +622,24 @@ mod tests {
531622 let project = store. selected_project ( ) . unwrap ( ) ;
532623 assert_eq ! ( project. contexts, vec![ String :: from( "local" ) ] ) ;
533624 }
625+
626+ #[ test]
627+ fn custom_namespaces_are_scoped_by_context ( ) {
628+ let mut project = Project {
629+ name : String :: from ( "Work" ) ,
630+ contexts : vec ! [ String :: from( "prod" ) , String :: from( "stage" ) ] ,
631+ custom_namespaces_by_context : Vec :: new ( ) ,
632+ } ;
633+
634+ assert ! ( project. add_custom_namespace( "prod" , "billing" ) ) ;
635+
636+ assert_eq ! (
637+ project. custom_namespaces_for_context( Some ( "prod" ) ) ,
638+ vec![ String :: from( "billing" ) ]
639+ ) ;
640+ assert ! ( project
641+ . custom_namespaces_for_context( Some ( "stage" ) )
642+ . is_empty( ) ) ;
643+ assert ! ( !project. has_custom_namespace( Some ( "stage" ) , "billing" ) ) ;
644+ }
534645}
0 commit comments