Skip to content

d0x7/errnumer

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

errnumer

A go:generate tool for usage with ProtoNATS.

License

Overview

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.

Usage

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 login

Or 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()
}

License

MIT © 2025 Dorian Heinrichs

About

Go Generate tool inspired by Enumer for ProtoNATS errors.

Topics

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages