/
mongo.go
168 lines (137 loc) · 4.52 KB
/
mongo.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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
/*
Package mongo implements a Mongo instance that connects to a
database and allows interactions with the datasource.
All functions that require a context to be passed should be given one
from the service handler request to correctly handle cancellations.
*/
package mongo
import (
"context"
"encoding/base64"
"errors"
"fmt"
"net/url"
"strings"
"time"
"github.com/jobaldw/shared/v2/config"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/mongo/readpref"
)
// package logging key
const packageKey = "mongo"
var (
ErrNoCollections = errors.New("no configured collections") // no collection to read from or write to
ErrNoDatabase = errors.New("no database found") // no database has been connected
ErrNoDocuments = errors.New("no documents in result") // no mongo documents are in the result set
)
// Holds the configurations for a mongo connection using the
// official mongo driver package.
type Mongo struct {
// host or host:port of the mongo uri
Host string
// mongo database name
Name string
// username/password information for uri authentication
User string
// handles to a MongoDB collection
Collections map[string]*mongo.Collection
// handle to a MongoDB database
Database *mongo.Database
// representation of a parsed URL (technically, a URI reference)
URI *url.URL
}
// New
// implements a new mongo object that connects to a database and creates a
// handle for mongo collections.
func New(conf config.Mongo) (*Mongo, error) {
// builds the mongo uri
url, err := getURI(conf)
if err != nil {
return nil, err
}
// build the mongo object with a connected mongo instance and database
// collection objects
db := &Mongo{URI: url, Name: conf.Database, Collections: make(map[string]*mongo.Collection)}
if err := db.connect(); err != nil {
return db, fmt.Errorf("%s: %s", packageKey, err)
}
if conf.Collections != nil {
for k, v := range conf.Collections {
db.Collections[k] = db.Database.Collection(v)
}
return db, nil
}
return db, fmt.Errorf("%s: %s", packageKey, ErrNoCollections)
}
// GetCollection
// retrieves a mongo collection object.
func (m *Mongo) GetCollection(key string) *mongo.Collection {
return m.Collections[key]
}
// Ping
// test the mongo database connection.
func (m *Mongo) Ping() (err error) {
if m.Database == nil {
return fmt.Errorf("%s: %s", packageKey, ErrNoDatabase)
}
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
if err = m.Database.Client().Ping(ctx, readpref.Primary()); err != nil {
return fmt.Errorf("%s: %s, could not ping database, %s", packageKey, err, m.Database.Name())
}
return err
}
// PingWithContext
// test the mongo database connection with any passed in context.Context().
func (m *Mongo) PingWithContext(ctx context.Context) (err error) {
if m.Database == nil {
return fmt.Errorf("%s: %s", packageKey, ErrNoDatabase)
}
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
if err = m.Database.Client().Ping(ctx, readpref.Primary()); err != nil {
return fmt.Errorf("%s: %s, could not ping database, %s", packageKey, err, m.Database.Name())
}
return err
}
/********** helper functions **********/
// connect
// creates, configures and connects to a mongo client.
func (m *Mongo) connect() (err error) {
client, err := mongo.NewClient(options.Client().ApplyURI(m.URI.String()))
if err != nil {
return fmt.Errorf("%s: %s, could not create mongo object", packageKey, err)
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err = client.Connect(ctx); err != nil {
return fmt.Errorf("%s: %s, could not connect to %s", packageKey, err, m.Database.Name())
}
m.Database = client.Database(m.Name)
m.Host, m.User = m.URI.Hostname(), m.URI.User.Username()
return err
}
// getURI
// builds the decodes and builds the mongo db url.
func getURI(conf config.Mongo) (*url.URL, error) {
// decode the Base64 encoded mongo uri
rawURI, err := base64.StdEncoding.DecodeString(conf.URI)
if err != nil {
return nil, fmt.Errorf("%s: %s", packageKey, err)
}
// use configured username and password over the uri
uri := string(rawURI)
if conf.Password != "" && conf.Username != "" {
user := url.QueryEscape(conf.Username)
pass := url.QueryEscape(conf.Password)
split := strings.Split(uri, "@")
uri = "mongodb+srv://" + user + ":" + pass + "@" + split[1]
}
// build url
url, err := url.Parse(uri)
if err != nil {
return nil, fmt.Errorf("%s: %s", packageKey, err)
}
return url, nil
}