A go:generate tool for usage with ProtoNATS.
Errnumer is a code generation tool that simplifies error handling in Go applications using ProtoNATS. It generates error enumerations and associated helper functions, so you can define your errors in a shared package and have it used both in client and server side.
Create a go file (e.g., errors.go, preferably in your shared Protobuf generated Go package) and add the following go:generate directive:
//go:generate go run xiam.li/errnumer/cmd/errnumer -type=errnumerAtlasError -output=error_enum.go
type errnumerAtlasError struct {
Code string
Description string
}
var (
errnumerAtlasErrorUnknown = errnumerAtlasError{"500", "An unknown or unhandled server error occurred."}
errnumerAtlasErrorPlayerNotFound = errnumerAtlasError{"1001", "The specified player could not be found."}
errnumerAtlasErrorPlayerAlreadyOnline = errnumerAtlasError{"1002", "A session for this player already exists."}
errnumerAtlasErrorSessionNotFound = errnumerAtlasError{"1101", "The specified session could not be found."}
errnumerAtlasErrorActiveSession = errnumerAtlasError{"1102", "The current player already has an active session."}
)Currently it's a bit repetitive to define the error struct, so I might change it in the future, but it's what I got for the moment. All of this should be declared unexported, such that only the generated code is exported.
The code generated would look like this:
// Code generated by errnumer; DO NOT EDIT.
package atlas
import "xiam.li/protonats/go/protonats"
type AtlasError int
const (
AtlasErrorUnknown AtlasError = iota
AtlasErrorPlayerNotFound
AtlasErrorPlayerAlreadyOnline
AtlasErrorSessionNotFound
AtlasErrorActiveSession
)
var AtlasErrorCodes = []string{
"500",
"1001",
"1002",
"1101",
"1102",
}
var AtlasErrorDescriptions = []string{
"An unknown or unhandled server error occurred.",
"The specified player could not be found.",
"A session for this player already exists.",
"The specified session could not be found.",
"The current player already has an active session.",
}
// AsServerError converts the enum to a protonats.ServerError.
func (e AtlasError) AsServerError() protonats.ServerError {
codeString := AtlasErrorCodes[e]
description := AtlasErrorDescriptions[e]
return protonats.NewServerErr(codeString, description)
}
// String returns the human-readable description of the error.
func (e AtlasError) String() string {
if e < 0 || int(e) >= len(AtlasErrorDescriptions) {
return AtlasErrorDescriptions[0] // Default to the 'Unknown' error
}
return AtlasErrorDescriptions[e]
}
// Code returns the error code string of the error.
func (e AtlasError) Code() string {
if e < 0 || int(e) >= len(AtlasErrorCodes) {
return AtlasErrorCodes[0] // Default to the 'Unknown' error code
}
return AtlasErrorCodes[e]
}
// AtlasErrorValues returns a slice of all possible values for the enum.
func AtlasErrorValues() []AtlasError {
return []AtlasError{
AtlasErrorUnknown,
AtlasErrorPlayerNotFound,
AtlasErrorPlayerAlreadyOnline,
AtlasErrorSessionNotFound,
AtlasErrorActiveSession,
}
}
// AtlasErrorFromCode returns the value for the given error code.
func AtlasErrorFromCode(s string) (AtlasError, bool) {
for i, code := range AtlasErrorCodes {
if code == s {
return AtlasError(i), true
}
}
// Return the first enum value (typically the 'Unknown' error) by default.
return AtlasError(0), false
}
// AtlasErrorFrom extracts the AtlasError from a ProtoNATS service error, if possible.
func AtlasErrorFrom(err error) (AtlasError, bool) {
if srvErr, ok := protonats.AsServiceError(err); ok {
return AtlasErrorFromCode(srvErr.Code)
}
return AtlasError(0), false
}On the client side, this could be used like this:
response, err := atlasClient.Login( /* [...] */)
if err != nil {
if atlasErr, ok := atlas.AtlasErrorFrom(err); ok {
fmt.Printf("Atlas service error: %s (code %s)\n", atlasErr.String(), atlasErr.Code())
switch atlasErr {
case atlas.AtlasErrorActiveSession:
fallthrough
case atlas.AtlasErrorPlayerAlreadyOnline:
return false, sessionAlreadyExistsComponent
default:
fmt.Printf("Got a valid, but unexpected Atlas error: %s\n", atlasErr.String())
return false, sessionServersUnavailableComponent
}
}
// Handle differently, as it's probably a networking issue or similar
// [...]
return false, sessionServersUnavailableComponent
}
// Successful loginOr you can do it manually like this:
code, ok := AtlasErrorFromCode("1002")
if !ok {
fmt.Print("Not a valid Atlas error code")
return
}
switch code {
case AtlasErrorPlayerNotFound:
fmt.Print("Player is not found")
case AtlasErrorPlayerAlreadyOnline:
fmt.Print("Player is already online")
default:
fmt.Printf("Got a valid, but unexpected error: %s", code.String())
}On the server side, you would use it like this:
// Inside your ProtoNATS service handler
if hasActiveSession {
return nil, atlas.AtlasErrorActiveSession.AsServerError()
}MIT © 2025 Dorian Heinrichs