{-# LANGUAGE OverloadedStrings #-}
module Data.ISBN.ISBN13
( ISBN(..)
, validateISBN13
, renderISBN13ValidationError
, ISBN13ValidationError(..)
, confirmISBN13CheckDigit
, calculateISBN13CheckDigitValue
, numericValueToISBN13Char
, unsafeToISBN13
) where
import Control.Monad
import Data.Char
import Data.Text as Text
import Data.ISBN.Types ( ISBN (ISBN13) )
validateISBN13 :: Text -> Either ISBN13ValidationError ISBN
validateISBN13 input = do
let inputWithoutHyphens = Text.filter (/= '-') input
unless (Text.length inputWithoutHyphens == 13) $
Left ISBN13InvalidInputLength
let illegalCharacters = Text.filter (not . isNumericCharacter) inputWithoutHyphens
unless (Text.length illegalCharacters == 0) $
Left ISBN13IllegalCharactersInInput
unless (confirmISBN13CheckDigit inputWithoutHyphens) $
Left ISBN13InvalidCheckDigit
pure $ ISBN13 inputWithoutHyphens
data ISBN13ValidationError
= ISBN13InvalidInputLength
| ISBN13IllegalCharactersInInput
| ISBN13InvalidCheckDigit
deriving (Show, Eq)
renderISBN13ValidationError :: ISBN13ValidationError -> Text
renderISBN13ValidationError validationError =
case validationError of
ISBN13InvalidInputLength ->
"An ISBN-13 must be 13 characters, not counting hyphens"
ISBN13IllegalCharactersInInput ->
"Every non-hyphen character of an ISBN-13 must be a number"
ISBN13InvalidCheckDigit ->
"The supplied ISBN-13 is not valid"
isNumericCharacter :: Char -> Bool
isNumericCharacter char = char `elem` ("1234567890" :: String)
confirmISBN13CheckDigit :: Text -> Bool
confirmISBN13CheckDigit isbn13 =
calculateISBN13CheckDigitValue (Text.init isbn13) == isbn13CharToNumericValue (Text.last isbn13)
calculateISBN13CheckDigitValue :: Text -> Int
calculateISBN13CheckDigitValue input =
go 1 (unpack input) 0
where
go w charList acc =
case charList of
[] -> (10 - (acc `mod` 10)) `mod` 10
c:clist -> go ((w + 2) `mod` 4) clist (acc + w * isbn13CharToNumericValue c)
isbn13CharToNumericValue :: Char -> Int
isbn13CharToNumericValue = digitToInt
numericValueToISBN13Char :: Int -> Char
numericValueToISBN13Char c = Text.head $ pack $ show c
unsafeToISBN13 :: Text -> ISBN
unsafeToISBN13 = ISBN13