-
Notifications
You must be signed in to change notification settings - Fork 7
/
client.go
167 lines (145 loc) · 5.16 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
package ankiconnect
import (
"net/http"
"github.com/go-resty/resty/v2"
"github.com/privatesquare/bkst-go-utils/utils/errors"
"github.com/privatesquare/bkst-go-utils/utils/httputils"
"github.com/privatesquare/bkst-go-utils/utils/logger"
)
const (
ankiConnectUrl = "http://localhost:8765"
ankiConnectVersion = 6
ankiConnectPingErrMsg = "AnkiConnect api is not accessible. Check if anki is running and the ankiconnect add-on is installed correctly"
)
type (
// Client represents the anki connect api client.
Client struct {
Url string
Version int
httpClient *resty.Client
// supported interfaces
Decks DecksManager
Notes NotesManager
Sync SyncManager
Cards CardsManager
Media MediaManager
Models ModelsManager
}
// RequestPayload represents the request payload for anki connect api.
// [P any] represents a generic type that can accept any type for the Params field.
RequestPayload[P any] struct {
Action string `json:"action,omitempty"`
Version int `json:"version,omitempty"`
Params *P `json:"params,omitempty"`
}
// ParamsDefault represent the default parameters for a action to be executed by the anki connect api.
ParamsDefault struct{}
// Result is the result returned by anki connect api.
// The Result filed will return different typed values depending on the action hence it is defined as a generic.
// [T any] represents a generic type that can accept any type for the Result field.
Result[T any] struct {
Result T `json:"result,omitempty"`
Error string `json:"error,omitempty"`
}
)
// NewClient returns a instance of Client with the default settings.
func NewClient() *Client {
c := &Client{
Url: ankiConnectUrl,
Version: ankiConnectVersion,
httpClient: resty.New(),
}
c.Decks = &decksManager{Client: c}
c.Notes = ¬esManager{Client: c}
c.Sync = &syncManager{Client: c}
c.Cards = &cardsManager{Client: c}
c.Media = &mediaManager{Client: c}
c.Models = &modelsManager{Client: c}
return c
}
// SetHTTPClient can be used set a custom httpClient.
func (c *Client) SetHTTPClient(httpClient *resty.Client) *Client {
c.httpClient = httpClient
return c
}
// SetURL can be used to set a custom url for the ankiconnect api.
func (c *Client) SetURL(url string) *Client {
c.Url = url
return c
}
// SetVersion can be used to set a custom version for the ankiconnect api.
func (c *Client) SetVersion(version int) *Client {
c.Version = version
return c
}
// SetDecksManager can be used to set a custom DecksManager interface.
// This function is added for testing the DecksManager interface.
func (c *Client) SetDecksManager(dm DecksManager) *Client {
c.Decks = dm
return c
}
// SetNotesManager can be used to set a custom NotesManager interface.
// This function is added for testing the NotesManager interface.
func (c *Client) SetNotesManager(nm NotesManager) *Client {
c.Notes = nm
return c
}
// SetSyncManager can be used to set a custom SyncManager interface.
// This function is added for testing the SyncManager interface.
func (c *Client) SetSyncManager(sm SyncManager) *Client {
c.Sync = sm
return c
}
// request formats and returns a base http request that can be extended later.
// as part of this the baseUrl and the default headers are set in the http client.
func (c *Client) request() *resty.Request {
c.httpClient.SetBaseURL(c.Url)
c.httpClient.SetDisableWarn(true)
return c.httpClient.R().SetHeader(httputils.ContentTypeHeaderKey, httputils.ApplicationJsonMIMEType).
SetHeader(httputils.AcceptHeaderKey, httputils.ApplicationJsonMIMEType)
}
// Ping checks if the anki connect api is online and healthy.
// If there is no response from the anki connect api a error will be returned.
func (c *Client) Ping() *errors.RestErr {
resp, err := c.request().Get("")
logger.RestyDebugLogs(resp)
if err != nil {
return &errors.RestErr{
Message: ankiConnectPingErrMsg,
StatusCode: http.StatusServiceUnavailable,
Error: http.StatusText(http.StatusServiceUnavailable),
}
}
logger.Info("ping: " + string(resp.Body()))
return nil
}
// post makes a POST request to the anki connect API with a payload of the action to be executed.
// The Params field in the RequestPayload struct and the Result filed of the Result struct can be different
// based on the action that needs to be executed. Hence post is defined with some generic types.
// R any - represents the type of the result that will be returned by the API.
// P any - represents the type of the params that will be sent along with the action to be executed to the API.
func post[R any, P any](c *Client, action string, params *P) (*R, *errors.RestErr) {
payload := RequestPayload[P]{
Action: action,
Version: c.Version,
Params: params,
}
result := new(Result[R])
resp, err := c.request().SetBody(payload).SetResult(result).Post("")
logger.RestyDebugLogs(resp)
if err != nil {
return nil, &errors.RestErr{
Message: http.StatusText(http.StatusInternalServerError),
StatusCode: http.StatusInternalServerError,
Error: err.Error(),
}
}
if result.Error != "" {
return nil, &errors.RestErr{
Message: result.Error,
StatusCode: http.StatusBadRequest,
Error: result.Error,
}
}
return &result.Result, nil
}