Commit 84d02fa2 authored by Kirill Smelkov's avatar Kirill Smelkov

exc: Convert documentation to Go style + package-level description

I was too used to my old Python habit while originally writing this.
Using the occasion add package-level description which outlines the
package to a user.
parent f7e2c635
// Copyright (C) 2015-2017 Nexedi SA and Contributors. // Copyright (C) 2015-2018 Nexedi SA and Contributors.
// Kirill Smelkov <kirr@nexedi.com> // Kirill Smelkov <kirr@nexedi.com>
// //
// This program is free software: you can Use, Study, Modify and Redistribute // This program is free software: you can Use, Study, Modify and Redistribute
...@@ -17,7 +17,35 @@ ...@@ -17,7 +17,35 @@
// See COPYING file for full licensing terms. // See COPYING file for full licensing terms.
// See https://www.nexedi.com/licensing for rationale and options. // See https://www.nexedi.com/licensing for rationale and options.
// Package exc provides exception-style error handling for Go // Package exc provides exception-style error handling for Go.
//
// Raise and Catch allows to raise and catch exceptions.
//
// By default the error caught is the same error that was raised. However with
// Context functions can arrange for context related to what they are doing to
// be added to raised error as prefix, for example
//
// func doSomething(path string) {
// defer exc.Context(func() interface{} {
// return fmt.Sprintf("doing something %s", path)
// })()
//
//
// Lacking such Context annotations Addcallingcontext allows to add function
// names up to the exception point as the calling context. However this way
// only actions without corresponding arguments (path in the above example) can
// be shown, and there have to be direct 1-1 relation between program and
// operational structures.
//
// Runx allows to run a function which raises exception, and return exception
// as regular error, if any. Similarly XRun allows to run a function which
// returns regular error, and raise exception if error is not nil.
//
// Last but not least it has to be taken into account that exceptions
// complicate control flow and are directly applicable only to serial programs.
// Their use is thus justified in only limited number of cases and by default
// one should always first strongly consider using explicit error returns
// programming style which is canonical in Go.
package exc package exc
import ( import (
...@@ -29,7 +57,7 @@ import ( ...@@ -29,7 +57,7 @@ import (
"lab.nexedi.com/kirr/go123/xruntime" "lab.nexedi.com/kirr/go123/xruntime"
) )
// error type which is raised by Raise(arg) // Error is the type which is raised by Raise(arg).
type Error struct { type Error struct {
arg interface{} arg interface{}
link *Error // chain of linked Error(s) - see e.g. Context() link *Error // chain of linked Error(s) - see e.g. Context()
...@@ -53,9 +81,10 @@ func (e *Error) Error() string { ...@@ -53,9 +81,10 @@ func (e *Error) Error() string {
return strings.Join(msgv, ": ") return strings.Join(msgv, ": ")
} }
// turn any value into Error // Aserror turns any value into Error.
// if v is already Error - it stays the same //
// otherwise new Error is created // if v is already Error - it stays the same,
// otherwise new Error is created.
func Aserror(v interface{}) *Error { func Aserror(v interface{}) *Error {
if e, ok := v.(*Error); ok { if e, ok := v.(*Error); ok {
return e return e
...@@ -63,17 +92,20 @@ func Aserror(v interface{}) *Error { ...@@ -63,17 +92,20 @@ func Aserror(v interface{}) *Error {
return &Error{v, nil} return &Error{v, nil}
} }
// raise error to upper level // Raise raise error to upper level.
//
// See Catch which receives raised error.
func Raise(arg interface{}) { func Raise(arg interface{}) {
panic(Aserror(arg)) panic(Aserror(arg))
} }
// raise formatted string // Raisef raises formatted string.
func Raisef(format string, a ...interface{}) { func Raisef(format string, a ...interface{}) {
panic(Aserror(fmt.Sprintf(format, a...))) panic(Aserror(fmt.Sprintf(format, a...)))
} }
// raise if err != nil // Raiseif raises if err != nil.
//
// NOTE err can be != nil even if typed obj = nil: // NOTE err can be != nil even if typed obj = nil:
// var obj *T; // var obj *T;
// err = obj // err = obj
...@@ -85,9 +117,10 @@ func Raiseif(err error) { ...@@ -85,9 +117,10 @@ func Raiseif(err error) {
} }
} }
// checks recovered value to be of *Error // _errcatch checks recovered value to be of type *Error.
// if there is non-Error error - repanic it //
// otherwise return Error either nil (no panic), or actual value // if there is non-Error error - repanic it,
// otherwise return Error either nil (no panic), or actual value.
func _errcatch(r interface{}) *Error { func _errcatch(r interface{}) *Error {
e, _ := r.(*Error) e, _ := r.(*Error)
if e == nil && r != nil { if e == nil && r != nil {
...@@ -96,8 +129,9 @@ func _errcatch(r interface{}) *Error { ...@@ -96,8 +129,9 @@ func _errcatch(r interface{}) *Error {
return e return e
} }
// catch error and call f(e) if it was caught. // Catch catches error and calls f(e) if it was caught.
// must be called under defer //
// Must be called under defer.
func Catch(f func(e *Error)) { func Catch(f func(e *Error)) {
e := _errcatch(recover()) e := _errcatch(recover())
if e == nil { if e == nil {
...@@ -107,10 +141,12 @@ func Catch(f func(e *Error)) { ...@@ -107,10 +141,12 @@ func Catch(f func(e *Error)) {
f(e) f(e)
} }
// be notified when error unwinding is being happening. // Onunwind installs error filter to be applied on error unwinding.
// hook into unwinding process with f() call. Returned error is reraised. //
// It hooks into unwinding process with f() call. Returned error is reraised.
// see also: Context() // see also: Context()
// must be called under defer //
// Must be called under defer.
func Onunwind(f func(e *Error) *Error) { func Onunwind(f func(e *Error) *Error) {
// cannot do Catch(...) // cannot do Catch(...)
// as recover() works only in first-level called functions // as recover() works only in first-level called functions
...@@ -123,10 +159,12 @@ func Onunwind(f func(e *Error) *Error) { ...@@ -123,10 +159,12 @@ func Onunwind(f func(e *Error) *Error) {
panic(e) panic(e)
} }
// provide error context to automatically add on unwinding. // Context provides error context to be added on unwinding.
// f is called if error unwinding is happening. //
// call result is added to raised error as "prefix" context // f is called if error unwinding is happening and its
// must be called under defer // result is added to raised error as "prefix" context.
//
// Must be called under defer.
func Context(f func() interface{}) { func Context(f func() interface{}) {
e := _errcatch(recover()) e := _errcatch(recover())
if e == nil { if e == nil {
...@@ -137,7 +175,7 @@ func Context(f func() interface{}) { ...@@ -137,7 +175,7 @@ func Context(f func() interface{}) {
panic(Addcontext(e, arg)) panic(Addcontext(e, arg))
} }
// add "prefix" context to error // Addcontext adds "prefix" context to error.
func Addcontext(e *Error, arg interface{}) *Error { func Addcontext(e *Error, arg interface{}) *Error {
return &Error{arg, e} return &Error{arg, e}
} }
...@@ -154,9 +192,11 @@ func init() { ...@@ -154,9 +192,11 @@ func init() {
_errorraise = _errorpkgname + ".Raise" _errorraise = _errorpkgname + ".Raise"
} }
// add calling context to error. // Addcallingcontext adds calling context to error.
//
// Add calling function frames as error context up-to topfunc not including. // Add calling function frames as error context up-to topfunc not including.
// see also: Addcontext() //
// See also: Addcontext()
func Addcallingcontext(topfunc string, e *Error) *Error { func Addcallingcontext(topfunc string, e *Error) *Error {
seenraise := false seenraise := false
for _, f := range xruntime.Traceback(2) { for _, f := range xruntime.Traceback(2) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment