Skip to content

errs.Err

Takayuki Sato edited this page Sep 11, 2023 · 1 revision

Error handling in Sabi framework is a little unique. Sabi uses errs.Err type instead of Golang standard error. errs.Err takes an any structure type which indicates a reason of an error on creating it. And errs.Err has some functionalities to make it easier to hold and get error informations.

Creating an error

The code which creates an Golang standard error is as follows. It is needed for error to implement a structure type and its Error method, at least. if there is an error causing the new error, it is needed to implement Unwrap method, too. The amount of these codes are non-negligible, and demotivate to implements a lot of error(s) for detailed error handling.

(error)
    type FailToDoSomethingError struct { Name string, Amount int }
    func (err FailToDoSomethingError) Error() string { ... }
    err := FailToDoSomethingError{Name:name, Amount:amount }

On the other hand, The code which creates a errs.Err is easier, as follows. It is only needed for errs.Err to implement a structure type.

(errs.Err)
    type FailToDoSomething struct { Name string, Amount int }
    err := errs.New(FailToDoSomething{Name:name, Amount:amount})

If there is a causing error, the code to create an error becomes as follows:

(error)
    type FailToDoSomethingError struct { Name string, Amount int, Cause error }
    func (err FailToDoSomethingError) Error() string { ... }
    func (err FailToDoSomethingError) Unwrap() error { return err.Cause }
    err := FailToDoSomethingError{Name:name, Amount:amount, Cause:cause }
(errs.Err)
    type FailToDoSomething struct { Name string, Amount int }
    err := errs.New(FailToDoSomething{Name:name, Amount:amount}, cause)

The code of an error which indicates no error is as follows:

(error)
    var err error = nil
(errs.Err)
    err := errs.Ok()

Distinction of error kinds

To distinct error kinds, a type switch statement can be used. For Golang error, type switch is applied to a type of an error.

    switch err.(type) {
    case nil:
        ...
    case FailToDoSomethingError:
        ...
    default;
        ...
    }

For errs.Err, type switch is applied to a type of its reason.

    switch err.Reason().(type) {
    case nil:
        ...
    case FailToDoSomething:
        ...
    default:
        ...
    }     

Getting informations in an error

Since Golang error is an interface for a structure type, error informations are held in its structure fields. These field values can be gotten with type cast of the error structure.

    type FailToDoSomethingError struct { Name string }
    func (e FailToDoSomethingError) Error() string { ... }

    func doSomething() error { ... }

    err := doSomething()
    casted, ok := err.(FailToDoSomethingError)
    fmt.Printf("Name = %s\n", casted.Name)

errs.Err is also a structure type but it holds another structure type which indicates a reason of the error, and error informations are held in the fields of the reason structure type. These field values can be gotten with type cast of the reason structure.

    type FailToDoSomething struct { Name string }

    func doSomething() errs.Err { ... }

    err := doSomething()
    reason, ok := err.Reason().(FailToDoSomething)
    fmt.Printf("Name = %s\n", reason.Name)

In addition, errs.Err provides another way to get informations in itself, Get method and Situation method. Get method gets a field value of an error reason by a field name, and Situation method gets all fields of an error reason with a map. Since these two methods are not needed to cast type, the code become more simple. However both of these methods uses reflection features of Golang, and take more time to process. Therefore, these two method should be suppressed to use.

    type FailToDoSomething struct { Name string }

    func doSomething() errs.Err { ... }

    err := doSomething()
    fmt.Printf("Name = %s\n", reason.Get("Name"))

    m := err.Situation()
    fmt.Printf("Name = %s\n", m["Name"])

Error check for next functions

In general, a function returns an error, and the error is checked if it is non error, then next function is executed. The example code is following:

    func doSomething() error { ... }
    func doNextThing() error { ... }
    
    err := doSomething()
    if err != null {
        return err
    }
    return doNextThing()

In the case using Sabi framework, the similar code can be written more simply by using IfOk methods, like:

    func doSomething() errs.Err { ... }
    func doNextThing() errs.Err { ... }

    return doSomething().IfOk(doNextThing)

In Sabi framework, a function with the signature func() errs.Err can be operated as a 'runner' and used with sabi.Seq or sabi.Para, etc, too.

Error notifications

errs.Err supports notification of error creations. Notification handlers can be registered with errs.AddSyncHandler, which is for synchronous handling, or errs.AddAsyncHandler, which is for asynchronous handling. When a errs.Err is created with errs.New function, those registered handlers are executed in the function. Handlers are passed a errs.Err instance and a errs.ErrOcc instance. errs.ErrOcc is a structure having time when the error occurred, file name and line number where the error occurred.

The following code is a sample of adding error notification handlers:

    errs.AddSyncHandler(func(err errs.Err, occ errs.ErrOcc) {
        fmt.Printf("%s (%s:%d) %v\n",
            occ.Time().Format("2006-01-02T15:04:05"),
            occ.File(), occ.Line(), err)
    })
    errs.FixCfg()

    type FailToDoSomething struct {Name string}

    errs.New(FailToDoSomething{Name:"abc"})

---
(stdout)
2023-05-25T00:34:20 (main.go:19) {reason=FailToDoSomething, Name=abc}