11package config
22
33import (
4+ "fmt"
45 "net/url"
56 "os"
67 "strings"
78)
89
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 ) {
1091 switch {
1192 case s == "" :
1293 // Nothing specified, make sure something's in the environment.
@@ -27,16 +108,69 @@ func checkDSN(s string) (w []Warning, err error) {
27108 if _ , err := url .Parse (s ); err != nil {
28109 w = append (w , Warning {inner : err })
29110 }
30- case strings .ContainsRune (s , '=' ):
31- // Looks like a DSN
32111 case strings .Contains (s , `://` ):
33112 w = append (w , Warning {
34113 msg : "connection string looks like a URL but scheme is unrecognized" ,
35114 })
115+ case strings .ContainsRune (s , '=' ):
116+ // Looks like a DSN
36117 default :
37118 w = append (w , Warning {
38119 msg : "unable to make sense of connection string" ,
39120 })
40121 }
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 ()
42176}
0 commit comments