Description
I have multiple applications in which I periodically load Dhall files, and need to safely handle any ill-formed input expression without bringing down the whole app. Essentially, I want a version of input
that doesn't throw exceptions. AFAICT, the current API doesn't provide any easy way to do this. I've ended up with:
EDIT: The previous version of the code below was totally wrong as it attempted to use the type in place of the value. Also it required turning off the monomorphism restriction for printError
, and had an unnecessary FromDhall
constraint.
import Control.Exception
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Trans.Maybe
import Data.Either.Validation
import Data.Text (Text)
import Dhall qualified
import Dhall.Core qualified as Dhall
import Dhall.Import qualified as Dhall
import Dhall.Parser qualified as Dhall
import Dhall.TypeCheck qualified as Dhall
-- | Like 'Dhall.input', but handles any exceptions and prints them to stdout.
inputSafe :: Dhall.Decoder a -> Text -> IO (Maybe a)
inputSafe decoder =
runMaybeT
. ( printError . Dhall.toMonadic . Dhall.extract decoder . Dhall.renote . Dhall.normalize
<=< checkType
<=< printError
<=< liftIO . try @(Dhall.SourcedException Dhall.MissingImports) . Dhall.load
<=< printError . Dhall.exprFromText ""
)
where
printError :: Show a => Either a b -> MaybeT IO b
printError = either (\e -> liftIO (print e) >> mzero) pure
checkType e = do
expectedType <- printError . validationToEither $ Dhall.expected decoder
_ <- printError . Dhall.typeOf $ Dhall.Annot e expectedType
pure e
Is this even correct - are there other errors that could be thrown (I'm happy to treat IOException
etc. separately)? Could the library export a function like this, or at least make it easier to construct? This took a lot of trial and error, and poring through documentation.