├── .gitignore ├── README.md ├── go.mod ├── go.sum ├── logger └── logger.go └── rest_errors ├── rest_errors.go └── rest_errors_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | .idea 15 | *.DS_Store 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bookstore_utils-go 2 | Go utils shared across our entire microservices 3 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/federicoleon/bookstore_utils-go 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/stretchr/testify v1.5.1 7 | go.uber.org/zap v1.14.1 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 6 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 7 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 8 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 9 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 10 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 11 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 12 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 13 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 14 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 15 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 16 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 17 | github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= 18 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 19 | go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= 20 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 21 | go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= 22 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= 23 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= 24 | go.uber.org/zap v1.14.1 h1:nYDKopTbvAPq/NrUVZwT15y2lpROBiLLyoRTbXOYWOo= 25 | go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= 26 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 27 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 28 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 29 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 30 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 31 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 32 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 33 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 34 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 35 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 36 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 37 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 38 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 39 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 40 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 41 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 42 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 43 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 44 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 45 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 46 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 47 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 48 | -------------------------------------------------------------------------------- /logger/logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "fmt" 5 | "go.uber.org/zap" 6 | "go.uber.org/zap/zapcore" 7 | "os" 8 | "strings" 9 | ) 10 | 11 | const ( 12 | envLogLevel = "LOG_LEVEL" 13 | envLogOutput = "LOG_OUTPUT" 14 | ) 15 | 16 | var ( 17 | log logger 18 | ) 19 | 20 | type bookstoreLogger interface { 21 | Print(v ...interface{}) 22 | Printf(format string, v ...interface{}) 23 | } 24 | 25 | type logger struct { 26 | log *zap.Logger 27 | } 28 | 29 | func init() { 30 | logConfig := zap.Config{ 31 | OutputPaths: []string{getOutput()}, 32 | Level: zap.NewAtomicLevelAt(getLevel()), 33 | Encoding: "json", 34 | EncoderConfig: zapcore.EncoderConfig{ 35 | LevelKey: "level", 36 | TimeKey: "time", 37 | MessageKey: "msg", 38 | EncodeTime: zapcore.ISO8601TimeEncoder, 39 | EncodeLevel: zapcore.LowercaseLevelEncoder, 40 | EncodeCaller: zapcore.ShortCallerEncoder, 41 | }, 42 | } 43 | 44 | var err error 45 | if log.log, err = logConfig.Build(); err != nil { 46 | panic(err) 47 | } 48 | } 49 | 50 | func getLevel() zapcore.Level { 51 | switch strings.ToLower(strings.TrimSpace(os.Getenv(envLogLevel))) { 52 | case "debug": 53 | return zap.DebugLevel 54 | case "info": 55 | return zap.InfoLevel 56 | case "error": 57 | return zap.ErrorLevel 58 | default: 59 | return zap.InfoLevel 60 | } 61 | } 62 | 63 | func getOutput() string { 64 | output := strings.TrimSpace(os.Getenv(envLogOutput)) 65 | if output == "" { 66 | return "stdout" 67 | } 68 | return output 69 | } 70 | 71 | func GetLogger() bookstoreLogger { 72 | return log 73 | } 74 | 75 | func (l logger) Printf(format string, v ...interface{}) { 76 | if len(v) == 0 { 77 | Info(format) 78 | } else { 79 | Info(fmt.Sprintf(format, v...)) 80 | } 81 | } 82 | 83 | func (l logger) Print(v ...interface{}) { 84 | Info(fmt.Sprintf("%v", v)) 85 | } 86 | 87 | func Info(msg string, tags ...zap.Field) { 88 | log.log.Info(msg, tags...) 89 | log.log.Sync() 90 | } 91 | 92 | func Error(msg string, err error, tags ...zap.Field) { 93 | tags = append(tags, zap.NamedError("error", err)) 94 | log.log.Error(msg, tags...) 95 | log.log.Sync() 96 | } 97 | -------------------------------------------------------------------------------- /rest_errors/rest_errors.go: -------------------------------------------------------------------------------- 1 | package rest_errors 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "net/http" 8 | ) 9 | 10 | type RestErr interface { 11 | Message() string 12 | Status() int 13 | Error() string 14 | Causes() []interface{} 15 | } 16 | 17 | type restErr struct { 18 | ErrMessage string `json:"message"` 19 | ErrStatus int `json:"status"` 20 | ErrError string `json:"error"` 21 | ErrCauses []interface{} `json:"causes"` 22 | } 23 | 24 | func (e restErr) Error() string { 25 | return fmt.Sprintf("message: %s - status: %d - error: %s - causes: %v", 26 | e.ErrMessage, e.ErrStatus, e.ErrError, e.ErrCauses) 27 | } 28 | 29 | func (e restErr) Message() string { 30 | return e.ErrMessage 31 | } 32 | 33 | func (e restErr) Status() int { 34 | return e.ErrStatus 35 | } 36 | 37 | func (e restErr) Causes() []interface{} { 38 | return e.ErrCauses 39 | } 40 | 41 | func NewRestError(message string, status int, err string, causes []interface{}) RestErr { 42 | return restErr{ 43 | ErrMessage: message, 44 | ErrStatus: status, 45 | ErrError: err, 46 | ErrCauses: causes, 47 | } 48 | } 49 | 50 | func NewRestErrorFromBytes(bytes []byte) (RestErr, error) { 51 | var apiErr restErr 52 | if err := json.Unmarshal(bytes, &apiErr); err != nil { 53 | return nil, errors.New("invalid json") 54 | } 55 | return apiErr, nil 56 | } 57 | 58 | func NewBadRequestError(message string) RestErr { 59 | return restErr{ 60 | ErrMessage: message, 61 | ErrStatus: http.StatusBadRequest, 62 | ErrError: "bad_request", 63 | } 64 | } 65 | 66 | func NewNotFoundError(message string) RestErr { 67 | return restErr{ 68 | ErrMessage: message, 69 | ErrStatus: http.StatusNotFound, 70 | ErrError: "not_found", 71 | } 72 | } 73 | 74 | func NewUnauthorizedError(message string) RestErr { 75 | return restErr{ 76 | ErrMessage: message, 77 | ErrStatus: http.StatusUnauthorized, 78 | ErrError: "unauthorized", 79 | } 80 | } 81 | 82 | func NewInternalServerError(message string, err error) RestErr { 83 | result := restErr{ 84 | ErrMessage: message, 85 | ErrStatus: http.StatusInternalServerError, 86 | ErrError: "internal_server_error", 87 | } 88 | if err != nil { 89 | result.ErrCauses = append(result.ErrCauses, err.Error()) 90 | } 91 | return result 92 | } 93 | -------------------------------------------------------------------------------- /rest_errors/rest_errors_test.go: -------------------------------------------------------------------------------- 1 | package rest_errors 2 | 3 | import ( 4 | "testing" 5 | "github.com/stretchr/testify/assert" 6 | "net/http" 7 | "errors" 8 | ) 9 | 10 | func TestNewInternalServerError(t *testing.T) { 11 | err := NewInternalServerError("this is the message", errors.New("database error")) 12 | assert.NotNil(t, err) 13 | assert.EqualValues(t, http.StatusInternalServerError, err.Status()) 14 | assert.EqualValues(t, "this is the message", err.Message()) 15 | assert.EqualValues(t, "message: this is the message - status: 500 - error: internal_server_error - causes: [database error]", err.Error()) 16 | 17 | assert.NotNil(t, err.Causes) 18 | assert.EqualValues(t, 1, len(err.Causes())) 19 | assert.EqualValues(t, "database error", err.Causes()[0]) 20 | } 21 | 22 | func TestNewBadRequestError(t *testing.T) { 23 | //TODO: Test! 24 | } 25 | 26 | func TestNewNotFoundError(t *testing.T) { 27 | //TODO: Test! 28 | } 29 | 30 | func TestNewError(t *testing.T) { 31 | //TODO: Test! 32 | } 33 | --------------------------------------------------------------------------------