How to properly check type of error returned by plugin.Open - go

I would like to know how can I check the type of error returned by plugin.Open, e.g:
package main
import "plugin"
func main() {
_, err := plugin.Open("./module.so")
// here
}
I would like to do something different if the error is:
plugin.Open("./module.so"): realpath failed
Which basically means that the file doesn't exist.
Example of desired result:
package main
import "plugin"
func main() {
_, err := plugin.Open("./module.so")
if err.Error() == "plugin.Open(\"./module.so\"): realpath failed" {
// do something different here
} else {
log.Fatal(err)
}
}
The string that I pass to plugin.Open can have other values, so it needs to be something more smart than that.
Thanks in advance.

Inspection of the code for plugin.Open() reveals the package calls out to some C code to determine whether the path exists. If it doesn't, it returns a plain error value. In particular, the package does not define any sentinel errors which you can compare against, nor does it return its own concrete implementer of the error interface which carries custom metadata. This is the code which produces that error:
return nil, errors.New(`plugin.Open("` + name + `"): realpath failed`)
errors.New is a basic implementation of the error interface which doesn't allow any additional information to be passed. Unlike other locations in the standard library which return errors (such as path non-existent errors from the os package), you can't get such metadata in this instance.
Check whether the module file exists first
My preference would be to verify whether the module exists before attempting to load it, using the native capabilities provided by the os package:
modulePath := "./module.so"
if _, err := os.Stat(modulePath); os.IsNotExist(err) {
// Do whatever is required on module not existing
}
// Continue to load the module – can be another branch of the if block
// above if necessary, depending on your desired control flow.
Compare a subset of the error values
You could also use strings.Contains to search for the value realpath failed in the returned error value. This is not a good idea in the event that string changes in future, so if you adopt this pattern, at the very least you should ensure you have rigorous tests around it (and even then it's still not great).
_, err := plugin.Open("./module.so")
if err != nil {
if strings.Contains(err.Error(), "realpath failed") {
// Do your fallback behavior for module not existing
log.Fatalf("module doesn't exist")
} else {
// Some other type of error
log.Fatalf("%+v", err)
}
}

Related

unserialize php in goland

I have a file with serialized array in PHP.
The content of the file locks like this
a:2:{i:250;s:7:"my_catz";s:7:"abcd.jp";a:2:{s:11:"category_id";i:250;s:13:"category_name";s:7:"my_catz";}}
The array unserialized is this
(
[250] => my_catz
[abcd.jp] => Array
(
[category_id] => 250
[category_name] => my_catz
)
)
Now, i want to get the content of the file in GO, unserialize it convert it to an array.
In GO i can get the content of the file using
dat, err := os.ReadFile("/etc/squid3/compiled-categories.db")
if err != nil {
if e.Debug {
log.Printf("error reading /etc/squid3/compiled-categories.db: ", err)
}
}
And unserialized it using github.com/techoner/gophp library
package categorization
import (
"fmt"
"os"
"github.com/techoner/gophp"
"log"
"errors"
)
type Data struct {
Website string
Debug bool
}
func (e Data) CheckPersonalCategories() (int,string) {
if e.Debug {
log.Printf("Checking Personal Categories")
}
if _, err := os.Stat("/etc/squid3/compiled-categories.db"); errors.Is(err, os.ErrNotExist) {
if e.Debug {
log.Printf("/etc/squid3/compiled-categories.db not exit: ", err)
}
return 0,""
}
dat, err := os.ReadFile("/etc/squid3/compiled-categories.db")
if err != nil {
if e.Debug {
log.Printf("error reading /etc/squid3/compiled-categories.db: ", err)
}
}
out, _ := gophp.Unserialize(dat)
fmt.Println(out["abcd.jp"])
return 0,""
}
But I can't access to the array, for example, when I try access to array key using out["abcd.jp"] i get this error message
invalid operation: out["abcd.jp"] (type interface {} does not support indexing)
The result of out is
map[250:my_catz abcd.jp:map[category_id:250 category_name:my_catz]]
Seams that is unserializing
Don't make assumptions about what is and isn't succeeding in your code. Error responses are the only reliable way to know whether a function succeeded. In this case the assumption may hold, but ignoring errors is always a mistake. Invest time in catching errors and at least panic them - don't instead waste your time ignoring errors and then trying to debug unreliable code.
invalid operation: out["abcd.jp"] (type interface {} does not support indexing)
The package you're using unfortunately doesn't provide any documentation so you have to read the source to understand that gophp.Unserialize returns (interface{}, error). This makes sense; php can serialize any value, so Unserialize must be able to return any value.
out is therefore an interface{} whose underlying value depends on the data. To turn an interface{} into a particular value requires a type assertion. In this case, we think the underlying data should be map[string]interface{}. So we need to do a type assertion:
mout, ok := out.(map[string]interface{})
Before we get to the working code, one more point I'd like you to think about. Look at the code below: I started it from your code, but the resemblance is very slight. I took out almost all the code because it was completely irrelevant to your question. I added the input data to the code to make a minimal reproduction of your code (as I asked you to do and you declined to do). This is a very good use of your time for 2 reasons: first, it makes it a lot easier to get answers (both because it shows sufficient effort on your part and because it simplifies the description of the problem), and second, because it's excellent practice for debugging. I make minimal reproductions of code flows all the time to better understand how to do things.
You'll notice you can run this code now without any additional effort. That's the right way to provide a minimal reproducible example - not with a chunk of mostly irrelevant code which still can't be executed by anybody.
The Go Plaground is a great way to demonstrate go-specific code that others can execute and investigate. You can also see the code below at https://go.dev/play/p/QfCl08Gx53e
package main
import (
"fmt"
"github.com/techoner/gophp"
)
type Data struct {
Website string
Debug bool
}
func main() {
var dat = []byte(`a:2:{i:250;s:7:"my_catz";s:7:"abcd.jp";a:2:{s:11:"category_id";i:250;s:13:"category_name";s:7:"my_catz";}}`)
out, err := gophp.Unserialize(dat)
if err != nil {
panic(err)
}
if mout, ok := out.(map[string]interface{}); ok {
fmt.Println(mout["abcd.jp"])
}
}

To what extent are errors strings guaranteed to not change?

One of the main issues I have with Golang is that the error handling is basically a check for a string (I would love to be wrong, do not hesitate :))
In the example below, I am trying to create a directory, but will have different behaviour depending on the kind of issue. Specifically, if a directory exists I will just pass.
package main
import (
"fmt"
"os"
)
func main() {
err := os.Mkdir("test", 0644)
if err != nil {
fmt.Printf("error: %v", err)
if err.Error() == "mkdir test: Cannot create a file when that file already exists" {
fmt.Printf("the dir already exists")
} else {
panic(err)
}
}
}
It does not work, repeated attempts are not logged. Why? Ah, crap, I forgot the dot at the end of the mkdir test: Cannot create a file when that file already exists string.
I feel that relying on an error string is fragile, as opposed to having something like err.ErrorType().DirectoryExists() kind of check (which sometimes exists, in net for instance).
My question: to what extent can I rely on the fact that the error strings will not change? (in other words, that mkdir test: Cannot create a file when that file already exists. will not be worded differently, or ported to another national language, etc.)
I had some hope with errors.Is() but it ultimately relies on the string comparison.
Go error strings don't change arbitrarily, but they also aren't covered by the Go compatibility policy: they can be changed if the increase in clarity outweighs the (inevitable) cost of breaking programs that make (fragile, unsupported) assumptions about the string contents.
The errors package is the robust way to check for specific types of errors.
Use errors.Is to check for equivalence to a canonical error (https://play.golang.org/p/co6ukgQrr58):
err := os.Mkdir(dir, 0644)
if errors.Is(err, os.ErrExist) {
t.Logf("the dir already exists")
} else if err != nil {
t.Fatal(err)
}
Use errors.As to check for a particular type of error (https://play.golang.org/p/UR1nUCRMUY6):
err := os.Mkdir(dir, 0644)
var pe *os.PathError
if errors.As(err, &pe) {
t.Logf("error creating %q: %v", pe.Path, pe.Err)
} else if err != nil {
t.Fatal(err)
}
In this case, you can use os.IsExist(err)
err := os.Mkdir("test", 0644)
if err != nil {
if os.IsExist(err){
fmt.Printf("the dir already exists")
} else {
panic(err)
}
}
Good libraries should allow you to inspect errors without relying on string comparison. Various methods exist to do so:
Comparaison with sentinel values if err == os.EOF
Utility function: os.IsExist(err)
Type assertion: pathErr := err.(*os.PathError)
There is always a way to inspect errors in the standard library without relying on strings. Check the function/package documentation for details about how to do it.
Note:
errors.Is() and errors.As() are a (~recent) generalisation of == and type assertion but for errors that could contain other errors. See https://go.dev/blog/go1.13-errors
From https://pkg.go.dev/os#Mkdir:
Mkdir creates a new directory with the specified name and permission bits (before umask). If there is an error, it will be of type *PathError.
This means you could type-assert the returned error to get more information.
if err != nil {
pathErr := err.(*os.PathError)
}
With errors returned from functions in package os specifically, also take note of these two functions:
https://pkg.go.dev/os#IsExist
https://pkg.go.dev/os#IsNotExist
to what extent can I rely on the fact that the error strings will not change?
To the extent which is guaranteed by the function's contract, which as in most programming languages conventionally is written in documenting comments above the function. In the case of os.MkDir(): you cannot.

Returning custom error type vs just creating new errors

Let's assume for shake of simplicity I have a simple function that just tries to open a file based on a string passed as parameter (do not rely on the simplicity of the file opening process, given that the actual example will be more convoluted)
func retrieveConfig(fs billy.Filesystem, app string) (Spec, error) {
var v Spec
filename := "releases/" + app + ".yaml"
file, err := fs.Open(filename)
if err != nil {
errOpenFile := ErrOpeningConfigFile{filename}
return Spec{}, new errOpenFile{filename}
}
Here is the definition of the custom error I am using
type ErrOpeningConfigFile struct {
s string
}
func (errConfigFileNotFound *ErrOpeningConfigFile) Error() string {
return fmt.Sprintf("Error opening configuration file %s")
}
I thought of following this approach because my idea is that it would give more coverage and make the core more testable.
I can test the error paths more easily now given that I can type assert on the custom error in my unit tests.
However this definitely increases the codebase.
I assume the other way around is to just
return nil, errors.New("Configuration file not found")
that would limit the code, but make testing clumsier (or more narrowed scoped).
What is the appropriate (or Go idiomatic way) of going about this?

What is good practice for nested function's return error? [duplicate]

I wondering what is the best way to handle error form multiple level abstraction in go. Every time if I must add a new level abstraction to program, I am forced to transfer error code from level less to level high. Thereby is duplicate communitaces in log file or I must remmember to delete communicate form level low and transfer him to level higher. Below simply example. I skipped creating each object to more shortly and celar code, but I think You understand my problem
type ObjectOne struct{
someValue int
}
func (o* ObjectOne)CheckValue()error{
if o.someValue == 0 {
SomeLogger.Printf("Value is 0 error program") // communicate form first level abstraction to logger
return errors.New("Internal value in object is 0")
}
return nil
}
type ObjectTwoHigherLevel struct{
objectOne ObjectOne
}
func (oT* ObjectTwoHigherLevel)CheckObjectOneIsReady() error{
if err := oT.objectOne.CheckValue() ; err != nil{
SomeLogger.Printf("Value in objectOne is not correct for objectTwo %s" , err) // second communicate
return err
}
return nil
}
type ObjectThreeHiggerLevel struct{
oT ObjectTwoHigherLevel
}
func (oTh* ObjectThreeHiggerLevel)CheckObjectTwoIsReady()error{
if err := oTh.oT.CheckObjectOneIsReady() ; err != nil{
SomeLogger.Printf("Value in objectTwo is not correct for objectThree %s" , err)
return err
}
return nil
}
In result in log file I get duplicate posts
Value is 0 error program
Value in objectOne is not correct for objectTwo Internal value in object is 0
Value in objectTwo is not correct for objectThree Internal value in object is 0
In turn if I only transfer some err to higher level without additional log I lost information what happend in each level.
How this solve ? How privent duplicate communicates ? Or My way is the good and the only ?
Problem is more frustrating if I create a few object which search something in database on a few abstraction level then I get also few lines form this same task in logFile.
EDIT: This answer pre-dates Go 1.13 which provides something similar to the presented technique. Please check The Go Blog: Working with Errors in Go 1.13.
You should either handle an error, or not handle it but delegate it to a higher level (to the caller). Handling the error and returning it is bad practice as if the caller also does the same, the error might get handled several times.
Handling an error means inspecting it and making a decision based on that, which may be you simply log it, but that also counts as "handling" it.
If you choose to not handle but delegate it to a higher level, that may be perfectly fine, but don't just return the error value you got, as it may be meaningless to the caller without context.
Annotating errors
A really nice and recommended way of delegation is Annotating errors. This means you create and return a new error value, but the old one is also wrapped in the returned value. The wrapper provides the context for the wrapped error.
There is a public library for annotating errors: github.com/pkg/errors; and its godoc: errors
It basically has 2 functions: 1 for wrapping an existing error:
func Wrap(cause error, message string) error
And one for extracting a wrapped error:
func Cause(err error) error
Using these, this is how your error handling may look like:
func (o *ObjectOne) CheckValue() error {
if o.someValue == 0 {
return errors.New("Object1 illegal state: value is 0")
}
return nil
}
And the second level:
func (oT *ObjectTwoHigherLevel) CheckObjectOneIsReady() error {
if err := oT.objectOne.CheckValue(); err != nil {
return errors.Wrap(err, "Object2 illegal state: Object1 is invalid")
}
return nil
}
And the third level: call only the 2nd level check:
func (oTh *ObjectThreeHiggerLevel) CheckObjectTwoIsReady() error {
if err := oTh.ObjectTwoHigherLevel.CheckObjectOneIsReady(); err != nil {
return errors.Wrap(err, "Object3 illegal state: Object2 is invalid")
}
return nil
}
Note that since the CheckXX() methods do not handle the errors, they don't log anything. They are delegating annotated errors.
If someone using ObjectThreeHiggerLevel decides to handle the error:
o3 := &ObjectThreeHiggerLevel{}
if err := o3.CheckObjectTwoIsReady(); err != nil {
fmt.Println(err)
}
The following nice output will be presented:
Object3 illegal state: Object2 is invalid: Object2 illegal state: Object1 is invalid: Object1 illegal state: value is 0
There is no pollution of multiple logs, and all the details and context are preserved because we used errors.Wrap() which produces an error value which formats to a string which preserves the wrapped errors, recursively: the error stack.
You can read more about this technique in blog post:
Dave Cheney: Don’t just check errors, handle them gracefully
"Extending" errors
If you like things simpler and / or you don't want to hassle with external libraries and you're fine with not being able to extract the original error (the exact error value, not the error string which you can), then you may simply extend the error with the context and return this new, extended error.
Extending an error is easiest done by using fmt.Errorf() which allows you to create a "nice" formatted error message, and it returns you a value of type error so you can directly return that.
Using fmt.Errorf(), this is how your error handling may look like:
func (o *ObjectOne) CheckValue() error {
if o.someValue == 0 {
return fmt.Errorf("Object1 illegal state: value is %d", o.someValue)
}
return nil
}
And the second level:
func (oT *ObjectTwoHigherLevel) CheckObjectOneIsReady() error {
if err := oT.objectOne.CheckValue(); err != nil {
return fmt.Errorf("Object2 illegal state: %v", err)
}
return nil
}
And the third level: call only the 2nd level check:
func (oTh *ObjectThreeHiggerLevel) CheckObjectTwoIsReady() error {
if err := oTh.ObjectTwoHigherLevel.CheckObjectOneIsReady(); err != nil {
return fmt.Errorf("Object3 illegal state: %v", err)
}
return nil
}
And the following error message would be presented at ObjectThreeHiggerLevel should it decide to "handle" it:
o3 := &ObjectThreeHiggerLevel{}
if err := o3.CheckObjectTwoIsReady(); err != nil {
fmt.Println(err)
}
The following nice output will be presented:
Object3 illegal state: Object2 illegal state: Object1 illegal state: value is 0
Be sure to also read blog post: Error handling and Go
There are various libraries that embed stack traces in Go errors. Simply create your error with one of those, and it will bubble up with the full stack context you can later inspect or log.
One such library:
https://github.com/go-errors/errors
And there are a few others I forgot.

Best practice to handle error from multiple abstract level

I wondering what is the best way to handle error form multiple level abstraction in go. Every time if I must add a new level abstraction to program, I am forced to transfer error code from level less to level high. Thereby is duplicate communitaces in log file or I must remmember to delete communicate form level low and transfer him to level higher. Below simply example. I skipped creating each object to more shortly and celar code, but I think You understand my problem
type ObjectOne struct{
someValue int
}
func (o* ObjectOne)CheckValue()error{
if o.someValue == 0 {
SomeLogger.Printf("Value is 0 error program") // communicate form first level abstraction to logger
return errors.New("Internal value in object is 0")
}
return nil
}
type ObjectTwoHigherLevel struct{
objectOne ObjectOne
}
func (oT* ObjectTwoHigherLevel)CheckObjectOneIsReady() error{
if err := oT.objectOne.CheckValue() ; err != nil{
SomeLogger.Printf("Value in objectOne is not correct for objectTwo %s" , err) // second communicate
return err
}
return nil
}
type ObjectThreeHiggerLevel struct{
oT ObjectTwoHigherLevel
}
func (oTh* ObjectThreeHiggerLevel)CheckObjectTwoIsReady()error{
if err := oTh.oT.CheckObjectOneIsReady() ; err != nil{
SomeLogger.Printf("Value in objectTwo is not correct for objectThree %s" , err)
return err
}
return nil
}
In result in log file I get duplicate posts
Value is 0 error program
Value in objectOne is not correct for objectTwo Internal value in object is 0
Value in objectTwo is not correct for objectThree Internal value in object is 0
In turn if I only transfer some err to higher level without additional log I lost information what happend in each level.
How this solve ? How privent duplicate communicates ? Or My way is the good and the only ?
Problem is more frustrating if I create a few object which search something in database on a few abstraction level then I get also few lines form this same task in logFile.
EDIT: This answer pre-dates Go 1.13 which provides something similar to the presented technique. Please check The Go Blog: Working with Errors in Go 1.13.
You should either handle an error, or not handle it but delegate it to a higher level (to the caller). Handling the error and returning it is bad practice as if the caller also does the same, the error might get handled several times.
Handling an error means inspecting it and making a decision based on that, which may be you simply log it, but that also counts as "handling" it.
If you choose to not handle but delegate it to a higher level, that may be perfectly fine, but don't just return the error value you got, as it may be meaningless to the caller without context.
Annotating errors
A really nice and recommended way of delegation is Annotating errors. This means you create and return a new error value, but the old one is also wrapped in the returned value. The wrapper provides the context for the wrapped error.
There is a public library for annotating errors: github.com/pkg/errors; and its godoc: errors
It basically has 2 functions: 1 for wrapping an existing error:
func Wrap(cause error, message string) error
And one for extracting a wrapped error:
func Cause(err error) error
Using these, this is how your error handling may look like:
func (o *ObjectOne) CheckValue() error {
if o.someValue == 0 {
return errors.New("Object1 illegal state: value is 0")
}
return nil
}
And the second level:
func (oT *ObjectTwoHigherLevel) CheckObjectOneIsReady() error {
if err := oT.objectOne.CheckValue(); err != nil {
return errors.Wrap(err, "Object2 illegal state: Object1 is invalid")
}
return nil
}
And the third level: call only the 2nd level check:
func (oTh *ObjectThreeHiggerLevel) CheckObjectTwoIsReady() error {
if err := oTh.ObjectTwoHigherLevel.CheckObjectOneIsReady(); err != nil {
return errors.Wrap(err, "Object3 illegal state: Object2 is invalid")
}
return nil
}
Note that since the CheckXX() methods do not handle the errors, they don't log anything. They are delegating annotated errors.
If someone using ObjectThreeHiggerLevel decides to handle the error:
o3 := &ObjectThreeHiggerLevel{}
if err := o3.CheckObjectTwoIsReady(); err != nil {
fmt.Println(err)
}
The following nice output will be presented:
Object3 illegal state: Object2 is invalid: Object2 illegal state: Object1 is invalid: Object1 illegal state: value is 0
There is no pollution of multiple logs, and all the details and context are preserved because we used errors.Wrap() which produces an error value which formats to a string which preserves the wrapped errors, recursively: the error stack.
You can read more about this technique in blog post:
Dave Cheney: Don’t just check errors, handle them gracefully
"Extending" errors
If you like things simpler and / or you don't want to hassle with external libraries and you're fine with not being able to extract the original error (the exact error value, not the error string which you can), then you may simply extend the error with the context and return this new, extended error.
Extending an error is easiest done by using fmt.Errorf() which allows you to create a "nice" formatted error message, and it returns you a value of type error so you can directly return that.
Using fmt.Errorf(), this is how your error handling may look like:
func (o *ObjectOne) CheckValue() error {
if o.someValue == 0 {
return fmt.Errorf("Object1 illegal state: value is %d", o.someValue)
}
return nil
}
And the second level:
func (oT *ObjectTwoHigherLevel) CheckObjectOneIsReady() error {
if err := oT.objectOne.CheckValue(); err != nil {
return fmt.Errorf("Object2 illegal state: %v", err)
}
return nil
}
And the third level: call only the 2nd level check:
func (oTh *ObjectThreeHiggerLevel) CheckObjectTwoIsReady() error {
if err := oTh.ObjectTwoHigherLevel.CheckObjectOneIsReady(); err != nil {
return fmt.Errorf("Object3 illegal state: %v", err)
}
return nil
}
And the following error message would be presented at ObjectThreeHiggerLevel should it decide to "handle" it:
o3 := &ObjectThreeHiggerLevel{}
if err := o3.CheckObjectTwoIsReady(); err != nil {
fmt.Println(err)
}
The following nice output will be presented:
Object3 illegal state: Object2 illegal state: Object1 illegal state: value is 0
Be sure to also read blog post: Error handling and Go
There are various libraries that embed stack traces in Go errors. Simply create your error with one of those, and it will bubble up with the full stack context you can later inspect or log.
One such library:
https://github.com/go-errors/errors
And there are a few others I forgot.

Resources