module Vaultaire.Types.SourceDict
(
SourceDict,
unionSource,
diffSource,
lookupSource,
hashSource,
makeSourceDict
) where
import Blaze.ByteString.Builder (fromByteString, toByteString)
import Blaze.ByteString.Builder.Char8 (fromChar)
import Control.Applicative (many, optional, (<$>), (<*), (<*>))
import Control.Arrow ((***))
import Control.Exception (SomeException (..))
import Crypto.MAC.SipHash
import Data.Attoparsec.Text (parseOnly)
import qualified Data.Attoparsec.Text as PT
import Data.HashMap.Strict (HashMap, difference, foldlWithKey', fromList,
lookup, toList, union)
import Data.List (sortBy)
import Data.Maybe (isNothing)
import Data.Monoid (Monoid, mempty, (<>))
import Data.Ord (comparing)
import Data.Serialize
import Data.Text (Text, find, pack, unpack)
import Data.Text.Encoding (decodeUtf8', encodeUtf8)
import Data.Word
import Prelude hiding (lookup)
import Test.QuickCheck
import Vaultaire.Classes.WireFormat
newtype SourceDict = SourceDict { unSourceDict :: HashMap Text Text }
deriving (Eq, Monoid)
makeSourceDict :: HashMap Text Text -> Either String SourceDict
makeSourceDict hm = if foldlWithKey' allGoodKV True hm
then Right $ SourceDict hm
else Left "Bad character in source dict,\
\ no ',' or ':' allowed."
where allGoodKV acc k v = acc && (allGoodChars k && allGoodChars v)
allGoodChars = isNothing . find (\c -> c == ':' || c == ',')
instance Show SourceDict where
show (SourceDict sd) = "dict=" <> show (toList sd)
instance WireFormat SourceDict where
fromWire bs = either (Left . SomeException) parse (decodeUtf8' bs)
where
parse t = either (Left . SomeException . userError)
(Right . SourceDict . fromList)
(parseOnly tagParser t)
tagParser = many $ (,) <$> k <*> v
where
k = PT.takeWhile (/= ':') <* ":"
v = PT.takeWhile (/= ',') <* optional ","
toWire = toByteString . foldlWithKey' f mempty . unSourceDict
where
f acc k v = acc <> text k <> fromChar ':' <> text v <> fromChar ','
text = fromByteString . encodeUtf8
instance Arbitrary SourceDict where
arbitrary = do
attempt <- fromList . map (pack *** pack) <$> arbitrary
either (const arbitrary) return $ makeSourceDict attempt
unionSource :: SourceDict -> SourceDict -> SourceDict
unionSource (SourceDict a) (SourceDict b) = SourceDict $ union a b
diffSource :: SourceDict -> SourceDict -> SourceDict
diffSource (SourceDict a) (SourceDict b) = SourceDict $ difference a b
lookupSource :: Text -> SourceDict -> Maybe Text
lookupSource key sd = lookup key $ unSourceDict sd
hashSource :: SourceDict -> Word64
hashSource (SourceDict sd) =
let canonicalList = sortBy (comparing fst) (map (unpack *** unpack) $ toList sd) in
let (SipHash ret) = hash (SipKey 0 0) (encode canonicalList) in
ret