1package cloudflare
2
3import (
4	"encoding/json"
5	"net/http"
6	"time"
7
8	"github.com/pkg/errors"
9)
10
11// WorkerRequestParams provides parameters for worker requests for both enterprise and standard requests
12type WorkerRequestParams struct {
13	ZoneID     string
14	ScriptName string
15}
16
17// WorkerRoute aka filters are patterns used to enable or disable workers that match requests.
18//
19// API reference: https://api.cloudflare.com/#worker-filters-properties
20type WorkerRoute struct {
21	ID      string `json:"id,omitempty"`
22	Pattern string `json:"pattern"`
23	Enabled bool   `json:"enabled"`
24	Script  string `json:"script,omitempty"`
25}
26
27// WorkerRoutesResponse embeds Response struct and slice of WorkerRoutes
28type WorkerRoutesResponse struct {
29	Response
30	Routes []WorkerRoute `json:"result"`
31}
32
33// WorkerRouteResponse embeds Response struct and a single WorkerRoute
34type WorkerRouteResponse struct {
35	Response
36	WorkerRoute `json:"result"`
37}
38
39// WorkerScript Cloudflare Worker struct with metadata
40type WorkerScript struct {
41	WorkerMetaData
42	Script string `json:"script"`
43}
44
45// WorkerMetaData contains worker script information such as size, creation & modification dates
46type WorkerMetaData struct {
47	ID         string    `json:"id,omitempty"`
48	ETAG       string    `json:"etag,omitempty"`
49	Size       int       `json:"size,omitempty"`
50	CreatedOn  time.Time `json:"created_on,omitempty"`
51	ModifiedOn time.Time `json:"modified_on,omitempty"`
52}
53
54// WorkerListResponse wrapper struct for API response to worker script list API call
55type WorkerListResponse struct {
56	Response
57	WorkerList []WorkerMetaData `json:"result"`
58}
59
60// WorkerScriptResponse wrapper struct for API response to worker script calls
61type WorkerScriptResponse struct {
62	Response
63	WorkerScript `json:"result"`
64}
65
66// DeleteWorker deletes worker for a zone.
67//
68// API reference: https://api.cloudflare.com/#worker-script-delete-worker
69func (api *API) DeleteWorker(requestParams *WorkerRequestParams) (WorkerScriptResponse, error) {
70	// if ScriptName is provided we will treat as org request
71	if requestParams.ScriptName != "" {
72		return api.deleteWorkerWithName(requestParams.ScriptName)
73	}
74	uri := "/zones/" + requestParams.ZoneID + "/workers/script"
75	res, err := api.makeRequest("DELETE", uri, nil)
76	var r WorkerScriptResponse
77	if err != nil {
78		return r, errors.Wrap(err, errMakeRequestError)
79	}
80	err = json.Unmarshal(res, &r)
81	if err != nil {
82		return r, errors.Wrap(err, errUnmarshalError)
83	}
84	return r, nil
85}
86
87// DeleteWorkerWithName deletes worker for a zone.
88// This is an enterprise only feature https://developers.cloudflare.com/workers/api/config-api-for-enterprise
89// account must be specified as api option https://godoc.org/github.com/cloudflare/cloudflare-go#UsingAccount
90//
91// API reference: https://api.cloudflare.com/#worker-script-delete-worker
92func (api *API) deleteWorkerWithName(scriptName string) (WorkerScriptResponse, error) {
93	if api.AccountID == "" {
94		return WorkerScriptResponse{}, errors.New("account ID required for enterprise only request")
95	}
96	uri := "/accounts/" + api.AccountID + "/workers/scripts/" + scriptName
97	res, err := api.makeRequest("DELETE", uri, nil)
98	var r WorkerScriptResponse
99	if err != nil {
100		return r, errors.Wrap(err, errMakeRequestError)
101	}
102	err = json.Unmarshal(res, &r)
103	if err != nil {
104		return r, errors.Wrap(err, errUnmarshalError)
105	}
106	return r, nil
107}
108
109// DownloadWorker fetch raw script content for your worker returns []byte containing worker code js
110//
111// API reference: https://api.cloudflare.com/#worker-script-download-worker
112func (api *API) DownloadWorker(requestParams *WorkerRequestParams) (WorkerScriptResponse, error) {
113	if requestParams.ScriptName != "" {
114		return api.downloadWorkerWithName(requestParams.ScriptName)
115	}
116	uri := "/zones/" + requestParams.ZoneID + "/workers/script"
117	res, err := api.makeRequest("GET", uri, nil)
118	var r WorkerScriptResponse
119	if err != nil {
120		return r, errors.Wrap(err, errMakeRequestError)
121	}
122	r.Script = string(res)
123	r.Success = true
124	return r, nil
125}
126
127// DownloadWorkerWithName fetch raw script content for your worker returns string containing worker code js
128// This is an enterprise only feature https://developers.cloudflare.com/workers/api/config-api-for-enterprise/
129//
130// API reference: https://api.cloudflare.com/#worker-script-download-worker
131func (api *API) downloadWorkerWithName(scriptName string) (WorkerScriptResponse, error) {
132	if api.AccountID == "" {
133		return WorkerScriptResponse{}, errors.New("account ID required for enterprise only request")
134	}
135	uri := "/accounts/" + api.AccountID + "/workers/scripts/" + scriptName
136	res, err := api.makeRequest("GET", uri, nil)
137	var r WorkerScriptResponse
138	if err != nil {
139		return r, errors.Wrap(err, errMakeRequestError)
140	}
141	r.Script = string(res)
142	r.Success = true
143	return r, nil
144}
145
146// ListWorkerScripts returns list of worker scripts for given account.
147//
148// This is an enterprise only feature https://developers.cloudflare.com/workers/api/config-api-for-enterprise
149//
150// API reference: https://developers.cloudflare.com/workers/api/config-api-for-enterprise/
151func (api *API) ListWorkerScripts() (WorkerListResponse, error) {
152	if api.AccountID == "" {
153		return WorkerListResponse{}, errors.New("account ID required for enterprise only request")
154	}
155	uri := "/accounts/" + api.AccountID + "/workers/scripts"
156	res, err := api.makeRequest("GET", uri, nil)
157	if err != nil {
158		return WorkerListResponse{}, errors.Wrap(err, errMakeRequestError)
159	}
160	var r WorkerListResponse
161	err = json.Unmarshal(res, &r)
162	if err != nil {
163		return WorkerListResponse{}, errors.Wrap(err, errUnmarshalError)
164	}
165	return r, nil
166}
167
168// UploadWorker push raw script content for your worker.
169//
170// API reference: https://api.cloudflare.com/#worker-script-upload-worker
171func (api *API) UploadWorker(requestParams *WorkerRequestParams, data string) (WorkerScriptResponse, error) {
172	if requestParams.ScriptName != "" {
173		return api.uploadWorkerWithName(requestParams.ScriptName, data)
174	}
175	uri := "/zones/" + requestParams.ZoneID + "/workers/script"
176	headers := make(http.Header)
177	headers.Set("Content-Type", "application/javascript")
178	res, err := api.makeRequestWithHeaders("PUT", uri, []byte(data), headers)
179	var r WorkerScriptResponse
180	if err != nil {
181		return r, errors.Wrap(err, errMakeRequestError)
182	}
183	err = json.Unmarshal(res, &r)
184	if err != nil {
185		return r, errors.Wrap(err, errUnmarshalError)
186	}
187	return r, nil
188}
189
190// UploadWorkerWithName push raw script content for your worker.
191//
192// This is an enterprise only feature https://developers.cloudflare.com/workers/api/config-api-for-enterprise/
193//
194// API reference: https://api.cloudflare.com/#worker-script-upload-worker
195func (api *API) uploadWorkerWithName(scriptName string, data string) (WorkerScriptResponse, error) {
196	if api.AccountID == "" {
197		return WorkerScriptResponse{}, errors.New("account ID required for enterprise only request")
198	}
199	uri := "/accounts/" + api.AccountID + "/workers/scripts/" + scriptName
200	headers := make(http.Header)
201	headers.Set("Content-Type", "application/javascript")
202	res, err := api.makeRequestWithHeaders("PUT", uri, []byte(data), headers)
203	var r WorkerScriptResponse
204	if err != nil {
205		return r, errors.Wrap(err, errMakeRequestError)
206	}
207	err = json.Unmarshal(res, &r)
208	if err != nil {
209		return r, errors.Wrap(err, errUnmarshalError)
210	}
211	return r, nil
212}
213
214// CreateWorkerRoute creates worker route for a zone
215//
216// API reference: https://api.cloudflare.com/#worker-filters-create-filter
217func (api *API) CreateWorkerRoute(zoneID string, route WorkerRoute) (WorkerRouteResponse, error) {
218	// Check whether a script name is defined in order to determine whether
219	// to use the single-script or multi-script endpoint.
220	pathComponent := "filters"
221	if route.Script != "" {
222		if api.AccountID == "" {
223			return WorkerRouteResponse{}, errors.New("account ID required for enterprise only request")
224		}
225		pathComponent = "routes"
226	}
227
228	uri := "/zones/" + zoneID + "/workers/" + pathComponent
229	res, err := api.makeRequest("POST", uri, route)
230	if err != nil {
231		return WorkerRouteResponse{}, errors.Wrap(err, errMakeRequestError)
232	}
233	var r WorkerRouteResponse
234	err = json.Unmarshal(res, &r)
235	if err != nil {
236		return WorkerRouteResponse{}, errors.Wrap(err, errUnmarshalError)
237	}
238	return r, nil
239}
240
241// DeleteWorkerRoute deletes worker route for a zone
242//
243// API reference: https://api.cloudflare.com/#worker-filters-delete-filter
244func (api *API) DeleteWorkerRoute(zoneID string, routeID string) (WorkerRouteResponse, error) {
245	// For deleting a route, it doesn't matter whether we use the
246	// single-script or multi-script endpoint
247	uri := "/zones/" + zoneID + "/workers/filters/" + routeID
248	res, err := api.makeRequest("DELETE", uri, nil)
249	if err != nil {
250		return WorkerRouteResponse{}, errors.Wrap(err, errMakeRequestError)
251	}
252	var r WorkerRouteResponse
253	err = json.Unmarshal(res, &r)
254	if err != nil {
255		return WorkerRouteResponse{}, errors.Wrap(err, errUnmarshalError)
256	}
257	return r, nil
258}
259
260// ListWorkerRoutes returns list of worker routes
261//
262// API reference: https://api.cloudflare.com/#worker-filters-list-filters
263func (api *API) ListWorkerRoutes(zoneID string) (WorkerRoutesResponse, error) {
264	pathComponent := "filters"
265	if api.AccountID != "" {
266		pathComponent = "routes"
267	}
268	uri := "/zones/" + zoneID + "/workers/" + pathComponent
269	res, err := api.makeRequest("GET", uri, nil)
270	if err != nil {
271		return WorkerRoutesResponse{}, errors.Wrap(err, errMakeRequestError)
272	}
273	var r WorkerRoutesResponse
274	err = json.Unmarshal(res, &r)
275	if err != nil {
276		return WorkerRoutesResponse{}, errors.Wrap(err, errUnmarshalError)
277	}
278	for i := range r.Routes {
279		route := &r.Routes[i]
280		// The Enabled flag will not be set in the multi-script API response
281		// so we manually set it to true if the script name is not empty
282		// in case any multi-script customers rely on the Enabled field
283		if route.Script != "" {
284			route.Enabled = true
285		}
286	}
287	return r, nil
288}
289
290// UpdateWorkerRoute updates worker route for a zone.
291//
292// API reference: https://api.cloudflare.com/#worker-filters-update-filter
293func (api *API) UpdateWorkerRoute(zoneID string, routeID string, route WorkerRoute) (WorkerRouteResponse, error) {
294	// Check whether a script name is defined in order to determine whether
295	// to use the single-script or multi-script endpoint.
296	pathComponent := "filters"
297	if route.Script != "" {
298		if api.AccountID == "" {
299			return WorkerRouteResponse{}, errors.New("account ID required for enterprise only request")
300		}
301		pathComponent = "routes"
302	}
303	uri := "/zones/" + zoneID + "/workers/" + pathComponent + "/" + routeID
304	res, err := api.makeRequest("PUT", uri, route)
305	if err != nil {
306		return WorkerRouteResponse{}, errors.Wrap(err, errMakeRequestError)
307	}
308	var r WorkerRouteResponse
309	err = json.Unmarshal(res, &r)
310	if err != nil {
311		return WorkerRouteResponse{}, errors.Wrap(err, errUnmarshalError)
312	}
313	return r, nil
314}
315