try

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 20, 2024 License: MIT Imports: 5 Imported by: 176

Documentation

Overview

Package try is a package for To, To1, and To2 functions that implement the error checking. To functions check 'if err != nil' and if it throws the err to the error handlers, which are implemented by the err2 package. More information about err2 and try packager roles can be seen in the FileCopy example:

...
r := try.To1(os.Open(src))
defer r.Close()

w := try.To1(os.Create(dst))
defer err2.Handle(&err, func(error) error {
     try.Out(os.Remove(dst)).Logf()
     return nil
})
defer w.Close()
try.To1(io.Copy(w, r))
return nil
...

try.To — Fast Checking

All of the To functions are as fast as the simple 'if err != nil {' statement, thanks to the compiler inlining and optimization.

We have three error check functions: To, To1, and To2 because:

"No variadic type parameters. There is no support for variadic type parameters,
which would permit writing a single generic function that takes different
numbers of both type parameters and regular parameters." - Go Generics

For example, the leading number at the end of the To2 tells that To2 takes two different non-error arguments, and the third one must be an error value.

Looking at the [CopyFile] example again, you see that all the functions are directed to To1 are returning (type1, error) tuples. All of these tuples are the correct input to To1. However, if you have a function that returns (type1, type2, error), you must use To2 function to check the error. Currently the To3 takes (3 + 1) return values which is the greatest amount. If more is needed, let us know.

try.Out — Error Handling Language

The try package offers an error handling DSL that's based on Out, Out1, and Out2 functions and their corresponding return values Result, Result1, and Result2. DSL is for the cases where you want to do something specific after error returning function call. Those cases are rare. But you might want, for example, to ignore the specific error and use a default value without any special error handling. That's possible with the following code:

number := try.Out1(strconv.Atoi(str)).Catch(100)

Or you might want to ignore an error but write a log if something happens:

try.Out(os.Remove(dst)).Logf("file cleanup fail")

Or you might just want to change it later to error return:

try.Out(os.Remove(dst)).Handle("file cleanup fail")

Please see the documentation and examples of Result, Result1, and Result2 types and their methods.

Example (CopyFile)
package main

import (
	"fmt"
	"io"
	"os"

	"github.com/lainio/err2"
	"github.com/lainio/err2/try"
)

func main() {
	copyFile := func(src, dst string) (err error) {
		defer err2.Handle(&err, "copy file %s %s", src, dst)

		// These try package helpers are as fast as Check() calls which is as
		// fast as `if err != nil {}`

		r := try.To1(os.Open(src))
		defer r.Close()

		w := try.To1(os.Create(dst))
		defer err2.Handle(&err, err2.Err(func(error) {
			os.Remove(dst)
		}))
		defer w.Close()
		try.To1(io.Copy(w, r))
		return nil
	}

	err := copyFile("/notfound/path/file.go", "/notfound/path/file.bak")
	if err != nil {
		fmt.Println(err)
	}
}
Output:

copy file /notfound/path/file.go /notfound/path/file.bak: open /notfound/path/file.go: no such file or directory

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Is

func Is(err, filter error) bool

Is function performs a filtered error check for the given argument. It's the same as To function, but it checks if the error matches the filter before throwing an error. The false return value tells that there are no errors and the true value that the error is the filter.

Example (ErrorHappens)
package main

import (
	"fmt"
	"io"

	"github.com/lainio/err2"
	"github.com/lainio/err2/try"
)

var errForTesting = fmt.Errorf("error for %s", "testing")

func main() {
	copyStream := func(src string) (s string, err error) {
		defer err2.Handle(&err, "copy stream (%s)", src)

		err = errForTesting
		try.Is(err, io.EOF)
		return src, nil
	}

	str, err := copyStream("testing string")
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(str)
}
Output:

copy stream (testing string): error for testing
Example (ErrorHappensNot)
package main

import (
	"fmt"

	"github.com/lainio/err2"
	"github.com/lainio/err2/try"
)

var errForTesting = fmt.Errorf("error for %s", "testing")

func main() {
	copyStream := func(src string) (s string, err error) {
		defer err2.Handle(&err, "copy stream %s", src)

		err = fmt.Errorf("something: %w", errForTesting)
		if try.Is(err, errForTesting) {
			return "wrapping works", nil
		}

		return src, nil
	}

	str, err := copyStream("testing string")
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(str)
}
Output:

wrapping works

func IsAlreadyExist added in v0.9.0

func IsAlreadyExist(err error) bool

IsExist function performs a filtered error check for the given argument. It's the same as To function, but it checks if the error matches the err2.AlreadyExist before throwing an error. The false return value tells that there are no errors. The true tells that the err's chain includes err2.AlreadyExist.

func IsEOF

func IsEOF(err error) bool

IsEOF function performs a filtered error check for the given argument. It's the same as To function, but it checks if the error matches the io.EOF before throwing an error. The false return value tells that there are no errors. The true tells that the err's chain includes io.EOF.

func IsEOF1

func IsEOF1[T any](v T, err error) (bool, T)

IsEOF1 function performs a filtered error check for the given argument. It's the same as To function, but it checks if the error matches the io.EOF before throwing an error. The false return value tells that there are no errors and the true value that the error is the io.EOF.

Example
package main

import (
	"bytes"
	"fmt"

	"github.com/lainio/err2"
	"github.com/lainio/err2/try"
)

func main() {
	copyStream := func(src string) (s string, err error) {
		defer err2.Handle(&err)

		in := bytes.NewBufferString(src)
		tmp := make([]byte, 4)
		var out bytes.Buffer
		for eof, n := try.IsEOF1(in.Read(tmp)); !eof; eof, n = try.IsEOF1(in.Read(tmp)) {
			out.Write(tmp[:n])
		}

		return out.String(), nil
	}

	str, err := copyStream("testing string")
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(str)
}
Output:

testing string

func IsEOF2

func IsEOF2[T, U any](v1 T, v2 U, err error) (bool, T, U)

IsEOF2 function performs a filtered error check for the given argument. It's the same as To function, but it checks if the error matches the io.EOF before throwing an error. The false return value tells that there are no errors and the true value that the error is the io.EOF.

func IsNotAccess added in v0.8.11

func IsNotAccess(err error) bool

IsNotAccess function performs a filtered error check for the given argument. It's the same as To function, but it checks if the error matches the err2.NotAccess before throwing an error. The false return value tells that there are no errors. The true tells that the err's chain includes err2.NotAccess.

func IsNotEnabled added in v0.9.5

func IsNotEnabled(err error) bool

IsNotEnabled function performs a filtered error check for the given argument. It's the same as To function, but it checks if the error matches the err2.ErrNotEnabled before throwing an error. The false return value tells that there are no errors. The true tells that the err's chain includes err2.ErrNotEnabled.

func IsNotExist added in v0.8.11

func IsNotExist(err error) bool

IsNotExist function performs a filtered error check for the given argument. It's the same as To function, but it checks if the error matches the err2.NotExist before throwing an error. The false return value tells that there are no errors. The true tells that the err's chain includes err2.NotExist.

func IsNotFound added in v0.8.11

func IsNotFound(err error) bool

IsNotFound function performs a filtered error check for the given argument. It's the same as To function, but it checks if the error matches the err2.NotFound before throwing an error. The false return value tells that there are no errors. The true tells that the err's chain includes err2.NotFound.

func IsNotFound1 added in v0.8.11

func IsNotFound1[T any](v T, err error) (bool, T)

IsNotFound1 function performs a filtered error check for the given argument. It's the same as To function, but it checks if the error matches the err2.NotFound before throwing an error. The false return value tells that there are no errors. The true tells that the err's chain includes err2.NotFound.

Example
package main

import (
	"fmt"
	"os"

	"github.com/lainio/err2"
	"github.com/lainio/err2/try"
)

func FindObject(_ int) (val string, err error) {
	defer err2.Handle(&err)

	// both of the following lines can be used to transport err2.NotFound
	// you can try by outcommenting err2.Throwf
	//err2.Throwf("panic transport: %w", err2.ErrNotFound)
	return "", err2.ErrNotFound
}

func main() {
	// To see how automatic stack tracing works WITH panic transport please run
	// this example with:
	//   go test -v -run='^ExampleNotFound$'

	// pick up your poison: outcomment the nil line to see how error tracing
	// works.
	err2.SetErrorTracer(os.Stderr)
	err2.SetErrorTracer(nil)

	find := func(key int) string {
		defer err2.Catch(err2.Err(func(err error) {
			fmt.Println("ERROR:", err)
		}))
		notFound, value := try.IsNotFound1(FindObject(key))
		if notFound {
			return fmt.Sprintf("cannot find key (%d)", key)
		}
		return "value for key is:" + value
	}

	fmt.Println(find(1))
}
Output:

cannot find key (1)

func IsNotRecoverable added in v0.9.0

func IsNotRecoverable(err error) bool

IsNotRecoverable function performs a filtered error check for the given argument. It's the same as To function, but it checks if the error matches the err2.ErrNotRecoverable before throwing an error. The false return value tells that there are no errors. The true tells that the err's chain includes err2.ErrNotRecoverable.

func IsRecoverable added in v0.9.0

func IsRecoverable(err error) bool

IsRecoverable function performs a filtered error check for the given argument. It's the same as To function, but it checks if the error matches the err2.ErrRecoverable before throwing an error. The false return value tells that there are no errors. The true tells that the err's chain includes err2.ErrRecoverable.

func To

func To(err error)

To is a helper function to call functions which returns an error value and check the value. If an error occurs, it panics the error so that err2 handlers can catch it if needed. Note! If no err2.Handle or err2.Catch exist in the call stack and To panics an error, the error is not handled, and the app will crash. When using To function you should always have proper err2.Handle or err2.Catch statements in the call stack.

defer err2.Handle(&err)
...
try.To(w.Close())

func To1

func To1[T any](v T, err error) T

To1 is a helper function to call functions which returns values (T, error) and check the error value. If an error occurs, it panics the error so that err2 handlers can catch it if needed. Note! If no err2.Handle or err2.Catch exist in the call stack and To1 panics an error, the error is not handled, and the app will crash. When using To1 function you should always have proper err2.Handle or err2.Catch statements in the call stack.

defer err2.Handle(&err)
...
r := try.To1(os.Open(src))

func To2

func To2[T, U any](v1 T, v2 U, err error) (T, U)

To2 is a helper function to call functions which returns values (T, U, error) and check the error value. If an error occurs, it panics the error so that err2 handlers can catch it if needed. Note! If no err2.Handle or err2.Catch exist in the call stack and To2 panics an error, the error is not handled, and the app will crash. When using To2 function you should always have proper err2.Handle or err2.Catch statements in the call stack.

defer err2.Handle(&err)
...
kid, pk := try.To2(keys.CreateAndExportPubKeyBytes(kms.ED25519))

func To3

func To3[T, U, V any](v1 T, v2 U, v3 V, err error) (T, U, V)

To3 is a helper function to call functions which returns values (T, U, V, error) and check the error value. If an error occurs, it panics the error so that err2 handlers can catch it if needed. Note! If no err2.Handle or err2.Catch exist in the call stack and To3 panics an error, the error is not handled, and the app will crash. When using To3 function you should always have proper err2.Handle or err2.Catch statements in the call stack.

Types

type ErrFn added in v0.9.5

type ErrFn = handler.ErrorFn

ErrFn is function type for try.Out handlers.

type Result added in v0.9.5

type Result struct {
	// Err holds the error value returned from try.Out function result.
	Err error
}

Result is the base of our error handling language for try.Out functions.

func Out added in v0.9.5

func Out(err error) *Result

Out is a helper function to call functions which returns (error) and start error handling with DSL. For instance, to implement same as try.To, you could do the following:

d := try.Out(json.Unmarshal(b, &v)).Handle()

or in some other cases some of these would be desired action:

try.Out(os.Remove(dst)).Logf("file cleanup fail")
Example (ErrorHappensNot)
package main

import (
	"fmt"

	"github.com/lainio/err2"
	"github.com/lainio/err2/try"
)

var errForTesting = fmt.Errorf("error for %s", "testing")

func main() {
	var is bool
	var errFn = func(error) error {
		is = true
		return nil
	}
	copyStream := func(src string) (s string, err error) {
		defer err2.Handle(&err, "copy stream %s", src)

		err = fmt.Errorf("something: %w", errForTesting)

		try.Out(err).Handle(errForTesting, errFn)
		if is {
			return "wrapping works", nil
		}

		return src, nil
	}

	str, err := copyStream("testing string")
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(str)
}
Output:

wrapping works

func (*Result) Handle added in v0.9.5

func (o *Result) Handle(a ...any) *Result

Handle allows you to add an error handler to try.Out handler chain. Handle is a general purpose error handling function. It can handle several error handling cases:

  • if no argument is given and .Err != nil, it throws an error value immediately
  • if two arguments (errTarget, ErrFn) and Is(.Err, errTarget) ErrFn is called
  • if first argument is (string) and .Err != nil the error value is annotated and thrown
  • if first argument is (ErrFn) and .Err != nil, it calls ErrFn

The handler function ErrFn can process and annotate the incoming error how it wants and returning error value decides if error is thrown. Handle annotates and throws an error immediately i.e. terminates error handling DSL chain if [Result.Err] != nil. Handle supports error annotation similarly as fmt.Errorf.

For instance, to implement same as try.To, you could do the following:

d := try.Out(json.Unmarshal(b, &v)).Handle()

func (*Result) Logf added in v0.9.5

func (o *Result) Logf(a ...any) *Result

Logf prints a log line to pre-set logging stream err2.SetLogWriter if the current [Result.Err] != nil. Logf follows Printf formatting logic. The current error value will be added at the end of the logline with ": %v\n", err. For example, the line:

try.Out(server.Send(status)).Logf("error sending response")

would print the logline:

error sending response: UDP not listening

type Result1 added in v0.9.5

type Result1[T any] struct {
	// Val1 holds the first value returned from try.Out1 function result.
	Val1 T
	Result
}

Result1 is the base of our error handling DSL for try.Out1 functions.

func Out1 added in v0.9.5

func Out1[T any](v T, err error) *Result1[T]

Out1 is a helper function to call functions which returns (T, error). That allows you to use Result1, which makes possible to start error handling with DSL. For instance, instead of try.To1 you could do the following:

d := try.Out1(os.ReadFile(filename).Handle().Val1

or in some other cases, some of these would be desired action:

number := try.Out1(strconv.Atoi(str)).Catch(100)
x := try.Out1(strconv.Atoi(s)).Logf("not number").Catch(100)
Example (CopyFile)
package main

import (
	"fmt"
	"io"
	"os"

	"github.com/lainio/err2"
	"github.com/lainio/err2/try"
)

func main() {
	copyFile := func(src, dst string) (err error) {
		defer err2.Handle(&err, "copy file")

		r := try.Out1(os.Open(src)).Handle("source").Val1
		defer r.Close()

		w := try.Out1(os.Create(dst)).Handle("target").Val1

		// If you prefer immediate error handling for some reason.
		_ = try.Out1(io.Copy(w, r)).
			Handle(io.EOF, func(error) error {
				fmt.Println("err == io.EOF")
				return nil // by returning nil we can reset the error
				// return err // fallthru to next check if err != nil
			}).
			Handle(func(err error) error {
				try.Out(w.Close()).Logf()
				try.Out(os.Remove(dst)).Logf()
				return err // we don't want to change or annotate incoming
			}).
			Val1

		try.Out(w.Close()).Handle("target")
		return nil
	}

	err := copyFile("/notfound/path/file.go", "/notfound/path/file.bak")
	if err != nil {
		fmt.Println(err)
	}

}
Output:

copy file: source: open /notfound/path/file.go: no such file or directory

func (*Result1[T]) Catch added in v0.9.5

func (o *Result1[T]) Catch(v ...T) T

Catch catches the error and sets [Result1.Val1] if given. The value is used only in the case if [Result1.Err] != nil. Catch returns the Val1 in all cases.

Example
package main

import (
	"fmt"
	"strconv"

	"github.com/lainio/err2/try"
)

func main() {
	countSomething := func(s string) int {
		return try.Out1(strconv.Atoi(s)).Catch(100)
	}
	num1 := countSomething("1")
	num2 := countSomething("not number, getting default (=100)")
	fmt.Printf("results: %d, %d", num1, num2)

}
Output:

results: 1, 100

func (*Result1[T]) Def1 added in v0.9.5

func (o *Result1[T]) Def1(v T) *Result1[T]

Def1 sets default value for [Result.Val1.] The value is returned in case of [Result.Err] != nil.

Example
package main

import (
	"fmt"
	"strconv"

	"github.com/lainio/err2/try"
)

func main() {
	countSomething := func(s string) int {
		return try.Out1(strconv.Atoi(s)).Def1(100).Val1
	}
	num1 := countSomething("1")
	num2 := countSomething("not number, getting default (=100)")
	fmt.Printf("results: %d, %d", num1, num2)

}
Output:

results: 1, 100

func (*Result1[T]) Handle added in v0.9.5

func (o *Result1[T]) Handle(a ...any) *Result1[T]

Handle allows you to add an error handler to try.Out handler chain. Handle is a general purpose error handling function. It can handle several error handling cases:

  • if no argument is given and .Err != nil, it throws an error value immediately
  • if two arguments (errTarget, ErrFn) and Is(.Err, errTarget) ErrFn is called
  • if first argument is (string) and .Err != nil the error value is annotated and thrown
  • if first argument is ErrFn and .Err != nil, it calls ErrFn

The handler function ErrFn can process and annotate the incoming error how it wants and returning error value decides if error is thrown. Handle annotates and throws an error immediately i.e. terminates error handling DSL chain if [Result.Err] != nil. Handle supports error annotation similarly as fmt.Errorf.

For instance, to implement same as try.To, you could do the following:

d := try.Out(json.Unmarshal(b, &v)).Handle()
Example
package main

import (
	"bytes"
	"fmt"
	"io"

	"github.com/lainio/err2"
	"github.com/lainio/err2/try"
)

func main() {
	// try out f() |err| handle to show power of error handling language, EHL
	callRead := func(in io.Reader, b []byte) (eof bool, n int) {
		// we should use try.To1, but this is sample of try.Out.Handle
		n = try.Out1(in.Read(b)).
			Handle(io.EOF, func(error) error {
				eof = true
				return nil
			}).       // our errors.Is == true, handler to get eof status
			Handle(). // rest of the errors just throw
			Val1      // get count of read bytes, 1st retval of io.Read
		return
	}
	// simple function to copy stream with io.Reader
	copyStream := func(src string) (s string, err error) {
		defer err2.Handle(&err)

		in := bytes.NewBufferString(src)
		tmp := make([]byte, 4)
		var out bytes.Buffer

		for eof, n := callRead(in, tmp); !eof; eof, n = callRead(in, tmp) {
			out.Write(tmp[:n])
		}

		return out.String(), nil
	}

	str, err := copyStream("testing string")
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(str)
}
Output:

testing string

func (*Result1[T]) Logf added in v0.9.5

func (o *Result1[T]) Logf(a ...any) *Result1[T]

Logf prints a log line to pre-set logging stream err2.SetLogWriter if the current [Result.Err] != nil. Logf follows Printf formatting logic. The current error value will be added at the end of the logline with ": %v\n", err. For example, the line:

try.Out(server.Send(status)).Logf("error sending response")

would print the logline:

error sending response: UDP not listening
Example
package main

import (
	"fmt"
	"os"
	"strconv"

	"github.com/lainio/err2"
	"github.com/lainio/err2/try"
)

func main() {
	// Set log tracing to stdout that we can see it in Example output. In
	// normal cases that would be a Logging stream or stderr.
	err2.SetLogTracer(os.Stdout)

	countSomething := func(s string) int {
		return try.Out1(strconv.Atoi(s)).Logf("not number").Catch(100)
	}
	num1 := countSomething("1")
	num2 := countSomething("BAD")
	fmt.Printf("results: %d, %d", num1, num2)
	err2.SetLogTracer(nil)

}
Output:

not number: strconv.Atoi: parsing "BAD": invalid syntax
results: 1, 100

type Result2 added in v0.9.5

type Result2[T any, U any] struct {
	// Val2 holds the first value returned from try.Out2 function result.
	Val2 U
	Result1[T]
}

Result2 is the base of our error handling DSL for try.Out2 functions.

func Out2 added in v0.9.5

func Out2[T any, U any](v1 T, v2 U, err error) *Result2[T, U]

Out2 is a helper function to call functions which returns (T, error). That allows you to use Result2, which makes possible to start error handling with DSL. For instance, instead of try.To2 you could do the following:

token := try.Out2(p.ParseUnverified(tokenStr, &customClaims{})).Handle().Val1

or in some other cases, some of these would be desired action:

x, y := try.Out2(convTwoStr(s1, s2)).Logf("bad number").Catch(1, 2)
y := try.Out2(convTwoStr(s1, s2)).Handle().Val2

func (*Result2[T, U]) Catch added in v0.9.5

func (o *Result2[T, U]) Catch(a ...any) (T, U)

Catch catches the error and sets [Result2.Val1] [Result2.Val2] if given. The value(s) is used in the case of [Result2.Err] != nil. Catch returns the [Val1] and [Val2] in all cases. In case you want to set only [Val2]'s default value, use [Def2] before Catch call.

func (*Result2[T, U]) Def1 added in v1.0.0

func (o *Result2[T, U]) Def1(v T) *Result2[T, U]

Def1 sets default value for [Result.Val1]. The value is returned in case of [Result.Err] != nil.

func (*Result2[T, U]) Def2 added in v0.9.5

func (o *Result2[T, U]) Def2(v2 U) *Result2[T, U]

Def2 sets default value for [Result.Val2]. The value is returned in case of [Result.Err] != nil.

func (*Result2[T, U]) Handle added in v0.9.5

func (o *Result2[T, U]) Handle(a ...any) *Result2[T, U]

Handle allows you to add an error handler to try.Out handler chain. Handle is a general purpose error handling function. It can handle several error handling cases:

  • if no argument is given and .Err != nil, it throws an error value immediately
  • if two arguments (errTarget, ErrFn) and Is(.Err, errTarget) ErrFn is called
  • if first argument is (string) and .Err != nil the error value is annotated and thrown
  • if first argument is ErrFn and .Err != nil, it calls ErrFn

The handler function ErrFn can process and annotate the incoming error how it wants and returning error value decides if error is thrown. Handle annotates and throws an error immediately i.e. terminates error handling DSL chain if [Result.Err] != nil. Handle supports error annotation similarly as fmt.Errorf.

For instance, to implement same as try.To, you could do the following:

d := try.Out(json.Unmarshal(b, &v)).Handle()

func (*Result2[T, U]) Logf added in v0.9.5

func (o *Result2[T, U]) Logf(a ...any) *Result2[T, U]

Logf prints a log line to pre-set logging stream err2.SetLogWriter if the current [Result.Err] != nil. Logf follows Printf formatting logic. The current error value will be added at the end of the logline with ": %v\n", err. For example, the line:

try.Out(server.Send(status)).Logf("error sending response")

would print the logline:

error sending response: UDP not listening
Example
package main

import (
	"fmt"
	"os"
	"strconv"

	"github.com/lainio/err2"
	"github.com/lainio/err2/try"
)

func convTwoStr(s1, s2 string) (_ int, _ int, err error) {
	defer err2.Handle(&err)

	return try.To1(strconv.Atoi(s1)), try.To1(strconv.Atoi(s2)), nil
}

func main() {
	// Set log tracing to stdout that we can see it in Example output. In
	// normal cases that would be a Logging stream or stderr.
	err2.SetLogTracer(os.Stdout)

	countSomething := func(s1, s2 string) (int, int) {
		r := try.Out2(convTwoStr(s1, s2)).Logf().Def1(10).Def2(10)
		v1, v2 := r.Val1, r.Val2
		return v1 + v2, v2
	}
	_, _ = countSomething("1", "2")
	num1, num2 := countSomething("BAD", "2")
	fmt.Printf("results: %d, %d", num1, num2)
	err2.SetLogTracer(nil)
}
Output:

testing: run example: strconv.Atoi: parsing "BAD": invalid syntax
results: 20, 10

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL