reflex-gadt-api-0.4.0.0: src/Reflex/Dom/GadtApi/Codec.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE QuantifiedConstraints #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE UndecidableInstances #-}
-- | Pure request\/response codecs and dispatch for a GADT API.
--
-- "Reflex.Dom.GadtApi.WebSocket" and "Reflex.Dom.GadtApi.XHR" use these
-- operations internally. Use them on a server, or in a new transport, to
-- keep the same wire format.
--
-- Client and server constraints are separate. A client encodes requests and
-- decodes responses. A server decodes requests and encodes responses.
module Reflex.Dom.GadtApi.Codec
( encodeApiRequest
, serveApiRequest
) where
import Data.Aeson
import Data.Constraint.Extras
import Data.Some (Some (..))
import Data.Text (Text)
import qualified Data.Text as T
-- | Encode an API request as JSON, together with a decoder for its response.
--
-- The decoder uses the request to find the 'FromJSON' instance of the
-- response type. Mixed requests hide this type. The decoder makes it known
-- again. A payload that does not decode as the response type gives 'Left'
-- with the parser error.
encodeApiRequest
:: forall api a. (Has FromJSON api, forall x. ToJSON (api x))
=> api a
-> (Value, Value -> Either Text a)
encodeApiRequest r =
( toJSON r
, \v -> case has @FromJSON r $ fromJSON v of
Success x -> Right x
Error e -> Left $ T.pack e
)
-- | Decode an incoming request. Run the handler on it. Encode the response.
--
-- A request 'Value' that does not match a constructor of the API gives
-- 'Left' with the parser error. This function does not catch an exception
-- from the handler. The interpreter or the host application must handle
-- such exceptions.
serveApiRequest
:: (Monad m, FromJSON (Some api), Has ToJSON api)
=> (forall a. api a -> m a)
-> Value
-> m (Either String Value)
serveApiRequest f v = case fromJSON v of
Success (Some a) -> do
rsp <- f a
pure $ Right $ has @ToJSON a $ toJSON rsp
Error e -> pure $ Left e