diff --git a/enumer.go b/enumer.go index 85f0e5b..1d805d7 100644 --- a/enumer.go +++ b/enumer.go @@ -59,6 +59,16 @@ const altStringValuesMethod = `func (%[1]s) Values() []string { } ` +// Arguments to format are: [1]: type name +const validateMethod = `// Validate returns an error if the value is not listed in the enum definition. +func (i %[1]s) Validate() error { + if !i.IsA%[1]s() { + return fmt.Errorf("%%v is not a valid value for %[1]s values", i) + } + return nil +} +` + func (g *Generator) buildAltStringValuesMethod(typeName string) { g.Printf("\n") g.Printf(altStringValuesMethod, typeName) @@ -216,6 +226,10 @@ func (g *Generator) buildYAMLMethods(runs [][]Value, typeName string, runsThresh g.Printf(yamlMethods, typeName) } +func (g *Generator) buildValidateMethod(typeName string) { + g.Printf(validateMethod, typeName) +} + // Arguments to format are: [1]: type name const flagValueMethodSet = ` // Set allows flag and pflag libraries to set a value dynamically. diff --git a/golden_test.go b/golden_test.go index b31c854..fbe369d 100644 --- a/golden_test.go +++ b/golden_test.go @@ -76,6 +76,10 @@ var goldenLinecomment = []Golden{ {"dayWithLinecomment", linecommentIn}, } +var goldenValidateMethod = []Golden{ + {name: "validate", input: dayIn}, +} + var goldenFlagValue = []Golden{ {"flagvalue", dayIn}, } @@ -408,6 +412,12 @@ func TestGolden(t *testing.T) { lineComment: true, }) } + for _, test := range goldenValidateMethod { + runGoldenTest(t, test, generateOptions{ + transformMethod: "noop", + includeValidateFunc: true, + }) + } for _, test := range goldenFlagValue { runGoldenTest(t, test, generateOptions{ transformMethod: "noop", diff --git a/stringer.go b/stringer.go index 807e673..e040e8c 100644 --- a/stringer.go +++ b/stringer.go @@ -54,6 +54,7 @@ type generateOptions struct { addPrefix string lineComment bool includeValuesMethod bool + includeValidateFunc bool includeFlagMethods bool includePflagMethods bool useTypedErrors bool @@ -75,6 +76,7 @@ func init() { flag.BoolVar(&opts.includeText, "text", false, "if true, text marshaling methods will be generated. Default: false") flag.BoolVar(&opts.includeGQLGen, "gqlgen", false, "if true, GraphQL marshaling methods for gqlgen will be generated. Default: false") flag.BoolVar(&opts.includeValuesMethod, "values", false, "if true, alternative string values method will be generated. Default: false") + flag.BoolVar(&opts.includeValidateFunc, "validate", false, "if true, a `Validate() error` method will be generated. Default: false") flag.BoolVar(&opts.includeFlagMethods, "flag.value", false, "if true, ensure that the enumeration type implements stdlib flag.Value interface. Default: false") flag.BoolVar(&opts.includePflagMethods, "pflag.value", false, "if true, ensure that the enumeration type implements pflag.Value interface, see: https://pkg.go.dev/github.com/spf13/pflag#Value Default: false") flag.StringVar(&output, "output", "", "output file name; default srcdir/_string.go") @@ -490,6 +492,9 @@ func (g *Generator) generate(typeName string, opts generateOptions) { if opts.includeValuesMethod { g.buildAltStringValuesMethod(typeName) } + if opts.includeValidateFunc { + g.buildValidateMethod(typeName) + } g.buildNoOpOrderChangeDetect(runs, typeName) diff --git a/testdata/validate.golden b/testdata/validate.golden new file mode 100644 index 0000000..3f70b72 --- /dev/null +++ b/testdata/validate.golden @@ -0,0 +1,98 @@ + +const _DayName = "MondayTuesdayWednesdayThursdayFridaySaturdaySunday" + +var _DayIndex = [...]uint8{0, 6, 13, 22, 30, 36, 44, 50} + +const _DayLowerName = "mondaytuesdaywednesdaythursdayfridaysaturdaysunday" + +func (i Day) String() string { + if i < 0 || i >= Day(len(_DayIndex)-1) { + return fmt.Sprintf("Day(%d)", i) + } + return _DayName[_DayIndex[i]:_DayIndex[i+1]] +} + +// Validate returns an error if the value is not listed in the enum definition. +func (i Day) Validate() error { + if !i.IsADay() { + return fmt.Errorf("%v is not a valid value for Day values", i) + } + return nil +} + +// An "invalid array index" compiler error signifies that the constant values have changed. +// Re-run the stringer command to generate them again. +func _DayNoOp() { + var x [1]struct{} + _ = x[Monday-(0)] + _ = x[Tuesday-(1)] + _ = x[Wednesday-(2)] + _ = x[Thursday-(3)] + _ = x[Friday-(4)] + _ = x[Saturday-(5)] + _ = x[Sunday-(6)] +} + +var _DayValues = []Day{Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday} + +var _DayNameToValueMap = map[string]Day{ + _DayName[0:6]: Monday, + _DayLowerName[0:6]: Monday, + _DayName[6:13]: Tuesday, + _DayLowerName[6:13]: Tuesday, + _DayName[13:22]: Wednesday, + _DayLowerName[13:22]: Wednesday, + _DayName[22:30]: Thursday, + _DayLowerName[22:30]: Thursday, + _DayName[30:36]: Friday, + _DayLowerName[30:36]: Friday, + _DayName[36:44]: Saturday, + _DayLowerName[36:44]: Saturday, + _DayName[44:50]: Sunday, + _DayLowerName[44:50]: Sunday, +} + +var _DayNames = []string{ + _DayName[0:6], + _DayName[6:13], + _DayName[13:22], + _DayName[22:30], + _DayName[30:36], + _DayName[36:44], + _DayName[44:50], +} + +// DayString retrieves an enum value from the enum constants string name. +// Throws an error if the param is not part of the enum. +func DayString(s string) (Day, error) { + if val, ok := _DayNameToValueMap[s]; ok { + return val, nil + } + + if val, ok := _DayNameToValueMap[strings.ToLower(s)]; ok { + return val, nil + } + return 0, fmt.Errorf("%s does not belong to Day values", s) +} + +// DayValues returns all values of the enum +func DayValues() []Day { + return _DayValues +} + +// DayStrings returns a slice of all String values of the enum +func DayStrings() []string { + strs := make([]string, len(_DayNames)) + copy(strs, _DayNames) + return strs +} + +// IsADay returns "true" if the value is listed in the enum definition. "false" otherwise +func (i Day) IsADay() bool { + for _, v := range _DayValues { + if i == v { + return true + } + } + return false +}