Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions enumer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -215,3 +225,7 @@ func (g *Generator) buildYAMLMethods(runs [][]Value, typeName string, runsThresh
// We rely on the %[1]sString method to provide typed errors when enabled
g.Printf(yamlMethods, typeName)
}

func (g *Generator) buildValidateMethod(typeName string) {
g.Printf(validateMethod, typeName)
}
41 changes: 24 additions & 17 deletions golden_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
package main

import (
"io/ioutil"
"io"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -76,6 +76,10 @@ var goldenLinecomment = []Golden{
{"dayWithLinecomment", linecommentIn},
}

var goldenValidateMethod = []Golden{
{name: "validate", input: dayIn},
}

var goldenTypedErrors = []Golden{
{"typedErrors", typedErrorsIn},
}
Expand Down Expand Up @@ -327,48 +331,51 @@ const (

func TestGolden(t *testing.T) {
for _, test := range golden {
runGoldenTest(t, test, false, false, false, false, false, false, true, "", "", false)
runGoldenTest(t, test, false, false, false, false, false, false, true, false, "", "", false)
}
for _, test := range goldenJSON {
runGoldenTest(t, test, true, false, false, false, false, false, false, "", "", false)
runGoldenTest(t, test, true, false, false, false, false, false, false, false, "", "", false)
}
for _, test := range goldenText {
runGoldenTest(t, test, false, false, false, true, false, false, false, "", "", false)
runGoldenTest(t, test, false, false, false, true, false, false, false, false, "", "", false)
}
for _, test := range goldenYAML {
runGoldenTest(t, test, false, true, false, false, false, false, false, "", "", false)
runGoldenTest(t, test, false, true, false, false, false, false, false, false, "", "", false)
}
for _, test := range goldenSQL {
runGoldenTest(t, test, false, false, true, false, false, false, false, "", "", false)
runGoldenTest(t, test, false, false, true, false, false, false, false, false, "", "", false)
}
for _, test := range goldenJSONAndSQL {
runGoldenTest(t, test, true, false, true, false, false, false, false, "", "", false)
runGoldenTest(t, test, true, false, true, false, false, false, false, false, "", "", false)
}
for _, test := range goldenGQLGen {
runGoldenTest(t, test, false, false, false, false, false, true, false, "", "", false)
runGoldenTest(t, test, false, false, false, false, false, true, false, false, "", "", false)
}
for _, test := range goldenTrimPrefix {
runGoldenTest(t, test, false, false, false, false, false, false, false, "Day", "", false)
runGoldenTest(t, test, false, false, false, false, false, false, false, false, "Day", "", false)
}
for _, test := range goldenTrimPrefixMultiple {
runGoldenTest(t, test, false, false, false, false, false, false, false, "Day,Night", "", false)
runGoldenTest(t, test, false, false, false, false, false, false, false, false, "Day,Night", "", false)
}
for _, test := range goldenWithPrefix {
runGoldenTest(t, test, false, false, false, false, false, false, false, "", "Day", false)
runGoldenTest(t, test, false, false, false, false, false, false, false, false, "", "Day", false)
}
for _, test := range goldenTrimAndAddPrefix {
runGoldenTest(t, test, false, false, false, false, false, false, false, "Day", "Night", false)
runGoldenTest(t, test, false, false, false, false, false, false, false, false, "Day", "Night", false)
}
for _, test := range goldenLinecomment {
runGoldenTest(t, test, false, false, false, false, true, false, false, "", "", false)
runGoldenTest(t, test, false, false, false, false, true, false, false, false, "", "", false)
}
for _, test := range goldenValidateMethod {
runGoldenTest(t, test, false, false, false, false, false, false, false, true, "", "", false)
}
for _, test := range goldenTypedErrors {
runGoldenTest(t, test, false, false, false, false, false, false, false, "", "", true)
runGoldenTest(t, test, false, false, false, false, false, false, false, false, "", "", true)
}
}

func runGoldenTest(t *testing.T, test Golden,
generateJSON, generateYAML, generateSQL, generateText, linecomment, generateGQLGen, generateValuesMethod bool,
generateJSON, generateYAML, generateSQL, generateText, linecomment, generateGQLGen, generateValuesMethod, generateIsValidMethod bool,
trimPrefix string, prefix string, useTypedErrors bool) {

var g Generator
Expand Down Expand Up @@ -397,7 +404,7 @@ func runGoldenTest(t *testing.T, test Golden,
if len(tokens) != 3 {
t.Fatalf("%s: need type declaration on first line", test.name)
}
g.generate(tokens[1], generateJSON, generateYAML, generateSQL, generateText, generateGQLGen, "noop", trimPrefix, prefix, linecomment, generateValuesMethod, useTypedErrors)
g.generate(tokens[1], generateJSON, generateYAML, generateSQL, generateText, generateGQLGen, "noop", trimPrefix, prefix, linecomment, generateValuesMethod, generateIsValidMethod, useTypedErrors)
got := string(g.format())
if got != loadGolden(test.name) {
// Use this to help build a golden text when changes are needed
Expand All @@ -416,7 +423,7 @@ func loadGolden(name string) string {
return ""
}
defer fh.Close()
b, err := ioutil.ReadAll(fh)
b, err := io.ReadAll(fh)
if err != nil {
return ""
}
Expand Down
10 changes: 8 additions & 2 deletions stringer.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ var (
text = flag.Bool("text", false, "if true, text marshaling methods will be generated. Default: false")
gqlgen = flag.Bool("gqlgen", false, "if true, GraphQL marshaling methods for gqlgen will be generated. Default: false")
altValuesFunc = flag.Bool("values", false, "if true, alternative string values method will be generated. Default: false")
validateFunc = flag.Bool("validate", false, "if true, a `Validate() error` method will be generated. Default: false")
output = flag.String("output", "", "output file name; default srcdir/<type>_string.go")
transformMethod = flag.String("transform", "noop", "enum item name transformation method. Default: noop")
trimPrefix = flag.String("trimprefix", "", "transform each item name by removing a prefix or comma separated list of prefixes. Default: \"\"")
Expand Down Expand Up @@ -140,7 +141,7 @@ func main() {

// Run generate for each type.
for _, typeName := range typs {
g.generate(typeName, *json, *yaml, *sql, *text, *gqlgen, *transformMethod, *trimPrefix, *addPrefix, *linecomment, *altValuesFunc, *typedErrors)
g.generate(typeName, *json, *yaml, *sql, *text, *gqlgen, *transformMethod, *trimPrefix, *addPrefix, *linecomment, *altValuesFunc, *validateFunc, *typedErrors)
}

// Format the output.
Expand Down Expand Up @@ -420,7 +421,9 @@ func (g *Generator) prefixValueNames(values []Value, prefix string) {
// generate produces the String method for the named type.
func (g *Generator) generate(typeName string,
includeJSON, includeYAML, includeSQL, includeText, includeGQLGen bool,
transformMethod string, trimPrefix string, addPrefix string, lineComment bool, includeValuesMethod bool, useTypedErrors bool) {
transformMethod string, trimPrefix string, addPrefix string,
lineComment, includeValuesMethod, includeValidateFunc, useTypedErrors bool,
) {
values := make([]Value, 0, 100)
for _, file := range g.pkg.files {
file.lineComment = lineComment
Expand Down Expand Up @@ -470,6 +473,9 @@ func (g *Generator) generate(typeName string,
if includeValuesMethod {
g.buildAltStringValuesMethod(typeName)
}
if includeValidateFunc {
g.buildValidateMethod(typeName)
}

g.buildNoOpOrderChangeDetect(runs, typeName)

Expand Down
98 changes: 98 additions & 0 deletions testdata/validate.golden
Original file line number Diff line number Diff line change
@@ -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
}