@@ -4,34 +4,75 @@ package store
4
4
5
5
import (
6
6
"context"
7
+ "errors"
8
+ "fmt"
9
+ "maps"
7
10
"os"
11
+ "slices"
8
12
9
13
"github.com/ahamlinman/randomizer/internal/randomizer"
10
- "github.com/ahamlinman/randomizer/internal/store/bbolt"
11
- "github.com/ahamlinman/randomizer/internal/store/dynamodb"
12
- "github.com/ahamlinman/randomizer/internal/store/firestore"
14
+ "github.com/ahamlinman/randomizer/internal/store/registry"
13
15
)
14
16
17
+ // haveAllStoreBackends indicates whether we can safely use the bbolt fallback
18
+ // explained in the [FactoryFromEnv] comment. If we fall back to bbolt even
19
+ // though we don't know the full set of possible environment keys (because we
20
+ // used build tags to exclude some backends), we might activate it for a user
21
+ // who meant to configure one of those missing stores instead.
22
+ var haveAllStoreBackends bool
23
+
15
24
// Factory represents a type for functions that produce a store for the
16
25
// randomizer to use for a given "partition" (e.g. Slack channel). Factories
17
26
// may panic if a non-empty partition is required and not given.
18
27
//
19
28
// Factory is provided for documentation purposes. Do not import the store
20
- // package just to use this alias; this will link support for all possible
29
+ // package just to use this alias; this may link support for all possible
21
30
// store backends into the final program, even if this was not intended.
22
31
type Factory = func (partition string ) randomizer.Store
23
32
24
- // FactoryFromEnv constructs and returns a [Factory] based on available
25
- // environment variables. If a known DynamoDB environment variable is set, it
26
- // will return a DynamoDB store. Otherwise, it will return a bbolt store.
27
- func FactoryFromEnv (ctx context.Context ) (func (string ) randomizer.Store , error ) {
28
- if envHasAny ("DYNAMODB" , "DYNAMODB_TABLE" , "DYNAMODB_ENDPOINT" ) {
29
- return dynamodb .FactoryFromEnv (ctx )
33
+ // FactoryFromEnv constructs and returns a [Factory] based on runtime
34
+ // environment variables, using one of the store backends included in the
35
+ // binary based on Go build tags.
36
+ //
37
+ // Each store backend defines a set of environment variables for configuration.
38
+ // On startup, the randomizer selects one store backend based on the presence
39
+ // of its environment variables. It may fail if the environment has conflicting
40
+ // or missing store configurations, or use a default bbolt configuration if no
41
+ // build tags have been used to restrict the backends available in this binary.
42
+ func FactoryFromEnv (ctx context.Context ) (Factory , error ) {
43
+ if len (registry .Registry ) == 0 {
44
+ return nil , errors .New ("no store backends available in this build" )
45
+ }
46
+
47
+ candidates := make (map [string ]struct {})
48
+ for name , entry := range registry .Registry {
49
+ if envHasAny (entry .EnvironmentKeys ... ) {
50
+ candidates [name ] = struct {}{}
51
+ }
52
+ }
53
+
54
+ var chosen string
55
+ if len (candidates ) == 0 && haveAllStoreBackends {
56
+ chosen = "bbolt"
57
+ }
58
+ if len (candidates ) == 1 {
59
+ for name := range candidates {
60
+ chosen = name
61
+ }
62
+ }
63
+
64
+ if chosen == "" && len (candidates ) == 0 {
65
+ available := slices .Sorted (maps .Keys (registry .Registry ))
66
+ return nil , fmt .Errorf (
67
+ "can't find environment settings to select between store backends: %v" , available )
30
68
}
31
- if envHasAny ("FIRESTORE_PROJECT_ID" , "FIRESTORE_DATABASE_ID" ) {
32
- return firestore .FactoryFromEnv ()
69
+ if chosen == "" {
70
+ options := slices .Sorted (maps .Keys (candidates ))
71
+ return nil , fmt .Errorf (
72
+ "environment settings match multiple store backends: %v" , options )
33
73
}
34
- return bbolt .FactoryFromEnv ()
74
+
75
+ return registry .Registry [chosen ].FactoryFromEnv (ctx )
35
76
}
36
77
37
78
func envHasAny (names ... string ) bool {
0 commit comments