Good work in reducing the code.
I had some time to look at your code and to be honest I don’t understand the goal of the methodWrapper.
If you are trying to reduce the number of times you write if err != nil, then I have to agree with @calmh on this one.
The pattern you have implemented seems a bit much and as @dfc pointed out doesn’t fit the pattern you mentioned in your initial question.
Errors are values in Go and should be handled, not deferred, to ensure the integrity of the code. Yes, there are cases where deferring makes sense but this is not that case.
With that being said, here are my initial thoughts on the code as it stands.
The use of a custom error-like type for formatting is more than necessary. Especially, as a pointer - I know, I suggested it but I didn’t really understand your intent.
The use of anonymous functions, while flexible, introduces another level of complexity which is harder to read than the standard if err check. As you look through this code ask yourself: do I really need to wrap these statements and are type assertions really needed.
Lastly, at no point do you exit if any call to Do returns an error. Nor do you have any code in place to recover from that said error. Which means you are passing around invalid data and running a bunch of noops for no reason. For example, the call to get a users configuration file should never run if user.Current() returns with an error. There are some good posts on error handling and code correctness. Maybe @dfc or @goinggodotnet can provide insight.
Here is some sample code, using a pattern for handling errors that I’ve come across and used in the past. For the record, the intent was to terminate immediately after printing each error.
package main
import (
"encoding/json"
"fmt"
"os"
"os/user"
"path"
"github.com/ChimeraCoder/anaconda"
"github.com/pkg/errors"
)
// Credentials struct is read from ~/gotwitter/config.json config file
type Credentials struct {
ConsumerKey string `json:"consumerkey"`
ConsumerSecret string `json:"consumersecret"`
AccessToken string `json:"accesstoken"`
AccessSecret string `json:"accesssecret"`
}
func terminateOnError(err error) {
if err != nil {
fmt.Printf("%+v", errors.WithStack(err))
os.Exit(1) // or anything else ...
}
}
func main() {
// get the current os user
usr, err := user.Current()
terminateOnError(err)
// get the current users's configuration path for the gotwitter application
config := path.Join(usr.HomeDir, ".gotwitter/config.json")
_, err := os.Stat(config)
terminateOnError(err)
// open a file based on the specified config path
file, err := os.Open(config)
terminateOnErr(err)
defer file.Close()
// read the config json file into the Credentials struct
decoder := json.NewDecoder(file)
err = decoder.Decode(&creds)
terminateOnErr(err)
// get TwitterAPI based on stored credentials
var api *anaconda.TwitterApi
anaconda.SetConsumerKey(creds.ConsumerKey)
anaconda.SetConsumerSecret(creds.ConsumerSecret)
api = anaconda.NewTwitterApi(creds.AccessToken, creds.AccessSecret)
// I'm thinking of adding a context field to the methodWrapper struct...
// err := errors.New("Error creating TwitterAPI")
// search current Twitter timeline for golang content
tweets, err := api.GetSearch("golang", nil)
terminateOnErr(err)
for _, tweet := range tweets.Statuses {
fmt.Println(tweet.Text)
fmt.Println("")
}
}
Please note that this sample code is untested and is a copy/paste of your code, with some tweaks, so there might be typos or errors. But it should be enough to get the gist.
I hope the comments shed a little light and help you get to your end goal.