Skip to content

Commit 7e7072a

Browse files
authored
Merge pull request #29 from fastbill/go-update
Go update
2 parents 3c1bf40 + 7865551 commit 7e7072a

File tree

17 files changed

+922
-201
lines changed

17 files changed

+922
-201
lines changed

.golangci.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,27 @@ linters:
1111

1212
run:
1313
deadline: 10m
14+
modules-download-mode: vendor
1415

1516
issues:
17+
exclude-use-default: false
1618
exclude-rules:
1719
- path: _test\.go
1820
linters:
1921
- dupl
2022
- goconst
2123
- gosec
24+
- linters:
25+
- govet
26+
text: 'shadow: declaration of "err" shadows declaration'
27+
- linters:
28+
- golint
29+
text: 'in another file for this package'
2230

2331
linters-settings:
2432
gocyclo:
2533
min-complexity: 10
34+
golint:
35+
min-confidence: 0
36+
govet:
37+
check-shadowing: true

.travis.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
language: go
22

33
go:
4-
- 1.13.x
4+
- 1.15.x
55

66
install:
7-
- go build ./...
8-
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s v1.19.1
7+
- go mod vendor
8+
- curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.33.0
99

1010
script:
1111
- go test -race -cover ./...

README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ You need to tell the envloader in which folder to look for the `.env` files. By
1313
## Usage
1414
```go
1515
import (
16-
toolkit "github.com/fastbill/go-service-toolkit/v3"
16+
toolkit "github.com/fastbill/go-service-toolkit/v4"
1717
)
1818

1919

@@ -35,7 +35,7 @@ The `Obs` struct has a `PanicRecover` method that can be used as deferred functi
3535
import (
3636
"time"
3737

38-
"github.com/fastbill/go-service-toolkit/v3"
38+
"github.com/fastbill/go-service-toolkit/v4"
3939
)
4040

4141
func main() {
@@ -86,7 +86,7 @@ Additionally `MustEnsureDBMigrations` runs all migrations from the given folder
8686
## Usage
8787
```go
8888
import (
89-
"github.com/fastbill/go-service-toolkit/v3"
89+
"github.com/fastbill/go-service-toolkit/v4"
9090
)
9191

9292
func main() {
@@ -115,7 +115,7 @@ The function `MustNewCache` sets up a new REDIS client. A prefix can be provided
115115
## Usage
116116
```go
117117
import (
118-
"github.com/fastbill/go-service-toolkit/v3"
118+
"github.com/fastbill/go-service-toolkit/v4"
119119
)
120120

121121
func main() {
@@ -134,7 +134,7 @@ The server package sets up an [Echo](https://echo.labstack.com/) server that inc
134134
## Usage
135135
```go
136136
import (
137-
"github.com/fastbill/go-service-toolkit/v3/server"
137+
"github.com/fastbill/go-service-toolkit/v4/server"
138138
)
139139

140140
func main() {
@@ -222,7 +222,7 @@ As response, the `CallHandler` method returns the error that the echo handler re
222222
```go
223223
import (
224224
"testing"
225-
"github.com/fastbill/go-service-toolkit/v3/handlertest"
225+
"github.com/fastbill/go-service-toolkit/v4/handlertest"
226226
)
227227

228228
func TestMyHandler(t *testing.T) {
@@ -239,7 +239,7 @@ import (
239239
"testing"
240240
"time"
241241

242-
"github.com/fastbill/go-service-toolkit/v3/handlertest"
242+
"github.com/fastbill/go-service-toolkit/v4/handlertest"
243243
"github.com/labstack/echo/v4"
244244
)
245245

cache/cache.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
package cache
22

33
import (
4+
"context"
45
"encoding/json"
6+
"errors"
57
"fmt"
68
"strconv"
79
"time"
810

9-
"github.com/go-redis/redis/v7"
10-
"github.com/pkg/errors"
11+
"github.com/go-redis/redis/v8"
1112
)
1213

1314
// Common errors that might be returned by the cache package.
@@ -16,6 +17,10 @@ var (
1617
ErrNoTTLSet = errors.New("key does not have a TTL set")
1718
)
1819

20+
// Since we currently don't want to pass a Go context to all the cache methods,
21+
// we use this dummy context instead as recommended by the package autors.
22+
var ctx = context.TODO()
23+
1924
// Cache defines basic cache operations including methods for setting and getting JSON objects.
2025
type Cache interface {
2126
Prefix() string
@@ -53,7 +58,7 @@ func NewRedis(redisHost string, redisPort string, prefix string) (*RedisClient,
5358
}
5459

5560
client := redis.NewClient(&opts)
56-
_, err := client.Ping().Result()
61+
_, err := client.Ping(ctx).Result()
5762
if err != nil {
5863
return nil, fmt.Errorf("could not ping REDIS: %w", err)
5964
}
@@ -70,14 +75,14 @@ func NewRedis(redisHost string, redisPort string, prefix string) (*RedisClient,
7075
// Redis `SET key value [expiration]` command.
7176
// Use expiration for `SETEX`-like behavior. Zero expiration means the key has no expiration time.
7277
func (r *RedisClient) Set(key string, value string, expiration time.Duration) error {
73-
return r.Redis.Set(r.prefixedKey(key), value, expiration).Err()
78+
return r.Redis.Set(ctx, r.prefixedKey(key), value, expiration).Err()
7479
}
7580

7681
// Get retrieves a value from REDIS.
7782
// If the client was set up with a prefix it will be added in front of the key.
7883
// If the value was not found ErrNotFound will be returned.
7984
func (r *RedisClient) Get(key string) (string, error) {
80-
result, err := r.Redis.Get(r.prefixedKey(key)).Result()
85+
result, err := r.Redis.Get(ctx, r.prefixedKey(key)).Result()
8186
if err == redis.Nil {
8287
return "", ErrNotFound
8388
}
@@ -124,7 +129,7 @@ func (r *RedisClient) GetInt(key string) (int64, error) {
124129
// If the client was set up with a prefix it will be added in front of the key.
125130
// It returns the new (incremented) value.
126131
func (r *RedisClient) Incr(key string) (int64, error) {
127-
return r.Redis.Incr(r.prefixedKey(key)).Result()
132+
return r.Redis.Incr(ctx, r.prefixedKey(key)).Result()
128133
}
129134

130135
// SetJSON saves JSON data as string to REDIS.
@@ -152,7 +157,7 @@ func (r *RedisClient) GetJSON(key string, result interface{}) error {
152157
// Del deletes a key value pair from REDIS.
153158
// If the client was set up with a prefix it will be added in front of the key.
154159
func (r *RedisClient) Del(key string) error {
155-
return r.Redis.Del(r.prefixedKey(key)).Err()
160+
return r.Redis.Del(ctx, r.prefixedKey(key)).Err()
156161
}
157162

158163
// Close closes the connection to the REDIS server.
@@ -163,7 +168,7 @@ func (r *RedisClient) Close() error {
163168
// TTL returns remaining time to live of the given key found in REDIS.
164169
// If the key doesn't exist, it returns ErrNotFound.
165170
func (r *RedisClient) TTL(key string) (time.Duration, error) {
166-
result, err := r.Redis.TTL(r.prefixedKey(key)).Result()
171+
result, err := r.Redis.TTL(ctx, r.prefixedKey(key)).Result()
167172
if err != nil {
168173
return 0, err
169174
}

database/config.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
package database
22

3-
import "fmt"
3+
import (
4+
"fmt"
5+
6+
"gorm.io/driver/mysql"
7+
"gorm.io/driver/postgres"
8+
"gorm.io/gorm"
9+
)
410

511
const (
612
// DialectMysql is the mysql dialect.
@@ -20,20 +26,22 @@ type Config struct {
2026
SSLMode string // optional, only used for postgres
2127
}
2228

23-
// ConnectionString returns a valid string for sql.Open.
24-
func (c *Config) ConnectionString() string {
29+
// Driver selects the correct DB driver and passes the connection details (DSN). It does not yet open a database connection.
30+
func (c *Config) Driver() gorm.Dialector {
2531
switch c.Dialect {
2632
case DialectMysql:
27-
return fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=true&loc=UTC&multiStatements=true",
33+
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=true&loc=UTC&multiStatements=true",
2834
c.User, c.Password, c.Host, c.Port, c.Name)
35+
return mysql.Open(dsn)
2936
case DialectPostgres:
3037
dbName := c.Name
3138
if dbName == "" {
3239
// We probably don't have a DB yet, let's connect to `postgres` instead
3340
dbName = "postgres"
3441
}
35-
return fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s",
42+
dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s",
3643
c.Host, c.Port, c.User, c.Password, dbName, c.SSLMode)
44+
return postgres.Open(dsn)
3745
default:
3846
panic("Unknown database dialect: " + c.Dialect)
3947
}

database/gorm.go

Lines changed: 62 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,74 +4,96 @@ import (
44
"fmt"
55
"time"
66

7-
"github.com/fastbill/go-service-toolkit/v3/observance"
7+
"github.com/fastbill/go-service-toolkit/v4/observance"
88

9-
// import migrate mysql, postgres driver
10-
_ "github.com/golang-migrate/migrate/v4/database/mysql"
11-
_ "github.com/golang-migrate/migrate/v4/database/postgres"
12-
"github.com/jinzhu/gorm"
9+
"gorm.io/gorm"
10+
gormlogger "gorm.io/gorm/logger"
1311
)
1412

15-
// GormLogrus is a logrus logger that implements the gorm interface for logging.
16-
type GormLogrus struct {
17-
observance.Logger
18-
}
19-
20-
// Print implements the gorm.LogWriter interface, courtesy of https://gist.github.com/bnadland/2e4287b801a47dcfcc94.
21-
func (g GormLogrus) Print(v ...interface{}) {
22-
if v[0] == "sql" {
23-
g.WithFields(observance.Fields{"source": "go-service-toolkit/database"}).Debug(fmt.Sprintf("%v - %v", v[3], v[4]))
24-
}
25-
if v[0] == "log" {
26-
g.WithFields(observance.Fields{"source": "go-service-toolkit/database"}).Debug(fmt.Sprintf("%v", v[2]))
27-
}
28-
}
29-
3013
// SetupGORM loads the ORM with the given configuration
3114
// The setup includes sending a ping and creating the database if it didn't exist.
3215
// A logger will be activated if logLevel is 'debug'.
3316
func SetupGORM(config Config, logger observance.Logger) (*gorm.DB, error) {
34-
// We have two connection strings:
35-
// 1) For connecting to the server (and maybe creating the database)
36-
// 2) For connecting to the database directly.
3717
dbName := config.Name
18+
19+
// First we connect without the database name so we can create the database if it does not exist.
3820
config.Name = ""
39-
connectionStringWithoutDatabase := config.ConnectionString()
40-
config.Name = dbName
41-
connectionString := config.ConnectionString()
21+
driverWithoutDatabaseSet := config.Driver()
22+
23+
gormConfig := &gorm.Config{
24+
Logger: createLogger(logger),
25+
}
4226

4327
// Open includes sending a ping.
44-
db, err := gorm.Open(config.Dialect, connectionStringWithoutDatabase)
28+
db, err := gorm.Open(driverWithoutDatabaseSet, gormConfig)
4529
if err != nil {
46-
return nil, err
30+
return nil, fmt.Errorf("failed to open DB connection: %w", err)
4731
}
4832

49-
if config.Name != "" {
33+
if dbName != "" {
34+
config.Name = dbName
5035
// Ensure the DB exists.
5136
db.Exec(fmt.Sprintf(config.createDatabaseQuery(), config.Name))
52-
err := db.Close()
37+
err = Close(db)
5338
if err != nil {
54-
return nil, err
39+
return nil, fmt.Errorf("failed to close DB connection: %w", err)
5540
}
5641

5742
// Connect again with DB name.
58-
db, err = gorm.Open(config.Dialect, connectionString)
43+
driver := config.Driver()
44+
db, err = gorm.Open(driver, gormConfig)
5945
if err != nil {
60-
return nil, err
46+
return nil, fmt.Errorf("failed to open DB connection: %w", err)
6147
}
6248
}
6349

64-
if logger.Level() == "debug" || logger.Level() == "trace" {
65-
db.LogMode(true)
66-
gormLogger := GormLogrus{logger}
67-
db.SetLogger(gormLogger)
68-
} else {
69-
db.LogMode(false)
50+
dbConn, err := db.DB()
51+
if err != nil {
52+
return nil, fmt.Errorf("failed to retrieve DB connection: %w", err)
7053
}
7154

7255
// This setting addresses "invalid connection" errors in case of connections being closed by the DB server after the wait_timeout (8h).
7356
// See https://github.com/go-sql-driver/mysql/issues/657.
74-
db.DB().SetConnMaxLifetime(3500 * time.Second)
57+
dbConn.SetConnMaxLifetime(time.Hour)
7558

7659
return db, nil
7760
}
61+
62+
// Close closes the database connection(s) used by GORM.
63+
func Close(db *gorm.DB) error {
64+
dbConn, err := db.DB()
65+
if err != nil {
66+
return fmt.Errorf("failed to retrieve DB connection: %w", err)
67+
}
68+
69+
return dbConn.Close()
70+
}
71+
72+
// GormWriter implements the Writer interface for setting up the GORM logger.
73+
type GormWriter struct {
74+
observance.Logger
75+
}
76+
77+
// Printf writes a log entry.
78+
func (g GormWriter) Printf(msg string, data ...interface{}) {
79+
g.Logger.Debug(fmt.Sprintf(msg, data...))
80+
}
81+
82+
func createLogger(logger observance.Logger) gormlogger.Interface {
83+
var logLevel gormlogger.LogLevel
84+
if logger.Level() == "debug" || logger.Level() == "trace" {
85+
logLevel = gormlogger.Info
86+
} else {
87+
logLevel = gormlogger.Silent
88+
}
89+
90+
newLogger := gormlogger.New(
91+
GormWriter{Logger: logger},
92+
gormlogger.Config{
93+
LogLevel: logLevel,
94+
Colorful: false,
95+
},
96+
)
97+
98+
return newLogger
99+
}

database/migrate.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
package database
22

33
import (
4+
"errors"
5+
"fmt"
46
"path/filepath"
57

68
"github.com/golang-migrate/migrate/v4"
79

810
// import migrate mysql, postgres driver
911
_ "github.com/golang-migrate/migrate/v4/database/mysql"
1012
_ "github.com/golang-migrate/migrate/v4/database/postgres"
11-
"github.com/pkg/errors"
1213

1314
// import reading migrations from files
1415
_ "github.com/golang-migrate/migrate/v4/source/file"
@@ -22,7 +23,7 @@ func EnsureMigrations(folder string, config Config) (returnErr error) {
2223

2324
fullPathToMigrations, err := filepath.Abs(folder)
2425
if err != nil {
25-
return errors.Wrap(err, "could not determine absolute path to migrations")
26+
return fmt.Errorf("could not determine absolute path to migrations: %w", err)
2627
}
2728

2829
migrations, err := migrate.New("file://"+fullPathToMigrations, databaseURL)

0 commit comments

Comments
 (0)