-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathclient.go
157 lines (134 loc) · 5.01 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
package directus
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log/slog"
"net/http"
"strings"
)
// Client keeps a connection to a Directus instance.
type Client struct {
Collections *ResourceClient[Collection, string]
CustomTranslations *ResourceClient[CustomTranslation, string]
Dashboards *ResourceClient[Dashboard, string]
Files *ResourceClient[File, string]
Flows *ResourceClient[Flow, string]
Folders *ResourceClient[Folder, string]
Operations *ResourceClient[Operation, string]
Panels *ResourceClient[Panel, string]
Permissions *ResourceClient[Permission, int64]
Policies *ResourceClient[Policy, string]
Presets *ResourceClient[Preset, int64]
Roles *ResourceClient[Role, string]
Users *ResourceClient[User, string]
Fields *clientFields
Relations *clientRelations
Server *clientServer
Settings *clientSettings
instance, token string
logger *slog.Logger
bodyLogger bool
}
// ClientOption configures a client when creating it.
type ClientOption func(client *Client)
// WithLogger sets a custom logger for the sent requests and responses received from the server.
func WithLogger(logger *slog.Logger) ClientOption {
return func(client *Client) {
client.logger = logger
}
}
// WithBodyLogger prints the request and response bodies to the logger.
func WithBodyLogger() ClientOption {
return func(client *Client) {
client.bodyLogger = true
}
}
// NewClient creates a new connection to the Directus instance using the static token to authenticate.
func NewClient(instance string, token string, opts ...ClientOption) *Client {
client := &Client{
instance: strings.TrimRight(instance, "/"),
token: token,
logger: slog.New(slog.Default().Handler()),
}
for _, opt := range opts {
opt(client)
}
client.Collections = NewResourceClient[Collection, string](client, "collections")
client.CustomTranslations = NewResourceClient[CustomTranslation, string](client, "translations")
client.Dashboards = NewResourceClient[Dashboard, string](client, "dashboards")
client.Files = NewResourceClient[File, string](client, "files")
client.Flows = NewResourceClient[Flow, string](client, "flows")
client.Folders = NewResourceClient[Folder, string](client, "folders")
client.Operations = NewResourceClient[Operation, string](client, "operations")
client.Panels = NewResourceClient[Panel, string](client, "panels")
client.Permissions = NewResourceClient[Permission, int64](client, "permissions")
client.Policies = NewResourceClient[Policy, string](client, "policies")
client.Presets = NewResourceClient[Preset, int64](client, "presets")
client.Roles = NewResourceClient(client, "roles", WithResourceFields[Role, string]("*", "policies.id", "policies.policy"))
client.Users = NewResourceClient[User, string](client, "users")
client.Fields = &clientFields{client: client}
client.Relations = &clientRelations{client: client}
client.Server = &clientServer{client: client}
client.Settings = &clientSettings{client: client}
return client
}
func (client *Client) urlf(format string, a ...interface{}) string {
return fmt.Sprintf("%s%s", client.instance, fmt.Sprintf(format, a...))
}
func (client *Client) sendRequest(req *http.Request, dest interface{}) error {
client.logger.Debug("directus request", "method", req.Method, "url", req.URL.String())
if client.bodyLogger && req.Body != nil {
body, err := io.ReadAll(req.Body)
if err != nil {
return fmt.Errorf("directus: cannot read request body: %v", err)
}
req.Body = io.NopCloser(bytes.NewReader(body))
client.logger.Debug(string(body))
}
if req.Body != nil {
req.Header.Set("Content-Type", "application/json")
}
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", client.token))
resp, err := http.DefaultClient.Do(req)
if err != nil {
return fmt.Errorf("directus: request failed: %w", err)
}
defer resp.Body.Close()
client.logger.Debug("directus reply", "status", resp.StatusCode)
body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("directus: cannot read response body: %v", err)
}
if client.bodyLogger {
client.logger.Debug(string(body))
}
switch {
case resp.StatusCode == http.StatusOK:
// Everything is fine.
case req.Method == http.MethodDelete && resp.StatusCode == http.StatusNoContent:
// Everything is fine.
case resp.StatusCode == http.StatusBadRequest:
var reply errorsReply
if err := json.Unmarshal(body, &reply); err == nil && len(reply.Errors) > 0 {
return reply.Errors[0]
}
case (req.Method == http.MethodPost || req.Method == http.MethodPatch) && resp.StatusCode == http.StatusNoContent:
return ErrEmpty
default:
return &unexpectedStatusError{
status: resp.StatusCode,
url: req.URL,
}
}
if dest != nil && len(body) > 0 {
if err := json.Unmarshal(body, dest); err != nil {
return fmt.Errorf("directus: cannot decode response: %v", err)
}
}
return nil
}
type errorsReply struct {
Errors []Error `json:"errors"`
}