Skip to content

Commit

Permalink
feat(schema): add ISBN validation
Browse files Browse the repository at this point in the history
  • Loading branch information
ApacheTech committed Apr 13, 2023
1 parent 4b22702 commit 58ceaa9
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 1 deletion.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ When a client sends a TRACE request, the server responds with the exact same req

However, the TRACE method can also pose security risks, as it may expose sensitive information, such as authentication tokens or cookies, to attackers. For this reason, some web servers disable the TRACE method by default or only allow it to be used in restricted environments.

### ISBN Validation

There are three ISBN validators available on NPM; `isbn-validate` only validated ISBN-10 values. `isbn-validation` only validates ISBN-13 values. `isbn-validator` only validates ISBN-13 values that are not hyphenated. I wanted a validator that was able to validate hyphenated, and un-hyphenated ISBN-10 and ISBN-13 values. So, I took the working bits from all three libraries, and formed them into one single validation function.

For reference: [https://help.sap.com](https://help.sap.com/saphelp_gds20/helpdata/EN/8f/2be4b8983749f2be8a58c7925b6cb5/content.htm?no_cache=true)

## Retrospective

**TODO:** Write Retrospective.
33 changes: 33 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion src/db/schema/Book.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
const mongoose = require("mongoose");
const validateISBN = require("../validators/validateISBN");

module.exports = mongoose.model("Book", new mongoose.Schema({
isbn: {
type: String,
required: [true, "The book must have a valid ISBN."],
unique: [true, "The ISBN for any given book must be unique."]
unique: [true, "The ISBN for any given book must be unique."],
validate: {
validator: validateISBN,
message: props => `${props.value} is not a valid ISBN!`
}
},
genre: {
type: String,
Expand Down
26 changes: 26 additions & 0 deletions src/db/validators/__tests__/validateISBN.tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const validateISBN = require("../validateISBN");

describe("validateISBN Tests", () => {

test.each([
"978-3-16-148410-0",
"185723569X",
"9781857235692"
])("should be a valid ISBN.", (isbn) => {
const actual = validateISBN(isbn);
expect(actual).toBe(true);
});

test.each([
undefined,
null,
"",
"978-3-16-148410-1",
"185723569Y",
"9781857235691"
])("should not be a valid ISBN.", (isbn) => {
const actual = validateISBN(isbn);
expect(actual).toBe(false);
});

});
59 changes: 59 additions & 0 deletions src/db/validators/validateISBN.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* Validates a string representing an ISBN for books.
*
* This implementation cleans the input string by removing all non-numeric
* characters except 'X' at the end. It then checks the length of the cleaned
* string to ensure that it is either 10 or 13 characters long.
*
* For 10-digit ISBNs, it calculates the checksum by multiplying each digit by
* its weight (10 for the first digit, 9 for the second digit, etc.) and summing
* the results. If the last character is 'X', it is treated as a value of 10.
* The function then returns true if the sum is divisible by 11.
*
* For 13-digit ISBNs, it calculates the checksum by multiplying each digit by
* its weight (1 for the first digit, 3 for the second digit, 1 for the third digit,
* etc.) and summing the results. The function then returns true if the check digit
* (the last digit) is equal to 10 minus the sum modulo 10.
*
* @param {string} isbn - The string to validate.
* @returns {boolean} - Returns true if the string is a valid ISBN, and false otherwise.
*/
module.exports = function validateISBN(isbn)
{
if (isbn == null) return false;

// Remove all non-numeric characters except 'X' at the end.
const cleanedISBN = isbn.replace(/[^\dX]/gi, '');

// Check the length of the cleaned string.
if (cleanedISBN.length !== 10 && cleanedISBN.length !== 13) {
return false;
}

// For 10-digit ISBNs, calculate the checksum.
if (cleanedISBN.length === 10) {
let sum = 0;
for (let i = 0; i < 9; i++) {
sum += parseInt(cleanedISBN.charAt(i)) * (10 - i);
}
const lastChar = cleanedISBN.charAt(9);
if (lastChar === 'X') {
sum += 10;
} else {
sum += parseInt(lastChar);
}
return sum % 11 === 0;
}

// For 13-digit ISBNs, calculate the checksum.
if (cleanedISBN.length === 13) {
let sum = 0;
for (let i = 0; i < 12; i++) {
sum += parseInt(cleanedISBN.charAt(i)) * (i % 2 === 0 ? 1 : 3);
}
const checkDigit = (10 - (sum % 10)) % 10;
return checkDigit === parseInt(cleanedISBN.charAt(12));
}

return false;
};

0 comments on commit 58ceaa9

Please sign in to comment.