@@ -38,10 +38,12 @@ import (
3838// paramSingle An explicitly requested type.
3939// paramObject dig.In struct where each field in the struct can be another
4040// param.
41- // paramGroupedSlice
42- // A slice consuming a value group. This will receive all
41+ // paramGroupedCollection
42+ // A slice or map consuming a value group. This will receive all
4343// values produced with a `group:".."` tag with the same name
44- // as a slice.
44+ // as a slice or map. For a map, every value produced with the
45+ // same group name MUST have a name which will form the map key.
46+
4547type param interface {
4648 fmt.Stringer
4749
5961 _ param = paramSingle {}
6062 _ param = paramObject {}
6163 _ param = paramList {}
62- _ param = paramGroupedSlice {}
64+ _ param = paramGroupedCollection {}
6365)
6466
6567// newParam builds a param from the given type. If the provided type is a
@@ -342,7 +344,7 @@ func getParamOrder(gh *graphHolder, param param) []int {
342344 for _ , provider := range providers {
343345 orders = append (orders , provider .Order (gh .s ))
344346 }
345- case paramGroupedSlice :
347+ case paramGroupedCollection :
346348 // value group parameters have nodes of their own.
347349 // We can directly return that here.
348350 orders = append (orders , p .orders [gh .s ])
@@ -401,7 +403,7 @@ func (po paramObject) Build(c containerStore) (reflect.Value, error) {
401403 var softGroupsQueue []paramObjectField
402404 var fields []paramObjectField
403405 for _ , f := range po .Fields {
404- if p , ok := f .Param .(paramGroupedSlice ); ok && p .Soft {
406+ if p , ok := f .Param .(paramGroupedCollection ); ok && p .Soft {
405407 softGroupsQueue = append (softGroupsQueue , f )
406408 continue
407409 }
@@ -451,7 +453,7 @@ func newParamObjectField(idx int, f reflect.StructField, c containerStore) (para
451453
452454 case f .Tag .Get (_groupTag ) != "" :
453455 var err error
454- p , err = newParamGroupedSlice (f , c )
456+ p , err = newParamGroupedCollection (f , c )
455457 if err != nil {
456458 return pof , err
457459 }
@@ -488,29 +490,31 @@ func (pof paramObjectField) Build(c containerStore) (reflect.Value, error) {
488490 return v , nil
489491}
490492
491- // paramGroupedSlice is a param which produces a slice of values with the same
493+ // paramGroupedCollection is a param which produces a slice or map of values with the same
492494// group name.
493- type paramGroupedSlice struct {
495+ type paramGroupedCollection struct {
494496 // Name of the group as specified in the `group:".."` tag.
495497 Group string
496498
497- // Type of the slice.
499+ // Type of the map or slice.
498500 Type reflect.Type
499501
500502 // Soft is used to denote a soft dependency between this param and its
501503 // constructors, if it's true its constructors are only called if they
502504 // provide another value requested in the graph
503505 Soft bool
504506
507+ isMap bool
505508 orders map [* Scope ]int
506509}
507510
508- func (pt paramGroupedSlice ) String () string {
511+ func (pt paramGroupedCollection ) String () string {
509512 // io.Reader[group="foo"] refers to a group of io.Readers called 'foo'
510513 return fmt .Sprintf ("%v[group=%q]" , pt .Type .Elem (), pt .Group )
514+ // JQTODO, different string for map
511515}
512516
513- func (pt paramGroupedSlice ) DotParam () []* dot.Param {
517+ func (pt paramGroupedCollection ) DotParam () []* dot.Param {
514518 return []* dot.Param {
515519 {
516520 Node : & dot.Node {
@@ -521,28 +525,31 @@ func (pt paramGroupedSlice) DotParam() []*dot.Param {
521525 }
522526}
523527
524- // newParamGroupedSlice builds a paramGroupedSlice from the provided type with
528+ // newParamGroupedCollection builds a paramGroupedCollection from the provided type with
525529// the given name.
526530//
527- // The type MUST be a slice type.
528- func newParamGroupedSlice (f reflect.StructField , c containerStore ) (paramGroupedSlice , error ) {
531+ // The type MUST be a slice or map[string]T type.
532+ func newParamGroupedCollection (f reflect.StructField , c containerStore ) (paramGroupedCollection , error ) {
529533 g , err := parseGroupString (f .Tag .Get (_groupTag ))
530534 if err != nil {
531- return paramGroupedSlice {}, err
535+ return paramGroupedCollection {}, err
532536 }
533- pg := paramGroupedSlice {
537+ isMap := f .Type .Kind () == reflect .Map && f .Type .Key ().Kind () == reflect .String
538+ isSlice := f .Type .Kind () == reflect .Slice
539+ pg := paramGroupedCollection {
534540 Group : g .Name ,
535541 Type : f .Type ,
542+ isMap : isMap ,
536543 orders : make (map [* Scope ]int ),
537544 Soft : g .Soft ,
538545 }
539546
540547 name := f .Tag .Get (_nameTag )
541548 optional , _ := isFieldOptional (f )
542549 switch {
543- case f . Type . Kind () != reflect . Slice :
550+ case ! isMap && ! isSlice :
544551 return pg , newErrInvalidInput (
545- fmt .Sprintf ("value groups may be consumed as slices only: field %q (%v) is not a slice" , f .Name , f .Type ), nil )
552+ fmt .Sprintf ("value groups may be consumed as slices or string-keyed maps only: field %q (%v) is not a slice or string-keyed map " , f .Name , f .Type ), nil )
546553 case g .Flatten :
547554 return pg , newErrInvalidInput (
548555 fmt .Sprintf ("cannot use flatten in parameter value groups: field %q (%v) specifies flatten" , f .Name , f .Type ), nil )
@@ -560,7 +567,7 @@ func newParamGroupedSlice(f reflect.StructField, c containerStore) (paramGrouped
560567// any of the parent Scopes. In the case where there are multiple scopes that
561568// are decorating the same type, the closest scope in effect will be replacing
562569// any decorated value groups provided in further scopes.
563- func (pt paramGroupedSlice ) getDecoratedValues (c containerStore ) (reflect.Value , bool ) {
570+ func (pt paramGroupedCollection ) getDecoratedValues (c containerStore ) (reflect.Value , bool ) {
564571 for _ , c := range c .storesToRoot () {
565572 if items , ok := c .getDecoratedValueGroup (pt .Group , pt .Type ); ok {
566573 return items , true
@@ -575,7 +582,7 @@ func (pt paramGroupedSlice) getDecoratedValues(c containerStore) (reflect.Value,
575582// The order in which the decorators are invoked is from the top level scope to
576583// the current scope, to account for decorators that decorate values that were
577584// already decorated.
578- func (pt paramGroupedSlice ) callGroupDecorators (c containerStore ) error {
585+ func (pt paramGroupedCollection ) callGroupDecorators (c containerStore ) error {
579586 stores := c .storesToRoot ()
580587 for i := len (stores ) - 1 ; i >= 0 ; i -- {
581588 c := stores [i ]
@@ -600,7 +607,7 @@ func (pt paramGroupedSlice) callGroupDecorators(c containerStore) error {
600607// search the given container and its parent for matching group providers and
601608// call them to commit values. If an error is encountered, return the number
602609// of providers called and a non-nil error from the first provided.
603- func (pt paramGroupedSlice ) callGroupProviders (c containerStore ) (int , error ) {
610+ func (pt paramGroupedCollection ) callGroupProviders (c containerStore ) (int , error ) {
604611 itemCount := 0
605612 for _ , c := range c .storesToRoot () {
606613 providers := c .getGroupProviders (pt .Group , pt .Type .Elem ())
@@ -618,7 +625,7 @@ func (pt paramGroupedSlice) callGroupProviders(c containerStore) (int, error) {
618625 return itemCount , nil
619626}
620627
621- func (pt paramGroupedSlice ) Build (c containerStore ) (reflect.Value , error ) {
628+ func (pt paramGroupedCollection ) Build (c containerStore ) (reflect.Value , error ) {
622629 // do not call this if we are already inside a decorator since
623630 // it will result in an infinite recursion. (i.e. decorate -> params.BuildList() -> Decorate -> params.BuildList...)
624631 // this is safe since a value can be decorated at most once in a given scope.
@@ -644,6 +651,22 @@ func (pt paramGroupedSlice) Build(c containerStore) (reflect.Value, error) {
644651 }
645652
646653 stores := c .storesToRoot ()
654+ if pt .isMap {
655+ result := reflect .MakeMapWithSize (pt .Type , itemCount )
656+ for _ , c := range stores {
657+ kgvs := c .getValueGroup (pt .Group , pt .Type .Elem ())
658+ for _ , kgv := range kgvs {
659+ if kgv .key == "" {
660+ return _noValue , newErrInvalidInput (
661+ fmt .Sprintf ("every entry in a map value groups must have a name, group \" %v\" is missing a name" , pt .Group ),
662+ nil ,
663+ )
664+ }
665+ result .SetMapIndex (reflect .ValueOf (kgv .key ), kgv .value )
666+ }
667+ }
668+ return result , nil
669+ }
647670 result := reflect .MakeSlice (pt .Type , 0 , itemCount )
648671 for _ , c := range stores {
649672 kgvs := c .getValueGroup (pt .Group , pt .Type .Elem ())
0 commit comments