{-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} module Main where import Prelude (Double, Eq, IO, Integer, Show) import Control.Applicative ((<$>), (<*>)) import Control.Category ((.)) import Control.Lens import Data.Default (def) import Data.Either (Either (..)) import Data.Function (($)) import Data.Functor (fmap) import Data.Maybe (Maybe (..)) import Data.Monoid ((<>)) import Data.Text import Data.Time import Test.Tasty import Test.Tasty.HUnit import Text.XML (Document, readFile) import Text.XML.Decode data BookCategory = Haskell | Scala | Programming | FunctionalProgramming deriving (Eq,Show) bookCategoryFromText :: Text -> Maybe BookCategory bookCategoryFromText "Haskell" = Just Haskell bookCategoryFromText "Scala" = Just Scala bookCategoryFromText "Programming" = Just Programming bookCategoryFromText "Functional Programming" = Just FunctionalProgramming bookCategoryFromText _ = Nothing data LibrarySection = Fiction Text -- Fiction by author first letter(s) | NonFiction Double -- Dewey decimal number deriving (Eq,Show) makePrisms ''LibrarySection data Book = Book { _bookId :: Integer , _bookName :: Text , _bookSection :: LibrarySection , _bookPublished :: Day , _bookLastBorrowed :: Maybe UTCTime , _bookCategories :: [BookCategory] } deriving (Eq,Show) makeLenses ''Book data Library = Library { _books :: [Book] } deriving (Eq,Show) makeLenses ''Library instance DecodeCursor BookCategory where decode = parseCursor (parseMaybe "BookCategory" bookCategoryFromText) instance DecodeCursor LibrarySection where decode = decodeChoice [ choice (laxElement "fiction") decodeFiction , choice (laxElement "non_fiction") decodeNonFiction ] where decodeFiction c = Fiction <$> parseCursor parseText c decodeNonFiction c = NonFiction <$> parseCursor parseDouble c instance DecodeCursor Book where decode c = Book <$> decodeAttr "id" parseInteger c <*> decodeSingle (c %/ laxElement "name") <*> decodeSingle (c %/ laxElement "section") <*> (decodeSingle (c %/ laxElement "published") <&> toDay) <*> (decodeMay (c %/ laxElement "lastBorrowed") <&> fmap toUtcT) <*> decodeMany (c %/ laxElement "category") instance DecodeCursor Library where decode c = Library <$> decodeMany ( c %/ laxElement "book" ) decodeOk :: Assertion decodeOk = do c <- fromDocument <$> loadXmlForTest "ok" let r = decodeSingle c r @?= Right expectedLibrary where expectedLibrary = Library [ Book 1 "Learn you a haskell for great good!" (NonFiction 5.1) (fromGregorian 2011 4 21) (Just (UTCTime (fromGregorian 2015 1 5) 59400)) [Programming,Haskell,FunctionalProgramming] , Book 2 "Enterprise Pragmatic Scala" (Fiction "K") (fromGregorian 2013 5 1) Nothing [Programming,Scala] ] decodeBad :: Assertion decodeBad = do c <- fromDocument <$> loadXmlForTest "bad" let r = (decodeSingle c :: DecodeResult Library) r @?= Left ( "'For teh lulz!' was not BookCategory" ,[ MoveAxis Child , LaxElement "book" , MoveAxis Child , LaxElement "category" ]) decodeBadChoice :: Assertion decodeBadChoice = do c <- fromDocument <$> loadXmlForTest "bad_choice" let r = (decodeSingle c :: DecodeResult Library) r @?= Left ( "Choices Exhausted" , [ MoveAxis Child , LaxElement "book" , MoveAxis Child , LaxElement "section" , Choice [ [ MoveAxis Child , LaxElement "fiction" ] , [ MoveAxis Child , LaxElement "non_fiction" ]] Nothing ]) decodeFailedDecodeInsideChoice :: Assertion decodeFailedDecodeInsideChoice = do c <- fromDocument <$> loadXmlForTest "bad_decode_inside_choice" let r = (decodeSingle c :: DecodeResult Library) r @?= Left ( "'This is not a dewey decimal' was not a double" , [ MoveAxis Child , LaxElement "book" , MoveAxis Child , LaxElement "section" , Choice [ [ MoveAxis Child , LaxElement "fiction" ] ] (Just [ MoveAxis Child , LaxElement "non_fiction" ]) ]) loadXmlForTest :: Text -> IO Document loadXmlForTest tn = readFile def . unpack $ "tests/xml/" <> tn <> ".xml" tests :: TestTree tests = testGroup "DecodeTests" [ testCase "ok" decodeOk , testCase "bad" decodeBad , testCase "bad_choice" decodeBadChoice , testCase "bad_decode_inside_choice" decodeFailedDecodeInsideChoice ] main :: IO () main = defaultMain tests