packages feed

relapse-0.1.0.0: test/RLPTest.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards   #-}
{-# LANGUAGE TemplateHaskell   #-}
module RLPTest where

import           Control.Monad           (sequence)
import           Data.Aeson
import           Data.Aeson.Types        (typeMismatch)
import           Data.ByteString
import qualified Data.ByteString.Lazy    as BL
import qualified Data.Map.Strict         as M
import           Data.Maybe              (fromJust)
import           Data.Semigroup          ((<>))
import qualified Data.Text               as T
import qualified Data.Text.Encoding      as TE
import qualified Data.Vector             as V

import           Development.IncludeFile

import qualified Data.RLP                as RLP

data RLPTestInput = StringInput ByteString
                  | NumberInput Integer
                  | ListInput [RLPTestInput]
                  deriving (Read, Show)

-- The test JSON takes advantage of the fact that you can mix and match types in JSON arrays
-- so naturally, that doesn't play well with haskell, and we can't *REALLY* make FromJSON and ToJSON
-- maintain identity. So we cheat :P. Our biggest issue is that RLP doesn't have an "Integer" type
-- it's effectively stored as a big-endian String. So we need to really handle the case of
-- StringInput == NumberInput and vice versa
instance Eq RLPTestInput where
    (StringInput s1) == (StringInput s2) = s1 == s2
    (NumberInput n1) == (NumberInput n2) = n1 == n2
    (ListInput l1)   == (ListInput l2)   = l1 == l2

    StringInput{}   ==  ListInput{}    = False -- impossible
    (StringInput s) == (NumberInput n) = RLP.unpackBE (unpack s) == n -- todo this case

    NumberInput{}   ==  ListInput{}    = False -- also impossible
    n@NumberInput{} == s@StringInput{} = s == n -- take advantage of the commutative case

    o1 == o2 = False

instance RLP.RLPEncodable RLPTestInput where
    rlpEncode (StringInput s) = RLP.String s
    rlpEncode (NumberInput n) = RLP.rlpEncode n
    rlpEncode (ListInput xs)  = RLP.Array $ RLP.rlpEncode <$> xs

    rlpDecode (RLP.String s) = Right (StringInput s) -- todo this totes wont work for NumInputs
    rlpDecode (RLP.Array xs) = ListInput <$> sequence (RLP.rlpDecode <$> xs)

data RLPTest = RLPTest { input :: RLPTestInput, output :: T.Text }
    deriving (Eq, Read, Show)

instance FromJSON RLPTestInput where
    parseJSON (String s) | T.null s  = return (StringInput "")
                         | otherwise = case T.head s of
                            '#' -> return . NumberInput . read . T.unpack $ T.tail s
                            _   -> return . StringInput $ TE.encodeUtf8 s
    parseJSON (Number n) = return . NumberInput $ round n
    parseJSON (Array a)  = ListInput . V.toList <$> V.forM a parseJSON
    parseJSON x          = typeMismatch "RLPTestInput" x

instance ToJSON RLPTestInput where
    toJSON (StringInput s) = String $ TE.decodeUtf8 s
    toJSON (NumberInput n) = Number $ fromIntegral n
    toJSON (ListInput xs)  = toJSON xs

instance FromJSON RLPTest where
    parseJSON (Object o) = RLPTest <$> (o .: "in") <*> (o .: "out")
    parseJSON x          = typeMismatch "RLPTest" x

instance ToJSON RLPTest where
    toJSON     RLPTest{..} = object [ "in" .= input,   "out" .= output ]
    toEncoding RLPTest{..} = pairs  ( "in" .= input <> "out" .= output )

$(includeFileInSource "test/resources/rlptest.json" "officialRLPTests'")

officialRLPTests :: Either String [(T.Text, RLPTest)]
officialRLPTests = M.toList <$> eitherDecode (BL.fromStrict officialRLPTests')