Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ark/docs/components/dts/type.ts

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions ark/type/__tests__/integration/allConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ configure({
"string.creditCard": {
description: "configured"
},
"string.isbn": {
description: "configured"
},
"string.date.parse": {
description: "configured"
},
Expand Down
38 changes: 38 additions & 0 deletions ark/type/__tests__/keywords/string.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,44 @@ contextualize(() => {
).snap('must be a credit card number (was "5489582921773370")')
})

it("isbn", () => {
const Isbn = type("string.isbn")
// ISBN-10
attest(Isbn("4873118735")).equals("4873118735")
attest(Isbn("4-87311-873-5")).equals("4-87311-873-5")
attest(Isbn("4 87311 873 5")).equals("4 87311 873 5")
attest(Isbn("020530902X")).equals("020530902X")
attest(Isbn("0-205-30902-X")).equals("0-205-30902-X")
// ISBN-13
attest(Isbn("9784873118734")).equals("9784873118734")
attest(Isbn("978-4-87311-873-4")).equals("978-4-87311-873-4")
attest(Isbn("9790000000001")).equals("9790000000001")
// Invalid checksum
attest(Isbn("4873118736").toString()).equals(
'must be an ISBN (was "4873118736")'
)
attest(Isbn("9784873118735").toString()).equals(
'must be an ISBN (was "9784873118735")'
)
// Lowercase x is not accepted
attest(Isbn("020530902x").toString()).equals(
'must be an ISBN (was "020530902x")'
)
// X in wrong position
attest(Isbn("X234567890").toString()).equals(
'must be an ISBN (was "X234567890")'
)
// Wrong length
attest(Isbn("978487311873").toString()).equals(
'must be an ISBN (was "978487311873")'
)
// Non-digit characters
attest(Isbn("abc1234567890").toString()).equals(
'must be an ISBN (was "abc1234567890")'
)
attest(Isbn("").toString()).equals('must be an ISBN (was "")')
})

it("semver", () => {
attest(keywords.string.semver("1.0.0")).snap("1.0.0")
attest(keywords.string.semver("-1.0.0").toString()).snap(
Expand Down
40 changes: 40 additions & 0 deletions ark/type/keywords/string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,44 @@ export const creditCard = rootSchema({
}
})

// https://en.wikipedia.org/wiki/ISBN#ISBN-10_check_digits
export const isIsbn10 = (input: string): boolean => {
let sum = 0
for (let i = 0; i < 10; i++) {
const char = input[i]
const value = char === "X" ? 10 : Number.parseInt(char, 10)
sum += value * (10 - i)
}
return sum % 11 === 0
}

// https://en.wikipedia.org/wiki/ISBN#ISBN-13_check_digit_calculation
export const isIsbn13 = (input: string): boolean => {
let sum = 0
for (let i = 0; i < 13; i++) {
const value = Number.parseInt(input[i], 10)
sum += value * (i % 2 === 0 ? 1 : 3)
}
return sum % 10 === 0
}

const isbnSeparatorMatcher = /[ -]+/g
const isbn10Matcher = /^\d{9}[\dX]$/
const isbn13Matcher = /^\d{13}$/

export const isbn = rootSchema({
domain: "string",
predicate: {
meta: "an ISBN",
predicate: (input: string) => {
const sanitized = input.replace(isbnSeparatorMatcher, "")
if (isbn10Matcher.test(sanitized)) return isIsbn10(sanitized)
if (isbn13Matcher.test(sanitized)) return isIsbn13(sanitized)
return false
}
}
})

type DayDelimiter = "." | "/" | "-"

const dayDelimiterMatcher = /^[./-]$/
Expand Down Expand Up @@ -919,6 +957,7 @@ export const string = Scope.module(
email,
integer: stringInteger,
ip,
isbn,
json,
lower,
normalize,
Expand Down Expand Up @@ -953,6 +992,7 @@ export declare namespace string {
email: string
integer: stringInteger.submodule
ip: ip.submodule
isbn: string
json: stringJson.submodule
lower: lower.submodule
normalize: normalize.submodule
Expand Down
Loading