1package kivik
2
3import (
4	"context"
5	"encoding/json"
6
7	"github.com/imdario/mergo"
8
9	"github.com/go-kivik/kivik/driver"
10	"github.com/go-kivik/kivik/errors"
11)
12
13// Client is a client connection handle to a CouchDB-like server.
14type Client struct {
15	dsn          string
16	driverName   string
17	driverClient driver.Client
18}
19
20// Options is a collection of options. The keys and values are backend specific.
21type Options map[string]interface{}
22
23func mergeOptions(otherOpts ...Options) (Options, error) {
24	var options Options
25	for _, opts := range otherOpts {
26		if err := mergo.MergeWithOverwrite(&options, opts); err != nil {
27			return nil, err
28		}
29	}
30	return options, nil
31}
32
33// New creates a new client object specified by its database driver name
34// and a driver-specific data source name.
35func New(ctx context.Context, driverName, dataSourceName string) (*Client, error) {
36	driversMu.RLock()
37	driveri, ok := drivers[driverName]
38	driversMu.RUnlock()
39	if !ok {
40		return nil, errors.Statusf(StatusBadRequest, "kivik: unknown driver %q (forgotten import?)", driverName)
41	}
42	client, err := driveri.NewClient(ctx, dataSourceName)
43	if err != nil {
44		return nil, err
45	}
46	return &Client{
47		dsn:          dataSourceName,
48		driverName:   driverName,
49		driverClient: client,
50	}, nil
51}
52
53// Driver returns the name of the driver string used to connect this client.
54func (c *Client) Driver() string {
55	return c.driverName
56}
57
58// DSN returns the data source name used to connect this client.
59func (c *Client) DSN() string {
60	return c.dsn
61}
62
63// Version represents a server version response.
64type Version struct {
65	// Version is the version number reported by the server or backend.
66	Version string
67	// Vendor is the vendor string reported by the server or backend.
68	Vendor string
69	// RawResponse is the raw response body returned by the server, useful if
70	// you need additional backend-specific information.
71	//
72	// For the format of this document, see
73	// http://docs.couchdb.org/en/2.0.0/api/server/common.html#get
74	RawResponse json.RawMessage
75}
76
77// Version returns version and vendor info about the backend.
78func (c *Client) Version(ctx context.Context) (*Version, error) {
79	ver, err := c.driverClient.Version(ctx)
80	if err != nil {
81		return nil, err
82	}
83	return &Version{
84		Version:     ver.Version,
85		Vendor:      ver.Vendor,
86		RawResponse: ver.RawResponse,
87	}, nil
88}
89
90// DB returns a handle to the requested database. Any options parameters
91// passed are merged, with later values taking precidence.
92func (c *Client) DB(ctx context.Context, dbName string, options ...Options) (*DB, error) {
93	opts, err := mergeOptions(options...)
94	if err != nil {
95		return nil, err
96	}
97	db, err := c.driverClient.DB(ctx, dbName, opts)
98	return &DB{
99		client:   c,
100		name:     dbName,
101		driverDB: db,
102	}, err
103}
104
105// AllDBs returns a list of all databases.
106func (c *Client) AllDBs(ctx context.Context, options ...Options) ([]string, error) {
107	opts, err := mergeOptions(options...)
108	if err != nil {
109		return nil, err
110	}
111	return c.driverClient.AllDBs(ctx, opts)
112}
113
114// DBExists returns true if the specified database exists.
115func (c *Client) DBExists(ctx context.Context, dbName string, options ...Options) (bool, error) {
116	opts, err := mergeOptions(options...)
117	if err != nil {
118		return false, err
119	}
120	return c.driverClient.DBExists(ctx, dbName, opts)
121}
122
123// CreateDB creates a DB of the requested name.
124func (c *Client) CreateDB(ctx context.Context, dbName string, options ...Options) (*DB, error) {
125	opts, err := mergeOptions(options...)
126	if err != nil {
127		return nil, err
128	}
129	if e := c.driverClient.CreateDB(ctx, dbName, opts); e != nil {
130		return nil, e
131	}
132	return c.DB(ctx, dbName, nil)
133}
134
135// DestroyDB deletes the requested DB.
136func (c *Client) DestroyDB(ctx context.Context, dbName string, options ...Options) error {
137	opts, err := mergeOptions(options...)
138	if err != nil {
139		return err
140	}
141	return c.driverClient.DestroyDB(ctx, dbName, opts)
142}
143
144// Authenticate authenticates the client with the passed authenticator, which
145// is driver-specific. If the driver does not understand the authenticator, an
146// error will be returned.
147func (c *Client) Authenticate(ctx context.Context, a interface{}) error {
148	if auth, ok := c.driverClient.(driver.Authenticator); ok {
149		return auth.Authenticate(ctx, a)
150	}
151	return errors.Status(StatusNotImplemented, "kivik: driver does not support authentication")
152}
153
154func missingArg(arg string) error {
155	return errors.Statusf(StatusBadRequest, "kivik: %s required", arg)
156}
157