Skip to content

Conversation

@bincyber
Copy link
Contributor

This PR adds a flag to allow a custom error type to be generated for the stringNameToValueMethod, so that the error type can be used with errors.Is()

Example:

//go:generate enumer -type=Day -text -customerror -output=generated_day.go
type Day int

const (
	Monday Day = iota
	Tuesday
	Wednesday
	Thursday
	Friday
	Saturday
	Sunday
)

This would generate:

// ErrInvalidDay is a custom error type for Day
var ErrInvalidDay = errors.New("invalid value for Day")

// 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, ErrInvalidDay
}

Resolves #110

@bincyber
Copy link
Contributor Author

Please review @samiam2013 @dmarkham when you get a chance. Thanks in advance

@samiam2013 samiam2013 self-assigned this Jul 22, 2025
@bincyber bincyber requested a review from samiam2013 July 26, 2025 16:13
@samiam2013
Copy link
Collaborator

Why use a custom error for every different enumer type you've generated? What if enumer just included a single type enumer.InvalidValue or similar which could be error.Joined so that error.Is could be used to catch it without having to guess or check a custom error type?

@samiam2013
Copy link
Collaborator

@bincyber giving you an @ so you can see my question above

@samiam2013
Copy link
Collaborator

@bincyber if I don't get a response here, since this seems like an in-demand feature and the other user seemed to have a usecase for non-enum-specific errors (so they work with any enum and you don't have to know a specific error type) I might make a run at this myself. Hope that won't offend

@bincyber
Copy link
Contributor Author

bincyber commented Aug 1, 2025

@bincyber if I don't get a response here, since this seems like an in-demand feature and the other user seemed to have a usecase for non-enum-specific errors (so they work with any enum and you don't have to know a specific error type) I might make a run at this myself. Hope that won't offend

Sorry for the delayed reply.

Why use a custom error for every different enumer type you've generated? What if enumer just included a single type enumer.InvalidValue or similar which could be error.Joined so that error.Is could be used to catch it without having to guess or check a custom error type?

This makes sense to me. I'll make the necessary changes shortly.

…rror types

- added a new public package containing a custom error type: InvalidEnumValueError
- modified the code to return this new custom error type when -customerror flag is used
- removed usage of deprecated ioutil library
@bincyber bincyber force-pushed the generate-custom-error-type branch from 721e15b to b69b324 Compare August 1, 2025 12:06
@bincyber
Copy link
Contributor Author

bincyber commented Aug 1, 2025

@samiam2013 I thought about returning the new custom error by default and removing the -customerror flag, but that would be a breaking change for existing users:

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, enumer.InvalidEnumValueError
}

Like you suggested, this could be modified to use errors.Join instead which would allow us to drop the -customError flag while maintaining backwards compatibility:

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, errors.Join(enumer.InvalidEnumValueError, fmt.Errorf("%s does not belong to Day values", s))
}

I'm leaning towards the second option. What do you prefer?

@samiam2013
Copy link
Collaborator

I think the second option is great, especially if we can keep the string text and prove it still handles any naive substring or regex check emulating a "sentinel error".

I'm worried specifically about anyone with a regex including a specification of ^, requiring that their pattern match the beginning of the string representation of this error. This particular case exists in the check provided in the attached issue #110. This join will cause the text representation of our new typed error to be printed first, breaking any code matching on this kind of pattern.

I'd like to avoid a breaking change for anyone if possible, so one option is to take the route of joining an exported error but still gating it behind a flag, -typederrors or similar. This way the error handling change would be adopted at the go generate caller's discretion (maybe API-utilized structs need this behavior but lots of internal tooling doesn't) rather than being forced on them across the board by a version change.

@bincyber
Copy link
Contributor Author

bincyber commented Aug 3, 2025

I'll close this PR in favor of #112. Let's get this in!

@bincyber bincyber closed this Aug 3, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Introduce an error for invalid enum values to distinguish 400 and 422 responses during JSON parsing

2 participants