diff --git a/paramgen/internal/paramgen.go b/paramgen/internal/paramgen.go index 3af7409..ac4b2f9 100644 --- a/paramgen/internal/paramgen.go +++ b/paramgen/internal/paramgen.go @@ -241,6 +241,8 @@ func (p *parameterParser) parseTypeSpec(ts *ast.TypeSpec, f *ast.Field) (params return p.parseSelectorExpr(v, f) case *ast.Ident: return p.parseIdent(v, f) + case *ast.MapType: + return p.parseMapType(v, f) default: return nil, fmt.Errorf("unexpected type: %T", ts.Type) } @@ -296,6 +298,8 @@ func (p *parameterParser) parseField(f *ast.Field) (params map[string]config.Par return p.parseStructType(v, f) case *ast.SelectorExpr: return p.parseSelectorExpr(v, f) + case *ast.MapType: + return p.parseMapType(v, f) case *ast.ArrayType: strType := fmt.Sprintf("%s", v.Elt) if !p.isBuiltinType(strType) && !strings.Contains(strType, "time Duration") { @@ -312,6 +316,43 @@ func (p *parameterParser) parseField(f *ast.Field) (params map[string]config.Par } } +func (p *parameterParser) parseMapType(mt *ast.MapType, f *ast.Field) (params map[string]config.Parameter, err error) { + if fmt.Sprintf("%s", mt.Key) != "string" { + return nil, fmt.Errorf("unsupported map key type: %s", mt.Key) + } + + // parse map value as if it was a field + var tmpParams map[string]config.Parameter + switch val := mt.Value.(type) { + case *ast.Ident: + // identifier (builtin type or type in same package) + tmpParams, err = p.parseIdent(val, f) + case *ast.StructType: + // nested type + tmpParams, err = p.parseStructType(val, f) + case *ast.SelectorExpr: + tmpParams, err = p.parseSelectorExpr(val, f) + } + if err != nil { + return nil, err + } + + // inject wildcard + params = make(map[string]config.Parameter, len(tmpParams)) + for k, p := range tmpParams { + index := strings.Index(k, ".") + if index == -1 { + index = len(k) + } + name := k[:index] + ".*" + if index < len(k) { + name += k[index:] + } + params[name] = p + } + return params, nil +} + func (p *parameterParser) parseSelectorExpr(se *ast.SelectorExpr, f *ast.Field) (params map[string]config.Parameter, err error) { defer func() { if err != nil { diff --git a/paramgen/internal/paramgen_test.go b/paramgen/internal/paramgen_test.go index 377fca5..9d71bed 100644 --- a/paramgen/internal/paramgen_test.go +++ b/paramgen/internal/paramgen_test.go @@ -59,23 +59,26 @@ func TestParseSpecificationSuccess(t *testing.T) { }, }, }, - "myUint": {Type: config.ParameterTypeInt}, - "myInt8": {Type: config.ParameterTypeInt}, - "myUint8": {Type: config.ParameterTypeInt}, - "myInt16": {Type: config.ParameterTypeInt}, - "myUint16": {Type: config.ParameterTypeInt}, - "myInt32": {Type: config.ParameterTypeInt}, - "myUint32": {Type: config.ParameterTypeInt}, - "myInt64": {Type: config.ParameterTypeInt}, - "myUint64": {Type: config.ParameterTypeInt}, - "myByte": {Type: config.ParameterTypeString}, - "myRune": {Type: config.ParameterTypeInt}, - "myFloat32": {Type: config.ParameterTypeFloat}, - "myFloat64": {Type: config.ParameterTypeFloat}, - "myDuration": {Type: config.ParameterTypeDuration}, - "myIntSlice": {Type: config.ParameterTypeString}, - "myFloatSlice": {Type: config.ParameterTypeString}, - "myDurSlice": {Type: config.ParameterTypeString}, + "myUint": {Type: config.ParameterTypeInt}, + "myInt8": {Type: config.ParameterTypeInt}, + "myUint8": {Type: config.ParameterTypeInt}, + "myInt16": {Type: config.ParameterTypeInt}, + "myUint16": {Type: config.ParameterTypeInt}, + "myInt32": {Type: config.ParameterTypeInt}, + "myUint32": {Type: config.ParameterTypeInt}, + "myInt64": {Type: config.ParameterTypeInt}, + "myUint64": {Type: config.ParameterTypeInt}, + "myByte": {Type: config.ParameterTypeString}, + "myRune": {Type: config.ParameterTypeInt}, + "myFloat32": {Type: config.ParameterTypeFloat}, + "myFloat64": {Type: config.ParameterTypeFloat}, + "myDuration": {Type: config.ParameterTypeDuration}, + "myIntSlice": {Type: config.ParameterTypeString}, + "myFloatSlice": {Type: config.ParameterTypeString}, + "myDurSlice": {Type: config.ParameterTypeString}, + "myStringMap.*": {Type: config.ParameterTypeString}, + "myStructMap.*.myInt": {Type: config.ParameterTypeInt}, + "myStructMap.*.myString": {Type: config.ParameterTypeString}, }, }, { @@ -88,6 +91,20 @@ func TestParseSpecificationSuccess(t *testing.T) { Description: "Duration does not have a name so the type name is used.", Type: config.ParameterTypeDuration, }, + "global.wildcardStrings.*": { + Default: "foo", + Type: config.ParameterTypeString, + Validations: []config.Validation{ + config.ValidationRequired{}, + }, + }, + "global.renamed.*": { + Default: "1s", + Type: config.ParameterTypeDuration, + }, + "global.wildcardStructs.*.name": { + Type: config.ParameterTypeString, + }, "nestMeHere.anotherNested": { Type: config.ParameterTypeInt, Description: "AnotherNested is also nested under nestMeHere.\nThis is a block comment.", diff --git a/paramgen/internal/testdata/basic/specs.go b/paramgen/internal/testdata/basic/specs.go index 6032931..efed368 100644 --- a/paramgen/internal/testdata/basic/specs.go +++ b/paramgen/internal/testdata/basic/specs.go @@ -58,6 +58,14 @@ type SourceConfig struct { MyFloatSlice []float32 MyDurSlice []time.Duration + MyStringMap map[string]string + MyStructMap map[string]structMapVal + // this field is ignored because it is not exported ignoreThis http.Client } + +type structMapVal struct { + MyString string + MyInt int +} diff --git a/paramgen/internal/testdata/complex/internal/global.go b/paramgen/internal/testdata/complex/internal/global.go index 81efeaa..c51b5c8 100644 --- a/paramgen/internal/testdata/complex/internal/global.go +++ b/paramgen/internal/testdata/complex/internal/global.go @@ -20,4 +20,12 @@ import "time" type GlobalConfig struct { // Duration does not have a name so the type name is used. time.Duration `default:"1s"` // line comments on fields with doc comments are ignored + + WildcardStrings map[string]string `default:"foo" validate:"required"` + WildcardInts map[string]time.Duration `json:"renamed" default:"1s"` + WildcardStructs WildcardStruct +} + +type WildcardStruct map[string]struct { + Name string }