Documentation
¶
Overview ¶
Package errortree provides primitives for working with errors in tree structure
errortree is intended to be used in places where errors are generated from an arbitrary tree structure, like the validation of a configuration file. This allows adding additional context as to why an error has happened in a clean and structured way.
errortree fully supports nesting of multiple trees, including simplified retrieval of errors which, among other things, should help remove repeated boilerplate code from unit tests.
Example ¶
package main import ( "errors" "fmt" "io/ioutil" "net" "os" "github.com/speijnik/go-errortree" ) // ErrOptionMissing indicates that a configuration option is missing var ErrOptionMissing = errors.New("Configuration option is missing") type NetworkConfiguration struct { ListenAddress string MaxClients uint } // Validate validates the network configuration func (c *NetworkConfiguration) Validate() (err error) { if c.ListenAddress == "" { err = errortree.Add(err, "ListenAddress", ErrOptionMissing) } else if _, _, splitErr := net.SplitHostPort(c.ListenAddress); splitErr != nil { err = errortree.Add(err, "ListenAddress", errors.New("Must be in host:port format")) } if c.MaxClients < 1 { err = errortree.Add(err, "MaxClients", errors.New("Must be at least 1")) } return } type StorageConfiguration struct { DataDirectory string } func (c *StorageConfiguration) Validate() (err error) { if c.DataDirectory == "" { err = errortree.Add(err, "DataDirectory", ErrOptionMissing) } else if fileInfo, statErr := os.Stat(c.DataDirectory); statErr != nil { err = errortree.Add(err, "DataDirectory", errors.New("Directory does not exist")) } else if !fileInfo.IsDir() { err = errortree.Add(err, "DataDirectory", errors.New("Not a directory")) } return } // Configuration represents a configuration struct // which may be filled from a file. // // It provides a Validate function which ensures that the configuration // is correct and can be used. type Configuration struct { // Network configuration Network NetworkConfiguration // Storage configuration Storage StorageConfiguration } func (c *Configuration) Validate() (err error) { err = errortree.Add(err, "Network", c.Network.Validate()) err = errortree.Add(err, "Storage", c.Storage.Validate()) return } func main() { // Initialize an empty configuration. Validating this configuration should give us three errors: c := Configuration{} fmt.Println("[0] " + c.Validate().Error()) // Now set some values and validate again c.Network.ListenAddress = "[[:8080" c.Network.MaxClients = 1 c.Storage.DataDirectory = "/non-existing" fmt.Println("[1] " + c.Validate().Error()) // Set the data directory to a temporary file (!) and validate again f, err := ioutil.TempFile("", "go-errortree-test-") if err != nil { panic(err) } defer os.Remove(f.Name()) defer f.Close() c.Network.ListenAddress = "0.0.0.0:8080" c.Storage.DataDirectory = f.Name() fmt.Println("[2] " + c.Validate().Error()) // Fix everything and run again tempDir, err := ioutil.TempDir("", "go-errortree-test-") if err != nil { panic(err) } defer os.Remove(tempDir) c.Storage.DataDirectory = tempDir if err := c.Validate(); err == nil { fmt.Println("[3] Config OK") } }
Output: [0] 3 errors occurred: * Network:ListenAddress: Configuration option is missing * Network:MaxClients: Must be at least 1 * Storage:DataDirectory: Configuration option is missing [1] 2 errors occurred: * Network:ListenAddress: Must be in host:port format * Storage:DataDirectory: Directory does not exist [2] 1 error occurred: * Storage:DataDirectory: Not a directory [3] Config OK
Index ¶
- Constants
- func Add(parent error, key string, err error) error
- func Flatten(err error) map[string]error
- func Get(err error, key string, path ...string) error
- func GetAny(err error, key string, path ...string) error
- func Keys(err error) []string
- func Set(parent error, key string, err error) error
- func SimpleFormatter(errorMap map[string]error) string
- type Formatter
- type Tree
Examples ¶
Constants ¶
const DefaultDelimiter = ":"
DefaultDelimiter defines the delimiter that is by default used for representing paths to nested Errors
Variables ¶
This section is empty.
Functions ¶
func Add ¶
Add adds an error under a given key to the provided tree.
This function panics if the key is already present in the tree. Otherwise it behaves like Set.
Example ¶
var err error // Using Add on a nil-error automatically creates an error tree and adds the desired error err = errortree.Add(err, "test", errors.New("test error")) fmt.Println(err.Error())
Output: 1 error occurred: * test: test error
Example (Duplicate) ¶
// Add an error with the key "test" err := errortree.Add(nil, "test", errors.New("test error")) // Recover from the panic, this will the output we expect below defer func() { if r := recover(); r != nil { fmt.Println(r) } }() errortree.Add(err, "test", errors.New("key re-used"))
Output: Cannot add error: key test exists.
Example (Nested) ¶
// Create an error which will acts as the child for our top-level error childError := errortree.Add(nil, "test0", errors.New("child error")) // Add another error to our child childError = errortree.Add(childError, "test1", errors.New("another child error")) // Create the top-level error, adding the child in the process err := errortree.Add(nil, "child", childError) // Add another top-level error err = errortree.Add(err, "second", errors.New("top-level error")) fmt.Println(err.Error())
Output: 3 errors occurred: * child:test0: child error * child:test1: another child error * second: top-level error
func Flatten ¶
Flatten returns the error tree in flattened form.
Each error inside the complete tree is stored under its full key. The full key is constructed from the each error's path inside the tree and joined together with the tree's delimiter.
Example ¶
tree := &errortree.Tree{ Errors: map[string]error{ "a": errors.New("top-level"), "b": &errortree.Tree{ Errors: map[string]error{ "c": errors.New("nested"), }, }, }, } flattened := errortree.Flatten(tree) // Sort keys alphabetically so we get reproducible output keys := errortree.Keys(tree) for _, key := range keys { fmt.Println("key: " + key + ", value: " + flattened[key].Error()) }
Output: key: a, value: top-level key: b:c, value: nested
func Get ¶
Get retrieves the error for the given key from the provided error. The path parameter may be used for specifying a nested error's key.
If the error is not an errortree.Tree or the child cannot be found on the exact path this function returns nil.
Example ¶
tree := &errortree.Tree{ Errors: map[string]error{ "a": errors.New("top-level"), "test": errors.New("test"), "b": &errortree.Tree{ Errors: map[string]error{ "c": errors.New("nested"), }, }, }, } // Get can be used to retrieve an error by its key fmt.Println(errortree.Get(tree, "a")) // Nested retrieval is supported as well fmt.Println(errortree.Get(tree, "b", "c"))
Output: top-level nested
Example (Nested) ¶
tree := &errortree.Tree{ Errors: map[string]error{ "a": errors.New("top-level"), "test": errors.New("test"), "b": &errortree.Tree{ Errors: map[string]error{ "c": errors.New("nested"), }, }, }, } // Get tries to resolve the path exactly and returns nil if // the path does not exist fmt.Println(errortree.Get(tree, "b", "non-existent"))
Output: <nil>
Example (Non_tree) ¶
// Get returns nil if the passed error is not a tree fmt.Println(errortree.Get(errors.New("test"), "key"))
Output: <nil>
func GetAny ¶
GetAny retrieves the error for a given key from the tree. The path parameter may be used for specifying a nested error's key.
This function returns the most-specific match:
If the provided error is not an errortree.Tree, the provided error is returned. If at any step the path cannot be fully followed, the previous error on the path will be returned.
Example (Nested) ¶
// When GetAny does not encounter an exact match in the tree, it returns the most-specific match tree := &errortree.Tree{ Errors: map[string]error{ "a": errors.New("top-level"), "test": errors.New("test"), "b": &errortree.Tree{ Errors: map[string]error{ "c": errors.New("nested"), }, }, }, } // Get tries to resolve the path exactly and returns nil if // the path does not exist fmt.Println(errortree.GetAny(tree, "b", "non-existent"))
Output: 1 error occurred: * c: nested
Example (Non_tree) ¶
// GetAny always returns the error it got passed even if it is not a tree fmt.Println(errortree.GetAny(errors.New("test"), "key"))
Output: test
func Keys ¶
Keys returns all error keys present in a given tree.
The value returned by this function is an alphabetically sorted, flattened list of all keys in a tree of Tree structs.
The delimiter configured for the top-level tree is guaranteed to be used throughout the complete tree.
Example ¶
tree := &errortree.Tree{ Errors: map[string]error{ "a": errors.New("top-level"), "test": errors.New("test"), "b": &errortree.Tree{ Errors: map[string]error{ "c": errors.New("nested"), }, }, }, } fmt.Println(strings.Join(errortree.Keys(tree), ", "))
Output: a, b:c, test
func Set ¶
Set creates or replaces an error under a given key in a tree.
The parent value may be nil, in which case a new *Tree is created, to which the key is added and the new *Tree is returned. Otherwise the *Tree to which the key was added is returned.
Example ¶
var err error // Using Set on a nil-error automatically creates an error tree and adds the desired error err = errortree.Set(err, "test", errors.New("test error")) fmt.Println(err.Error())
Output: 1 error occurred: * test: test error
Example (Duplicate) ¶
// Add an error with the key "test" err := errortree.Add(nil, "test", errors.New("test error")) // Call Set on the key, which will override it err = errortree.Set(err, "test", errors.New("key re-used")) fmt.Println(err.Error())
Output: 1 error occurred: * test: key re-used
func SimpleFormatter ¶
SimpleFormatter provides a simple Formatter which returns a message indicating how many Errors occurred and details for every error. The reported Errors are sorted alphabetically by key.
Types ¶
type Formatter ¶
Formatter defines the Formatter type This function can expected that the provided map contains a flattened map of all Errors
type Tree ¶
type Tree struct { // Errors holds the tree's items Errors map[string]error // Delimiter specifies the tree's delimiter for building nested paths Delimiter string // Formatter specifies the formatter to use when Error is invoked Formatter Formatter }
Tree is an error type which acts as a container for storing multiple errors in a tree structure.
func (*Tree) ErrorOrNil ¶
ErrorOrNil returns nil if the tree is empty or the tree itself otherwise.
func (*Tree) WrappedErrors ¶
WrappedErrors returns the errors wrapped by the tree.
The ordering of the returned errors is determined by the alphabetical ordering of the corresponding error keys.