|
1 |
| -const util = require('util'); |
2 |
| -const httpRequest = require('requestretry'); |
| 1 | +const { axiosReqWithRetryOnServerError } = require('./utils'); |
3 | 2 | const API_VERSION = 'v1';
|
4 |
| -const _ = require('lodash'); |
5 |
| -const { |
6 |
| - MAP: RETRY_STRATEGIES_MAP, |
7 |
| - STRATEGIES: { ON_HTTP_OR_NETWORK_ERROR } |
8 |
| -} = require('./retryStrategies'); |
9 | 3 |
|
10 |
| -module.exports = ApiResource; |
11 |
| - |
12 |
| - |
13 |
| -function ApiResource(client) { |
| 4 | +class ApiResource { |
| 5 | + constructor(client) { |
14 | 6 | this.client = client;
|
15 |
| -} |
16 |
| - |
17 |
| -var hasOwn = {}.hasOwnProperty; |
18 |
| - |
19 |
| -ApiResource.extend = function extend(sub) { |
20 |
| - var self = this; |
21 |
| - |
22 |
| - function Constructor() { |
23 |
| - self.apply(this, arguments); |
24 |
| - } |
25 |
| - |
26 |
| - Constructor.prototype = Object.create(self.prototype); |
27 |
| - |
28 |
| - for (var i in sub) { |
29 |
| - if (hasOwn.call(sub, i)) { |
30 |
| - Constructor.prototype[i] = sub[i]; |
31 |
| - } |
32 |
| - } |
33 |
| - for (i in self) { |
34 |
| - if (hasOwn.call(self, i)) { |
35 |
| - Constructor[i] = self[i]; |
36 |
| - } |
37 |
| - } |
| 7 | + } |
38 | 8 |
|
39 |
| - return Constructor; |
40 |
| -}; |
| 9 | + static extend(sub) { |
| 10 | + class SubResource extends this { } |
| 11 | + Object.assign(SubResource.prototype, sub); |
| 12 | + Object.assign(SubResource, this); |
| 13 | + return SubResource; |
| 14 | + } |
41 | 15 |
|
42 |
| -ApiResource.method = function method(spec) { |
| 16 | + static method(spec) { |
43 | 17 | const verb = spec.method || ApiResource.GET;
|
44 |
| - async function sendRequest(...args) { |
45 |
| - const defaultOptions = this.client.options || {}; |
46 |
| - var self = this; |
47 |
| - var api = self.client._api; |
48 |
| - var path = self.path || ''; |
49 |
| - |
50 |
| - function provideOptions() { |
51 |
| - if (!path) { |
52 |
| - throw new Error("A resource must define 'path'"); |
53 |
| - } |
54 |
| - |
55 |
| - if (spec.path) { |
56 |
| - path = path + spec.path; |
57 |
| - path = (spec.apiVersion || API_VERSION) + '/' + path; |
58 |
| - } |
59 |
| - path = interpolatePath(path, args); |
60 |
| - const [requestBody, options] = args; |
61 |
| - |
62 |
| - return createRequestOptions(verb, api, path, requestBody, _.defaults(options || {}, defaultOptions)); |
63 |
| - } |
64 |
| - |
65 |
| - |
66 |
| - const response = await httpRequest(provideOptions()); |
67 |
| - const { body, statusCode } = response; |
68 |
| - |
69 |
| - if (statusCode >= 400) { |
70 |
| - var message = typeof body === 'object' ? JSON.stringify(body) : body; |
71 |
| - |
72 |
| - var error = new Error(message); |
73 |
| - error.statusCode = statusCode; |
74 |
| - |
75 |
| - throw error; |
76 |
| - } |
77 |
| - |
78 |
| - if (spec.prepareResponse) { |
79 |
| - return spec.prepareResponse(response, body); |
80 |
| - } |
81 |
| - |
82 |
| - return body; |
83 |
| - } |
84 |
| - |
85 |
| - function interpolatePath(path, args) { |
86 |
| - var parameters = path.match(/{(\w+)}/g); |
87 |
| - |
88 |
| - if (!parameters) { |
89 |
| - return path; |
90 |
| - } |
91 |
| - |
92 |
| - for (var index in parameters) { |
93 |
| - var param = parameters[index]; |
94 |
| - |
95 |
| - var value = args.shift(); |
96 |
| - |
97 |
| - if (!value) { |
98 |
| - throw new Error(util.format( |
99 |
| - "Missing value for parameter '%s'. Please provide argument: %s", |
100 |
| - param, index)); |
101 |
| - } |
102 |
| - |
103 |
| - path = path.replace(param, value); |
| 18 | + return async function (...args) { |
| 19 | + const client = this.client; |
| 20 | + const defaultOptions = client.options || {}; |
| 21 | + const api = client._api; |
| 22 | + let path = this.path || ''; |
| 23 | + |
| 24 | + if (!path) throw new Error("A resource must define 'path'"); |
| 25 | + |
| 26 | + if (spec.path) { |
| 27 | + path += spec.path; |
| 28 | + path = `${spec.apiVersion || API_VERSION}/${path}`; |
| 29 | + } |
| 30 | + path = interpolatePath(path, args); |
| 31 | + |
| 32 | + const [requestBody, options] = args; |
| 33 | + const mergedOptions = { ...defaultOptions, ...(options || {}) }; |
| 34 | + |
| 35 | + const requestOptions = createAxiosRequestOptions(verb, api, path, requestBody, mergedOptions); |
| 36 | + |
| 37 | + let response |
| 38 | + try { |
| 39 | + response = await axiosReqWithRetryOnServerError(requestOptions); |
| 40 | + } catch (error) { |
| 41 | + if (error.response) { |
| 42 | + const { data, status } = error.response; |
| 43 | + const message = typeof data === 'object' ? JSON.stringify(data) : (data || ''); |
| 44 | + const e = new Error(message); |
| 45 | + e.statusCode = status; |
| 46 | + throw e; |
104 | 47 | }
|
| 48 | + error.req = { path: requestOptions.url }; |
| 49 | + throw error; |
| 50 | + } |
| 51 | + |
| 52 | + const { data, status } = response; |
| 53 | + |
| 54 | + if (status >= 400) { |
| 55 | + const message = typeof data === 'object' ? JSON.stringify(data) : (data || ''); |
| 56 | + const error = new Error(message); |
| 57 | + error.statusCode = status; |
| 58 | + throw error; |
| 59 | + } |
| 60 | + |
| 61 | + if (spec.prepareResponse) { |
| 62 | + return spec.prepareResponse(response, data); |
| 63 | + } |
| 64 | + return data; |
| 65 | + }; |
| 66 | + } |
| 67 | +} |
105 | 68 |
|
106 |
| - return path; |
| 69 | +function interpolatePath(path, args) { |
| 70 | + const parameters = path.match(/{(\w+)}/g); |
| 71 | + if (!parameters) return path; |
| 72 | + |
| 73 | + let newPath = path; |
| 74 | + for (const [index, param] of parameters.entries()) { |
| 75 | + const value = args.shift(); |
| 76 | + if (value === undefined) { |
| 77 | + throw new Error( |
| 78 | + `Missing value for parameter '${param}'. Please provide argument: ${index}` |
| 79 | + ); |
107 | 80 | }
|
| 81 | + newPath = newPath.replace(param, value); |
| 82 | + } |
| 83 | + return newPath; |
| 84 | +} |
108 | 85 |
|
109 |
| - function createRequestOptions( |
110 |
| - verb, |
111 |
| - api, |
112 |
| - path, |
113 |
| - body, |
114 |
| - { |
115 |
| - retryCount, |
116 |
| - retryDelay, |
117 |
| - retryStrategy |
118 |
| - } = {} |
119 |
| - ) { |
120 |
| - const defaultRetryStrategy = RETRY_STRATEGIES_MAP[ON_HTTP_OR_NETWORK_ERROR]; |
121 |
| - var options = { |
122 |
| - url: util.format("%s/%s", api.basePath, path), |
123 |
| - method: verb, |
124 |
| - json: true, |
125 |
| - forever: true, |
126 |
| - headers: { |
127 |
| - Connection: 'Keep-Alive' |
128 |
| - }, |
129 |
| - auth: { |
130 |
| - username: api.user, |
131 |
| - password: api.password |
132 |
| - }, |
133 |
| - maxAttempts: retryCount || 3, |
134 |
| - retryDelay: retryDelay || 100, |
135 |
| - retryStrategy: retryStrategy ? RETRY_STRATEGIES_MAP[retryStrategy] : defaultRetryStrategy, |
136 |
| - fullResponse: true |
137 |
| - }; |
138 |
| - |
139 |
| - if (body) { |
140 |
| - options.body = body; |
141 |
| - } |
142 |
| - |
143 |
| - return options; |
144 |
| - } |
| 86 | +function createAxiosRequestOptions(verb, api, path, body, options = {}) { |
| 87 | + return { |
| 88 | + url: `${api.basePath}/${path}`, |
| 89 | + method: verb, |
| 90 | + headers: { |
| 91 | + Connection: 'Keep-Alive', |
| 92 | + ...(options?.headers || {}) |
| 93 | + }, |
| 94 | + auth: { |
| 95 | + username: api.user, |
| 96 | + password: api.password |
| 97 | + }, |
| 98 | + ...(body ? { data: body } : {}) |
| 99 | + }; |
| 100 | +} |
145 | 101 |
|
146 |
| - return sendRequest; |
147 |
| -}; |
148 | 102 |
|
149 | 103 | ApiResource.GET = 'get';
|
150 | 104 | ApiResource.POST = 'post';
|
151 | 105 | ApiResource.PUT = 'put';
|
152 | 106 | ApiResource.DELETE = 'delete';
|
| 107 | + |
| 108 | +module.exports = ApiResource; |
0 commit comments