@@ -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,10 @@ module Obelisk.Route
85 , readShowEncoder
86 , readShowEncoder
86 , integralEncoder
87 , integralEncoder
87 , pathSegmentEncoder
88 , pathSegmentEncoder
90import Prelude hiding ((.), id)
95import Prelude hiding ((.), id)
@@ -124,6 +129,7 @@ import Network.HTTP.Types.URI
124import qualified Numeric.Lens
129import qualified Numeric.Lens
125import Obelisk.Route.TH
130import Obelisk.Route.TH
126import Text.Read (readMaybe)
131import Text.Read (readMaybe)
132+import Data.Tabulation
128-- Design goals:
134-- Design goals:
129-- No start-up time on the frontend (not yet met)
135-- No start-up time on the frontend (not yet met)
@@ -547,13 +553,10 @@ pathOnlyEncoderIgnoringQuery = unsafeMkEncoder $ EncoderImpl
549pathOnlyEncoder :: (Applicative check, MonadError Text parse) => Encoder check parse [Text] PageName
555pathOnlyEncoder :: (Applicative check, MonadError Text parse) => Encoder check parse [Text] PageName
550−pathOnlyEncoder = unsafeMkEncoder $ EncoderImpl
556+pathOnlyEncoder = second (unitEncoder mempty) . coidr
551− { _encoderImpl_decode = \(path, query) ->
557+
552− if query == mempty
558+queryOnlyEncoder :: (Applicative check, MonadError Text parse) => Encoder check parse (Map Text (Maybe Text)) PageName
553− then pure path
559+queryOnlyEncoder = first (unitEncoder []) . coidl
554− else throwError "pathOnlyEncoderImpl: query was provided"
555− , _encoderImpl_encode = \path -> (path, mempty)
558singletonListEncoder :: (Applicative check, MonadError Text parse) => Encoder check parse a [a]
561singletonListEncoder :: (Applicative check, MonadError Text parse) => Encoder check parse a [a]
559singletonListEncoder = unsafeMkEncoder $ EncoderImpl
562singletonListEncoder = unsafeMkEncoder $ EncoderImpl
@@ -866,7 +869,6 @@ renderObeliskRoute e r =
866readShowEncoder :: (MonadError Text parse, Read a, Show a, Applicative check) => Encoder check parse a PageName
869readShowEncoder :: (MonadError Text parse, Read a, Show a, Applicative check) => Encoder check parse a PageName
867readShowEncoder = singlePathSegmentEncoder . unsafeTshowEncoder
870readShowEncoder = singlePathSegmentEncoder . unsafeTshowEncoder
870integralEncoder :: (MonadError Text parse, Applicative check, Integral a) => Encoder check parse a Integer
872integralEncoder :: (MonadError Text parse, Applicative check, Integral a) => Encoder check parse a Integer
871integralEncoder = prismEncoder (Numeric.Lens.integral)
873integralEncoder = prismEncoder (Numeric.Lens.integral)
@@ -879,4 +881,56 @@ pathSegmentEncoder = unsafeMkEncoder EncoderImpl
879 (x:xs) -> pure (x, (xs, y))
881 (x:xs) -> pure (x, (xs, y))
884+newtype Decoder check parse b a = Decoder { toEncoder :: Encoder check parse a b }
886+dmapEncoder :: forall check parse k k' v.
888+ , MonadError Text parse
889+ , Universe (Some k')
894+ => Encoder check parse (Some k') k
895+ -> (forall v'. k' v' -> Encoder check parse v' v)
896+ -> Encoder check parse (DMap k' Identity) (Map k v)
897+dmapEncoder keyEncoder' valueEncoderFor = unsafeEncoder $ do
898+ keyEncoder :: Encoder Identity parse (Some k') k <- checkEncoder keyEncoder'
899+ valueDecoders :: DMap k' (Decoder Identity parse v) <- fmap DMap.fromList . forM universe $ \(Some.This (k' :: k' t)) -> do
900+ ve :: Encoder Identity parse t v <- checkEncoder (valueEncoderFor k')
901+ return $ (k' :: k' t) :=> (Decoder ve :: Decoder Identity parse v t)
902+ let keyError k = "dmapEncoder: key `" <> k <> "' was missing from the Universe instance for its type."
903+ return $ EncoderImpl
904+ { _encoderImpl_encode = \dm -> Map.fromList $ do
905+ ((k' :: k' t) :=> Identity v') <- DMap.toList dm
906+ return ( encode keyEncoder (Some.This k')
907+ , encode (toEncoder (DMap.findWithDefault (error . keyError $ gshow k') k' valueDecoders)) v'
909+ , _encoderImpl_decode = \m -> fmap DMap.fromList . forM (Map.toList m) $ \(k,v) -> do
910+ Some.This (k' :: k' t) <- tryDecode keyEncoder k
911+ case DMap.lookup k' valueDecoders of
912+ Nothing -> throwError . T.pack . keyError $ gshow k'
913+ Just (Decoder e) -> do
915+ return (k' :=> Identity v')
918+fieldMapEncoder :: forall check parse r.
920+ , MonadError Text parse
922+ , Universe (Some (Field r))
924+ , GCompare (Field r)
926+ => Encoder check parse r (DMap (Field r) Identity)
927+fieldMapEncoder = unsafeEncoder $ do
929+ { _encoderImpl_encode = \r -> DMap.fromList [ f :=> Identity (indexField r f) | Some.This f <- universe ]
930+ , _encoderImpl_decode = \dm -> tabulateFieldsA $ \f -> do
931+ case DMap.lookup f dm of
932+ Nothing -> throwError $ "fieldMapEncoder: Couldn't find key for `" <> T.pack (gshow f) <> "' in DMap."
933+ Just (Identity v) -> return v
882--TODO: decodeURIComponent as appropriate
936--TODO: decodeURIComponent as appropriate