@cumulus/message 10.1.2 → 11.1.1
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.
- package/Collections.d.ts +3 -0
- package/Collections.d.ts.map +1 -1
- package/Collections.js +7 -5
- package/Collections.js.map +1 -1
- package/Executions.d.ts +19 -4
- package/Executions.d.ts.map +1 -1
- package/Executions.js +51 -3
- package/Executions.js.map +1 -1
- package/Granules.d.ts +76 -10
- package/Granules.d.ts.map +1 -1
- package/Granules.js +141 -2
- package/Granules.js.map +1 -1
- package/PDRs.d.ts +18 -12
- package/PDRs.d.ts.map +1 -1
- package/PDRs.js +73 -4
- package/PDRs.js.map +1 -1
- package/README.md +132 -28
- package/package.json +7 -7
- package/src/Collections.ts +4 -1
- package/src/Executions.ts +77 -7
- package/src/Granules.ts +218 -3
- package/src/PDRs.ts +99 -13
- package/src/types.ts +5 -0
- package/src/utils.ts +17 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/types.d.ts +4 -0
- package/types.d.ts.map +1 -1
- package/utils.d.ts +8 -0
- package/utils.d.ts.map +1 -0
- package/utils.js +26 -0
- package/utils.js.map +1 -0
package/src/Granules.ts
CHANGED
|
@@ -8,8 +8,23 @@
|
|
|
8
8
|
* const Granules = require('@cumulus/message/Granules');
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import isInteger from 'lodash/isInteger';
|
|
12
|
+
import isNil from 'lodash/isNil';
|
|
13
|
+
import mapValues from 'lodash/mapValues';
|
|
14
|
+
import omitBy from 'lodash/omitBy';
|
|
15
|
+
|
|
16
|
+
import { CumulusMessageError } from '@cumulus/errors';
|
|
11
17
|
import { Message } from '@cumulus/types';
|
|
12
|
-
import {
|
|
18
|
+
import { ExecutionProcessingTimes } from '@cumulus/types/api/executions';
|
|
19
|
+
import {
|
|
20
|
+
ApiGranule,
|
|
21
|
+
GranuleStatus,
|
|
22
|
+
GranuleTemporalInfo,
|
|
23
|
+
MessageGranule,
|
|
24
|
+
} from '@cumulus/types/api/granules';
|
|
25
|
+
import { ApiFile } from '@cumulus/types/api/files';
|
|
26
|
+
|
|
27
|
+
import { CmrUtilsClass } from './types';
|
|
13
28
|
|
|
14
29
|
interface MetaWithGranuleQueryFields extends Message.Meta {
|
|
15
30
|
granule?: {
|
|
@@ -53,14 +68,14 @@ export const messageHasGranules = (
|
|
|
53
68
|
* Determine the status of a granule.
|
|
54
69
|
*
|
|
55
70
|
* @param {string} workflowStatus - The workflow status
|
|
56
|
-
* @param {
|
|
71
|
+
* @param {MessageGranule} granule - A granule record conforming to the 'api' schema
|
|
57
72
|
* @returns {string} The granule status
|
|
58
73
|
*
|
|
59
74
|
* @alias module:Granules
|
|
60
75
|
*/
|
|
61
76
|
export const getGranuleStatus = (
|
|
62
77
|
workflowStatus: Message.WorkflowStatus,
|
|
63
|
-
granule:
|
|
78
|
+
granule: MessageGranule
|
|
64
79
|
): Message.WorkflowStatus | GranuleStatus => workflowStatus || granule.status;
|
|
65
80
|
|
|
66
81
|
/**
|
|
@@ -74,3 +89,203 @@ export const getGranuleStatus = (
|
|
|
74
89
|
export const getGranuleQueryFields = (
|
|
75
90
|
message: MessageWithGranules
|
|
76
91
|
) => message.meta?.granule?.queryFields;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Calculate granule product volume, which is the sum of the file
|
|
95
|
+
* sizes in bytes
|
|
96
|
+
*
|
|
97
|
+
* @param {Array<Object>} granuleFiles - array of granule file objects that conform to the
|
|
98
|
+
* Cumulus 'api' schema
|
|
99
|
+
* @returns {string} - sum of granule file sizes in bytes as a string
|
|
100
|
+
*/
|
|
101
|
+
export const getGranuleProductVolume = (granuleFiles: ApiFile[] = []): string => {
|
|
102
|
+
if (granuleFiles.length === 0) return '0';
|
|
103
|
+
return String(granuleFiles
|
|
104
|
+
.map((f) => f.size ?? 0)
|
|
105
|
+
.filter(isInteger)
|
|
106
|
+
.reduce((x, y) => x + BigInt(y), BigInt(0)));
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export const getGranuleTimeToPreprocess = ({
|
|
110
|
+
sync_granule_duration = 0,
|
|
111
|
+
} = {}) => sync_granule_duration / 1000;
|
|
112
|
+
|
|
113
|
+
export const getGranuleTimeToArchive = ({
|
|
114
|
+
post_to_cmr_duration = 0,
|
|
115
|
+
} = {}) => post_to_cmr_duration / 1000;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Convert date string to standard ISO format.
|
|
119
|
+
*
|
|
120
|
+
* @param {string} date - Date string, possibly in multiple formats
|
|
121
|
+
* @returns {string} Standardized ISO date string
|
|
122
|
+
*/
|
|
123
|
+
const convertDateToISOString = (date: string) => new Date(date).toISOString();
|
|
124
|
+
|
|
125
|
+
function isProcessingTimeInfo(
|
|
126
|
+
info: ExecutionProcessingTimes | {} = {}
|
|
127
|
+
): info is ExecutionProcessingTimes {
|
|
128
|
+
return (info as ExecutionProcessingTimes)?.processingStartDateTime !== undefined
|
|
129
|
+
&& (info as ExecutionProcessingTimes)?.processingEndDateTime !== undefined;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Convert granule processing timestamps to a standardized ISO string
|
|
134
|
+
* format for compatibility across database systems.
|
|
135
|
+
*
|
|
136
|
+
* @param {ExecutionProcessingTimes} [processingTimeInfo]
|
|
137
|
+
* Granule processing time info, if any
|
|
138
|
+
* @returns {Promise<ExecutionProcessingTimes | undefined>}
|
|
139
|
+
*/
|
|
140
|
+
export const getGranuleProcessingTimeInfo = (
|
|
141
|
+
processingTimeInfo?: ExecutionProcessingTimes
|
|
142
|
+
): ExecutionProcessingTimes | {} => {
|
|
143
|
+
const updatedProcessingTimeInfo = isProcessingTimeInfo(processingTimeInfo)
|
|
144
|
+
? { ...processingTimeInfo }
|
|
145
|
+
: {};
|
|
146
|
+
return mapValues(
|
|
147
|
+
updatedProcessingTimeInfo,
|
|
148
|
+
convertDateToISOString
|
|
149
|
+
);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
function isGranuleTemporalInfo(
|
|
153
|
+
info: GranuleTemporalInfo | {} = {}
|
|
154
|
+
): info is GranuleTemporalInfo {
|
|
155
|
+
return (info as GranuleTemporalInfo)?.beginningDateTime !== undefined
|
|
156
|
+
&& (info as GranuleTemporalInfo)?.endingDateTime !== undefined
|
|
157
|
+
&& (info as GranuleTemporalInfo)?.productionDateTime !== undefined
|
|
158
|
+
&& (info as GranuleTemporalInfo)?.lastUpdateDateTime !== undefined;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Get granule temporal information from argument or directly from CMR.
|
|
163
|
+
*
|
|
164
|
+
* Converts temporal information timestamps to a standardized ISO string
|
|
165
|
+
* format for compatibility across database systems.
|
|
166
|
+
*
|
|
167
|
+
* @param {Object} params
|
|
168
|
+
* @param {MessageGranule} params.granule - Granule from workflow message
|
|
169
|
+
* @param {Object} [params.cmrTemporalInfo] - CMR temporal info, if any
|
|
170
|
+
* @param {CmrUtilsClass} params.cmrUtils - CMR utilities object
|
|
171
|
+
* @returns {Promise<GranuleTemporalInfo | undefined>}
|
|
172
|
+
*/
|
|
173
|
+
export const getGranuleCmrTemporalInfo = async ({
|
|
174
|
+
granule,
|
|
175
|
+
cmrTemporalInfo,
|
|
176
|
+
cmrUtils,
|
|
177
|
+
}: {
|
|
178
|
+
granule: MessageGranule,
|
|
179
|
+
cmrTemporalInfo?: GranuleTemporalInfo,
|
|
180
|
+
cmrUtils: CmrUtilsClass
|
|
181
|
+
}): Promise<GranuleTemporalInfo | {}> => {
|
|
182
|
+
// Get CMR temporalInfo (beginningDateTime, endingDateTime,
|
|
183
|
+
// productionDateTime, lastUpdateDateTime)
|
|
184
|
+
const temporalInfo = isGranuleTemporalInfo(cmrTemporalInfo)
|
|
185
|
+
? { ...cmrTemporalInfo }
|
|
186
|
+
: await cmrUtils.getGranuleTemporalInfo(granule);
|
|
187
|
+
return mapValues(
|
|
188
|
+
temporalInfo,
|
|
189
|
+
convertDateToISOString
|
|
190
|
+
);
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Generate an API granule record
|
|
195
|
+
*
|
|
196
|
+
* @param {MessageWithGranules} message - A workflow message
|
|
197
|
+
* @returns {Promise<ApiGranule>} The granule API record
|
|
198
|
+
*
|
|
199
|
+
* @alias module:Granules
|
|
200
|
+
*/
|
|
201
|
+
export const generateGranuleApiRecord = async ({
|
|
202
|
+
granule,
|
|
203
|
+
executionUrl,
|
|
204
|
+
collectionId,
|
|
205
|
+
provider,
|
|
206
|
+
workflowStartTime,
|
|
207
|
+
error,
|
|
208
|
+
pdrName,
|
|
209
|
+
status,
|
|
210
|
+
queryFields,
|
|
211
|
+
updatedAt,
|
|
212
|
+
files,
|
|
213
|
+
processingTimeInfo,
|
|
214
|
+
cmrUtils,
|
|
215
|
+
timestamp,
|
|
216
|
+
duration,
|
|
217
|
+
productVolume,
|
|
218
|
+
timeToPreprocess,
|
|
219
|
+
timeToArchive,
|
|
220
|
+
cmrTemporalInfo,
|
|
221
|
+
}: {
|
|
222
|
+
granule: MessageGranule,
|
|
223
|
+
executionUrl?: string,
|
|
224
|
+
collectionId: string,
|
|
225
|
+
provider?: string,
|
|
226
|
+
workflowStartTime: number,
|
|
227
|
+
error?: Object,
|
|
228
|
+
pdrName?: string,
|
|
229
|
+
status: GranuleStatus,
|
|
230
|
+
queryFields?: Object,
|
|
231
|
+
updatedAt: number,
|
|
232
|
+
processingTimeInfo?: ExecutionProcessingTimes,
|
|
233
|
+
files?: ApiFile[],
|
|
234
|
+
timestamp: number,
|
|
235
|
+
cmrUtils: CmrUtilsClass
|
|
236
|
+
cmrTemporalInfo?: GranuleTemporalInfo,
|
|
237
|
+
duration: number,
|
|
238
|
+
productVolume: string,
|
|
239
|
+
timeToPreprocess: number,
|
|
240
|
+
timeToArchive: number,
|
|
241
|
+
}): Promise<ApiGranule> => {
|
|
242
|
+
if (!granule.granuleId) throw new CumulusMessageError(`Could not create granule record, invalid granuleId: ${granule.granuleId}`);
|
|
243
|
+
|
|
244
|
+
if (!collectionId) {
|
|
245
|
+
throw new CumulusMessageError('collectionId required to generate a granule record');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const {
|
|
249
|
+
granuleId,
|
|
250
|
+
cmrLink,
|
|
251
|
+
published = false,
|
|
252
|
+
createdAt,
|
|
253
|
+
} = granule;
|
|
254
|
+
|
|
255
|
+
const now = Date.now();
|
|
256
|
+
const recordUpdatedAt = updatedAt ?? now;
|
|
257
|
+
const recordTimestamp = timestamp ?? now;
|
|
258
|
+
|
|
259
|
+
// Get CMR temporalInfo
|
|
260
|
+
const temporalInfo = await getGranuleCmrTemporalInfo({
|
|
261
|
+
granule,
|
|
262
|
+
cmrTemporalInfo,
|
|
263
|
+
cmrUtils,
|
|
264
|
+
});
|
|
265
|
+
const updatedProcessingTimeInfo = getGranuleProcessingTimeInfo(processingTimeInfo);
|
|
266
|
+
|
|
267
|
+
const record = {
|
|
268
|
+
granuleId,
|
|
269
|
+
pdrName,
|
|
270
|
+
collectionId,
|
|
271
|
+
status,
|
|
272
|
+
provider,
|
|
273
|
+
execution: executionUrl,
|
|
274
|
+
cmrLink,
|
|
275
|
+
files,
|
|
276
|
+
error,
|
|
277
|
+
published,
|
|
278
|
+
createdAt: createdAt || workflowStartTime,
|
|
279
|
+
timestamp: recordTimestamp,
|
|
280
|
+
updatedAt: recordUpdatedAt,
|
|
281
|
+
duration,
|
|
282
|
+
productVolume,
|
|
283
|
+
timeToPreprocess,
|
|
284
|
+
timeToArchive,
|
|
285
|
+
...updatedProcessingTimeInfo,
|
|
286
|
+
...temporalInfo,
|
|
287
|
+
queryFields,
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
return <ApiGranule>omitBy(record, isNil);
|
|
291
|
+
};
|
package/src/PDRs.ts
CHANGED
|
@@ -1,4 +1,25 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CumulusMessageError,
|
|
3
|
+
} from '@cumulus/errors';
|
|
4
|
+
import Logger from '@cumulus/logger';
|
|
1
5
|
import { Message } from '@cumulus/types';
|
|
6
|
+
import { ApiPdr } from '@cumulus/types/api/pdrs';
|
|
7
|
+
|
|
8
|
+
import { getCollectionIdFromMessage } from './Collections';
|
|
9
|
+
import {
|
|
10
|
+
getMessageExecutionArn,
|
|
11
|
+
getExecutionUrlFromArn,
|
|
12
|
+
} from './Executions';
|
|
13
|
+
import {
|
|
14
|
+
getMessageProviderId,
|
|
15
|
+
} from './Providers';
|
|
16
|
+
import {
|
|
17
|
+
getMetaStatus,
|
|
18
|
+
getMessageWorkflowStartTime,
|
|
19
|
+
getWorkflowDuration,
|
|
20
|
+
} from './workflows';
|
|
21
|
+
|
|
22
|
+
const logger = new Logger({ sender: '@cumulus/message/PDRs' });
|
|
2
23
|
|
|
3
24
|
interface PDR {
|
|
4
25
|
name: string
|
|
@@ -9,12 +30,6 @@ interface PDR {
|
|
|
9
30
|
interface MessageWithOptionalPayloadPdr extends Message.CumulusMessage {
|
|
10
31
|
payload: {
|
|
11
32
|
pdr?: PDR
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface MessageWithOptionalPdrStats extends Message.CumulusMessage {
|
|
16
|
-
payload: {
|
|
17
|
-
pdr: PDR
|
|
18
33
|
failed?: unknown[]
|
|
19
34
|
running?: unknown[]
|
|
20
35
|
completed?: unknown[]
|
|
@@ -91,37 +106,37 @@ export const getMessagePdrName = (
|
|
|
91
106
|
/**
|
|
92
107
|
* Get the number of running executions for a PDR, if any.
|
|
93
108
|
*
|
|
94
|
-
* @param {
|
|
109
|
+
* @param {MessageWithOptionalPayloadPdr} message - A workflow message
|
|
95
110
|
* @returns {number} Number of running executions
|
|
96
111
|
*
|
|
97
112
|
* @alias module:PDRs
|
|
98
113
|
*/
|
|
99
114
|
export const getMessagePdrRunningExecutions = (
|
|
100
|
-
message:
|
|
115
|
+
message: MessageWithOptionalPayloadPdr
|
|
101
116
|
): number => (message.payload.running ?? []).length;
|
|
102
117
|
|
|
103
118
|
/**
|
|
104
119
|
* Get the number of completed executions for a PDR, if any.
|
|
105
120
|
*
|
|
106
|
-
* @param {
|
|
121
|
+
* @param {MessageWithOptionalPayloadPdr} message - A workflow message
|
|
107
122
|
* @returns {number} Number of completed executions
|
|
108
123
|
*
|
|
109
124
|
* @alias module:PDRs
|
|
110
125
|
*/
|
|
111
126
|
export const getMessagePdrCompletedExecutions = (
|
|
112
|
-
message:
|
|
127
|
+
message: MessageWithOptionalPayloadPdr
|
|
113
128
|
): number => (message.payload.completed ?? []).length;
|
|
114
129
|
|
|
115
130
|
/**
|
|
116
131
|
* Get the number of failed executions for a PDR, if any.
|
|
117
132
|
*
|
|
118
|
-
* @param {
|
|
133
|
+
* @param {MessageWithOptionalPayloadPdr} message - A workflow message
|
|
119
134
|
* @returns {number} Number of failed executions
|
|
120
135
|
*
|
|
121
136
|
* @alias module:PDRs
|
|
122
137
|
*/
|
|
123
138
|
export const getMessagePdrFailedExecutions = (
|
|
124
|
-
message:
|
|
139
|
+
message: MessageWithOptionalPayloadPdr
|
|
125
140
|
): number => (message.payload.failed ?? []).length;
|
|
126
141
|
|
|
127
142
|
/**
|
|
@@ -134,7 +149,7 @@ export const getMessagePdrFailedExecutions = (
|
|
|
134
149
|
* @alias module:PDRs
|
|
135
150
|
*/
|
|
136
151
|
export const getMessagePdrStats = (
|
|
137
|
-
message:
|
|
152
|
+
message: MessageWithOptionalPayloadPdr
|
|
138
153
|
): PdrStats => {
|
|
139
154
|
const processing = getMessagePdrRunningExecutions(message);
|
|
140
155
|
const completed = getMessagePdrCompletedExecutions(message);
|
|
@@ -167,3 +182,74 @@ export const getPdrPercentCompletion = (
|
|
|
167
182
|
}
|
|
168
183
|
return progress;
|
|
169
184
|
};
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Generate a PDR record for the API from the message.
|
|
188
|
+
*
|
|
189
|
+
* @param {MessageWithOptionalPayloadPdr} message - A workflow message object
|
|
190
|
+
* @param {string} [updatedAt] - Optional updated timestamp to apply to record
|
|
191
|
+
* @returns {ExecutionRecord} An PDR API record
|
|
192
|
+
*
|
|
193
|
+
* @alias module:Executions
|
|
194
|
+
*/
|
|
195
|
+
export const generatePdrApiRecordFromMessage = (
|
|
196
|
+
message: MessageWithOptionalPayloadPdr,
|
|
197
|
+
updatedAt = Date.now()
|
|
198
|
+
): ApiPdr | undefined => {
|
|
199
|
+
const pdr = getMessagePdr(message);
|
|
200
|
+
|
|
201
|
+
// We got a message with no PDR (OK)
|
|
202
|
+
if (!pdr) {
|
|
203
|
+
logger.info('No PDRs to process on the message');
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// We got a message with a PDR but no name to identify it (Not OK)
|
|
208
|
+
if (!pdr.name) {
|
|
209
|
+
throw new CumulusMessageError(`Could not find name on PDR object ${JSON.stringify(pdr)}`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const collectionId = getCollectionIdFromMessage(message);
|
|
213
|
+
if (!collectionId) {
|
|
214
|
+
throw new CumulusMessageError('meta.collection required to generate a PDR record');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const providerId = getMessageProviderId(message);
|
|
218
|
+
if (!providerId) {
|
|
219
|
+
throw new CumulusMessageError('meta.provider required to generate a PDR record');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const status = getMetaStatus(message);
|
|
223
|
+
if (!status) {
|
|
224
|
+
throw new CumulusMessageError('meta.status required to generate a PDR record');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const arn = getMessageExecutionArn(message);
|
|
228
|
+
if (!arn) {
|
|
229
|
+
throw new CumulusMessageError('cumulus_meta.state_machine and cumulus_meta.execution_name required to generate a PDR record');
|
|
230
|
+
}
|
|
231
|
+
const execution = getExecutionUrlFromArn(arn);
|
|
232
|
+
|
|
233
|
+
const stats = getMessagePdrStats(message);
|
|
234
|
+
const progress = getPdrPercentCompletion(stats);
|
|
235
|
+
const now = Date.now();
|
|
236
|
+
const workflowStartTime = getMessageWorkflowStartTime(message);
|
|
237
|
+
|
|
238
|
+
const record = {
|
|
239
|
+
pdrName: pdr.name,
|
|
240
|
+
collectionId,
|
|
241
|
+
status,
|
|
242
|
+
provider: providerId,
|
|
243
|
+
progress,
|
|
244
|
+
execution,
|
|
245
|
+
PANSent: getMessagePdrPANSent(message),
|
|
246
|
+
PANmessage: getMessagePdrPANMessage(message),
|
|
247
|
+
stats,
|
|
248
|
+
createdAt: getMessageWorkflowStartTime(message),
|
|
249
|
+
timestamp: now,
|
|
250
|
+
updatedAt,
|
|
251
|
+
duration: getWorkflowDuration(workflowStartTime, now),
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
return record;
|
|
255
|
+
};
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Message } from '@cumulus/types';
|
|
2
|
+
import { GranuleTemporalInfo, MessageGranule } from '@cumulus/types/api/granules';
|
|
2
3
|
|
|
3
4
|
export interface WorkflowMessageTemplateCumulusMeta {
|
|
4
5
|
queueExecutionLimits: Message.QueueExecutionLimits
|
|
@@ -15,3 +16,7 @@ export interface Workflow {
|
|
|
15
16
|
arn: string
|
|
16
17
|
name: string
|
|
17
18
|
}
|
|
19
|
+
|
|
20
|
+
export interface CmrUtilsClass {
|
|
21
|
+
getGranuleTemporalInfo(granule: MessageGranule): Promise<GranuleTemporalInfo | {}>
|
|
22
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import isNil from 'lodash/isNil';
|
|
2
|
+
import isObject from 'lodash/isObject';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Ensures that the exception is returned as an object
|
|
6
|
+
*
|
|
7
|
+
* @param {Object|undefined} exception - the exception
|
|
8
|
+
* @returns {string} an stringified exception
|
|
9
|
+
*/
|
|
10
|
+
export const parseException = (exception: Object | undefined) => {
|
|
11
|
+
if (isNil(exception)) return {};
|
|
12
|
+
if (isObject(exception)) return exception;
|
|
13
|
+
return {
|
|
14
|
+
Error: 'Unknown Error',
|
|
15
|
+
Cause: exception,
|
|
16
|
+
};
|
|
17
|
+
};
|