From 59751c02e663a717935dd6f4a2537c71a8df8219 Mon Sep 17 00:00:00 2001
From: Asim Aslam <asim@aslam.me>
Date: Mon, 16 Dec 2019 14:38:51 +0000
Subject: [PATCH] change store options

---
 store/cloudflare/cloudflare.go      |  42 +++------
 store/cloudflare/options.go         |  57 ++++++++++--
 store/etcd/etcd.go                  |  21 ++---
 store/memory/memory.go              |  12 ++-
 store/options.go                    |  24 +++--
 store/postgresql/postgresql.go      | 139 ++++++++++------------------
 store/postgresql/postgresql_test.go |   5 +-
 store/service/service.go            |  35 ++-----
 8 files changed, 156 insertions(+), 179 deletions(-)

diff --git a/store/cloudflare/cloudflare.go b/store/cloudflare/cloudflare.go
index 3b9a5e8c..20ce2bac 100644
--- a/store/cloudflare/cloudflare.go
+++ b/store/cloudflare/cloudflare.go
@@ -17,7 +17,6 @@ import (
 	"strconv"
 	"time"
 
-	"github.com/micro/go-micro/config/options"
 	"github.com/micro/go-micro/store"
 	"github.com/pkg/errors"
 )
@@ -27,7 +26,7 @@ const (
 )
 
 type workersKV struct {
-	options.Options
+	options store.Options
 	// cf account id
 	account string
 	// cf api token
@@ -291,38 +290,25 @@ func (w *workersKV) request(ctx context.Context, method, path string, body inter
 // CF_API_TOKEN to a cloudflare API token scoped to Workers KV.
 // CF_ACCOUNT_ID to contain a string with your cloudflare account ID.
 // KV_NAMESPACE_ID to contain the namespace UUID for your KV storage.
-func NewStore(opts ...options.Option) store.Store {
-	// create new Options
-	options := options.NewOptions(opts...)
+func NewStore(opts ...store.Option) store.Store {
+	var options store.Options
+	for _, o := range opts {
+		o(&options)
+	}
 
-	// get values from the environment
+	// get options from environment
 	account, token, namespace := getOptions()
 
-	// set api token from options if exists
-	if apiToken, ok := options.Values().Get("CF_API_TOKEN"); ok {
-		tk, ok := apiToken.(string)
-		if !ok {
-			log.Fatal("Store: Option CF_API_TOKEN contains a non-string")
-		}
-		token = tk
+	if len(account) == 0 {
+		account = getAccount(options.Context)
 	}
 
-	// set account id from options if exists
-	if accountID, ok := options.Values().Get("CF_ACCOUNT_ID"); ok {
-		id, ok := accountID.(string)
-		if !ok {
-			log.Fatal("Store: Option CF_ACCOUNT_ID contains a non-string")
-		}
-		account = id
+	if len(token) == 0 {
+		token = getToken(options.Context)
 	}
 
-	// set namespace from options if exists
-	if uuid, ok := options.Values().Get("KV_NAMESPACE_ID"); ok {
-		ns, ok := uuid.(string)
-		if !ok {
-			log.Fatal("Store: Option KV_NAMESPACE_ID contains a non-string")
-		}
-		namespace = ns
+	if len(namespace) == 0 {
+		namespace = getNamespace(options.Context)
 	}
 
 	// validate options are not blank or log.Fatal
@@ -332,7 +318,7 @@ func NewStore(opts ...options.Option) store.Store {
 		account:    account,
 		namespace:  namespace,
 		token:      token,
-		Options:    options,
+		options:    options,
 		httpClient: &http.Client{},
 	}
 }
diff --git a/store/cloudflare/options.go b/store/cloudflare/options.go
index 36dc7c10..d9670ff4 100644
--- a/store/cloudflare/options.go
+++ b/store/cloudflare/options.go
@@ -1,23 +1,60 @@
 package cloudflare
 
 import (
-	"github.com/micro/go-micro/config/options"
+	"context"
+
+	"github.com/micro/go-micro/store"
 )
 
+func getOption(ctx context.Context, key string) string {
+	if ctx == nil {
+		return ""
+	}
+	val, ok := ctx.Value(key).(string)
+	if !ok {
+		return ""
+	}
+	return val
+}
+
+func getToken(ctx context.Context) string {
+	return getOption(ctx, "CF_API_TOKEN")
+}
+
+func getAccount(ctx context.Context) string {
+	return getOption(ctx, "CF_ACCOUNT_ID")
+}
+
+func getNamespace(ctx context.Context) string {
+	return getOption(ctx, "KV_NAMESPACE_ID")
+}
+
 // Token sets the cloudflare api token
-func Token(t string) options.Option {
-	// TODO: change to store.cf.api_token
-	return options.WithValue("CF_API_TOKEN", t)
+func Token(t string) store.Option {
+	return func(o *store.Options) {
+		if o.Context == nil {
+			o.Context = context.Background()
+		}
+		o.Context = context.WithValue(o.Context, "CF_API_TOKEN", t)
+	}
 }
 
 // Account sets the cloudflare account id
-func Account(id string) options.Option {
-	// TODO: change to store.cf.account_id
-	return options.WithValue("CF_ACCOUNT_ID", id)
+func Account(id string) store.Option {
+	return func(o *store.Options) {
+		if o.Context == nil {
+			o.Context = context.Background()
+		}
+		o.Context = context.WithValue(o.Context, "CF_ACCOUNT_ID", id)
+	}
 }
 
 // Namespace sets the KV namespace
-func Namespace(ns string) options.Option {
-	// TODO: change to store.cf.namespace
-	return options.WithValue("KV_NAMESPACE_ID", ns)
+func Namespace(ns string) store.Option {
+	return func(o *store.Options) {
+		if o.Context == nil {
+			o.Context = context.Background()
+		}
+		o.Context = context.WithValue(o.Context, "KV_NAMESPACE_ID", ns)
+	}
 }
diff --git a/store/etcd/etcd.go b/store/etcd/etcd.go
index 8ffa2d7d..c9bc4047 100644
--- a/store/etcd/etcd.go
+++ b/store/etcd/etcd.go
@@ -7,13 +7,12 @@ import (
 
 	client "github.com/coreos/etcd/clientv3"
 	"github.com/coreos/etcd/mvcc/mvccpb"
-	"github.com/micro/go-micro/config/options"
 	"github.com/micro/go-micro/store"
 )
 
 type ekv struct {
-	options.Options
-	kv client.KV
+	options store.Options
+	kv      client.KV
 }
 
 func (e *ekv) Read(keys ...string) ([]*store.Record, error) {
@@ -91,15 +90,15 @@ func (e *ekv) String() string {
 	return "etcd"
 }
 
-func NewStore(opts ...options.Option) store.Store {
-	options := options.NewOptions(opts...)
-
-	var endpoints []string
-
-	if e, ok := options.Values().Get("store.nodes"); ok {
-		endpoints = e.([]string)
+func NewStore(opts ...store.Option) store.Store {
+	var options store.Options
+	for _, o := range opts {
+		o(&options)
 	}
 
+	// get the endpoints
+	endpoints := options.Nodes
+
 	if len(endpoints) == 0 {
 		endpoints = []string{"http://127.0.0.1:2379"}
 	}
@@ -113,7 +112,7 @@ func NewStore(opts ...options.Option) store.Store {
 	}
 
 	return &ekv{
-		Options: options,
+		options: options,
 		kv:      client.NewKV(c),
 	}
 }
diff --git a/store/memory/memory.go b/store/memory/memory.go
index 01d1a44c..78e85e82 100644
--- a/store/memory/memory.go
+++ b/store/memory/memory.go
@@ -5,12 +5,11 @@ import (
 	"sync"
 	"time"
 
-	"github.com/micro/go-micro/config/options"
 	"github.com/micro/go-micro/store"
 )
 
 type memoryStore struct {
-	options.Options
+	options store.Options
 
 	sync.RWMutex
 	values map[string]*memoryRecord
@@ -110,11 +109,14 @@ func (m *memoryStore) Delete(keys ...string) error {
 }
 
 // NewStore returns a new store.Store
-func NewStore(opts ...options.Option) store.Store {
-	options := options.NewOptions(opts...)
+func NewStore(opts ...store.Option) store.Store {
+	var options store.Options
+	for _, o := range opts {
+		o(&options)
+	}
 
 	return &memoryStore{
-		Options: options,
+		options: options,
 		values:  make(map[string]*memoryRecord),
 	}
 }
diff --git a/store/options.go b/store/options.go
index f5dbf7e2..0161ed20 100644
--- a/store/options.go
+++ b/store/options.go
@@ -1,7 +1,7 @@
 package store
 
 import (
-	"github.com/micro/go-micro/config/options"
+	"context"
 )
 
 type Options struct {
@@ -11,20 +11,30 @@ type Options struct {
 	Namespace string
 	// Prefix of the keys used
 	Prefix string
+	// Alternative options
+	Context context.Context
 }
 
+type Option func(o *Options)
+
 // Nodes is a list of nodes used to back the store
-func Nodes(a ...string) options.Option {
-	return options.WithValue("store.nodes", a)
+func Nodes(a ...string) Option {
+	return func(o *Options) {
+		o.Nodes = a
+	}
 }
 
 // Prefix sets a prefix to any key ids used
-func Prefix(p string) options.Option {
-	return options.WithValue("store.prefix", p)
+func Prefix(p string) Option {
+	return func(o *Options) {
+		o.Prefix = p
+	}
 }
 
 // Namespace offers a way to have multiple isolated
 // stores in the same backend, if supported.
-func Namespace(n string) options.Option {
-	return options.WithValue("store.namespace", n)
+func Namespace(ns string) Option {
+	return func(o *Options) {
+		o.Namespace = ns
+	}
 }
diff --git a/store/postgresql/postgresql.go b/store/postgresql/postgresql.go
index 4162e1e1..84c07d58 100644
--- a/store/postgresql/postgresql.go
+++ b/store/postgresql/postgresql.go
@@ -4,15 +4,13 @@ package postgresql
 import (
 	"database/sql"
 	"fmt"
-	"strings"
 	"time"
 	"unicode"
 
 	"github.com/lib/pq"
-	"github.com/pkg/errors"
-
-	"github.com/micro/go-micro/config/options"
 	"github.com/micro/go-micro/store"
+	"github.com/micro/go-micro/util/log"
+	"github.com/pkg/errors"
 )
 
 // DefaultNamespace is the namespace that the sql store
@@ -27,7 +25,8 @@ type sqlStore struct {
 
 	database string
 	table    string
-	options.Options
+
+	options store.Options
 }
 
 // List all the known records
@@ -151,38 +150,16 @@ func (s *sqlStore) Delete(keys ...string) error {
 	return nil
 }
 
-func (s *sqlStore) initDB(options options.Options) error {
-	// Get the store.namespace option, or use sql.DefaultNamespace
-	namespaceOpt, found := options.Values().Get("store.namespace")
-	if !found {
-		s.database = DefaultNamespace
-	} else {
-		if namespace, ok := namespaceOpt.(string); ok {
-			s.database = namespace
-		} else {
-			return errors.New("store.namespace option must be a string")
-		}
-	}
-	// Get the store.namespace option, or use sql.DefaultNamespace
-	prefixOpt, found := options.Values().Get("store.prefix")
-	if !found {
-		s.table = DefaultPrefix
-	} else {
-		if prefix, ok := prefixOpt.(string); ok {
-			s.table = prefix
-		} else {
-			return errors.New("store.namespace option must be a string")
-		}
-	}
-
+func (s *sqlStore) initDB() {
 	// Create "micro" schema
 	schema, err := s.db.Prepare(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s ;", s.database))
 	if err != nil {
-		return err
+		log.Fatal(err)
 	}
+
 	_, err = schema.Exec()
 	if err != nil {
-		return errors.Wrap(err, "Couldn't create database")
+		log.Fatal(errors.Wrap(err, "Couldn't create database"))
 	}
 
 	// Create a table for the Store namespace
@@ -194,73 +171,59 @@ func (s *sqlStore) initDB(options options.Options) error {
 		CONSTRAINT %s_pkey PRIMARY KEY (key)
 	);`, s.database, s.table, s.table))
 	if err != nil {
-		return errors.Wrap(err, "SQL statement preparation failed")
-	}
-	_, err = tableq.Exec()
-	if err != nil {
-		return errors.Wrap(err, "Couldn't create table")
+		log.Fatal(errors.Wrap(err, "SQL statement preparation failed"))
 	}
 
-	return nil
+	_, err = tableq.Exec()
+	if err != nil {
+		log.Fatal(errors.Wrap(err, "Couldn't create table"))
+	}
 }
 
 // New returns a new micro Store backed by sql
-func New(opts ...options.Option) (store.Store, error) {
-	options := options.NewOptions(opts...)
-	driver, dataSourceName, err := validateOptions(options)
+func New(opts ...store.Option) store.Store {
+	var options store.Options
+	for _, o := range opts {
+		o(&options)
+	}
+
+	nodes := options.Nodes
+	if len(nodes) == 0 {
+		nodes = []string{"localhost:26257"}
+	}
+
+	namespace := options.Namespace
+	if len(namespace) == 0 {
+		namespace = DefaultNamespace
+	}
+
+	prefix := options.Prefix
+	if len(prefix) == 0 {
+		prefix = DefaultPrefix
+	}
+
+	for _, r := range namespace {
+		if !unicode.IsLetter(r) {
+			log.Fatal("store.namespace must only contain letters")
+		}
+	}
+
+	// create source from first node
+	source := fmt.Sprintf("host=%s", nodes[0])
+	db, err := sql.Open("pq", source)
 	if err != nil {
-		return nil, err
-	}
-	if !strings.Contains(dataSourceName, " ") {
-		dataSourceName = fmt.Sprintf("host=%s", dataSourceName)
-	}
-	db, err := sql.Open(driver, dataSourceName)
-	if err != nil {
-		return nil, err
+		log.Fatal(err)
 	}
+
 	if err := db.Ping(); err != nil {
-		return nil, err
+		log.Fatal(err)
 	}
+
 	s := &sqlStore{
-		db: db,
+		db:       db,
+		database: namespace,
+		table:    prefix,
 	}
 
-	return s, s.initDB(options)
-}
-
-// validateOptions checks whether the provided options are valid, then returns the driver
-// and data source name.
-func validateOptions(options options.Options) (driver, dataSourceName string, err error) {
-	driverOpt, found := options.Values().Get("store.sql.driver")
-	if !found {
-		return "", "", errors.New("No store.sql.driver option specified")
-	}
-	nodesOpt, found := options.Values().Get("store.nodes")
-	if !found {
-		return "", "", errors.New("No store.nodes option specified (expected a database connection string)")
-	}
-	driver, ok := driverOpt.(string)
-	if !ok {
-		return "", "", errors.New("store.sql.driver option must be a string")
-	}
-	nodes, ok := nodesOpt.([]string)
-	if !ok {
-		return "", "", errors.New("store.nodes option must be a []string")
-	}
-	if len(nodes) != 1 {
-		return "", "", errors.New("expected only 1 store.nodes option")
-	}
-	namespaceOpt, found := options.Values().Get("store.namespace")
-	if found {
-		namespace, ok := namespaceOpt.(string)
-		if !ok {
-			return "", "", errors.New("store.namespace must me a string")
-		}
-		for _, r := range namespace {
-			if !unicode.IsLetter(r) {
-				return "", "", errors.New("store.namespace must only contain letters")
-			}
-		}
-	}
-	return driver, nodes[0], nil
+	return s
 }
diff --git a/store/postgresql/postgresql_test.go b/store/postgresql/postgresql_test.go
index fffc974e..692aebeb 100644
--- a/store/postgresql/postgresql_test.go
+++ b/store/postgresql/postgresql_test.go
@@ -27,13 +27,10 @@ func TestSQL(t *testing.T) {
 	}
 	db.Close()
 
-	sqlStore, err := New(
+	sqlStore := New(
 		store.Namespace("testsql"),
 		store.Nodes(connection),
 	)
-	if err != nil {
-		t.Fatal(err.Error())
-	}
 
 	records, err := sqlStore.List()
 	if err != nil {
diff --git a/store/service/service.go b/store/service/service.go
index e0432016..37a7a030 100644
--- a/store/service/service.go
+++ b/store/service/service.go
@@ -7,14 +7,13 @@ import (
 	"time"
 
 	"github.com/micro/go-micro/client"
-	"github.com/micro/go-micro/config/options"
 	"github.com/micro/go-micro/metadata"
 	"github.com/micro/go-micro/store"
 	pb "github.com/micro/go-micro/store/service/proto"
 )
 
 type serviceStore struct {
-	options.Options
+	options store.Options
 
 	// Namespace to use
 	Namespace string
@@ -123,33 +122,17 @@ func (s *serviceStore) Delete(keys ...string) error {
 }
 
 // NewStore returns a new store service implementation
-func NewStore(opts ...options.Option) store.Store {
-	options := options.NewOptions(opts...)
-
-	var nodes []string
-	var namespace string
-	var prefix string
-
-	n, ok := options.Values().Get("store.nodes")
-	if ok {
-		nodes = n.([]string)
-	}
-
-	ns, ok := options.Values().Get("store.namespace")
-	if ok {
-		namespace = ns.(string)
-	}
-
-	prx, ok := options.Values().Get("store.prefix")
-	if ok {
-		prefix = prx.(string)
+func NewStore(opts ...store.Option) store.Store {
+	var options store.Options
+	for _, o := range opts {
+		o(&options)
 	}
 
 	service := &serviceStore{
-		Options:   options,
-		Namespace: namespace,
-		Prefix:    prefix,
-		Nodes:     nodes,
+		options:   options,
+		Namespace: options.Namespace,
+		Prefix:    options.Prefix,
+		Nodes:     options.Nodes,
 		Client:    pb.NewStoreService("go.micro.store", client.DefaultClient),
 	}