-
Notifications
You must be signed in to change notification settings - Fork 3
/
client.go
223 lines (194 loc) · 6.31 KB
/
client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
// Package fauna HTTP client for fqlx
package fauna
import (
"context"
"crypto/tls"
_ "embed"
"encoding/json"
"fmt"
"log"
"net"
"net/http"
"os"
"strings"
"time"
"github.com/fauna/fauna-go/internal/fingerprinting"
"golang.org/x/net/http2"
)
// DriverVersion semantic version of the driver
//
//go:embed version
var DriverVersion string
const (
// EndpointProduction constant for Fauna Production endpoint
EndpointProduction = "https://db.fauna.com/query/1"
// EndpointPreview constant for Fauna Preview endpoint
EndpointPreview = "https://db.fauna-preview.com/query/1"
// EndpointLocal constant for local (Docker) endpoint
EndpointLocal = "http://localhost:8443/query/1"
// EnvFaunaEndpoint environment variable for Fauna Client HTTP endpoint
EnvFaunaEndpoint = "FAUNA_ENDPOINT"
// EnvFaunaSecret environment variable for Fauna Client authentication
EnvFaunaSecret = "FAUNA_SECRET"
// EnvFaunaTimeout environment variable for Fauna Client Read-Idle Timeout
EnvFaunaTimeout = "FAUNA_TIMEOUT"
// EnvFaunaTypeCheckEnabled environment variable for Fauna Client TypeChecking
EnvFaunaTypeCheckEnabled = "FAUNA_TYPE_CHECK_ENABLED"
// EnvFaunaTrackTxnTimeEnabled environment variable for Fauna Client tracks Transaction time
EnvFaunaTrackTxnTimeEnabled = "FAUNA_TRACK_TXN_TIME_ENABLED"
EnvFaunaVerboseDebugEnabled = "FAUNA_VERBOSE_DEBUG_ENABLED"
// DefaultHttpReadIdleTimeout Fauna Client default HTTP read idle timeout
DefaultHttpReadIdleTimeout = time.Minute * 3
// Reuest/response Headers
HeaderContentType = "Content-Type"
HeaderTxnTime = "X-Txn-Time"
// Request Headers
HeaderAuthorization = "Authorization"
HeaderLastSeenTxn = "X-Last-Seen-Txn"
HeaderLinearized = "X-Linearized"
HeaderMaxContentionRetries = "X-Max-Contention-Retries"
HeaderTimeoutMs = "X-Timeout-Ms"
HeaderTypeChecking = "X-Fauna-Type-Checking"
// Response Headers
HeaderTraceparent = "Traceparent"
HeaderByteReadOps = "X-Byte-Read-Ops"
HeaderByteWriteOps = "X-Byte-Write-Ops"
HeaderComputeOps = "X-Compute-Ops"
HeaderFaunaBuild = "X-Faunadb-Build"
HeaderQueryBytesIn = "X-Query-Bytes-In"
HeaderQueryBytesOut = "X-Query-Bytes-Out"
HeaderQueryTime = "X-Query-Time"
HeaderReadOps = "X-Read-Ops"
HeaderStorageBytesRead = "X-Storage-Bytes-Read"
HeaderStorageBytesWrite = "X-Storage-Bytes-Write"
HeaderTxnRetries = "X-Txn-Retries"
HeaderWriteOps = "X-Write-Ops"
)
// Client is the Fauna Client.
type Client struct {
url string
secret string
headers map[string]string
txnTimeEnabled bool
lastTxnTime int64
typeCheckingEnabled bool
verboseDebugEnabled bool
http *http.Client
log *log.Logger
ctx context.Context
// tags?
// traceParent?
}
// NewDefaultClient initialize a [fauna.Client] with recommend default settings
func NewDefaultClient() (*Client, error) {
var secret string
if val, found := os.LookupEnv(EnvFaunaSecret); !found {
return nil, fmt.Errorf("unable to load key from environment variable '%s'", EnvFaunaSecret)
} else {
secret = val
}
url, urlFound := os.LookupEnv(EnvFaunaEndpoint)
if !urlFound {
url = EndpointProduction
}
readIdleTimeout := DefaultHttpReadIdleTimeout
if val, found := os.LookupEnv(EnvFaunaTimeout); found {
timeoutFromEnv, err := time.ParseDuration(val)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "failed to parse timeout, using default\n")
} else {
if timeoutFromEnv.Seconds() <= 0 {
_, _ = fmt.Fprintf(os.Stderr, "timeout must be greater than 0, using default\n")
} else {
readIdleTimeout = timeoutFromEnv
}
}
}
return NewClient(
secret,
URL(url),
HTTPClient(&http.Client{
Transport: &http2.Transport{
DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
return net.Dial(network, addr)
},
AllowHTTP: url == EndpointLocal,
ReadIdleTimeout: readIdleTimeout,
PingTimeout: time.Second * 3,
WriteByteTimeout: time.Second * 5,
},
}),
Context(context.TODO()),
), nil
}
// NewClient initialize a new [fauna.Client] with custom settings
func NewClient(secret string, configFns ...ClientConfigFn) *Client {
// sensible default
typeCheckEnabled := true
if typeCheckEnabledVal, found := os.LookupEnv(EnvFaunaTypeCheckEnabled); found {
typeCheckEnabled = strings.ToLower(typeCheckEnabledVal) != "false"
}
txnTimeEnabled := true
if val, found := os.LookupEnv(EnvFaunaTrackTxnTimeEnabled); found {
txnTimeEnabled = strings.ToLower(val) != "false"
}
verboseDebugEnabled := false
if val, found := os.LookupEnv(EnvFaunaVerboseDebugEnabled); found {
verboseDebugEnabled = strings.ToLower(val) == "true"
}
client := &Client{
ctx: context.TODO(),
log: log.Default(),
secret: secret,
http: http.DefaultClient,
url: EndpointProduction,
headers: map[string]string{
HeaderAuthorization: fmt.Sprintf("Bearer %s", secret),
HeaderContentType: "application/json; charset=utf-8",
"X-Fauna-Driver": DriverVersion,
"X-Runtime-Environment-OS": fingerprinting.EnvironmentOS(),
"X-Runtime-Environment": fingerprinting.Environment(),
"X-Go-Version": fingerprinting.Version(),
},
typeCheckingEnabled: typeCheckEnabled,
txnTimeEnabled: txnTimeEnabled,
verboseDebugEnabled: verboseDebugEnabled,
}
// set options to override defaults
for _, configFn := range configFns {
configFn(client)
}
return client
}
// Query invoke fql with args and map to the provided obj, optionally set [QueryOptFn]
func (c *Client) Query(fql string, args QueryArgs, obj interface{}, opts ...QueryOptFn) (*Response, error) {
req := &fqlRequest{
Context: c.ctx,
Query: fql,
Arguments: args,
Headers: c.headers,
TxnTimeEnabled: c.txnTimeEnabled,
VerboseDebugEnabled: c.verboseDebugEnabled,
}
for _, o := range opts {
o(req)
}
res, err := c.do(req)
if err != nil {
return res, err
}
if obj != nil {
unmarshalErr := json.Unmarshal(res.Data, obj)
if unmarshalErr != nil {
return res, unmarshalErr
}
}
return res, nil
}
// GetLastTxnTime gets the freshest timestamp reported to this client.
func (c *Client) GetLastTxnTime() int64 {
if c.txnTimeEnabled {
return c.lastTxnTime
}
return 0
}