-
Notifications
You must be signed in to change notification settings - Fork 2
/
example_db_test.go
135 lines (120 loc) · 3.65 KB
/
example_db_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package nject_test
import (
"context"
"database/sql"
"errors"
"fmt"
"github.com/muir/nject"
)
// InjectDB injects both an *sql.DB and an *sql.Tx if they're needed.
// Errors from opening and closing the database can be returned
// so a consumer of downstream errors is necessary.
// A context.Context is used in the creation of the transaction
// inject that earlier in the chain. txOptions can be nil. If a
// transaction is injected, it will be automatically committed if the
// returned error from downstream is nil. It will be rolled back if
// the returned error is not nil.
func InjectDB(driver, uri string, txOptions *sql.TxOptions) *nject.Collection {
return nject.Sequence("database-sequence",
driverType(driver),
uriType(uri),
txOptions,
// We tag the db injector as MustConsume so that we don't inject
// the database unless there is a consumer for it. When a wrapper
// returns error, it should usually consume error too and pass
// that error along, otherwise it can mask a downstream error.
nject.MustConsume(nject.Provide("db", injectDB)),
// We tag the tx injector as MustConsume so that we don't inject
// the transaction unless there is a consumer for it. When a wrapper
// returns error, it should usually consume error too and pass
// that error along, otherwise it can mask a downstream error.
nject.MustConsume(nject.Provide("tx", injectTx)),
// Since injectTx or injectDB consumes an error, this provider
// will supply that error if there is no other downstream supplier.
nject.Shun(nject.Provide("fallback error", fallbackErrorSource)),
)
}
type (
driverType string
uriType string
)
func injectDB(inner func(*sql.DB) error, driver driverType, uri uriType) (finalError error) {
db, err := sql.Open(string(driver), string(uri))
if err != nil {
return err
}
defer func() {
err := db.Close()
if err != nil && finalError == nil {
finalError = err
}
}()
return inner(db)
}
func injectTx(inner func(*sql.Tx) error, ctx context.Context, db *sql.DB, opts *sql.TxOptions) (finalError error) {
tx, err := db.BeginTx(ctx, opts)
if err != nil {
return err
}
defer func() {
if finalError == nil {
finalError = tx.Commit()
if errors.Is(finalError, sql.ErrTxDone) {
finalError = nil
}
} else {
_ = tx.Rollback()
}
}()
return inner(tx)
}
// This has to be nject.TerminalError instead of error so that
// it gets consumed upstream instead of downstream
func fallbackErrorSource() nject.TerminalError {
fmt.Println("fallback error returns nil")
return nil
}
// This example explores injecting a database
// handle or transaction only when they're used.
func Example_transaction() {
// InjectDB will want a context and will return an error
upstream := func(inner func(context.Context) error) {
err := inner(context.Background())
if err != nil {
fmt.Println(err.Error())
}
}
fmt.Println("No database used...")
nject.MustRun("A", upstream, InjectDB("dummy", "ignored", nil),
func() {
fmt.Println("final-func")
})
fmt.Println("\nDatabase used...")
nject.MustRun("B", upstream, InjectDB("dummy", "ignored", nil),
func(db *sql.DB) error {
// nolint:sqlclosecheck
_, _ = db.Prepare("ignored") // database opens are lazy so this triggers the logging
fmt.Println("final-func")
return nil
})
fmt.Println("\nTransaction used...")
nject.MustRun("C", upstream, InjectDB("dummy", "ignored", nil),
func(_ *sql.Tx) {
fmt.Println("final-func")
})
// Output: No database used...
// final-func
//
// Database used...
// db open
// final-func
// db close
//
// Transaction used...
// db open
// tx begin
// fallback error returns nil
// final-func
// tx committed
// db close
}