Refactored Errors Messages to be references instead of looked up

This commit is contained in:
Alexander Cerutti
2021-06-21 21:56:04 +02:00
parent 07321fba92
commit d35cb627e5
6 changed files with 105 additions and 105 deletions

View File

@@ -1,12 +1,12 @@
import { createPass } from "../lib/factory"; import { createPass } from "../lib/factory";
import formatMessage from "../lib/messages"; import formatMessage, { ERROR } from "../lib/messages";
import * as fs from "fs"; import * as fs from "fs";
import * as path from "path"; import * as path from "path";
describe("createPass", () => { describe("createPass", () => {
it("should throw if first argument is not provided", async () => { it("should throw if first argument is not provided", async () => {
await expectAsync(createPass(undefined)).toBeRejectedWithError( await expectAsync(createPass(undefined)).toBeRejectedWithError(
formatMessage("CP_NO_OPTS"), formatMessage(ERROR.CP_NO_OPTS),
); );
}); });

View File

@@ -1,6 +1,6 @@
import * as Schemas from "./schemas"; import * as Schemas from "./schemas";
import { getModelContents, readCertificatesFromOptions } from "./parser"; import { getModelContents, readCertificatesFromOptions } from "./parser";
import formatMessage from "./messages"; import formatMessage, { ERROR } from "./messages";
const abmCertificates = Symbol("certificates"); const abmCertificates = Symbol("certificates");
const abmModel = Symbol("model"); const abmModel = Symbol("model");
@@ -25,7 +25,7 @@ interface AbstractModelOptions {
export async function createAbstractModel(options: AbstractFactoryOptions) { export async function createAbstractModel(options: AbstractFactoryOptions) {
if (!(options && Object.keys(options).length)) { if (!(options && Object.keys(options).length)) {
throw new Error(formatMessage("CP_NO_OPTS")); throw new Error(formatMessage(ERROR.CP_NO_OPTS));
} }
try { try {
@@ -40,7 +40,7 @@ export async function createAbstractModel(options: AbstractFactoryOptions) {
overrides: options.overrides, overrides: options.overrides,
}); });
} catch (err) { } catch (err) {
throw new Error(formatMessage("CP_INIT_ERROR", "abstract model", err)); throw new Error(formatMessage(ERROR.CP_INIT, "abstract model", err));
} }
} }

View File

@@ -1,6 +1,6 @@
import { Pass } from "./pass"; import { Pass } from "./pass";
import * as Schemas from "./schemas"; import * as Schemas from "./schemas";
import formatMessage from "./messages"; import formatMessage, { ERROR } from "./messages";
import { getModelContents, readCertificatesFromOptions } from "./parser"; import { getModelContents, readCertificatesFromOptions } from "./parser";
import { splitBufferBundle } from "./utils"; import { splitBufferBundle } from "./utils";
import { AbstractModel, AbstractFactoryOptions } from "./abstract"; import { AbstractModel, AbstractFactoryOptions } from "./abstract";
@@ -24,7 +24,7 @@ export async function createPass(
(options instanceof AbstractModel || Object.keys(options).length) (options instanceof AbstractModel || Object.keys(options).length)
) )
) { ) {
throw new Error(formatMessage("CP_NO_OPTS")); throw new Error(formatMessage(ERROR.CP_NO_OPTS));
} }
try { try {
@@ -74,7 +74,7 @@ export async function createPass(
); );
} }
} catch (err) { } catch (err) {
throw new Error(formatMessage("CP_INIT_ERROR", "pass", err)); throw new Error(formatMessage(ERROR.CP_INIT, "pass", err));
} }
} }
@@ -85,9 +85,8 @@ function createPassInstance(
additionalBuffers?: Schemas.BundleUnit, additionalBuffers?: Schemas.BundleUnit,
) { ) {
if (additionalBuffers) { if (additionalBuffers) {
const [additionalL10n, additionalBundle] = splitBufferBundle( const [additionalL10n, additionalBundle] =
additionalBuffers, splitBufferBundle(additionalBuffers);
);
Object.assign(model["l10nBundle"], additionalL10n); Object.assign(model["l10nBundle"], additionalL10n);
Object.assign(model["bundle"], additionalBundle); Object.assign(model["bundle"], additionalBundle);
} }

View File

@@ -1,5 +1,5 @@
const errors = { export const ERROR = {
CP_INIT_ERROR: CP_INIT:
"Something went really bad in the %s initialization! Look at the log below this message. It should contain all the infos about the problem: \n%s", "Something went really bad in the %s initialization! Look at the log below this message. It should contain all the infos about the problem: \n%s",
CP_NO_OPTS: CP_NO_OPTS:
"Cannot initialize the pass or abstract model creation: no options were passed.", "Cannot initialize the pass or abstract model creation: no options were passed.",
@@ -24,9 +24,9 @@ const errors = {
"Cannot proceed with pass creation due to bad keys format in overrides.", "Cannot proceed with pass creation due to bad keys format in overrides.",
NO_PASS_TYPE: NO_PASS_TYPE:
"Cannot proceed with pass creation. Model definition (pass.json) has no valid type in it.\nRefer to https://apple.co/2wzyL5J to choose a valid pass type.", "Cannot proceed with pass creation. Model definition (pass.json) has no valid type in it.\nRefer to https://apple.co/2wzyL5J to choose a valid pass type.",
}; } as const;
const debugMessages = { export const DEBUG = {
TRSTYPE_NOT_VALID: TRSTYPE_NOT_VALID:
'Transit type changing rejected as not compliant with Apple Specifications. Transit type would become "%s" but should be in [PKTransitTypeAir, PKTransitTypeBoat, PKTransitTypeBus, PKTransitTypeGeneric, PKTransitTypeTrain]', 'Transit type changing rejected as not compliant with Apple Specifications. Transit type would become "%s" but should be in [PKTransitTypeAir, PKTransitTypeBoat, PKTransitTypeBus, PKTransitTypeGeneric, PKTransitTypeTrain]',
BRC_NOT_SUPPORTED: BRC_NOT_SUPPORTED:
@@ -46,9 +46,11 @@ const debugMessages = {
"Unable to parse Personalization.json. File is not a valid JSON. Error: %s", "Unable to parse Personalization.json. File is not a valid JSON. Error: %s",
PRS_REMOVED: PRS_REMOVED:
"Personalization has been removed as it requires an NFC-enabled pass to work.", "Personalization has been removed as it requires an NFC-enabled pass to work.",
}; } as const;
type AllMessages = keyof (typeof debugMessages & typeof errors); type ERROR_OR_DEBUG_MESSAGE =
| typeof ERROR[keyof typeof ERROR]
| typeof DEBUG[keyof typeof DEBUG];
/** /**
* Creates a message with replaced values * Creates a message with replaced values
@@ -56,24 +58,14 @@ type AllMessages = keyof (typeof debugMessages & typeof errors);
* @param {any[]} values * @param {any[]} values
*/ */
export default function format(messageName: AllMessages, ...values: any[]) { export default function format(
messageName: ERROR_OR_DEBUG_MESSAGE,
...values: any[]
) {
// reversing because it is better popping than shifting. // reversing because it is better popping than shifting.
let replaceValues = values.reverse(); let replaceValues = values.reverse();
return resolveMessageName(messageName).replace(/%s/g, () => { return messageName.replace(/%s/g, () => {
let next = replaceValues.pop(); let next = replaceValues.pop();
return next !== undefined ? next : "<passedValueIsUndefined>"; return next !== undefined ? next : "<passedValueIsUndefined>";
}); });
} }
/**
* Looks among errors and debugMessages for the specified message name
* @param {string} name
*/
function resolveMessageName(name: AllMessages): string {
if (!errors[name] && !debugMessages[name]) {
return `<ErrorName "${name}" is not linked to any error messages>`;
}
return errors[name] || debugMessages[name];
}

View File

@@ -1,6 +1,6 @@
import * as path from "path"; import * as path from "path";
import forge from "node-forge"; import forge from "node-forge";
import formatMessage from "./messages"; import formatMessage, { ERROR, DEBUG } from "./messages";
import * as Schemas from "./schemas"; import * as Schemas from "./schemas";
import { import {
removeHidden, removeHidden,
@@ -29,7 +29,7 @@ export async function getModelContents(model: Schemas.FactoryOptions["model"]) {
} else if (typeof model === "object" && Object.keys(model).length) { } else if (typeof model === "object" && Object.keys(model).length) {
modelContents = getModelBufferContents(model); modelContents = getModelBufferContents(model);
} else { } else {
throw new Error(formatMessage("MODEL_NOT_VALID")); throw new Error(formatMessage(ERROR.MODEL_NOT_VALID));
} }
const modelFiles = Object.keys(modelContents.bundle); const modelFiles = Object.keys(modelContents.bundle);
@@ -38,7 +38,9 @@ export async function getModelContents(model: Schemas.FactoryOptions["model"]) {
hasFilesWithName("icon", modelFiles, "startsWith"); hasFilesWithName("icon", modelFiles, "startsWith");
if (!isModelInitialized) { if (!isModelInitialized) {
throw new Error(formatMessage("MODEL_UNINITIALIZED", "parse result")); throw new Error(
formatMessage(ERROR.MODEL_UNINITIALIZED, "parse result"),
);
} }
// ======================= // // ======================= //
@@ -83,7 +85,7 @@ export async function getModelContents(model: Schemas.FactoryOptions["model"]) {
return modelContents; return modelContents;
} }
} catch (err) { } catch (err) {
prsDebug(formatMessage("PRS_INVALID", err)); prsDebug(formatMessage(DEBUG.PRS_INVALID, err));
deletePersonalization(modelContents.bundle, logoFullNames); deletePersonalization(modelContents.bundle, logoFullNames);
} }
@@ -117,7 +119,10 @@ export async function getModelFolderContents(
// Icon is required to proceed // Icon is required to proceed
if (!isModelInitialized) { if (!isModelInitialized) {
throw new Error( throw new Error(
formatMessage("MODEL_UNINITIALIZED", path.parse(model).name), formatMessage(
ERROR.MODEL_UNINITIALIZED,
path.parse(model).name,
),
); );
} }
@@ -144,53 +149,55 @@ export async function getModelFolderContents(
// Reading concurrently localizations folder // Reading concurrently localizations folder
// and their files and their buffers // and their files and their buffers
const L10N_FilesListByFolder: Array<Schemas.BundleUnit> = await Promise.all( const L10N_FilesListByFolder: Array<Schemas.BundleUnit> =
l10nFolders.map(async (folderPath) => { await Promise.all(
// Reading current folder l10nFolders.map(async (folderPath) => {
const currentLangPath = path.join(modelPath, folderPath); // Reading current folder
const currentLangPath = path.join(modelPath, folderPath);
const files = await readDir(currentLangPath); const files = await readDir(currentLangPath);
// Transforming files path to a model-relative path // Transforming files path to a model-relative path
const validFiles = removeHidden(files).map((file) => const validFiles = removeHidden(files).map((file) =>
path.join(currentLangPath, file), path.join(currentLangPath, file),
); );
// Getting all the buffers from file paths // Getting all the buffers from file paths
const buffers = await Promise.all( const buffers = await Promise.all(
validFiles.map((file) => validFiles.map((file) =>
readFile(file).catch(() => Buffer.alloc(0)), readFile(file).catch(() => Buffer.alloc(0)),
), ),
); );
// Assigning each file path to its buffer // Assigning each file path to its buffer
// and discarding the empty ones // and discarding the empty ones
return validFiles.reduce<Schemas.BundleUnit>( return validFiles.reduce<Schemas.BundleUnit>(
(acc, file, index) => { (acc, file, index) => {
if (!buffers[index].length) { if (!buffers[index].length) {
return acc; return acc;
} }
const fileComponents = file.split(path.sep); const fileComponents = file.split(path.sep);
const fileName = const fileName =
fileComponents[fileComponents.length - 1]; fileComponents[fileComponents.length - 1];
return { return {
...acc, ...acc,
[fileName]: buffers[index], [fileName]: buffers[index],
}; };
}, },
{}, {},
); );
}), }),
); );
const l10nBundle: Schemas.PartitionedBundle["l10nBundle"] = Object.assign( const l10nBundle: Schemas.PartitionedBundle["l10nBundle"] =
{}, Object.assign(
...L10N_FilesListByFolder.map((folder, index) => ({ {},
[l10nFolders[index]]: folder, ...L10N_FilesListByFolder.map((folder, index) => ({
})), [l10nFolders[index]]: folder,
); })),
);
return { return {
bundle, bundle,
@@ -200,13 +207,15 @@ export async function getModelFolderContents(
if (err?.code === "ENOENT") { if (err?.code === "ENOENT") {
if (err.syscall === "open") { if (err.syscall === "open") {
// file opening failed // file opening failed
throw new Error(formatMessage("MODELF_NOT_FOUND", err.path)); throw new Error(
formatMessage(ERROR.MODELF_NOT_FOUND, err.path),
);
} else if (err.syscall === "scandir") { } else if (err.syscall === "scandir") {
// directory reading failed // directory reading failed
const pathContents = (err.path as string).split(/(\/|\\\?)/); const pathContents = (err.path as string).split(/(\/|\\\?)/);
throw new Error( throw new Error(
formatMessage( formatMessage(
"MODELF_FILE_NOT_FOUND", ERROR.MODELF_FILE_NOT_FOUND,
pathContents[pathContents.length - 1], pathContents[pathContents.length - 1],
), ),
); );
@@ -246,7 +255,7 @@ export function getModelBufferContents(
// Icon is required to proceed // Icon is required to proceed
if (!isModelInitialized) { if (!isModelInitialized) {
throw new Error(formatMessage("MODEL_UNINITIALIZED", "Buffers")); throw new Error(formatMessage(ERROR.MODEL_UNINITIALIZED, "Buffers"));
} }
// separing localization folders from bundle files // separing localization folders from bundle files
@@ -278,7 +287,7 @@ export async function readCertificatesFromOptions(
Schemas.isValid(options, Schemas.CertificatesSchema) Schemas.isValid(options, Schemas.CertificatesSchema)
) )
) { ) {
throw new Error(formatMessage("CP_NO_CERTS")); throw new Error(formatMessage(ERROR.CP_NO_CERTS));
} }
let signerKey: string; let signerKey: string;
@@ -320,7 +329,7 @@ export async function readCertificatesFromOptions(
const pem = parsePEM(certName, file, passphrase); const pem = parsePEM(certName, file, passphrase);
if (!pem) { if (!pem) {
throw new Error(formatMessage("INVALID_CERTS", certName)); throw new Error(formatMessage(ERROR.INVALID_CERTS, certName));
} }
return { [certName]: pem }; return { [certName]: pem };
@@ -333,7 +342,7 @@ export async function readCertificatesFromOptions(
} }
throw new Error( throw new Error(
formatMessage("INVALID_CERT_PATH", path.parse(err.path).base), formatMessage(ERROR.INVALID_CERT_PATH, path.parse(err.path).base),
); );
} }
} }

View File

@@ -6,7 +6,7 @@ import { ZipFile } from "yazl";
import type Joi from "joi"; import type Joi from "joi";
import * as Schemas from "./schemas"; import * as Schemas from "./schemas";
import formatMessage from "./messages"; import formatMessage, { ERROR, DEBUG } from "./messages";
import FieldsArray from "./fieldsArray"; import FieldsArray from "./fieldsArray";
import { import {
generateStringFile, generateStringFile,
@@ -56,7 +56,7 @@ export class Pass {
constructor(options: Schemas.PassInstance) { constructor(options: Schemas.PassInstance) {
if (!Schemas.isValid(options, Schemas.PassInstance)) { if (!Schemas.isValid(options, Schemas.PassInstance)) {
throw new Error(formatMessage("REQUIR_VALID_FAILED")); throw new Error(formatMessage(ERROR.REQUIR_VALID_FAILED));
} }
this.Certificates = options.certificates; this.Certificates = options.certificates;
@@ -68,7 +68,7 @@ export class Pass {
this.bundle["pass.json"].toString("utf8"), this.bundle["pass.json"].toString("utf8"),
); );
} catch (err) { } catch (err) {
throw new Error(formatMessage("PASSFILE_VALIDATION_FAILED")); throw new Error(formatMessage(ERROR.PASSFILE_VALIDATION_FAILED));
} }
// Parsing the options and extracting only the valid ones. // Parsing the options and extracting only the valid ones.
@@ -78,7 +78,7 @@ export class Pass {
); );
if (validOverrides === null) { if (validOverrides === null) {
throw new Error(formatMessage("OVV_KEYS_BADFORMAT")); throw new Error(formatMessage(ERROR.OVV_KEYS_BADFORMAT));
} }
this.type = Object.keys(this.passCore).find((key) => this.type = Object.keys(this.passCore).find((key) =>
@@ -86,7 +86,7 @@ export class Pass {
) as keyof Schemas.ValidPassType; ) as keyof Schemas.ValidPassType;
if (!this.type) { if (!this.type) {
throw new Error(formatMessage("NO_PASS_TYPE")); throw new Error(formatMessage(ERROR.NO_PASS_TYPE));
} }
// Parsing and validating pass.json keys // Parsing and validating pass.json keys
@@ -184,7 +184,7 @@ export class Pass {
!this[passProps].nfc && !this[passProps].nfc &&
currentBundleFiles.includes("personalization.json") currentBundleFiles.includes("personalization.json")
) { ) {
genericDebug(formatMessage("PRS_REMOVED")); genericDebug(formatMessage(DEBUG.PRS_REMOVED));
deletePersonalization( deletePersonalization(
this.bundle, this.bundle,
getAllFilesWithName( getAllFilesWithName(
@@ -222,13 +222,13 @@ export class Pass {
this.l10nBundles[languageBundleDirname] = {}; this.l10nBundles[languageBundleDirname] = {};
} }
this.l10nBundles[languageBundleDirname][ this.l10nBundles[languageBundleDirname]["pass.strings"] =
"pass.strings" Buffer.concat([
] = Buffer.concat([ this.l10nBundles[languageBundleDirname][
this.l10nBundles[languageBundleDirname]["pass.strings"] || "pass.strings"
Buffer.alloc(0), ] || Buffer.alloc(0),
strings, strings,
]); ]);
} }
if ( if (
@@ -453,7 +453,7 @@ export class Pass {
const autogen = barcodesFromUncompleteData(data[0]); const autogen = barcodesFromUncompleteData(data[0]);
if (!autogen.length) { if (!autogen.length) {
barcodeDebug(formatMessage("BRC_AUTC_MISSING_DATA")); barcodeDebug(formatMessage(DEBUG.BRC_AUTC_MISSING_DATA));
return this; return this;
} }
@@ -520,17 +520,17 @@ export class Pass {
} }
if (typeof chosenFormat !== "string") { if (typeof chosenFormat !== "string") {
barcodeDebug(formatMessage("BRC_FORMATTYPE_UNMATCH")); barcodeDebug(formatMessage(DEBUG.BRC_FORMATTYPE_UNMATCH));
return this; return this;
} }
if (chosenFormat === "PKBarcodeFormatCode128") { if (chosenFormat === "PKBarcodeFormatCode128") {
barcodeDebug(formatMessage("BRC_BW_FORMAT_UNSUPPORTED")); barcodeDebug(formatMessage(DEBUG.BRC_BW_FORMAT_UNSUPPORTED));
return this; return this;
} }
if (!(barcodes && barcodes.length)) { if (!(barcodes && barcodes.length)) {
barcodeDebug(formatMessage("BRC_NO_POOL")); barcodeDebug(formatMessage(DEBUG.BRC_NO_POOL));
return this; return this;
} }
@@ -540,7 +540,7 @@ export class Pass {
); );
if (index === -1) { if (index === -1) {
barcodeDebug(formatMessage("BRC_NOT_SUPPORTED")); barcodeDebug(formatMessage(DEBUG.BRC_NOT_SUPPORTED));
return this; return this;
} }
@@ -571,7 +571,7 @@ export class Pass {
Schemas.isValid(data, Schemas.NFC) Schemas.isValid(data, Schemas.NFC)
) )
) { ) {
genericDebug(formatMessage("NFC_INVALID")); genericDebug(formatMessage(DEBUG.NFC_INVALID));
return this; return this;
} }
@@ -633,7 +633,7 @@ export class Pass {
}); });
if (this.type === "boardingPass" && !this[transitType]) { if (this.type === "boardingPass" && !this[transitType]) {
throw new Error(formatMessage("TRSTYPE_REQUIRED")); throw new Error(formatMessage(ERROR.TRSTYPE_REQUIRED));
} }
passFile[this.type]["transitType"] = this[transitType]; passFile[this.type]["transitType"] = this[transitType];
@@ -643,7 +643,7 @@ export class Pass {
set transitType(value: string) { set transitType(value: string) {
if (!Schemas.isValid(value, Schemas.TransitType)) { if (!Schemas.isValid(value, Schemas.TransitType)) {
genericDebug(formatMessage("TRSTYPE_NOT_VALID", value)); genericDebug(formatMessage(DEBUG.TRSTYPE_NOT_VALID, value));
this[transitType] = this[transitType] || ""; this[transitType] = this[transitType] || "";
return; return;
} }
@@ -701,7 +701,7 @@ function processDate(key: string, date: Date): string | null {
const dateParse = dateToW3CString(date); const dateParse = dateToW3CString(date);
if (!dateParse) { if (!dateParse) {
genericDebug(formatMessage("DATE_FORMAT_UNMATCH", key)); genericDebug(formatMessage(DEBUG.DATE_FORMAT_UNMATCH, key));
return null; return null;
} }