1
1
package config
2
2
3
3
import (
4
+ "fmt"
4
5
"net/url"
5
6
"os"
6
7
"strings"
7
8
)
8
9
9
- func checkDSN (s string ) (w []Warning , err error ) {
10
+ // CheckConnString reports a warning for using the "connstring" member instead
11
+ // of the "database" member.
12
+ //
13
+ // This will panic if the type parameter is not [Matcher], [Indexer], or [Notifier].
14
+ func checkConnString [T any ](ws * []Warning , v * T ) {
15
+ var cs * string
16
+ var d * Database
17
+ switch v := any (v ).(type ) {
18
+ case * Matcher :
19
+ cs = & v .ConnString
20
+ d = v .Database
21
+ case * Indexer :
22
+ cs = & v .ConnString
23
+ d = v .Database
24
+ case * Notifier :
25
+ cs = & v .ConnString
26
+ d = v .Database
27
+ default :
28
+ panic (fmt .Sprintf ("programmer error: passed unexpected type: %T" , v ))
29
+ }
30
+ if * cs != "" {
31
+ * ws = append (* ws , errConnString )
32
+ }
33
+ if d == nil {
34
+ * ws = append (* ws , Warning {
35
+ path : ".database" ,
36
+ msg : `missing database configuration` ,
37
+ })
38
+ }
39
+ }
40
+
41
+ // ErrConnString is reported by [checkConnString] if the "connstring" member is in use.
42
+ var errConnString = Warning {
43
+ path : ".connstring" ,
44
+ inner : fmt .Errorf (`using bare-string for database configuration deprecated: %w` , ErrDeprecated ),
45
+ }
46
+
47
+ // SetConnString adjusts the passed variable by porting from the "connstring"
48
+ // member if necessary.
49
+ //
50
+ // This will panic if the type parameter is not [Matcher], [Indexer], or [Notifier].
51
+ func setConnString [T any ](ws * []Warning , v * T ) {
52
+ var cs * string
53
+ var d * Database
54
+ var m * bool
55
+ switch v := any (v ).(type ) {
56
+ case * Matcher :
57
+ cs = & v .ConnString
58
+ d = v .Database
59
+ m = v .Migrations
60
+ case * Indexer :
61
+ cs = & v .ConnString
62
+ d = v .Database
63
+ m = v .Migrations
64
+ case * Notifier :
65
+ cs = & v .ConnString
66
+ d = v .Database
67
+ m = v .Migrations
68
+ default :
69
+ panic (fmt .Sprintf ("programmer error: passed unexpected type: %T" , v ))
70
+ }
71
+ switch {
72
+ case * cs != "" && d != nil :
73
+ * cs = ""
74
+ case * cs != "" && d == nil :
75
+ d = & Database {
76
+ Name : `postgresql` ,
77
+ PostgreSQL : & DatabasePostgreSQL {DSN : * cs },
78
+ Migrations : m ,
79
+ }
80
+ * cs = ""
81
+ case * cs == "" && d != nil : // OK, use as-is.
82
+ case * cs == "" && d == nil : // Will probably explode later.
83
+ }
84
+ }
85
+
86
+ // CheckPostgresqlDSN is a (very) light check that the value provided isn't completely bogus.
87
+ //
88
+ // Implementing more rigorous checks would be much more complicated.
89
+ // That's not to say it'd be an unwelcome addition, just that it's very large and probably not needed.
90
+ func checkPostgresqlDSN (s string ) (w []Warning ) {
10
91
switch {
11
92
case s == "" :
12
93
// Nothing specified, make sure something's in the environment.
@@ -27,16 +108,69 @@ func checkDSN(s string) (w []Warning, err error) {
27
108
if _ , err := url .Parse (s ); err != nil {
28
109
w = append (w , Warning {inner : err })
29
110
}
30
- case strings .ContainsRune (s , '=' ):
31
- // Looks like a DSN
32
111
case strings .Contains (s , `://` ):
33
112
w = append (w , Warning {
34
113
msg : "connection string looks like a URL but scheme is unrecognized" ,
35
114
})
115
+ case strings .ContainsRune (s , '=' ):
116
+ // Looks like a DSN
36
117
default :
37
118
w = append (w , Warning {
38
119
msg : "unable to make sense of connection string" ,
39
120
})
40
121
}
41
- return w , nil
122
+ return w
123
+ }
124
+
125
+ // Database indicates the database configuration.
126
+ type Database struct {
127
+ // Name indicates which database backend to use.
128
+ //
129
+ // This value must match the json/yaml tag.
130
+ Name string `json:"name" yaml:"name"`
131
+ // Migrations indicates if database migrations should run automatically.
132
+ Migrations * bool `json:"migrations,omitempty" yaml:"migrations,omitempty"`
133
+ // PostgreSQL is the PostgreSQL configuration.
134
+ PostgreSQL * DatabasePostgreSQL `json:"postgresql,omitempty" yaml:"postgresql,omitempty"`
135
+ }
136
+
137
+ func (d * Database ) lint () (ws []Warning , err error ) {
138
+ switch n := d .Name ; n {
139
+ case "postgresql" : // OK
140
+ case "postgres" :
141
+ ws = append (ws , Warning {
142
+ msg : fmt .Sprintf ("unknown database: %q (did you mean %q?)" , n , "postgresql" ),
143
+ path : ".name" ,
144
+ })
145
+ default :
146
+ ws = append (ws , Warning {
147
+ msg : fmt .Sprintf ("unknown database: %q" , n ),
148
+ path : ".name" ,
149
+ })
150
+ }
151
+ return ws , nil
152
+ }
153
+ func (d * Database ) validate (_ Mode ) ([]Warning , error ) {
154
+ return d .lint ()
155
+ }
156
+
157
+ // DatabasePostgreSQL is the PostgreSQL-specific database configuration.
158
+ //
159
+ // Validation assumes that if the "DSN" member is empty but any environment variables with a "PG" prefix are present,
160
+ // the configuration is specified in via environment variables.
161
+ // This package implements no checking for the specifics of the DSN/URL/environment variables;
162
+ // providing malformed values will fail at the point of use instead of configuration validation.
163
+ type DatabasePostgreSQL struct {
164
+ // DSN is a data source name (aka "connection string") as documented for [libpq], with the extensions supported by [pgxpool].
165
+ //
166
+ // [libpq]: https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING
167
+ // [pgxpool]: https://pkg.go.dev/github.com/jackc/pgx/v4/pgxpool#ParseConfig
168
+ DSN string `json:"dsn" yaml:"dsn"`
169
+ }
170
+
171
+ func (d * DatabasePostgreSQL ) lint () ([]Warning , error ) {
172
+ return checkPostgresqlDSN (d .DSN ), nil
173
+ }
174
+ func (d * DatabasePostgreSQL ) validate (_ Mode ) ([]Warning , error ) {
175
+ return d .lint ()
42
176
}
0 commit comments