Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENG-6200: Separate pure http handling into apiRequest #137

Merged
merged 1 commit into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"math/rand"
"net"
"net/http"
"net/url"
"os"
"strconv"
"strings"
Expand Down Expand Up @@ -70,6 +71,9 @@ type Client struct {

maxAttempts int
maxBackoff time.Duration

// lazily cached URLs
queryURL *url.URL
}

// NewDefaultClient initialize a [fauna.Client] with recommend default settings
Expand Down Expand Up @@ -176,6 +180,16 @@ func NewClient(secret string, timeouts Timeouts, configFns ...ClientConfigFn) *C
return client
}

func (c *Client) parseQueryURL() (url *url.URL, err error) {
if c.queryURL != nil {
url = c.queryURL
} else if url, err = url.Parse(c.url); err == nil {
url = url.JoinPath("query", "1")
c.queryURL = url
}
return
}

func (c *Client) doWithRetry(req *http.Request) (attempts int, r *http.Response, err error) {
req2 := req.Clone(req.Context())
body, rerr := io.ReadAll(req.Body)
Expand Down Expand Up @@ -247,17 +261,19 @@ func (c *Client) backoff(attempt int) (sleep time.Duration) {

// Query invoke fql optionally set multiple [QueryOptFn]
func (c *Client) Query(fql *Query, opts ...QueryOptFn) (*QuerySuccess, error) {
req := &fqlRequest{
Context: c.ctx,
Query: fql,
Headers: c.headers,
req := &queryRequest{
apiRequest: apiRequest{
Context: c.ctx,
Headers: c.headers,
},
Query: fql,
}

for _, queryOptionFn := range opts {
queryOptionFn(req)
}

return c.do(req)
return req.do(c)
}

// Paginate invoke fql with pagination optionally set multiple [QueryOptFn]
Expand Down
12 changes: 6 additions & 6 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,18 +93,18 @@ func URL(url string) ClientConfigFn {
}

// QueryOptFn function to set options on the [Client.Query]
type QueryOptFn func(req *fqlRequest)
type QueryOptFn func(req *queryRequest)

// QueryContext set the [context.Context] for a single [Client.Query]
func QueryContext(ctx context.Context) QueryOptFn {
return func(req *fqlRequest) {
return func(req *queryRequest) {
req.Context = ctx
}
}

// Tags set the tags header on a single [Client.Query]
func Tags(tags map[string]string) QueryOptFn {
return func(req *fqlRequest) {
return func(req *queryRequest) {
if val, exists := req.Headers[HeaderTags]; exists {
req.Headers[HeaderTags] = argsStringFromMap(tags, strings.Split(val, ",")...)
} else {
Expand All @@ -115,19 +115,19 @@ func Tags(tags map[string]string) QueryOptFn {

// Traceparent sets the header on a single [Client.Query]
func Traceparent(id string) QueryOptFn {
return func(req *fqlRequest) { req.Headers[HeaderTraceparent] = id }
return func(req *queryRequest) { req.Headers[HeaderTraceparent] = id }
}

// Timeout set the query timeout on a single [Client.Query]
func Timeout(dur time.Duration) QueryOptFn {
return func(req *fqlRequest) {
return func(req *queryRequest) {
req.Headers[HeaderQueryTimeoutMs] = fmt.Sprintf("%d", dur.Milliseconds())
}
}

// Typecheck sets the header on a single [Client.Query]
func Typecheck(enabled bool) QueryOptFn {
return func(req *fqlRequest) { req.Headers[HeaderTypecheck] = fmt.Sprintf("%v", enabled) }
return func(req *queryRequest) { req.Headers[HeaderTypecheck] = fmt.Sprintf("%v", enabled) }
}

func argsStringFromMap(input map[string]string, currentArgs ...string) string {
Expand Down
128 changes: 73 additions & 55 deletions request.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,42 @@ import (
"strings"
)

type fqlRequest struct {
Context context.Context
Headers map[string]string
Query any `fauna:"query"`
Arguments map[string]any `fauna:"arguments"`
type apiRequest struct {
Context context.Context
Headers map[string]string
}

func (apiReq *apiRequest) post(cli *Client, url *url.URL, bytesOut []byte) (attempts int, httpRes *http.Response, err error) {
var httpReq *http.Request
if httpReq, err = http.NewRequestWithContext(
apiReq.Context,
http.MethodPost,
url.String(),
bytes.NewReader(bytesOut),
); err != nil {
err = fmt.Errorf("failed to init request: %w", err)
return
}

httpReq.Header.Set(headerAuthorization, `Bearer `+cli.secret)
if lastTxnTs := cli.lastTxnTime.string(); lastTxnTs != "" {
httpReq.Header.Set(HeaderLastTxnTs, lastTxnTs)
}

for k, v := range apiReq.Headers {
httpReq.Header.Set(k, v)
}

if attempts, httpRes, err = cli.doWithRetry(httpReq); err != nil {
err = ErrNetwork(fmt.Errorf("network error: %w", err))
}
return
}

type queryRequest struct {
apiRequest
Query any
Arguments map[string]any
}

type queryResponse struct {
Expand Down Expand Up @@ -43,72 +74,59 @@ func (r *queryResponse) queryTags() map[string]string {

return ret
}

func (c *Client) do(request *fqlRequest) (*QuerySuccess, error) {
bytesOut, bytesErr := marshal(request)
if bytesErr != nil {
return nil, fmt.Errorf("marshal request failed: %w", bytesErr)
}

reqURL, urlErr := url.Parse(c.url)
if urlErr != nil {
return nil, urlErr
}

if path, err := url.JoinPath(reqURL.Path, "query", "1"); err != nil {
return nil, err
} else {
reqURL.Path = path
func (qReq *queryRequest) do(cli *Client) (qSus *QuerySuccess, err error) {
var bytesOut []byte
if bytesOut, err = marshal(qReq); err != nil {
err = fmt.Errorf("marshal request failed: %w", err)
return
}

req, reqErr := http.NewRequestWithContext(request.Context, http.MethodPost, reqURL.String(), bytes.NewReader(bytesOut))
if reqErr != nil {
return nil, fmt.Errorf("failed to init request: %w", reqErr)
var queryURL *url.URL
if queryURL, err = cli.parseQueryURL(); err != nil {
return
}

req.Header.Set(headerAuthorization, `Bearer `+c.secret)
if lastTxnTs := c.lastTxnTime.string(); lastTxnTs != "" {
req.Header.Set(HeaderLastTxnTs, lastTxnTs)
var (
attempts int
httpRes *http.Response
)
if attempts, httpRes, err = qReq.post(cli, queryURL, bytesOut); err != nil {
return
}

for k, v := range request.Headers {
req.Header.Set(k, v)
}
var (
qRes queryResponse
bytesIn []byte
)

attempts, r, doErr := c.doWithRetry(req)
if doErr != nil {
return nil, ErrNetwork(fmt.Errorf("network error: %w", doErr))
if bytesIn, err = io.ReadAll(httpRes.Body); err != nil {
err = fmt.Errorf("failed to read response body: %w", err)
return
}

var res queryResponse

bin, readErr := io.ReadAll(r.Body)
if readErr != nil {
return nil, fmt.Errorf("failed to read response body: %w", readErr)
if err = json.Unmarshal(bytesIn, &qRes); err != nil {
err = fmt.Errorf("failed to umarmshal response: %w", err)
return
}

if unmarshalErr := json.Unmarshal(bin, &res); unmarshalErr != nil {
return nil, fmt.Errorf("failed to umarmshal response: %w", unmarshalErr)
}
cli.lastTxnTime.sync(qRes.TxnTime)
qRes.Header = httpRes.Header

c.lastTxnTime.sync(res.TxnTime)
res.Header = r.Header

if serviceErr := getErrFauna(r.StatusCode, &res, attempts); serviceErr != nil {
return nil, serviceErr
if err = getErrFauna(httpRes.StatusCode, &qRes, attempts); err != nil {
return
}

data, decodeErr := decode(res.Data)
if decodeErr != nil {
return nil, fmt.Errorf("failed to decode data: %w", decodeErr)
var data any
if data, err = decode(qRes.Data); err != nil {
err = fmt.Errorf("failed to decode data: %w", err)
return
}

ret := &QuerySuccess{
QueryInfo: newQueryInfo(&res),
qSus = &QuerySuccess{
QueryInfo: newQueryInfo(&qRes),
Data: data,
StaticType: res.StaticType,
StaticType: qRes.StaticType,
}
ret.Stats.Attempts = attempts

return ret, nil
qSus.Stats.Attempts = attempts
return
}
2 changes: 1 addition & 1 deletion serializer.go
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,7 @@ func encode(v any, hint string) (any, error) {
case time.Time:
return encodeTime(vt, hint)

case fqlRequest:
case queryRequest:
query, err := encode(vt.Query, hint)
if err != nil {
return nil, err
Expand Down
Loading