{-|

Instances for anonymizing sensitive data in various types.

Note that there is no clear way to anonymize numbers.

-}

module Hledger.Cli.Anon
    ( Anon(..)
    , anonAccount
    )
where

import Control.Arrow (first)
import Data.Hashable (hash)
import Data.Word (Word32)
import Numeric (showHex)
import qualified Data.Text as T

import Hledger.Data
import Data.Map (mapKeys)

class Anon a where
    -- | Consistent converter to structure with sensitive data anonymized
    anon :: a -> a

instance Anon Journal where
    -- Apply the anonymisation transformation on a journal after finalisation
    anon :: Journal -> Journal
anon Journal
j = Journal
j { jtxns :: [Transaction]
jtxns = (Transaction -> Transaction) -> [Transaction] -> [Transaction]
forall a b. (a -> b) -> [a] -> [b]
map Transaction -> Transaction
forall a. Anon a => a -> a
anon ([Transaction] -> [Transaction])
-> (Journal -> [Transaction]) -> Journal -> [Transaction]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Journal -> [Transaction]
jtxns (Journal -> [Transaction]) -> Journal -> [Transaction]
forall a b. (a -> b) -> a -> b
$ Journal
j
               , jparseparentaccounts :: [Text]
jparseparentaccounts  = (Text -> Text) -> [Text] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map Text -> Text
anonAccount ([Text] -> [Text]) -> [Text] -> [Text]
forall a b. (a -> b) -> a -> b
$ Journal -> [Text]
jparseparentaccounts Journal
j
               , jparsealiases :: [AccountAlias]
jparsealiases         = []  -- already applied
               , jdeclaredaccounts :: [(Text, AccountDeclarationInfo)]
jdeclaredaccounts     = ((Text, AccountDeclarationInfo) -> (Text, AccountDeclarationInfo))
-> [(Text, AccountDeclarationInfo)]
-> [(Text, AccountDeclarationInfo)]
forall a b. (a -> b) -> [a] -> [b]
map ((Text -> Text)
-> (Text, AccountDeclarationInfo) -> (Text, AccountDeclarationInfo)
forall b c d. (b -> c) -> (b, d) -> (c, d)
forall (a :: * -> * -> *) b c d.
Arrow a =>
a b c -> a (b, d) (c, d)
first Text -> Text
forall a. Anon a => a -> a
anon) ([(Text, AccountDeclarationInfo)]
 -> [(Text, AccountDeclarationInfo)])
-> [(Text, AccountDeclarationInfo)]
-> [(Text, AccountDeclarationInfo)]
forall a b. (a -> b) -> a -> b
$ Journal -> [(Text, AccountDeclarationInfo)]
jdeclaredaccounts Journal
j
               , jdeclaredaccounttags :: Map Text [Tag]
jdeclaredaccounttags  = (Text -> Text) -> Map Text [Tag] -> Map Text [Tag]
forall k2 k1 a. Ord k2 => (k1 -> k2) -> Map k1 a -> Map k2 a
mapKeys Text -> Text
forall a. Anon a => a -> a
anon (Map Text [Tag] -> Map Text [Tag])
-> Map Text [Tag] -> Map Text [Tag]
forall a b. (a -> b) -> a -> b
$ Journal -> Map Text [Tag]
jdeclaredaccounttags Journal
j
               , jdeclaredaccounttypes :: Map AccountType [Text]
jdeclaredaccounttypes = ((Text -> Text) -> [Text] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map Text -> Text
forall a. Anon a => a -> a
anon) ([Text] -> [Text])
-> Map AccountType [Text] -> Map AccountType [Text]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Journal -> Map AccountType [Text]
jdeclaredaccounttypes Journal
j
               }

instance Anon Posting where
    anon :: Posting -> Posting
anon Posting
p = Posting
p { paccount :: Text
paccount = Text -> Text
anonAccount (Text -> Text) -> (Posting -> Text) -> Posting -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Posting -> Text
paccount (Posting -> Text) -> Posting -> Text
forall a b. (a -> b) -> a -> b
$ Posting
p
               , pcomment :: Text
pcomment = Text
T.empty
               , ptransaction :: Maybe Transaction
ptransaction = (Transaction -> Transaction)
-> Maybe Transaction -> Maybe Transaction
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Transaction -> Transaction
forall a. Anon a => a -> a
anon (Maybe Transaction -> Maybe Transaction)
-> (Posting -> Maybe Transaction) -> Posting -> Maybe Transaction
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Posting -> Maybe Transaction
ptransaction (Posting -> Maybe Transaction) -> Posting -> Maybe Transaction
forall a b. (a -> b) -> a -> b
$ Posting
p  -- Note that this will be overridden
               , poriginal :: Maybe Posting
poriginal = Posting -> Posting
forall a. Anon a => a -> a
anon (Posting -> Posting) -> Maybe Posting -> Maybe Posting
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Posting -> Maybe Posting
poriginal Posting
p
               }

instance Anon Transaction where
    anon :: Transaction -> Transaction
anon Transaction
txn = Transaction -> Transaction
txnTieKnot (Transaction -> Transaction) -> Transaction -> Transaction
forall a b. (a -> b) -> a -> b
$ Transaction
txn { tpostings :: [Posting]
tpostings = (Posting -> Posting) -> [Posting] -> [Posting]
forall a b. (a -> b) -> [a] -> [b]
map Posting -> Posting
forall a. Anon a => a -> a
anon ([Posting] -> [Posting])
-> (Transaction -> [Posting]) -> Transaction -> [Posting]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Transaction -> [Posting]
tpostings (Transaction -> [Posting]) -> Transaction -> [Posting]
forall a b. (a -> b) -> a -> b
$ Transaction
txn
                                , tdescription :: Text
tdescription = Text -> Text
forall a. Anon a => a -> a
anon (Text -> Text) -> (Transaction -> Text) -> Transaction -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Transaction -> Text
tdescription (Transaction -> Text) -> Transaction -> Text
forall a b. (a -> b) -> a -> b
$ Transaction
txn
                                , tcode :: Text
tcode = Text -> Text
forall a. Anon a => a -> a
anon (Text -> Text) -> (Transaction -> Text) -> Transaction -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Transaction -> Text
tcode (Transaction -> Text) -> Transaction -> Text
forall a b. (a -> b) -> a -> b
$ Transaction
txn
                                , tcomment :: Text
tcomment = Text
T.empty
                                }

-- | Anonymize account name preserving hierarchy
anonAccount :: AccountName -> AccountName
anonAccount :: Text -> Text
anonAccount = Text -> [Text] -> Text
T.intercalate (String -> Text
T.pack String
":") ([Text] -> Text) -> (Text -> [Text]) -> Text -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Text -> Text) -> [Text] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map Text -> Text
forall a. Anon a => a -> a
anon ([Text] -> [Text]) -> (Text -> [Text]) -> Text -> [Text]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. HasCallStack => Text -> Text -> [Text]
Text -> Text -> [Text]
T.splitOn (String -> Text
T.pack String
":")

instance Anon T.Text where anon :: Text -> Text
anon = String -> Text
T.pack (String -> Text) -> (Text -> String) -> Text -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Word32 -> String -> String) -> String -> Word32 -> String
forall a b c. (a -> b -> c) -> b -> a -> c
flip Word32 -> String -> String
forall a. Integral a => a -> String -> String
showHex String
"" (Word32 -> String) -> (Text -> Word32) -> Text -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Int -> Word32
forall a b. (Integral a, Num b) => a -> b
fromIntegral :: Int -> Word32) (Int -> Word32) -> (Text -> Int) -> Text -> Word32
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Int
forall a. Hashable a => a -> Int
hash