{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Servant.Dhall (
DHALL,
DHALL',
HasInterpretOptions,
DefaultInterpretOptions,
) where
import Prelude ()
import Prelude.Compat
import Control.Monad
(unless)
import Data.Either.Validation
(Validation (..))
import Data.Proxy
(Proxy (..))
import Data.Text.Encoding.Error
(lenientDecode)
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Encoding as TLE
import Data.Text.Prettyprint.Doc
(Pretty (pretty), defaultLayoutOptions, layoutPretty,
layoutSmart, line)
import Data.Text.Prettyprint.Doc.Render.String
(renderString)
import Data.Text.Prettyprint.Doc.Render.Text
(renderLazy)
import Data.Traversable
(for)
import Data.Typeable
(Typeable)
import Dhall
(ToDhall (..), Encoder (..), FromDhall (..),
InterpretOptions, Decoder (..), defaultInterpretOptions)
import qualified Dhall.Core
import Dhall.Parser
(exprFromText, unwrap)
import qualified Dhall.TypeCheck
import qualified Network.HTTP.Media as M
import Servant.API
(Accept (..), MimeRender (..), MimeUnrender (..))
import qualified Text.Megaparsec as MP
type DHALL = DHALL' DefaultInterpretOptions
data DHALL' opt deriving (Typeable)
instance Accept (DHALL' opts) where
contentType _ = "application" M.// "x-dhall"
instance (ToDhall a, HasInterpretOptions opts) => MimeRender (DHALL' opts) a where
mimeRender _ x
= TLE.encodeUtf8
$ renderLazy
$ layoutSmart defaultLayoutOptions
$ (`mappend` line)
$ pretty
$ embed ty x
where
ty :: Encoder a
ty = injectWith (interpretOptions (Proxy :: Proxy opts))
instance (FromDhall a, HasInterpretOptions opts) => MimeUnrender (DHALL' opts) a where
mimeUnrender _ lbs = do
expr0 <- firstEither showParseError $ exprFromText "(input)" te
expr1 <- for expr0 $ \i -> Left $ "Import found: " ++ ppExpr i
tyExpr <- firstEither showTypeError $ Dhall.TypeCheck.typeOf expr1
unless (Dhall.Core.judgmentallyEqual tyExpr $ expected ty) $
Left $ "Expected and actual types don't match : "
++ ppExpr (expected ty) ++ " /= " ++ ppExpr tyExpr
case extract ty (Dhall.Core.normalizeWith Nothing expr1) of
Success x -> Right x
Failure _ -> Left "Invalid type"
where
showParseError = MP.errorBundlePretty . unwrap
showTypeError e = "Type error: " ++ ppExpr e
te = TL.toStrict $
TLE.decodeUtf8With lenientDecode lbs
ty :: Decoder a
ty = autoWith (interpretOptions (Proxy :: Proxy opts))
ppExpr :: Pretty pp => pp -> String
ppExpr = renderString . layoutPretty defaultLayoutOptions . pretty
firstEither :: (a -> b) -> Either a c -> Either b c
firstEither f (Left a) = Left (f a)
firstEither _ (Right c) = Right c
class HasInterpretOptions opts where
interpretOptions :: Proxy opts -> InterpretOptions
data DefaultInterpretOptions deriving (Typeable)
instance HasInterpretOptions DefaultInterpretOptions where
interpretOptions _ = defaultInterpretOptions