-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstorefront.go
131 lines (109 loc) · 3.15 KB
/
storefront.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
package storefront
import (
"encoding/json"
"errors"
"io"
"io/ioutil"
"net/http"
"net/url"
"path"
"strings"
"time"
)
// APIVersion is the Shopify Storefront API version. This value should match
// that of the generated types.
var APIVersion = "2022-01"
// Set describes the full result set from a query. It encloses multiple result
// sets within an enclosed data property.
type Set struct {
Data QueryRoot `json:"data"`
}
// Connection describes a type for paginating through multiple objects. It
// essentially encompasses a list of edges, whose edges contain a node, which
// thereby contains the object data.
type Connection[T any] struct {
// Edges is a list edges.
Edges []Edge[T] `json:"edges,omitempty"`
// PageInfo is a set of data to aid in pagination.
PageInfo map[string]interface{} `json:"pageInfo,omitempty"`
}
// Edge describes a node/cursor pair.
type Edge[T any] struct {
// Cursor is a cursor for use in pagination.
Cursor string `json:"cursor,omitempty"`
// Node is the item at the end of edge.
Node T `json:"node,omitempty"`
}
// Client describes a wrapper object of an HTTP client for the Storefront API
// with credentials.
type Client struct {
endpoint string
accessToken string
HTTPClient *http.Client
}
// NewClient constructs a new instance of a Storefront client given the store
// domain and access token. Optionally, provide a single *http.Client to use
// rather than a defaulted http.Client.
//
// Note that the default HTTP client sets a 10-second timeout.
func NewClient(domain, accessToken string, httpClient ...*http.Client) *Client {
endpoint := url.URL{
Scheme: "https",
Host: domain,
Path: path.Join("api", APIVersion, "graphql.json"),
}
if len(httpClient) != 0 {
return &Client{
endpoint: endpoint.String(),
accessToken: accessToken,
HTTPClient: httpClient[0],
}
}
httpC := &http.Client{
Timeout: 10 * time.Second,
}
return &Client{
endpoint: endpoint.String(),
accessToken: accessToken,
HTTPClient: httpC,
}
}
// Query executes a query against the Storefront API endpoint.
func (c *Client) Query(q string, out interface{}) error {
reader := strings.NewReader(q)
req, err := http.NewRequest(http.MethodPost, c.endpoint, reader)
if err != nil {
return err
}
req.Header.Add("Accept", "application/json")
req.Header.Add("Content-Type", "application/graphql")
req.Header.Add("X-Shopify-Storefront-Access-Token", c.accessToken)
res, err := c.HTTPClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
bs, err := io.ReadAll(res.Body)
if err != nil {
return err
}
if err := json.Unmarshal(bs, &out); err != nil {
return err
}
return nil
}
// ErrEmptyFilename indicates that an empty filename was supplied to LoadQuery.
var ErrEmptyFilename = errors.New("an empty filename was specified")
// LoadQuery opens the specified file and returns a string value of the query.
// It's the caller's responsibility to verify the file contains a valid
// GraphQL query.
func LoadQuery(filename string) (string, error) {
if filename == "" {
return "", ErrEmptyFilename
}
bs, err := ioutil.ReadFile(filename)
if err != nil {
return "", err
}
return string(bs), nil
}