@@ -4,34 +4,75 @@ package store
44
55import (
66 "context"
7+ "errors"
8+ "fmt"
9+ "maps"
710 "os"
11+ "slices"
812
913 "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"
1315)
1416
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+
1524// Factory represents a type for functions that produce a store for the
1625// randomizer to use for a given "partition" (e.g. Slack channel). Factories
1726// may panic if a non-empty partition is required and not given.
1827//
1928// 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
2130// store backends into the final program, even if this was not intended.
2231type Factory = func (partition string ) randomizer.Store
2332
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 )
3068 }
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 )
3373 }
34- return bbolt .FactoryFromEnv ()
74+
75+ return registry .Registry [chosen ].FactoryFromEnv (ctx )
3576}
3677
3778func envHasAny (names ... string ) bool {
0 commit comments