{-# LANGUAGE ApplicativeDo #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
module Dhall.JSON (
dhallToJSON
, omitNull
, Conversion(..)
, convertToHomogeneousMaps
, parseConversion
, codeToValue
, CompileError(..)
) where
import Control.Applicative (empty, (<|>))
import Control.Monad (guard)
import Control.Exception (Exception, throwIO)
import Data.Aeson (Value(..))
import Data.Monoid ((<>))
import Data.Text.Lazy (Text)
import Data.Typeable (Typeable)
import Dhall.Core (Expr)
import Dhall.TypeCheck (X)
import Options.Applicative (Parser)
import qualified Data.Aeson
import qualified Data.Foldable
import qualified Data.HashMap.Strict
import qualified Data.HashMap.Strict.InsOrd
import qualified Data.Text
import qualified Data.Text.Lazy
import qualified Data.Text.Lazy.Builder
import qualified Dhall.Core
import qualified Dhall.Import
import qualified Dhall.Parser
import qualified Dhall.TypeCheck
import qualified Options.Applicative
data CompileError = Unsupported (Expr X X) deriving (Typeable)
instance Show CompileError where
show (Unsupported e) =
Data.Text.unpack $
"" <> _ERROR <> ": Cannot translate to JSON \n\
\ \n\
\Explanation: Only primitive values, records, unions, ❰List❱s, and ❰Optional❱ \n\
\values can be translated from Dhall to JSON \n\
\ \n\
\The following Dhall expression could not be translated to JSON: \n\
\ \n\
\↳ " <> txt <> " "
where
txt = Data.Text.Lazy.toStrict (Dhall.Core.pretty e)
_ERROR :: Data.Text.Text
_ERROR = "\ESC[1;31mError\ESC[0m"
instance Exception CompileError
dhallToJSON :: Expr s X -> Either CompileError Value
dhallToJSON e0 = loop (Dhall.Core.normalize e0)
where
loop e = case e of
Dhall.Core.BoolLit a -> return (Data.Aeson.toJSON a)
Dhall.Core.NaturalLit a -> return (Data.Aeson.toJSON a)
Dhall.Core.IntegerLit a -> return (Data.Aeson.toJSON a)
Dhall.Core.DoubleLit a -> return (Data.Aeson.toJSON a)
Dhall.Core.TextLit (Dhall.Core.Chunks [] a) -> do
return (Data.Aeson.toJSON (Data.Text.Lazy.Builder.toLazyText a))
Dhall.Core.ListLit _ a -> do
a' <- traverse loop a
return (Data.Aeson.toJSON a')
Dhall.Core.OptionalLit _ a -> do
a' <- traverse loop a
return (Data.Aeson.toJSON a')
Dhall.Core.RecordLit a -> do
a' <- traverse loop a
return (Data.Aeson.toJSON a')
Dhall.Core.UnionLit _ b _ -> loop b
_ -> Left (Unsupported e)
omitNull :: Value -> Value
omitNull (Object object) =
Object (fmap omitNull (Data.HashMap.Strict.filter (/= Null) object))
omitNull (Array array) =
Array (fmap omitNull array)
omitNull (String string) =
String string
omitNull (Number number) =
Number number
omitNull (Bool bool) =
Bool bool
omitNull Null =
Null
data Conversion
= NoConversion
| Conversion { mapKey :: Text, mapValue :: Text }
convertToHomogeneousMaps :: Conversion -> Expr s X -> Expr s X
convertToHomogeneousMaps NoConversion e0 = e0
convertToHomogeneousMaps (Conversion {..}) e0 = loop (Dhall.Core.normalize e0)
where
loop e = case e of
Dhall.Core.Const a ->
Dhall.Core.Const a
Dhall.Core.Var v ->
Dhall.Core.Var v
Dhall.Core.Lam a b c ->
Dhall.Core.Lam a b' c'
where
b' = loop b
c' = loop c
Dhall.Core.Pi a b c ->
Dhall.Core.Pi a b' c'
where
b' = loop b
c' = loop c
Dhall.Core.App a b ->
Dhall.Core.App a' b'
where
a' = loop a
b' = loop b
Dhall.Core.Let a b c d ->
Dhall.Core.Let a b' c' d'
where
b' = fmap loop b
c' = loop c
d' = loop d
Dhall.Core.Annot a b ->
Dhall.Core.Annot a' b'
where
a' = loop a
b' = loop b
Dhall.Core.Bool ->
Dhall.Core.Bool
Dhall.Core.BoolLit a ->
Dhall.Core.BoolLit a
Dhall.Core.BoolAnd a b ->
Dhall.Core.BoolAnd a' b'
where
a' = loop a
b' = loop b
Dhall.Core.BoolOr a b ->
Dhall.Core.BoolOr a' b'
where
a' = loop a
b' = loop b
Dhall.Core.BoolEQ a b ->
Dhall.Core.BoolEQ a' b'
where
a' = loop a
b' = loop b
Dhall.Core.BoolNE a b ->
Dhall.Core.BoolNE a' b'
where
a' = loop a
b' = loop b
Dhall.Core.BoolIf a b c ->
Dhall.Core.BoolIf a' b' c'
where
a' = loop a
b' = loop b
c' = loop c
Dhall.Core.Natural ->
Dhall.Core.Natural
Dhall.Core.NaturalLit a ->
Dhall.Core.NaturalLit a
Dhall.Core.NaturalFold ->
Dhall.Core.NaturalFold
Dhall.Core.NaturalBuild ->
Dhall.Core.NaturalBuild
Dhall.Core.NaturalIsZero ->
Dhall.Core.NaturalIsZero
Dhall.Core.NaturalEven ->
Dhall.Core.NaturalEven
Dhall.Core.NaturalOdd ->
Dhall.Core.NaturalOdd
Dhall.Core.NaturalToInteger ->
Dhall.Core.NaturalToInteger
Dhall.Core.NaturalShow ->
Dhall.Core.NaturalShow
Dhall.Core.NaturalPlus a b ->
Dhall.Core.NaturalPlus a' b'
where
a' = loop a
b' = loop b
Dhall.Core.NaturalTimes a b ->
Dhall.Core.NaturalTimes a' b'
where
a' = loop a
b' = loop b
Dhall.Core.Integer ->
Dhall.Core.Integer
Dhall.Core.IntegerLit a ->
Dhall.Core.IntegerLit a
Dhall.Core.IntegerShow ->
Dhall.Core.IntegerShow
Dhall.Core.Double ->
Dhall.Core.Double
Dhall.Core.DoubleLit a ->
Dhall.Core.DoubleLit a
Dhall.Core.DoubleShow ->
Dhall.Core.DoubleShow
Dhall.Core.Text ->
Dhall.Core.Text
Dhall.Core.TextLit (Dhall.Core.Chunks a b) ->
Dhall.Core.TextLit (Dhall.Core.Chunks a' b)
where
a' = fmap (fmap loop) a
Dhall.Core.TextAppend a b ->
Dhall.Core.TextAppend a' b'
where
a' = loop a
b' = loop b
Dhall.Core.List ->
Dhall.Core.List
Dhall.Core.ListLit a b ->
case transform of
Just c -> c
Nothing -> Dhall.Core.ListLit a' b'
where
elements = Data.Foldable.toList b
toKeyValue :: Expr s X -> Maybe (Text, Expr s X)
toKeyValue (Dhall.Core.RecordLit m) = do
guard (Data.HashMap.Strict.InsOrd.size m == 2)
key <- Data.HashMap.Strict.InsOrd.lookup mapKey m
value <- Data.HashMap.Strict.InsOrd.lookup mapValue m
keyText <- case key of
Dhall.Core.TextLit (Dhall.Core.Chunks [] keyText) ->
return keyText
_ ->
empty
return (Data.Text.Lazy.Builder.toLazyText keyText, value)
toKeyValue _ = do
empty
transform =
case elements of
[] ->
case a of
Just (Dhall.Core.Record m) -> do
guard (Data.HashMap.Strict.InsOrd.size m == 2)
guard (Data.HashMap.Strict.InsOrd.member mapKey m)
guard (Data.HashMap.Strict.InsOrd.member mapValue m)
return (Dhall.Core.RecordLit Data.HashMap.Strict.InsOrd.empty)
_ -> do
empty
_ -> do
keyValues <- traverse toKeyValue elements
let recordLiteral =
Data.HashMap.Strict.InsOrd.fromList keyValues
return (Dhall.Core.RecordLit recordLiteral)
a' = fmap loop a
b' = fmap loop b
Dhall.Core.ListAppend a b ->
Dhall.Core.ListAppend a' b'
where
a' = loop a
b' = loop b
Dhall.Core.ListBuild ->
Dhall.Core.ListBuild
Dhall.Core.ListFold ->
Dhall.Core.ListFold
Dhall.Core.ListLength ->
Dhall.Core.ListLength
Dhall.Core.ListHead ->
Dhall.Core.ListHead
Dhall.Core.ListLast ->
Dhall.Core.ListLast
Dhall.Core.ListIndexed ->
Dhall.Core.ListIndexed
Dhall.Core.ListReverse ->
Dhall.Core.ListReverse
Dhall.Core.Optional ->
Dhall.Core.Optional
Dhall.Core.OptionalLit a b ->
Dhall.Core.OptionalLit a' b'
where
a' = loop a
b' = fmap loop b
Dhall.Core.OptionalFold ->
Dhall.Core.OptionalFold
Dhall.Core.OptionalBuild ->
Dhall.Core.OptionalBuild
Dhall.Core.Record a ->
Dhall.Core.Record a'
where
a' = fmap loop a
Dhall.Core.RecordLit a ->
Dhall.Core.RecordLit a'
where
a' = fmap loop a
Dhall.Core.Union a ->
Dhall.Core.Union a'
where
a' = fmap loop a
Dhall.Core.UnionLit a b c ->
Dhall.Core.UnionLit a b' c'
where
b' = loop b
c' = fmap loop c
Dhall.Core.Combine a b ->
Dhall.Core.Combine a' b'
where
a' = loop a
b' = loop b
Dhall.Core.CombineTypes a b ->
Dhall.Core.CombineTypes a' b'
where
a' = loop a
b' = loop b
Dhall.Core.Prefer a b ->
Dhall.Core.Prefer a' b'
where
a' = loop a
b' = loop b
Dhall.Core.Merge a b c ->
Dhall.Core.Merge a' b' c'
where
a' = loop a
b' = loop b
c' = fmap loop c
Dhall.Core.Constructors a ->
Dhall.Core.Constructors a'
where
a' = loop a
Dhall.Core.Field a b ->
Dhall.Core.Field a' b
where
a' = loop a
Dhall.Core.Project a b ->
Dhall.Core.Project a' b
where
a' = loop a
Dhall.Core.Note a b ->
Dhall.Core.Note a b'
where
b' = loop b
Dhall.Core.Embed a ->
Dhall.Core.Embed a
parseConversion :: Parser Conversion
parseConversion =
conversion
<|> noConversion
where
conversion = do
mapKey <- parseKeyField
mapValue <- parseValueField
return (Conversion {..})
where
parseKeyField =
Options.Applicative.strOption
( Options.Applicative.long "key"
<> Options.Applicative.help "Reserved key field name for association lists"
<> Options.Applicative.value "mapKey"
<> Options.Applicative.showDefaultWith Data.Text.Lazy.unpack
)
parseValueField =
Options.Applicative.strOption
( Options.Applicative.long "value"
<> Options.Applicative.help "Reserved value field name for association lists"
<> Options.Applicative.value "mapValue"
<> Options.Applicative.showDefaultWith Data.Text.Lazy.unpack
)
noConversion =
Options.Applicative.flag'
NoConversion
( Options.Applicative.long "noMaps"
<> Options.Applicative.help "Disable conversion of association lists to homogeneous maps"
)
codeToValue
:: Conversion
-> Data.Text.Text
-> Data.Text.Text
-> IO Value
codeToValue conversion name code = do
parsedExpression <- case Dhall.Parser.exprFromText (Data.Text.unpack name) (Data.Text.Lazy.fromStrict code) of
Left err -> Control.Exception.throwIO err
Right parsedExpression -> return parsedExpression
resolvedExpression <- Dhall.Import.load parsedExpression
case Dhall.TypeCheck.typeOf resolvedExpression of
Left err -> Control.Exception.throwIO err
Right _ -> return ()
let convertedExpression =
convertToHomogeneousMaps conversion resolvedExpression
case dhallToJSON convertedExpression of
Left err -> Control.Exception.throwIO err
Right json -> return json