@@ -13,6 +13,7 @@
13{-# LANGUAGE RankNTypes #-}
13{-# LANGUAGE RankNTypes #-}
14{-# LANGUAGE ScopedTypeVariables #-}
14{-# LANGUAGE ScopedTypeVariables #-}
15{-# LANGUAGE TemplateHaskell #-}
15{-# LANGUAGE TemplateHaskell #-}
16+{-# LANGUAGE TypeApplications #-}
16{-# LANGUAGE TypeFamilies #-}
17{-# LANGUAGE TypeFamilies #-}
17{-# LANGUAGE TypeOperators #-}
18{-# LANGUAGE TypeOperators #-}
18{-# LANGUAGE ViewPatterns #-}
19{-# LANGUAGE ViewPatterns #-}
@@ -85,6 +86,11 @@ module Obelisk.Route
85 , readShowEncoder
86 , readShowEncoder
86 , integralEncoder
87 , integralEncoder
87 , pathSegmentEncoder
88 , pathSegmentEncoder
90import Prelude hiding ((.), id)
96import Prelude hiding ((.), id)
@@ -97,6 +103,7 @@ import Control.Category.Associative
97import Control.Category.Monoidal
103import Control.Category.Monoidal
98import Control.Lens (Identity (..), Prism', makePrisms, itraverse, imap, prism, (^.), re, matching, (^?), _Just, _Nothing, Iso', from, view, Wrapped (..))
104import Control.Lens (Identity (..), Prism', makePrisms, itraverse, imap, prism, (^.), re, matching, (^?), _Just, _Nothing, Iso', from, view, Wrapped (..))
99import Control.Monad.Except
105import Control.Monad.Except
106+import qualified Data.ByteString.Lazy as BSL
100import Data.Dependent.Sum (DSum (..))
107import Data.Dependent.Sum (DSum (..))
101import Data.Dependent.Map (DMap)
108import Data.Dependent.Map (DMap)
102import qualified Data.Dependent.Map as DMap
109import qualified Data.Dependent.Map as DMap
@@ -123,6 +130,9 @@ import Network.HTTP.Types.URI
123import qualified Numeric.Lens
130import qualified Numeric.Lens
124import Obelisk.Route.TH
131import Obelisk.Route.TH
125import Text.Read (readMaybe)
132import Text.Read (readMaybe)
133+import Data.Tabulation
134+import qualified Data.Aeson as Aeson
135+import Data.Aeson (FromJSON, ToJSON)
127-- Design goals:
137-- Design goals:
128-- No start-up time on the frontend (not yet met)
138-- No start-up time on the frontend (not yet met)
@@ -342,7 +352,7 @@ maybeEncoder f g = shadowEncoder f g . maybeToEitherEncoder
342justEncoder :: (Applicative check, MonadError Text parse) => Encoder check parse a (Maybe a)
352justEncoder :: (Applicative check, MonadError Text parse) => Encoder check parse a (Maybe a)
343justEncoder = prismEncoder _Just
353justEncoder = prismEncoder _Just
345−-- |
355+-- | Encode () to 'Nothing'.
346nothingEncoder :: (Applicative check, MonadError Text parse) => Encoder check parse () (Maybe a)
356nothingEncoder :: (Applicative check, MonadError Text parse) => Encoder check parse () (Maybe a)
347nothingEncoder = prismEncoder _Nothing
357nothingEncoder = prismEncoder _Nothing
@@ -546,13 +556,10 @@ pathOnlyEncoderIgnoringQuery = unsafeMkEncoder $ EncoderImpl
548pathOnlyEncoder :: (Applicative check, MonadError Text parse) => Encoder check parse [Text] PageName
558pathOnlyEncoder :: (Applicative check, MonadError Text parse) => Encoder check parse [Text] PageName
549−pathOnlyEncoder = unsafeMkEncoder $ EncoderImpl
559+pathOnlyEncoder = second (unitEncoder mempty) . coidr
550− { _encoderImpl_decode = \(path, query) ->
560+
551− if query == mempty
561+queryOnlyEncoder :: (Applicative check, MonadError Text parse) => Encoder check parse (Map Text (Maybe Text)) PageName
552− then pure path
562+queryOnlyEncoder = first (unitEncoder []) . coidl
553− else throwError "pathOnlyEncoderImpl: query was provided"
554− , _encoderImpl_encode = \path -> (path, mempty)
557singletonListEncoder :: (Applicative check, MonadError Text parse) => Encoder check parse a [a]
564singletonListEncoder :: (Applicative check, MonadError Text parse) => Encoder check parse a [a]
558singletonListEncoder = unsafeMkEncoder $ EncoderImpl
565singletonListEncoder = unsafeMkEncoder $ EncoderImpl
@@ -867,7 +874,6 @@ renderObeliskRoute e r =
867readShowEncoder :: (MonadError Text parse, Read a, Show a, Applicative check) => Encoder check parse a PageName
874readShowEncoder :: (MonadError Text parse, Read a, Show a, Applicative check) => Encoder check parse a PageName
868readShowEncoder = singlePathSegmentEncoder . unsafeTshowEncoder
875readShowEncoder = singlePathSegmentEncoder . unsafeTshowEncoder
871integralEncoder :: (MonadError Text parse, Applicative check, Integral a) => Encoder check parse a Integer
877integralEncoder :: (MonadError Text parse, Applicative check, Integral a) => Encoder check parse a Integer
872integralEncoder = prismEncoder (Numeric.Lens.integral)
878integralEncoder = prismEncoder (Numeric.Lens.integral)
@@ -880,4 +886,72 @@ pathSegmentEncoder = unsafeMkEncoder EncoderImpl
880 (x:xs) -> pure (x, (xs, y))
886 (x:xs) -> pure (x, (xs, y))
889+newtype Decoder check parse b a = Decoder { toEncoder :: Encoder check parse a b }
891+dmapEncoder :: forall check parse k k' v.
893+ , MonadError Text parse
894+ , Universe (Some k')
899+ => Encoder check parse (Some k') k
900+ -> (forall v'. k' v' -> Encoder check parse v' v)
901+ -> Encoder check parse (DMap k' Identity) (Map k v)
902+dmapEncoder keyEncoder' valueEncoderFor = unsafeEncoder $ do
903+ keyEncoder :: Encoder Identity parse (Some k') k <- checkEncoder keyEncoder'
904+ valueDecoders :: DMap k' (Decoder Identity parse v) <- fmap DMap.fromList . forM universe $ \(Some.This (k' :: k' t)) -> do
905+ ve :: Encoder Identity parse t v <- checkEncoder (valueEncoderFor k')
906+ return $ (k' :: k' t) :=> (Decoder ve :: Decoder Identity parse v t)
907+ let keyError k = "dmapEncoder: key `" <> k <> "' was missing from the Universe instance for its type."
908+ return $ EncoderImpl
909+ { _encoderImpl_encode = \dm -> Map.fromList $ do
910+ ((k' :: k' t) :=> Identity v') <- DMap.toList dm
911+ return ( encode keyEncoder (Some.This k')
912+ , encode (toEncoder (DMap.findWithDefault (error . keyError $ gshow k') k' valueDecoders)) v'
914+ , _encoderImpl_decode = \m -> fmap DMap.fromList . forM (Map.toList m) $ \(k,v) -> do
915+ Some.This (k' :: k' t) <- tryDecode keyEncoder k
916+ case DMap.lookup k' valueDecoders of
917+ Nothing -> throwError . T.pack . keyError $ gshow k'
918+ Just (Decoder e) -> do
920+ return (k' :=> Identity v')
923+fieldMapEncoder :: forall check parse r.
925+ , MonadError Text parse
927+ , Universe (Some (Field r))
929+ , GCompare (Field r)
931+ => Encoder check parse r (DMap (Field r) Identity)
932+fieldMapEncoder = unsafeEncoder $ do
934+ { _encoderImpl_encode = \r -> DMap.fromList [ f :=> Identity (indexField r f) | Some.This f <- universe ]
935+ , _encoderImpl_decode = \dm -> tabulateFieldsA $ \f -> do
936+ case DMap.lookup f dm of
937+ Nothing -> throwError $ "fieldMapEncoder: Couldn't find key for `" <> T.pack (gshow f) <> "' in DMap."
938+ Just (Identity v) -> return v
941+-- | Use ToJSON/FromJSON to encode to Text. The correctness of this encoder is dependent on the encoding being injective and round-tripping correctly.
942+jsonEncoder :: forall check parse r.
946+ , MonadError Text parse
948+ => Encoder check parse r Text
949+jsonEncoder = unsafeEncoder $ do
951+ { _encoderImpl_encode = \r -> decodeUtf8 . BSL.toStrict $ Aeson.encode r
952+ , _encoderImpl_decode = \t -> case Aeson.eitherDecodeStrict $ encodeUtf8 t of
953+ Left err -> throwError ("jsonEncoder: " <> T.pack err)
883--TODO: decodeURIComponent as appropriate
957--TODO: decodeURIComponent as appropriate