-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add data model validation (#43)
* feat(validator): Add validate function * feat(validator): Define ValidationError * test(validator): Add test for IBAN validation * feat(validator): Add validator package * feat(validator): Add validateBankAccount function * feat(payment): Implement partial validateSimplePayment * chore: Cleanup code * feat(validator): Validate currency code * feat(payment): Implement payment due date validation * feat(validator): Add validation for validateDataModel * refactor(validator): Export validateDataModel * refactor(validator): Rename validateModel to validate and set default to true * fix(validator): Fix typo in validations.test.ts * docs: Update documentation * refactor(validator): Adjust ValidationErrorMessage enum * refactor(validator): Remove TODO from validateSimplePayment * fix(validator): Handle arbitrary fields equal to an empty string without throwing an error * feat(cli): Enhance CLI functionality
- Loading branch information
1 parent
d08c57c
commit f969570
Showing
7 changed files
with
268 additions
and
6 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
export { decode, detect, parse } from "./decode.js"; | ||
export { encode, generate } from "./encode.js"; | ||
export { validateDataModel, ValidationErrorMessage } from "./validations.js"; | ||
|
||
export * from "./types.js"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
import assert from "node:assert"; | ||
import test, { describe } from "node:test"; | ||
import { PaymentOptions } from "./types.js"; | ||
import { | ||
validateBankAccount, | ||
validateDataModel, | ||
validateSimplePayment, | ||
ValidationError, | ||
ValidationErrorMessage, | ||
} from "./validations.js"; | ||
|
||
const iban = "LC14BOSL123456789012345678901234"; | ||
const validBankAccount = { | ||
iban, | ||
}; | ||
|
||
describe("validateBankAccount", () => { | ||
const path = "payments[0].bankAccounts[0]"; | ||
test("validate IBAN", () => { | ||
assert.throws( | ||
() => | ||
validateBankAccount({ | ||
iban: "1234567890", | ||
}, path), | ||
new ValidationError(ValidationErrorMessage.IBAN, `${path}.iban`), | ||
); | ||
assert.doesNotThrow(() => validateBankAccount(validBankAccount, path)); | ||
}); | ||
|
||
test("validate BIC", () => { | ||
assert.throws( | ||
() => | ||
validateBankAccount({ | ||
iban, | ||
bic: "123", | ||
}, path), | ||
new ValidationError(ValidationErrorMessage.BIC, `${path}.bic`), | ||
); | ||
assert.doesNotThrow( | ||
() => | ||
validateBankAccount({ | ||
iban, | ||
bic: "", | ||
}, path), | ||
); | ||
assert.doesNotThrow(() => | ||
validateBankAccount({ | ||
iban, | ||
bic: "DEUTDEFF500", | ||
}, path) | ||
); | ||
}); | ||
}); | ||
|
||
describe("validateSimplePayment", () => { | ||
const path = "payments[0]"; | ||
test("validate bankAccounts", () => { | ||
assert.throws( | ||
() => { | ||
validateSimplePayment({ | ||
bankAccounts: [validBankAccount, { | ||
iban: "123", | ||
}], | ||
currencyCode: "EUR", | ||
}, path); | ||
}, | ||
new ValidationError(ValidationErrorMessage.IBAN, `${path}.bankAccounts[1].iban`), | ||
); | ||
}); | ||
|
||
test("validate currencyCode", () => { | ||
assert.doesNotThrow(() => | ||
validateSimplePayment({ | ||
bankAccounts: [validBankAccount], | ||
currencyCode: "EUR", | ||
}, path) | ||
); | ||
assert.throws( | ||
() => | ||
validateSimplePayment({ | ||
bankAccounts: [validBankAccount], | ||
currencyCode: "e", | ||
}, path), | ||
new ValidationError(ValidationErrorMessage.CurrencyCode, `${path}.currencyCode`), | ||
); | ||
}); | ||
|
||
test("validate paymentDueDate", () => { | ||
assert.doesNotThrow(() => | ||
validateSimplePayment({ | ||
bankAccounts: [validBankAccount], | ||
currencyCode: "EUR", | ||
paymentDueDate: "2024-08-08", | ||
}, path) | ||
); | ||
|
||
assert.throws( | ||
() => | ||
validateSimplePayment({ | ||
bankAccounts: [validBankAccount], | ||
currencyCode: "EUR", | ||
paymentDueDate: "2024-08-52", | ||
}, path), | ||
new ValidationError(ValidationErrorMessage.Date, `${path}.paymentDueDate`), | ||
); | ||
}); | ||
}); | ||
|
||
describe("validateDataModel", () => { | ||
assert.doesNotThrow(() => | ||
validateDataModel({ | ||
payments: [{ | ||
type: PaymentOptions.PaymentOrder, | ||
currencyCode: "EUR", | ||
bankAccounts: [validBankAccount], | ||
}], | ||
}) | ||
); | ||
|
||
assert.throws( | ||
() => | ||
validateDataModel({ | ||
payments: [{ | ||
type: PaymentOptions.PaymentOrder, | ||
currencyCode: "E", | ||
bankAccounts: [validBankAccount], | ||
}], | ||
}), | ||
new ValidationError(ValidationErrorMessage.CurrencyCode, `payments[0].currencyCode`), | ||
); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import validator from "validator"; | ||
import { | ||
BankAccount, | ||
DataModel, | ||
SimplePayment, | ||
} from "./types.js"; | ||
|
||
export enum ValidationErrorMessage { | ||
IBAN = "Invalid IBAN. Make sure ISO 13616 format is used.", | ||
BIC = "Invalid BIC. Make sure ISO 9362 format is used.", | ||
CurrencyCode = "Invalid currency code. Make sure ISO 4217 format is used.", | ||
Date = "Invalid date. Make sure ISO 8601 format is used.", | ||
} | ||
|
||
/** | ||
* This error will be thrown in case of a validation issue. It provides message with error description and specific path to issue in dataModel object. | ||
*/ | ||
export class ValidationError extends Error { | ||
override name = "ValidationError"; | ||
path: string; | ||
|
||
/** | ||
* @param message - explains, what is wrong on the specific field | ||
* @param path - navigates to the specific field in DataModel, where error occurred | ||
*/ | ||
constructor(message: ValidationErrorMessage, path: string) { | ||
super(String(message)); | ||
this.path = path; | ||
} | ||
} | ||
|
||
/** | ||
* validates bankAccount fields: | ||
* - iban (ISO 13616) | ||
* - bic (ISO 9362) | ||
*/ | ||
export function validateBankAccount(bankAccount: BankAccount, path: string) { | ||
if (!validator.isIBAN(bankAccount.iban)) { | ||
throw new ValidationError(ValidationErrorMessage.IBAN, `${path}.iban`); | ||
} | ||
if (bankAccount.bic && !validator.isBIC(bankAccount.bic)) { | ||
throw new ValidationError(ValidationErrorMessage.BIC, `${path}.bic`); | ||
} | ||
} | ||
|
||
/** | ||
* validate simple payment fields: | ||
* - currencyCode (ISO 4217) | ||
* - paymentDueDate (ISO 8601) | ||
* - bankAccounts | ||
* | ||
* @see validateBankAccount | ||
*/ | ||
export function validateSimplePayment(simplePayment: SimplePayment, path: string) { | ||
for (const [index, bankAccount] of simplePayment.bankAccounts.entries()) { | ||
validateBankAccount(bankAccount, `${path}.bankAccounts[${index}]`); | ||
} | ||
if (simplePayment.currencyCode && !validator.isISO4217(simplePayment.currencyCode)) { | ||
throw new ValidationError( | ||
ValidationErrorMessage.CurrencyCode, | ||
`${path}.currencyCode`, | ||
); | ||
} | ||
if (simplePayment.paymentDueDate && !validator.isDate(simplePayment.paymentDueDate)) { | ||
throw new ValidationError( | ||
ValidationErrorMessage.Date, | ||
`${path}.paymentDueDate`, | ||
); | ||
} | ||
} | ||
|
||
/** | ||
* Validate `payments` field of dataModel. | ||
* | ||
* @see validateSimplePayment | ||
* @see ValidationError | ||
*/ | ||
export function validateDataModel(dataModel: DataModel): DataModel { | ||
for (const [index, payment] of dataModel.payments.entries()) { | ||
validateSimplePayment(payment, `payments[${index}]`); | ||
} | ||
return dataModel; | ||
} |