The code looks very clean! Perhaps you could add version bounds in your cabal file.
I also saw this:
if not exists
then do
createDirectory configFolder ownerModes
pure ()
else pure ()
Which could be written as:
unless exists $ createDirectory configFolder ownerModes
I also see:
contents <- C8.readFile filepath
let (encSalt:_:encRest) = C8.lines contents
rest = decodeSecrets encRest
case decodeBase64 encSalt of
Left _ -> error "Decoding error"
Right salt -> pure $ Vault salt rest
Which I would rewrite for a bit more safety
contents <- C8.readFile filepath
case C8.lines contents of
encSalt:_:encRest ->
case decodeBase64 encSalt of
Left _ -> error "Decoding error"
Right salt -> pure $ Vault salt $ decodeSecrets encRest
_ -> error "Decoding error"
Also, note that in this case laziness might do unexpected things. The contents of the vault will only be evaluated once you actually ask for the values. You might want to use strict fields for it like so:
data Vault =
Vault
{ salt :: !ByteString
, secrets :: ![ByteString]
}
But that's not enough, because this will only force the first element (more precisely the first cons-cell). To truly force all values in the list of secrets you'd have to chose another data type. Unfortunately, there's not really any really popular strict vector type. The simplest fix is probably just to do the forcing of evaluation yourself like so:
Right salt -> pure $! Vault salt $!! decodeSecrets encRest
Where $!!
is from Control.DeepSeq
from the deepseq
package.