Skip to content

Commit 9cf4b58

Browse files
committed
fix: better JSON parsing, serialization for UserErrors
1 parent e8410da commit 9cf4b58

File tree

3 files changed

+58
-10
lines changed

3 files changed

+58
-10
lines changed

meteor/server/api/rest/v1/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ function sofieAPIRequest<API, Params, Body, Response>(
153153
ctx.body = JSON.stringify({ status: response.success, result: response.result })
154154
ctx.status = response.success
155155
} catch (e) {
156+
console.log('LOOK HERE', e)
156157
const userError = validateUserError(e)
157158
const errCode = extractErrorCode(userError)
158159
let errMsg = extractErrorUserMessage(userError)

packages/corelib/src/error.ts

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ITranslatableMessage } from '@sofie-automation/blueprints-integration'
22
import { stringifyError } from '@sofie-automation/shared-lib/dist/lib/stringifyError'
3-
import { interpollateTranslation, translateMessage } from './TranslatableMessage.js'
3+
import { interpollateTranslation, isTranslatableMessage, translateMessage } from './TranslatableMessage.js'
44

55
// Mock 't' function for i18next to find the keys
66
function t(key: string): string {
@@ -137,6 +137,12 @@ export interface UserErrorObj {
137137
readonly userMessage: ITranslatableMessage
138138
}
139139

140+
export interface StringifiedUserErrorObject extends Omit<UserErrorObj, 'rawError'> {
141+
rawError: Pick<Error, 'name' | 'message' | 'stack'>
142+
}
143+
144+
export type StringifiedErrorObject = Omit<Error, 'cause'>
145+
140146
export class UserError extends Error implements UserErrorObj {
141147
public readonly errorCode: number
142148

@@ -165,9 +171,9 @@ export class UserError extends Error implements UserErrorObj {
165171
/** Create a UserError from an unknown possibly error input */
166172
static fromUnknown(err: unknown, errorCode?: number): UserError {
167173
if (err instanceof UserError) return err
168-
if (this.isUserError(err))
169-
return new UserError(new Error(err.rawError.toString()), err.key, err.userMessage, err.errorCode)
170-
174+
if (this.isUserError(err)) {
175+
return new UserError(err.rawError, err.key, err.userMessage, err.errorCode)
176+
}
171177
const err2 = err instanceof Error ? err : new Error(stringifyError(err))
172178
return new UserError(
173179
err2,
@@ -184,9 +190,11 @@ export class UserError extends Error implements UserErrorObj {
184190

185191
static tryFromJSON(str: string): UserError | undefined {
186192
try {
187-
const p = JSON.parse(str)
188-
if (UserError.isUserError(p)) {
189-
return new UserError(new Error(p.rawError.toString()), p.key, p.userMessage, p.errorCode)
193+
const p = UserError.fromJSON(str)
194+
if (p) {
195+
const newError = p.rawError as Error
196+
const newUserError = new UserError(newError, p.key, p.userMessage, p.errorCode)
197+
return newUserError
190198
} else {
191199
return undefined
192200
}
@@ -196,12 +204,22 @@ export class UserError extends Error implements UserErrorObj {
196204
}
197205

198206
static toJSON(e: UserErrorObj): string {
199-
return JSON.stringify({
200-
rawError: stringifyError(e.rawError),
207+
const o: StringifiedUserErrorObject = {
208+
rawError: {
209+
name: e.rawError.name,
210+
message: e.rawError.message,
211+
stack: e.rawError.stack,
212+
},
201213
userMessage: e.userMessage,
202214
key: e.key,
203215
errorCode: e.errorCode,
204-
})
216+
}
217+
return JSON.stringify(o)
218+
}
219+
static fromJSON(str: string): StringifiedUserErrorObject | undefined {
220+
const o = JSON.parse(str)
221+
if (isStringifiedUserErrorObject(o)) return o
222+
return undefined
205223
}
206224

207225
static isUserError(e: unknown): e is UserErrorObj {
@@ -212,3 +230,22 @@ export class UserError extends Error implements UserErrorObj {
212230
return `${translateMessage(this.userMessage, interpollateTranslation)}\n${stringifyError(this.rawError)}`
213231
}
214232
}
233+
234+
function isStringifiedUserErrorObject(o: any): o is StringifiedUserErrorObject {
235+
return (
236+
o &&
237+
typeof o === 'object' &&
238+
'rawError' in o &&
239+
o.rawError &&
240+
isStringifiedErrorObject(o.rawError) &&
241+
isTranslatableMessage(o.userMessage) &&
242+
typeof o.errorCode === 'number' &&
243+
typeof o.key === 'number' &&
244+
o.key >= 0 &&
245+
o.key < Object.keys(UserErrorMessage).length / 2
246+
)
247+
}
248+
249+
function isStringifiedErrorObject(o: any): o is StringifiedErrorObject {
250+
return o && typeof o === 'object' && 'name' in o && 'message' in o && 'stack' in o
251+
}

packages/shared-lib/src/lib/translations.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,13 @@ interface ITranslatableMessage {
2828
key: string
2929
args?: { [key: string]: any }
3030
}
31+
32+
export function isITranslatableMessage(obj: unknown): obj is ITranslatableMessage {
33+
return (
34+
typeof obj === 'object' &&
35+
obj !== null &&
36+
'key' in obj &&
37+
typeof (obj as any).key === 'string' &&
38+
(!('args' in obj) || (typeof (obj as any).args === 'object' && (obj as any).args !== null))
39+
)
40+
}

0 commit comments

Comments
 (0)