diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index c0f1aa0..650b36d 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -26,3 +26,31 @@ jobs: - name: Test run: go test -v ./... + + golden-tests: + runs-on: ubuntu-latest + needs: build + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.22' + + - name: Run Golden Tests + run: go test -v -run TestGolden + + end-to-end-tests: + runs-on: ubuntu-latest + needs: build + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.22' + + - name: Run End-to-End Tests + run: go test -v -run TestEndToEnd diff --git a/README.md b/README.md index 084fcd8..86765d7 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,8 @@ Flags: transform each item name by removing a prefix or comma separated list of prefixes. Default: "" -type string comma-separated list of type names; must be set + -typederrors + if true, errors from enumerrs/ will be errors.Join()-ed for errors.Is(...) to simplify invalid value handling. Default: false -values if true, alternative string values method will be generated. Default: false -yaml @@ -70,6 +72,9 @@ When Enumer is applied to a type, it will generate: the enum conform to the `gopkg.in/yaml.v2.Marshaler` and `gopkg.in/yaml.v2.Unmarshaler` interfaces. - When the flag `sql` is provided, the methods for implementing the `Scanner` and `Valuer` interfaces. Useful when storing the enum in a database. +- When the flag `typederrors` is provided, the string conversion functions will return errors wrapped with + `errors.Join()` containing a typed error from the `enumerrs` package. This allows you to use `errors.Is()` to + check for specific enum validation failures. For example, if we have an enum type called `Pill`, @@ -200,7 +205,7 @@ For a module-aware repo with `enumer` in the `go.mod` file, generation can be ca //go:generate go run github.com/dmarkham/enumer -type=YOURTYPE ``` -There are four boolean flags: `json`, `text`, `yaml` and `sql`. You can use any combination of them (i.e. `enumer -type=Pill -json -text`), +There are five boolean flags: `json`, `text`, `yaml`, `sql`, and `typederrors`. You can use any combination of them (i.e. `enumer -type=Pill -json -text -typederrors`), For enum string representation transformation the `transform` and `trimprefix` flags were added (i.e. `enumer -type=MyType -json -transform=snake`). @@ -215,6 +220,28 @@ If a prefix is provided via the `addprefix` flag, it will be added to the start The boolean flag `values` will additionally create an alternative string values method `Values() []string` to fullfill the `EnumValues` interface of [ent](https://entgo.io/docs/schema-fields/#enum-fields). +## Typed Error Handling + +When using the `typederrors` flag, you can handle enum validation errors specifically using `errors.Is()`: + +```go +import ( + "errors" + "github.com/dmarkham/enumer/enumerrs" +) + +// This will return a typed error that can be checked +pill, err := PillString("InvalidValue") +if err != nil { + if errors.Is(err, enumerrs.ErrValueInvalid) { + // Handle invalid enum value specifically + fmt.Println("Invalid pill value provided") + } + // The error also contains a descriptive message + fmt.Printf("Error: %v\n", err) +} +``` + ## Inspiring projects - [Álvaro López Espinosa](https://github.com/alvaroloes/enumer) diff --git a/endtoend_test.go b/endtoend_test.go index 203ecd6..1f1d230 100644 --- a/endtoend_test.go +++ b/endtoend_test.go @@ -4,6 +4,7 @@ // go command is not available on android +//go:build !android // +build !android package main @@ -75,6 +76,7 @@ func TestEndToEnd(t *testing.T) { // Names are known to be ASCII and long enough. var typeName string var transformNameMethod string + var useTypedErrors bool switch name { case "transform_snake.go": @@ -110,18 +112,22 @@ func TestEndToEnd(t *testing.T) { case "transform_whitespace.go": typeName = "WhitespaceSeparatedValue" transformNameMethod = "whitespace" + case "typedErrors.go": + typeName = "TypedErrorsValue" + transformNameMethod = "noop" + useTypedErrors = true default: typeName = fmt.Sprintf("%c%s", name[0]+'A'-'a', name[1:len(name)-len(".go")]) transformNameMethod = "noop" } - stringerCompileAndRun(t, dir, stringer, typeName, name, transformNameMethod) + stringerCompileAndRun(t, dir, stringer, typeName, name, transformNameMethod, useTypedErrors) } } // stringerCompileAndRun runs stringer for the named file and compiles and // runs the target binary in directory dir. That binary will panic if the String method is incorrect. -func stringerCompileAndRun(t *testing.T, dir, stringer, typeName, fileName, transformNameMethod string) { +func stringerCompileAndRun(t *testing.T, dir, stringer, typeName, fileName, transformNameMethod string, useTypedErrors bool) { t.Logf("run: %s %s\n", fileName, typeName) source := filepath.Join(dir, fileName) err := copy(source, filepath.Join("testdata", fileName)) @@ -130,7 +136,12 @@ func stringerCompileAndRun(t *testing.T, dir, stringer, typeName, fileName, tran } stringSource := filepath.Join(dir, typeName+"_string.go") // Run stringer in temporary directory. - err = run(stringer, "-type", typeName, "-output", stringSource, "-transform", transformNameMethod, source) + args := []string{"-type", typeName, "-output", stringSource, "-transform", transformNameMethod} + if useTypedErrors { + args = append(args, "-typederrors", "-values") + } + args = append(args, source) + err = run(stringer, args...) if err != nil { t.Fatal(err) } diff --git a/enumer.go b/enumer.go index c234209..2e122e3 100644 --- a/enumer.go +++ b/enumer.go @@ -2,8 +2,7 @@ package main import "fmt" -// Arguments to format are: -// [1]: type name +// Arguments to format are: [1]: type name [2]: complete error expression const stringNameToValueMethod = `// %[1]sString retrieves an enum value from the enum constants string name. // Throws an error if the param is not part of the enum. func %[1]sString(s string) (%[1]s, error) { @@ -14,20 +13,18 @@ func %[1]sString(s string) (%[1]s, error) { if val, ok := _%[1]sNameToValueMap[strings.ToLower(s)]; ok { return val, nil } - return 0, fmt.Errorf("%%s does not belong to %[1]s values", s) + return 0, %[2]s } ` -// Arguments to format are: -// [1]: type name +// Arguments to format are: [1]: type name const stringValuesMethod = `// %[1]sValues returns all values of the enum func %[1]sValues() []%[1]s { return _%[1]sValues } ` -// Arguments to format are: -// [1]: type name +// Arguments to format are: [1]: type name const stringsMethod = `// %[1]sStrings returns a slice of all String values of the enum func %[1]sStrings() []string { strs := make([]string, len(_%[1]sNames)) @@ -36,8 +33,7 @@ func %[1]sStrings() []string { } ` -// Arguments to format are: -// [1]: type name +// Arguments to format are: [1]: type name const stringBelongsMethodLoop = `// IsA%[1]s returns "true" if the value is listed in the enum definition. "false" otherwise func (i %[1]s) IsA%[1]s() bool { for _, v := range _%[1]sValues { @@ -49,8 +45,7 @@ func (i %[1]s) IsA%[1]s() bool { } ` -// Arguments to format are: -// [1]: type name +// Arguments to format are: [1]: type name const stringBelongsMethodSet = `// IsA%[1]s returns "true" if the value is listed in the enum definition. "false" otherwise func (i %[1]s) IsA%[1]s() bool { _, ok := _%[1]sMap[i] @@ -58,8 +53,7 @@ func (i %[1]s) IsA%[1]s() bool { } ` -// Arguments to format are: -// [1]: type name +// Arguments to format are: [1]: type name const altStringValuesMethod = `func (%[1]s) Values() []string { return %[1]sStrings() } @@ -70,7 +64,7 @@ func (g *Generator) buildAltStringValuesMethod(typeName string) { g.Printf(altStringValuesMethod, typeName) } -func (g *Generator) buildBasicExtras(runs [][]Value, typeName string, runsThreshold int) { +func (g *Generator) buildBasicExtras(runs [][]Value, typeName string, runsThreshold int, useTypedErrors bool) { // At this moment, either "g.declareIndexAndNameVars()" or "g.declareNameVars()" has been called // Print the slice of values @@ -89,7 +83,13 @@ func (g *Generator) buildBasicExtras(runs [][]Value, typeName string, runsThresh g.printNamesSlice(runs, typeName, runsThreshold) // Print the basic extra methods - g.Printf(stringNameToValueMethod, typeName) + var errorCode string + if useTypedErrors { + errorCode = fmt.Sprintf(`errors.Join(enumerrs.ErrValueInvalid, fmt.Errorf("%%s does not belong to %s values", s))`, typeName) + } else { + errorCode = fmt.Sprintf(`fmt.Errorf("%%s does not belong to %s values", s)`, typeName) + } + g.Printf(stringNameToValueMethod, typeName, errorCode) g.Printf(stringValuesMethod, typeName) g.Printf(stringsMethod, typeName) if len(runs) <= runsThreshold { @@ -143,8 +143,7 @@ func (g *Generator) printNamesSlice(runs [][]Value, typeName string, runsThresho g.Printf("}\n\n") } -// Arguments to format are: -// [1]: type name +// Arguments to format are: [1]: type name const jsonMethods = ` // MarshalJSON implements the json.Marshaler interface for %[1]s func (i %[1]s) MarshalJSON() ([]byte, error) { @@ -164,12 +163,13 @@ func (i *%[1]s) UnmarshalJSON(data []byte) error { } ` -func (g *Generator) buildJSONMethods(runs [][]Value, typeName string, runsThreshold int) { +func (g *Generator) buildJSONMethods(runs [][]Value, typeName string, runsThreshold int, useTypedErrors bool) { + // For now, just use the standard template + // We rely on the %[1]sString method to provide typed errors when enabled g.Printf(jsonMethods, typeName) } -// Arguments to format are: -// [1]: type name +// Arguments to format are: [1]: type name const textMethods = ` // MarshalText implements the encoding.TextMarshaler interface for %[1]s func (i %[1]s) MarshalText() ([]byte, error) { @@ -184,12 +184,13 @@ func (i *%[1]s) UnmarshalText(text []byte) error { } ` -func (g *Generator) buildTextMethods(runs [][]Value, typeName string, runsThreshold int) { +func (g *Generator) buildTextMethods(runs [][]Value, typeName string, runsThreshold int, useTypedErrors bool) { + // For now, just use the standard template + // We rely on the %[1]sString method to provide typed errors when enabled g.Printf(textMethods, typeName) } -// Arguments to format are: -// [1]: type name +// Arguments to format are: [1]: type name const yamlMethods = ` // MarshalYAML implements a YAML Marshaler for %[1]s func (i %[1]s) MarshalYAML() (interface{}, error) { @@ -209,6 +210,8 @@ func (i *%[1]s) UnmarshalYAML(unmarshal func(interface{}) error) error { } ` -func (g *Generator) buildYAMLMethods(runs [][]Value, typeName string, runsThreshold int) { +func (g *Generator) buildYAMLMethods(runs [][]Value, typeName string, runsThreshold int, useTypedErrors bool) { + // For now, just use the standard template + // We rely on the %[1]sString method to provide typed errors when enabled g.Printf(yamlMethods, typeName) } diff --git a/enumerrs/errors.go b/enumerrs/errors.go new file mode 100644 index 0000000..a397a53 --- /dev/null +++ b/enumerrs/errors.go @@ -0,0 +1,8 @@ +package enumerrs + +import "errors" + +// This package defines custom error types for use in the generated code. + +// ErrValueInvalid is returned when a value does not belong to the set of valid values for a type. +var ErrValueInvalid = errors.New("the input value is not valid for the type") diff --git a/golden_test.go b/golden_test.go index 03479d2..2906f04 100644 --- a/golden_test.go +++ b/golden_test.go @@ -76,6 +76,10 @@ var goldenLinecomment = []Golden{ {"dayWithLinecomment", linecommentIn}, } +var goldenTypedErrors = []Golden{ + {"typedErrors", typedErrorsIn}, +} + // Each example starts with "type XXX [u]int", with a single space separating them. // Simple test: enumeration of type int starting at 0. @@ -313,54 +317,65 @@ const ( ) ` +const typedErrorsIn = `type TypedErrorsValue int +const ( + TypedErrorsValueOne TypedErrorsValue = iota + TypedErrorsValueTwo + TypedErrorsValueThree +) +` + func TestGolden(t *testing.T) { for _, test := range golden { - runGoldenTest(t, test, false, false, false, false, false, false, true, "", "") + runGoldenTest(t, test, false, false, false, false, false, false, true, "", "", false) } for _, test := range goldenJSON { - runGoldenTest(t, test, true, false, false, false, false, false, false, "", "") + runGoldenTest(t, test, true, false, false, false, false, false, false, "", "", false) } for _, test := range goldenText { - runGoldenTest(t, test, false, false, false, true, false, false, false, "", "") + runGoldenTest(t, test, false, false, false, true, false, false, false, "", "", false) } for _, test := range goldenYAML { - runGoldenTest(t, test, false, true, false, false, false, false, false, "", "") + runGoldenTest(t, test, false, true, false, false, false, false, false, "", "", false) } for _, test := range goldenSQL { - runGoldenTest(t, test, false, false, true, false, false, false, false, "", "") + runGoldenTest(t, test, false, false, true, false, false, false, false, "", "", false) } for _, test := range goldenJSONAndSQL { - runGoldenTest(t, test, true, false, true, false, false, false, false, "", "") + runGoldenTest(t, test, true, false, true, false, false, false, false, "", "", false) } for _, test := range goldenGQLGen { - runGoldenTest(t, test, false, false, false, false, false, true, false, "", "") + runGoldenTest(t, test, false, false, false, false, false, true, false, "", "", false) } for _, test := range goldenTrimPrefix { - runGoldenTest(t, test, false, false, false, false, false, false, false, "Day", "") + runGoldenTest(t, test, 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", "") + runGoldenTest(t, test, 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") + runGoldenTest(t, test, 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") + runGoldenTest(t, test, false, false, false, false, false, false, false, "Day", "Night", false) } for _, test := range goldenLinecomment { - runGoldenTest(t, test, false, false, false, false, true, false, false, "", "") + runGoldenTest(t, test, false, false, false, false, true, false, false, "", "", false) + } + for _, test := range goldenTypedErrors { + runGoldenTest(t, test, false, false, false, false, false, false, false, "", "", true) } } func runGoldenTest(t *testing.T, test Golden, generateJSON, generateYAML, generateSQL, generateText, linecomment, generateGQLGen, generateValuesMethod bool, - trimPrefix string, prefix string) { + trimPrefix string, prefix string, useTypedErrors bool) { var g Generator file := test.name + ".go" input := "package test\n" + test.input - dir, err := ioutil.TempDir("", "stringer") + dir, err := os.MkdirTemp("", "stringer") if err != nil { t.Error(err) } @@ -372,7 +387,7 @@ func runGoldenTest(t *testing.T, test Golden, }() absFile := filepath.Join(dir, file) - err = ioutil.WriteFile(absFile, []byte(input), 0644) + err = os.WriteFile(absFile, []byte(input), 0644) if err != nil { t.Error(err) } @@ -382,15 +397,15 @@ 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) + g.generate(tokens[1], generateJSON, generateYAML, generateSQL, generateText, generateGQLGen, "noop", trimPrefix, prefix, linecomment, generateValuesMethod, useTypedErrors) got := string(g.format()) if got != loadGolden(test.name) { // Use this to help build a golden text when changes are needed - //goldenFile := fmt.Sprintf("./testdata/%v.golden", test.name) - //err = ioutil.WriteFile(goldenFile, []byte(got), 0644) - //if err != nil { - // t.Error(err) - //} + // goldenFile := fmt.Sprintf("./testdata/%v.golden", test.name) + // err = os.WriteFile(goldenFile, []byte(got), 0644) + // if err != nil { + // t.Error(err) + // } t.Errorf("%s: got\n====\n%s====\nexpected\n====%s", test.name, got, loadGolden(test.name)) } } diff --git a/gqlgen.go b/gqlgen.go index 45dddbb..ded8fbf 100644 --- a/gqlgen.go +++ b/gqlgen.go @@ -1,7 +1,6 @@ package main -// Arguments to format are: -// [1]: type name +// Arguments to format are: [1]: type name const gqlgenMethods = ` // MarshalGQL implements the graphql.Marshaler interface for %[1]s func (i %[1]s) MarshalGQL(w io.Writer) { diff --git a/sql.go b/sql.go index 09f050c..54f0e95 100644 --- a/sql.go +++ b/sql.go @@ -1,7 +1,6 @@ package main -// Arguments to format are: -// [1]: type name +// Arguments to format are: [1]: type name const valueMethod = `func (i %[1]s) Value() (driver.Value, error) { return i.String(), nil } diff --git a/stringer.go b/stringer.go index 33d3d09..4c99758 100644 --- a/stringer.go +++ b/stringer.go @@ -56,6 +56,7 @@ var ( trimPrefix = flag.String("trimprefix", "", "transform each item name by removing a prefix or comma separated list of prefixes. Default: \"\"") addPrefix = flag.String("addprefix", "", "transform each item name by adding a prefix. Default: \"\"") linecomment = flag.Bool("linecomment", false, "use line comment text as printed text when present") + typedErrors = flag.Bool("typederrors", false, "if true, use typed errors for enum string conversion methods. Default: false") ) var comments arrayFlags @@ -119,6 +120,10 @@ func main() { g.Printf("package %s", g.pkg.name) g.Printf("\n") g.Printf("import (\n") + if *typedErrors { + g.Printf("\t\"errors\"\n") + g.Printf("\t\"github.com/dmarkham/enumer/enumerrs\"\n") + } g.Printf("\t\"fmt\"\n") g.Printf("\t\"strings\"\n") if *sql { @@ -135,7 +140,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) + g.generate(typeName, *json, *yaml, *sql, *text, *gqlgen, *transformMethod, *trimPrefix, *addPrefix, *linecomment, *altValuesFunc, *typedErrors) } // Format the output. @@ -415,7 +420,7 @@ 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) { + transformMethod string, trimPrefix string, addPrefix string, lineComment bool, includeValuesMethod bool, useTypedErrors bool) { values := make([]Value, 0, 100) for _, file := range g.pkg.files { file.lineComment = lineComment @@ -468,15 +473,15 @@ func (g *Generator) generate(typeName string, g.buildNoOpOrderChangeDetect(runs, typeName) - g.buildBasicExtras(runs, typeName, runsThreshold) + g.buildBasicExtras(runs, typeName, runsThreshold, useTypedErrors) if includeJSON { - g.buildJSONMethods(runs, typeName, runsThreshold) + g.buildJSONMethods(runs, typeName, runsThreshold, useTypedErrors) } if includeText { - g.buildTextMethods(runs, typeName, runsThreshold) + g.buildTextMethods(runs, typeName, runsThreshold, useTypedErrors) } if includeYAML { - g.buildYAMLMethods(runs, typeName, runsThreshold) + g.buildYAMLMethods(runs, typeName, runsThreshold, useTypedErrors) } if includeSQL { g.addValueAndScanMethod(typeName) diff --git a/testdata/typedErrors.go b/testdata/typedErrors.go new file mode 100644 index 0000000..5f89be2 --- /dev/null +++ b/testdata/typedErrors.go @@ -0,0 +1,25 @@ +package main + +import "fmt" + +type TypedErrorsValue int + +const ( + TypedErrorsValueOne TypedErrorsValue = iota + TypedErrorsValueTwo + TypedErrorsValueThree +) + +func main() { + checkMatch(TypedErrorsValueOne, "TypedErrorsValueOne") + checkMatch(TypedErrorsValueTwo, "TypedErrorsValueTwo") + checkMatch(TypedErrorsValueThree, "TypedErrorsValueThree") + checkMatch(-127, "TypedErrorsValue(-127)") + checkMatch(127, "TypedErrorsValue(127)") +} + +func checkMatch(value TypedErrorsValue, str string) { + if fmt.Sprint(value) != str { + panic("transform_upper.go: " + str) + } +} diff --git a/testdata/typedErrors.golden b/testdata/typedErrors.golden new file mode 100644 index 0000000..9c6e7d1 --- /dev/null +++ b/testdata/typedErrors.golden @@ -0,0 +1,74 @@ + +const _TypedErrorsValueName = "TypedErrorsValueOneTypedErrorsValueTwoTypedErrorsValueThree" + +var _TypedErrorsValueIndex = [...]uint8{0, 19, 38, 59} + +const _TypedErrorsValueLowerName = "typederrorsvalueonetypederrorsvaluetwotypederrorsvaluethree" + +func (i TypedErrorsValue) String() string { + if i < 0 || i >= TypedErrorsValue(len(_TypedErrorsValueIndex)-1) { + return fmt.Sprintf("TypedErrorsValue(%d)", i) + } + return _TypedErrorsValueName[_TypedErrorsValueIndex[i]:_TypedErrorsValueIndex[i+1]] +} + +// An "invalid array index" compiler error signifies that the constant values have changed. +// Re-run the stringer command to generate them again. +func _TypedErrorsValueNoOp() { + var x [1]struct{} + _ = x[TypedErrorsValueOne-(0)] + _ = x[TypedErrorsValueTwo-(1)] + _ = x[TypedErrorsValueThree-(2)] +} + +var _TypedErrorsValueValues = []TypedErrorsValue{TypedErrorsValueOne, TypedErrorsValueTwo, TypedErrorsValueThree} + +var _TypedErrorsValueNameToValueMap = map[string]TypedErrorsValue{ + _TypedErrorsValueName[0:19]: TypedErrorsValueOne, + _TypedErrorsValueLowerName[0:19]: TypedErrorsValueOne, + _TypedErrorsValueName[19:38]: TypedErrorsValueTwo, + _TypedErrorsValueLowerName[19:38]: TypedErrorsValueTwo, + _TypedErrorsValueName[38:59]: TypedErrorsValueThree, + _TypedErrorsValueLowerName[38:59]: TypedErrorsValueThree, +} + +var _TypedErrorsValueNames = []string{ + _TypedErrorsValueName[0:19], + _TypedErrorsValueName[19:38], + _TypedErrorsValueName[38:59], +} + +// TypedErrorsValueString retrieves an enum value from the enum constants string name. +// Throws an error if the param is not part of the enum. +func TypedErrorsValueString(s string) (TypedErrorsValue, error) { + if val, ok := _TypedErrorsValueNameToValueMap[s]; ok { + return val, nil + } + + if val, ok := _TypedErrorsValueNameToValueMap[strings.ToLower(s)]; ok { + return val, nil + } + return 0, errors.Join(enumerrs.ErrValueInvalid, fmt.Errorf("%s does not belong to TypedErrorsValue values", s)) +} + +// TypedErrorsValueValues returns all values of the enum +func TypedErrorsValueValues() []TypedErrorsValue { + return _TypedErrorsValueValues +} + +// TypedErrorsValueStrings returns a slice of all String values of the enum +func TypedErrorsValueStrings() []string { + strs := make([]string, len(_TypedErrorsValueNames)) + copy(strs, _TypedErrorsValueNames) + return strs +} + +// IsATypedErrorsValue returns "true" if the value is listed in the enum definition. "false" otherwise +func (i TypedErrorsValue) IsATypedErrorsValue() bool { + for _, v := range _TypedErrorsValueValues { + if i == v { + return true + } + } + return false +}