@forge/events 1.1.0-next.0 → 2.0.0-next.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  InternalServerError,
3
3
  InvalidQueueNameError,
4
+ InvalidPayloadError,
4
5
  NoEventsToPushError,
5
6
  PartialSuccessError,
6
7
  PayloadTooBigError,
@@ -30,21 +31,57 @@ describe('Queue methods', () => {
30
31
  });
31
32
  });
32
33
 
34
+ const PAYLOAD = { body: { page: 1 } } as const;
35
+
33
36
  describe('push', () => {
34
- it('should call the queue/publish endpoint', async () => {
37
+ it.each([
38
+ ['single payload', PAYLOAD, [PAYLOAD]],
39
+ ['payload array', [PAYLOAD, PAYLOAD], [PAYLOAD, PAYLOAD]]
40
+ ])('should call the queue/publish endpoint when given %s', async (_, payload, expectedPayload) => {
41
+ const apiClientMock = getMockFetchMethod();
42
+ const queue = getQueue('name', apiClientMock);
43
+ await queue.push(payload);
44
+ verifyApiClientCalledPushPathWith(apiClientMock, expectedPayload, 'name');
45
+ });
46
+
47
+ it.each([
48
+ ['number', 1],
49
+ ['string', 'test'],
50
+ ['boolean', true],
51
+ ['array', [1, 2, 3]],
52
+ ['null', null],
53
+ ['undefined', undefined],
54
+ ['array with non-object', [1]],
55
+ ['array with an array', [[2, 3]]]
56
+ ])('rejects non-object (%s) event', async (_, payload) => {
57
+ const apiClientMock = getMockFetchMethod();
58
+ const queue = getQueue('name', apiClientMock);
59
+ // @ts-expect-error Testing invalid payload type on purpose
60
+ await expect(queue.push(payload)).rejects.toThrow(new InvalidPayloadError(`Event must be an object.`));
61
+ expect(apiClientMock).toHaveBeenCalledTimes(0);
62
+ });
63
+
64
+ it.each([
65
+ ['number', 1],
66
+ ['string', 'test'],
67
+ ['boolean', true],
68
+ ['array', [1, 2, 3]],
69
+ ['null', null],
70
+ ['undefined', undefined],
71
+ ['array with non-object', [1]],
72
+ ['array with an array', [[2, 3]]]
73
+ ])('rejects non-object (%s) event body', async (_, body) => {
35
74
  const apiClientMock = getMockFetchMethod();
36
75
  const queue = getQueue('name', apiClientMock);
37
- const payload = {
38
- page: 1
39
- };
40
- await queue.push([payload]);
41
- verifyApiClientCalledPushPathWith(apiClientMock, [payload], 'name');
76
+ // @ts-expect-error Testing invalid payload type on purpose
77
+ await expect(queue.push({ body })).rejects.toThrow(new InvalidPayloadError(`Event body must be an object.`));
78
+ expect(apiClientMock).toHaveBeenCalledTimes(0);
42
79
  });
43
80
 
44
81
  it('should throw InvalidPushSettingsError for delay', async () => {
45
82
  const apiClientMock = getMockFetchMethod();
46
83
  const queue = getQueue('name', apiClientMock);
47
- await expect(queue.push(1, { delayInSeconds: 901 })).rejects.toThrow(
84
+ await expect(queue.push({ ...PAYLOAD, delayInSeconds: 901 })).rejects.toThrow(
48
85
  new InvalidPushSettingsError(`The delayInSeconds setting must be between 0 and 900.`)
49
86
  );
50
87
  expect(apiClientMock).toHaveBeenCalledTimes(0);
@@ -60,7 +97,7 @@ describe('Queue methods', () => {
60
97
  it('should throw TooManyEventsError', async () => {
61
98
  const apiClientMock = getMockFetchMethod();
62
99
  const queue = getQueue('name', apiClientMock);
63
- await expect(queue.push([...Array(51).keys()])).rejects.toThrow(
100
+ await expect(queue.push(Array(51).fill(PAYLOAD))).rejects.toThrow(
64
101
  new TooManyEventsError(`This push contains more than the 50 events allowed.`)
65
102
  );
66
103
  expect(apiClientMock).toHaveBeenCalledTimes(0);
@@ -69,7 +106,7 @@ describe('Queue methods', () => {
69
106
  it('should throw PayloadTooBigError', async () => {
70
107
  const apiClientMock = getMockFetchMethod();
71
108
  const queue = getQueue('name', apiClientMock);
72
- await expect(queue.push('x'.repeat(201 * 1024))).rejects.toThrow(
109
+ await expect(queue.push({ body: { content: 'x'.repeat(201 * 1024) } })).rejects.toThrow(
73
110
  new PayloadTooBigError(`The maximum payload size is 200KB.`)
74
111
  );
75
112
  expect(apiClientMock).toHaveBeenCalledTimes(0);
@@ -78,19 +115,17 @@ describe('Queue methods', () => {
78
115
  it('should throw RateLimitError', async () => {
79
116
  const apiClientMock = getMockFetchMethod({}, 429);
80
117
  const queue = getQueue('name', apiClientMock);
81
- const payload = [...Array(10).keys()];
82
- await expect(queue.push(payload)).rejects.toThrow(new RateLimitError(`Too many requests.`));
83
- verifyApiClientCalledPushPathWith(apiClientMock, payload, 'name');
118
+ await expect(queue.push(PAYLOAD)).rejects.toThrow(new RateLimitError(`Too many requests.`));
119
+ verifyApiClientCalledPushPathWith(apiClientMock, [PAYLOAD], 'name');
84
120
  });
85
121
 
86
122
  it('should throw InvocationLimitReachedError', async () => {
87
123
  const apiClientMock = getMockFetchMethod({}, 405);
88
124
  const queue = getQueue('name', apiClientMock);
89
- const payload = [...Array(5).keys()];
90
- await expect(queue.push(payload)).rejects.toThrow(
125
+ await expect(queue.push(PAYLOAD)).rejects.toThrow(
91
126
  new InvocationLimitReachedError(`The limit on cyclic invocation has been reached.`)
92
127
  );
93
- verifyApiClientCalledPushPathWith(apiClientMock, payload, 'name');
128
+ verifyApiClientCalledPushPathWith(apiClientMock, [PAYLOAD], 'name');
94
129
  });
95
130
 
96
131
  it('should throw PartialSuccessError when there are failed events', async () => {
@@ -112,27 +147,37 @@ describe('Queue methods', () => {
112
147
  const queue = getQueue('name', apiClientMock);
113
148
  const payload = [
114
149
  {
115
- content: 'payload-1'
150
+ body: {
151
+ content: 'payload-1'
152
+ }
116
153
  },
117
154
  {
118
- content: 'payload-2'
155
+ body: {
156
+ content: 'payload-2'
157
+ }
119
158
  },
120
159
  {
121
- content: 'payload-3'
160
+ body: {
161
+ content: 'payload-3'
162
+ }
122
163
  }
123
164
  ];
124
165
  await expect(queue.push(payload)).rejects.toThrow(
125
- new PartialSuccessError(`Failed to process 2 event(s).`, [
166
+ new PartialSuccessError(`Failed to process 2 event(s).`, { jobId: 'some-job-id' }, [
126
167
  {
127
168
  errorMessage: 'failed-1',
128
- payload: {
129
- content: 'payload-1'
169
+ event: {
170
+ body: {
171
+ content: 'payload-1'
172
+ }
130
173
  }
131
174
  },
132
175
  {
133
176
  errorMessage: 'failed-3',
134
- payload: {
135
- content: 'payload-3'
177
+ event: {
178
+ body: {
179
+ content: 'payload-3'
180
+ }
136
181
  }
137
182
  }
138
183
  ])
@@ -150,17 +195,23 @@ describe('Queue methods', () => {
150
195
  const queue = getQueue('name', apiClientMock);
151
196
  const payload = [
152
197
  {
153
- content: 'payload-1'
198
+ body: {
199
+ content: 'payload-1'
200
+ }
154
201
  },
155
202
  {
156
- content: 'payload-2'
203
+ body: {
204
+ content: 'payload-2'
205
+ }
157
206
  },
158
207
  {
159
- content: 'payload-3'
208
+ body: {
209
+ content: 'payload-3'
210
+ }
160
211
  }
161
212
  ];
162
213
  await expect(queue.push(payload)).rejects.toThrow(
163
- new PartialSuccessError(`Failed to create stats for job name#12345`, [])
214
+ new PartialSuccessError(`Failed to create stats for job name#12345`, { jobId: 'some-job-id' }, [])
164
215
  );
165
216
  verifyApiClientCalledPushPathWith(apiClientMock, payload, 'name');
166
217
  });
@@ -181,21 +232,31 @@ describe('Queue methods', () => {
181
232
  const queue = getQueue('name', apiClientMock);
182
233
  const payload = [
183
234
  {
184
- content: 'payload-1'
235
+ body: {
236
+ content: 'payload-1'
237
+ }
185
238
  },
186
239
  {
187
- content: 'payload-2'
240
+ body: {
241
+ content: 'payload-2'
242
+ }
188
243
  }
189
244
  ];
190
245
  await expect(queue.push(payload)).rejects.toThrow(
191
- new PartialSuccessError(`Failed to process 1 event(s). Failed to create stats for job name#12345`, [
192
- {
193
- errorMessage: 'failed-1',
194
- payload: {
195
- content: 'payload-1'
246
+ new PartialSuccessError(
247
+ `Failed to process 1 event(s). Failed to create stats for job name#12345`,
248
+ { jobId: 'some-job-id' },
249
+ [
250
+ {
251
+ errorMessage: 'failed-1',
252
+ event: {
253
+ body: {
254
+ content: 'payload-1'
255
+ }
256
+ }
196
257
  }
197
- }
198
- ])
258
+ ]
259
+ )
199
260
  );
200
261
  verifyApiClientCalledPushPathWith(apiClientMock, payload, 'name');
201
262
  });
@@ -210,28 +271,26 @@ describe('Queue methods', () => {
210
271
  500
211
272
  );
212
273
  const queue = getQueue('name', apiClientMock);
213
- const payload = [...Array(9).keys()];
214
- await expect(queue.push(payload)).rejects.toThrow(
274
+ await expect(queue.push(PAYLOAD)).rejects.toThrow(
215
275
  new InternalServerError(
216
276
  `500 Status Text: AWS SQS timed out`,
217
277
  500,
218
278
  'The request processing has failed because of an unknown error, exception or failure'
219
279
  )
220
280
  );
221
- verifyApiClientCalledPushPathWith(apiClientMock, payload, 'name');
281
+ verifyApiClientCalledPushPathWith(apiClientMock, [PAYLOAD], 'name');
222
282
  });
223
283
 
224
284
  it('should throw InternalServerError for error response without response body', async () => {
225
285
  const apiClientMock = getApiClientMockWithoutResponseBody(504, 'Gateway Timeout');
226
286
  const queue = getQueue('name', apiClientMock);
227
- const payload = [1];
228
- await expect(queue.push(1)).rejects.toThrow(new InternalServerError(`504 Gateway Timeout`, 504));
229
- verifyApiClientCalledPushPathWith(apiClientMock, payload, 'name');
287
+ await expect(queue.push(PAYLOAD)).rejects.toThrow(new InternalServerError(`504 Gateway Timeout`, 504));
288
+ verifyApiClientCalledPushPathWith(apiClientMock, [PAYLOAD], 'name');
230
289
  });
231
290
 
232
291
  it('requests stargate if no api client is provided', async () => {
233
292
  const queue = getQueue('queue');
234
- await queue.push({ test: 'stargate' });
293
+ await queue.push({ body: { test: 'stargate' } });
235
294
  expect(__requestAtlassianAsApp).toHaveBeenCalledTimes(1);
236
295
  });
237
296
  });
@@ -241,7 +300,7 @@ describe('Queue methods', () => {
241
300
  const apiClientMock = getMockFetchMethod();
242
301
  const queue = getQueue('name', apiClientMock);
243
302
  const jobProgress = queue.getJob('test-job-id');
244
- expect(jobProgress).toEqual(new JobProgress('test-job-id', apiClientMock));
303
+ expect(jobProgress).toEqual(new JobProgress({ key: 'name' }, 'test-job-id', apiClientMock));
245
304
  });
246
305
  });
247
306
  });
@@ -40,7 +40,7 @@ export const verifyApiClientCalledPushPathWith = (
40
40
  ) => {
41
41
  verifyApiClientCalledWith(apiClientMock, '/webhook/queue/publish/{contextAri}/{environmentId}/{appId}/{appVersion}', {
42
42
  queueName: name,
43
- schema: 'ari:cloud:ecosystem::forge/app-event',
43
+ schema: 'ari:cloud:ecosystem::forge/app-event-2',
44
44
  type: 'avi:forge:app:event',
45
45
  payload: payloads
46
46
  });
package/src/errors.ts CHANGED
@@ -1,51 +1,36 @@
1
- import { FailedEvent } from './types';
1
+ import { FailedEvent, PushResult } from './types';
2
2
 
3
- export class InvalidPushSettingsError extends Error {
3
+ export class EventsError extends Error {
4
4
  constructor(message: string) {
5
5
  super(message);
6
6
  }
7
7
  }
8
8
 
9
- export class InvalidQueueNameError extends Error {
10
- constructor(message: string) {
11
- super(message);
12
- }
13
- }
9
+ export class InvalidPushSettingsError extends EventsError {}
14
10
 
15
- export class TooManyEventsError extends Error {
16
- constructor(message: string) {
17
- super(message);
18
- }
19
- }
11
+ export class InvalidQueueNameError extends EventsError {}
20
12
 
21
- export class PayloadTooBigError extends Error {
22
- constructor(message: string) {
23
- super(message);
24
- }
25
- }
13
+ export class InvalidPayloadError extends EventsError {}
26
14
 
27
- export class NoEventsToPushError extends Error {
28
- constructor(message: string) {
29
- super(message);
30
- }
31
- }
15
+ export class TooManyEventsError extends EventsError {}
32
16
 
33
- export class RateLimitError extends Error {
34
- constructor(message: string) {
35
- super(message);
36
- }
37
- }
17
+ export class PayloadTooBigError extends EventsError {}
38
18
 
39
- export class PartialSuccessError extends Error {
40
- constructor(message: string, failedEvents: FailedEvent[]) {
19
+ export class NoEventsToPushError extends EventsError {}
20
+
21
+ export class RateLimitError extends EventsError {}
22
+
23
+ export class PartialSuccessError extends EventsError {
24
+ constructor(
25
+ message: string,
26
+ public result: PushResult,
27
+ public failedEvents: FailedEvent[]
28
+ ) {
41
29
  super(message);
42
- this.failedEvents = failedEvents;
43
30
  }
44
-
45
- failedEvents: FailedEvent[];
46
31
  }
47
32
 
48
- export class InternalServerError extends Error {
33
+ export class InternalServerError extends EventsError {
49
34
  constructor(message: string, errorCode?: number, details?: string) {
50
35
  super(message);
51
36
  this.errorCode = errorCode;
@@ -56,14 +41,6 @@ export class InternalServerError extends Error {
56
41
  details?: string;
57
42
  }
58
43
 
59
- export class JobDoesNotExistError extends Error {
60
- constructor(message: string) {
61
- super(message);
62
- }
63
- }
44
+ export class JobDoesNotExistError extends EventsError {}
64
45
 
65
- export class InvocationLimitReachedError extends Error {
66
- constructor(message: string) {
67
- super(message);
68
- }
69
- }
46
+ export class InvocationLimitReachedError extends EventsError {}
package/src/index.ts CHANGED
@@ -11,11 +11,12 @@ export {
11
11
  InvalidPushSettingsError,
12
12
  InvocationLimitReachedError
13
13
  } from './errors';
14
- export { JobProgress } from './jobProgress';
14
+ export { JobProgress, JobStats } from './jobProgress';
15
15
  export { QueueResponse } from './queueResponse';
16
16
  export { InvocationError } from './invocationError';
17
17
  export { InvocationErrorCode } from './invocationErrorCode';
18
18
  export { RetryOptions } from './retryOptions';
19
+ export { AsyncEvent, PushEvent, PushResult } from './types';
19
20
  export {
20
21
  appEvents,
21
22
  AppEvent,
@@ -1,4 +1,4 @@
1
- import { __requestAtlassianAsApp, FetchMethod, APIResponse } from '@forge/api';
1
+ import { __requestAtlassianAsApp, FetchMethod } from '@forge/api';
2
2
  import { CANCEL_JOB_PATH, GET_STATS_PATH, post } from './queries';
3
3
  import {
4
4
  validateCancelJobAPIResponse,
@@ -6,19 +6,25 @@ import {
6
6
  validateGetStatsAPIResponse,
7
7
  validateGetStatsPayload
8
8
  } from './validators';
9
- import { CancelJobRequest, GetStatsRequest } from './types';
9
+ import { CancelJobRequest, GetStatsRequest, QueueParams } from './types';
10
+
11
+ export type JobStats = {
12
+ success: number;
13
+ inProgress: number;
14
+ failed: number;
15
+ };
10
16
 
11
17
  export class JobProgress {
12
18
  constructor(
19
+ private readonly queueParams: QueueParams,
13
20
  private readonly id: string,
14
21
  private readonly apiClient: FetchMethod = __requestAtlassianAsApp
15
22
  ) {}
16
23
 
17
- async getStats(): Promise<APIResponse> {
18
- const [queueName, jobId] = this.id.split('#');
24
+ async getStats(): Promise<JobStats> {
19
25
  const getStatsRequest: GetStatsRequest = {
20
- queueName: queueName,
21
- jobId: jobId,
26
+ queueName: this.queueParams.key,
27
+ jobId: this.id,
22
28
  time: new Date().toISOString()
23
29
  };
24
30
 
@@ -26,14 +32,14 @@ export class JobProgress {
26
32
 
27
33
  const response = await post(GET_STATS_PATH, getStatsRequest, this.apiClient);
28
34
  await validateGetStatsAPIResponse(response, getStatsRequest);
29
- return response;
35
+ const { success, inProgress, failed } = await response.json();
36
+ return { success, inProgress, failed };
30
37
  }
31
38
 
32
- async cancel(): Promise<APIResponse> {
33
- const [queueName, jobId] = this.id.split('#');
39
+ async cancel(): Promise<void> {
34
40
  const cancelJobRequest: CancelJobRequest = {
35
- queueName: queueName,
36
- jobId: jobId,
41
+ queueName: this.queueParams.key,
42
+ jobId: this.id,
37
43
  time: new Date().toISOString()
38
44
  };
39
45
 
@@ -41,6 +47,5 @@ export class JobProgress {
41
47
 
42
48
  const response = await post(CANCEL_JOB_PATH, cancelJobRequest, this.apiClient);
43
49
  await validateCancelJobAPIResponse(response, cancelJobRequest);
44
- return response;
45
50
  }
46
51
  }
package/src/queue.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { FetchMethod, __requestAtlassianAsApp } from '@forge/api';
2
2
  import { PUSH_PATH, post } from './queries';
3
- import { validatePushAPIResponse, validatePushPayloads, validateQueueKey, validatePushSettings } from './validators';
4
- import { Payload, QueueParams, PushSettings, PushRequest } from './types';
3
+ import { validatePushAPIResponse, validatePushEvents, validateQueueKey } from './validators';
4
+ import { QueueParams, PushEvent, PushRequest, PushResult } from './types';
5
5
  import { v4 as uuid } from 'uuid';
6
6
  import { JobProgress } from './jobProgress';
7
7
 
@@ -13,8 +13,8 @@ export class Queue {
13
13
  validateQueueKey(this.queueParams.key);
14
14
  }
15
15
 
16
- async push(payloads: Payload | Payload[], pushSettings?: PushSettings): Promise<string> {
17
- validatePushPayloads(payloads);
16
+ async push(events: PushEvent | PushEvent[]): Promise<PushResult> {
17
+ const validEvents = validatePushEvents(events);
18
18
  const queueName = this.queueParams.key;
19
19
  const jobId = uuid();
20
20
 
@@ -22,24 +22,18 @@ export class Queue {
22
22
  queueName: queueName,
23
23
  jobId: jobId,
24
24
  type: 'avi:forge:app:event',
25
- schema: 'ari:cloud:ecosystem::forge/app-event',
26
- payload: Array.isArray(payloads) ? payloads : [payloads],
25
+ schema: 'ari:cloud:ecosystem::forge/app-event-2',
26
+ payload: validEvents,
27
27
  time: new Date().toISOString()
28
28
  };
29
29
 
30
- if (pushSettings) {
31
- validatePushSettings(pushSettings);
32
- if (pushSettings.delayInSeconds) {
33
- pushRequest.delayInSeconds = pushSettings.delayInSeconds;
34
- }
35
- }
36
-
37
30
  const response = await post(PUSH_PATH, pushRequest, this.apiClient);
38
- await validatePushAPIResponse(response, pushRequest);
39
- return `${queueName}#${jobId}`;
31
+ const result = { jobId };
32
+ await validatePushAPIResponse(pushRequest, response, result);
33
+ return result;
40
34
  }
41
35
 
42
36
  getJob(jobId: string): JobProgress {
43
- return new JobProgress(jobId, this.apiClient);
37
+ return new JobProgress(this.queueParams, jobId, this.apiClient);
44
38
  }
45
39
  }
package/src/text.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export const Text = {
2
2
  error: {
3
3
  invalidQueueName: `Queue names can only contain alphanumeric characters, dashes and underscores.`,
4
+ invalidEvent: `Event must be an object.`,
5
+ invalidEventBody: `Event body must be an object.`,
4
6
  invalidDelayInSecondsSetting: `The delayInSeconds setting must be between 0 and 900.`,
5
7
  maxEventsAllowed: (maxEventsCount: number): string =>
6
8
  `This push contains more than the ${maxEventsCount} events allowed.`,
package/src/types.ts CHANGED
@@ -1,17 +1,16 @@
1
- export type Payload = string | number | boolean | { [key: string]: Payload };
1
+ import { InvocationErrorCode } from './invocationErrorCode';
2
+
3
+ export type Payload = Record<string, unknown>;
2
4
 
3
- export interface PushSettings {
4
- delayInSeconds: number;
5
- }
6
5
  export interface QueueParams {
7
6
  key: string;
8
7
  }
9
8
 
10
9
  export interface PushRequest extends APIRequest {
11
- payload: Payload[];
10
+ payload: PushEvent[];
12
11
  schema: string;
13
12
  type: string;
14
- delayInSeconds?: number;
13
+ jobId: string;
15
14
  }
16
15
 
17
16
  export interface APIRequest {
@@ -20,9 +19,33 @@ export interface APIRequest {
20
19
  time: string;
21
20
  }
22
21
 
22
+ export interface PushEvent {
23
+ body: Body;
24
+ delayInSeconds?: number;
25
+ }
26
+
27
+ export type Body = Record<string, unknown>;
28
+
29
+ export interface PushResult {
30
+ jobId: string;
31
+ }
32
+
23
33
  export interface FailedEvent {
24
34
  errorMessage: string;
25
- payload: Payload;
35
+ event: PushEvent;
36
+ }
37
+
38
+ export interface AsyncEvent extends PushEvent {
39
+ queueName: string;
40
+ jobId: string;
41
+ eventId: string;
42
+ retryContext?: RetryContext;
43
+ }
44
+
45
+ export interface RetryContext {
46
+ retryCount: number;
47
+ retryReason: InvocationErrorCode;
48
+ retryData: any;
26
49
  }
27
50
 
28
51
  export type GetStatsRequest = APIRequest;
package/src/validators.ts CHANGED
@@ -3,6 +3,7 @@ import {
3
3
  InternalServerError,
4
4
  InvalidQueueNameError,
5
5
  JobDoesNotExistError,
6
+ InvalidPayloadError,
6
7
  NoEventsToPushError,
7
8
  PartialSuccessError,
8
9
  PayloadTooBigError,
@@ -12,7 +13,7 @@ import {
12
13
  InvocationLimitReachedError
13
14
  } from './errors';
14
15
  import { Text } from './text';
15
- import { CancelJobRequest, GetStatsRequest, Payload, PushRequest, PushSettings } from './types';
16
+ import { CancelJobRequest, GetStatsRequest, PushEvent, PushRequest, PushResult } from './types';
16
17
 
17
18
  const VALID_QUEUE_NAME_PATTERN = /^[a-zA-Z0-9-_]+$/;
18
19
  const MAXIMUM_EVENTS = 50;
@@ -24,26 +25,42 @@ export const validateQueueKey = (queueName: string) => {
24
25
  }
25
26
  };
26
27
 
27
- export const validatePushSettings = (settings: PushSettings) => {
28
- if ((settings.delayInSeconds && settings.delayInSeconds > 900) || settings.delayInSeconds < 0) {
28
+ export const validatePushSettings = (event: PushEvent) => {
29
+ if ((event.delayInSeconds && event.delayInSeconds > 900) || (event.delayInSeconds && event.delayInSeconds < 0)) {
29
30
  throw new InvalidPushSettingsError(Text.error.invalidDelayInSecondsSetting);
30
31
  }
31
32
  };
32
33
 
33
- export const validatePushPayloads = (payloads: Payload | Payload[]) => {
34
- if (!payloads || (Array.isArray(payloads) && payloads.length === 0)) {
34
+ export function validatePushEvents(arg: PushEvent | PushEvent[]): PushEvent[] {
35
+ const events = Array.isArray(arg) ? arg : [arg];
36
+
37
+ if (events.length === 0) {
35
38
  throw new NoEventsToPushError(Text.error.noEventsPushed);
36
39
  }
37
40
 
38
- if (Array.isArray(payloads) && payloads.length > MAXIMUM_EVENTS) {
41
+ if (events.length > MAXIMUM_EVENTS) {
39
42
  throw new TooManyEventsError(Text.error.maxEventsAllowed(MAXIMUM_EVENTS));
40
43
  }
41
44
 
42
- const payloadSizeKB = Buffer.byteLength(JSON.stringify(payloads)) / 1024;
45
+ for (const event of events) {
46
+ if (typeof event !== 'object' || Array.isArray(event) || event === null) {
47
+ throw new InvalidPayloadError(Text.error.invalidEvent);
48
+ }
49
+
50
+ if (typeof event.body !== 'object' || Array.isArray(event.body) || event.body === null) {
51
+ throw new InvalidPayloadError(Text.error.invalidEventBody);
52
+ }
53
+
54
+ validatePushSettings(event);
55
+ }
56
+
57
+ const payloadSizeKB = Buffer.byteLength(JSON.stringify(events)) / 1024;
43
58
  if (payloadSizeKB > MAXIMUM_PAYLOAD_SIZE_KB) {
44
59
  throw new PayloadTooBigError(Text.error.maxPayloadAllowed(MAXIMUM_PAYLOAD_SIZE_KB));
45
60
  }
46
- };
61
+
62
+ return events;
63
+ }
47
64
 
48
65
  export const validateGetStatsPayload = (getStatsRequest: GetStatsRequest) => {
49
66
  if (!getStatsRequest.jobId) {
@@ -90,7 +107,7 @@ export const validateAPIResponse = async (response: APIResponse, expectedSuccess
90
107
  }
91
108
  };
92
109
 
93
- export const validatePushAPIResponse = async (response: APIResponse, requestBody: PushRequest) => {
110
+ export const validatePushAPIResponse = async (requestBody: PushRequest, response: APIResponse, result: PushResult) => {
94
111
  if (response.status === 413) {
95
112
  //Server can return this error response if it has a different max payload size and max number of events limits
96
113
  const responseBody = await response.json();
@@ -100,7 +117,7 @@ export const validatePushAPIResponse = async (response: APIResponse, requestBody
100
117
  if (response.status === 202) {
101
118
  const responseBody = await response.json();
102
119
  const defaultErrorMessage = 'Failed to process some events.';
103
- const partialSuccessError = new PartialSuccessError(defaultErrorMessage, []);
120
+ const partialSuccessError = new PartialSuccessError(defaultErrorMessage, result, []);
104
121
 
105
122
  if (responseBody.failedEvents && responseBody.failedEvents.length > 0) {
106
123
  partialSuccessError.message = `Failed to process ${responseBody.failedEvents.length} event(s).`;