@@ -12,6 +12,7 @@
12{-# LANGUAGE PatternSynonyms #-}
12{-# LANGUAGE PatternSynonyms #-}
13{-# LANGUAGE RankNTypes #-}
13{-# LANGUAGE RankNTypes #-}
14{-# LANGUAGE ScopedTypeVariables #-}
14{-# LANGUAGE ScopedTypeVariables #-}
15+{-# LANGUAGE StandaloneDeriving #-}
15{-# LANGUAGE TemplateHaskell #-}
16{-# LANGUAGE TemplateHaskell #-}
16{-# LANGUAGE TypeApplications #-}
17{-# LANGUAGE TypeApplications #-}
17{-# LANGUAGE TypeFamilies #-}
18{-# LANGUAGE TypeFamilies #-}
@@ -62,10 +63,15 @@ module Obelisk.Route
62 , shadowEncoder
63 , shadowEncoder
63 , prismEncoder
64 , prismEncoder
65 , obeliskRouteEncoder
67 , obeliskRouteEncoder
66 , obeliskRouteSegment
68 , obeliskRouteSegment
67 , pageNameEncoder
69 , pageNameEncoder
68 , handleEncoder
70 , handleEncoder
72+ , _FullRoute_Frontend
69 , ObeliskRoute (..)
75 , ObeliskRoute (..)
70 , _ObeliskRoute_App
76 , _ObeliskRoute_App
71 , _ObeliskRoute_Resource
77 , _ObeliskRoute_Resource
@@ -90,7 +96,9 @@ module Obelisk.Route
90 , Decoder(..)
96 , Decoder(..)
91 , dmapEncoder
97 , dmapEncoder
92 , fieldMapEncoder
98 , fieldMapEncoder
93 , jsonEncoder
100 , jsonEncoder
101+ , byteStringsToPageName
96import Prelude hiding ((.), id)
104import Prelude hiding ((.), id)
@@ -101,8 +109,31 @@ import qualified Control.Categorical.Functor as Cat
101import Control.Categorical.Bifunctor
109import Control.Categorical.Bifunctor
102import Control.Category.Associative
110import Control.Category.Associative
103import Control.Category.Monoidal
111import Control.Category.Monoidal
104−import Control.Lens (Identity (..), Prism', makePrisms, itraverse, imap, prism, (^.), re, matching, (^?), _Just, _Nothing, Iso', from, view, Wrapped (..))
112+import Control.Category.Braided
105import Control.Monad.Except
132import Control.Monad.Except
133+import Control.Monad.Writer (execWriter, tell)
134+import qualified Control.Monad.State.Strict as State
135+import Control.Monad.Trans (lift)
136+import qualified Data.ByteString as BS
106import qualified Data.ByteString.Lazy as BSL
137import qualified Data.ByteString.Lazy as BSL
107import Data.Dependent.Sum (DSum (..))
138import Data.Dependent.Sum (DSum (..))
108import Data.Dependent.Map (DMap)
139import Data.Dependent.Map (DMap)
@@ -252,6 +283,10 @@ instance Monad parse => Bifunctor (,) (EncoderImpl parse) (EncoderImpl parse) (E
252 , _encoderImpl_decode = \(a, b) -> liftA2 (,) (_encoderImpl_decode f a) (_encoderImpl_decode g b)
283 , _encoderImpl_decode = \(a, b) -> liftA2 (,) (_encoderImpl_decode f a) (_encoderImpl_decode g b)
286+instance (Monad parse, Applicative check) => Braided (Encoder check parse) (,) where
287+ braid = isoEncoder (iso swap swap)
255instance (Applicative check, Monad parse) => PFunctor (,) (Encoder check parse) (Encoder check parse) where
290instance (Applicative check, Monad parse) => PFunctor (,) (Encoder check parse) (Encoder check parse) where
256 first f = bimap f id
291 first f = bimap f id
257instance (Applicative check, Monad parse) => QFunctor (,) (Encoder check parse) (Encoder check parse) where
292instance (Applicative check, Monad parse) => QFunctor (,) (Encoder check parse) (Encoder check parse) where
@@ -265,6 +300,34 @@ instance (Traversable f, Monad parse) => Cat.Functor f (EncoderImpl parse) (Enco
265 , _encoderImpl_decode = traverse $ _encoderImpl_decode ve
300 , _encoderImpl_decode = traverse $ _encoderImpl_decode ve
303+instance Monad parse => PFunctor Either (EncoderImpl parse) (EncoderImpl parse) where
304+ first f = bimap f id
305+instance Monad parse => QFunctor Either (EncoderImpl parse) (EncoderImpl parse) where
306+ second g = bimap id g
307+instance Monad parse => Bifunctor Either (EncoderImpl parse) (EncoderImpl parse) (EncoderImpl parse) where
308+ bimap f g = EncoderImpl
309+ { _encoderImpl_encode = bimap (_encoderImpl_encode f) (_encoderImpl_encode g)
310+ , _encoderImpl_decode = \case
311+ Left a -> Left <$> _encoderImpl_decode f a
312+ Right b -> Right <$> _encoderImpl_decode g b
315+instance (Monad parse, Applicative check) => QFunctor Either (Encoder check parse) (Encoder check parse) where
316+ second g = bimap id g
317+instance (Monad parse, Applicative check) => PFunctor Either (Encoder check parse) (Encoder check parse) where
318+ first f = bimap f id
319+instance (Monad parse, Applicative check) => Bifunctor Either (Encoder check parse) (Encoder check parse) (Encoder check parse) where
320+ bimap f g = Encoder $ liftA2 bimap (unEncoder f) (unEncoder g)
322+instance (Applicative check, Monad parse) => Associative (Encoder check parse) Either where
323+ associate = isoEncoder (iso (associate @(->) @Either) disassociate)
324+ disassociate = isoEncoder (iso disassociate associate)
326+instance (Monad parse, Applicative check) => Braided (Encoder check parse) Either where
327+ braid = isoEncoder (iso swap swap)
268instance (Traversable f, Monad check, Monad parse) => Cat.Functor f (Encoder check parse) (Encoder check parse) where
331instance (Traversable f, Monad check, Monad parse) => Cat.Functor f (Encoder check parse) (Encoder check parse) where
269 fmap e = Encoder $ do
332 fmap e = Encoder $ do
270 ve <- unEncoder e
333 ve <- unEncoder e
@@ -663,10 +726,47 @@ joinPairTextEncoder = Encoder . \case
663 _ -> return (kt, T.drop (T.length separator) vt)
726 _ -> return (kt, T.drop (T.length separator) vt)
666−--TODO: Rewrite this by composing the given prism with a lens on the first element of the DSum
729+-- This slight generalization of 'rPrism' happens to be enough to write all our
667−-- Or, more likely, the user can compose it themselves
730+-- prism combinators so far.
668−rPrism :: forall f g. (forall a. Prism' (f a) (g a)) -> Prism' (R f) (R g)
733+ . (forall a. Prism' (f a) (f' a))
669−rPrism p = prism (\(g :/ x) -> g ^. re p :/ x) (\(f :/ x) -> bimap (:/ x) (:/ x) $ matching p f)
734+ -> Prism' (DSum f g) (DSum f' g)
736+ (\(f' :=> x) -> f' ^. re p :=> x)
737+ (\(f :=> x) -> (:=> x) <$> (f ^? p))
739+-- already in obelisk
742+ . (forall a. Prism' (f a) (f' a))
743+ -> Prism' (R f) (R f')
744+rPrism p = dSumPrism p
748+ . (forall b. Prism' (f b) (a :~: b))
749+ -> Prism' (DSum f g) (g a)
750+dSumPrism' p = dSumPrism p . iso (\(Refl :=> b) -> b) (Refl :=>)
755+ -> Prism' (DSum f g) (g a)
756+dSumGEqPrism variant = dSumPrism' $ prism' (\Refl -> variant) (\x -> geq variant x)
758+-- | Given a 'tag :: f a', make a prism for 'R f'. This generalizes the usual
759+-- prisms for a sum type (the ones that 'mkPrisms' would make), just as 'R'
760+-- generalized a usual sum type.
762+-- [This is given the '_R' name of the "cannonical" prism not because it is the
763+-- most general, but because it seems the most useful for routes, and 'R' itself
764+-- trades generality for route-specificity.]
769+_R variant = dSumGEqPrism variant . iso runIdentity Identity
671-- | An encoder that only works on the items available via the prism. An error will be thrown in the parse monad
771-- | An encoder that only works on the items available via the prism. An error will be thrown in the parse monad
672-- if the prism doesn't match.
772-- if the prism doesn't match.
@@ -710,6 +810,45 @@ handleEncoder recover e = Encoder $ do
710-- Actual obelisk route info
810-- Actual obelisk route info
711--------------------------------------------------------------------------------
811--------------------------------------------------------------------------------
813+-- | The typical full route type comprising all of an Obelisk application's routes.
814+-- Parameterised by the top level GADTs that define backend and frontend routes, respectively.
815+data FullRoute :: (* -> *) -> (* -> *) -> * -> * where
816+ FullRoute_Backend :: br a -> FullRoute br fr a
817+ FullRoute_Frontend :: ObeliskRoute fr a -> FullRoute br fr a
819+instance (GShow br, GShow fr) => GShow (FullRoute br fr) where
820+ gshowsPrec p = \case
821+ FullRoute_Backend x -> showParen (p > 10) (showString "FullRoute_Backend " . gshowsPrec 11 x)
822+ FullRoute_Frontend x -> showParen (p > 10) (showString "FullRoute_Frontend " . gshowsPrec 11 x)
824+instance (GEq br, GEq fr) => GEq (FullRoute br fr) where
825+ geq (FullRoute_Backend x) (FullRoute_Backend y) = geq x y
826+ geq (FullRoute_Frontend x) (FullRoute_Frontend y) = geq x y
829+instance (GCompare br, GCompare fr) => GCompare (FullRoute br fr) where
830+ gcompare (FullRoute_Backend _) (FullRoute_Frontend _) = GLT
831+ gcompare (FullRoute_Frontend _) (FullRoute_Backend _) = GGT
832+ gcompare (FullRoute_Backend x) (FullRoute_Backend y) = gcompare x y
833+ gcompare (FullRoute_Frontend x) (FullRoute_Frontend y) = gcompare x y
835+instance (UniverseSome br, UniverseSome fr) => UniverseSome (FullRoute br fr) where
836+ universeSome = [Some (FullRoute_Backend x) | Some x <- universeSome]
837+ ++ [Some (FullRoute_Frontend x) | Some x <- universeSome]
839+-- | Build the typical top level application route encoder from a route for handling 404's,
840+-- and segment encoders for backend and frontend routes.
842+ :: (GCompare br, GCompare fr, GShow br, GShow fr, UniverseSome br, UniverseSome fr)
843+ => R (FullRoute br fr) -- ^ 404 handler
844+ -> (forall a. br a -> SegmentResult (Either Text) (Either Text) a) -- ^ How to encode a single backend route segment
845+ -> (forall a. fr a -> SegmentResult (Either Text) (Either Text) a) -- ^ How to encode a single frontend route segment
846+ -> Encoder (Either Text) Identity (R (FullRoute br fr)) PageName
847+mkFullRouteEncoder missing backendSegment frontendSegment = handleEncoder (const missing) $
848+ pathComponentEncoder $ \case
849+ FullRoute_Backend backendRoute -> backendSegment backendRoute
850+ FullRoute_Frontend obeliskRoute -> obeliskRouteSegment obeliskRoute frontendSegment
713-- | A type which can represent Obelisk-specific resource routes, in addition to application specific routes which serve your
852-- | A type which can represent Obelisk-specific resource routes, in addition to application specific routes which serve your
714-- frontend.
853-- frontend.
715data ObeliskRoute :: (* -> *) -> * -> * where
854data ObeliskRoute :: (* -> *) -> * -> * where
@@ -839,7 +978,7 @@ concat <$> mapM deriveRouteComponent
841makePrisms ''ObeliskRoute
980makePrisms ''ObeliskRoute
842−
981+makePrisms ''FullRoute
843deriveGEq ''Void1
982deriveGEq ''Void1
844deriveGCompare ''Void1
983deriveGCompare ''Void1
@@ -847,27 +986,27 @@ deriveGCompare ''Void1
847-- and query string). See 'checkEncoder' for how to produce a checked encoder.
986-- and query string). See 'checkEncoder' for how to produce a checked encoder.
848renderBackendRoute
987renderBackendRoute
849 :: forall br a.
988 :: forall br a.
850− Encoder Identity Identity (R (Sum br a)) PageName
989+ Encoder Identity Identity (R (FullRoute br a)) PageName
853−renderBackendRoute enc = renderObeliskRoute enc . hoistR InL
992+renderBackendRoute enc = renderObeliskRoute enc . hoistR FullRoute_Backend
855-- | Renders a frontend route with the supplied checked encoder
994-- | Renders a frontend route with the supplied checked encoder
856renderFrontendRoute
995renderFrontendRoute
857 :: forall a fr.
996 :: forall a fr.
858− Encoder Identity Identity (R (Sum a (ObeliskRoute fr))) PageName
997+ Encoder Identity Identity (R (FullRoute a fr)) PageName
861−renderFrontendRoute enc = renderObeliskRoute enc . hoistR (InR . ObeliskRoute_App)
1000+renderFrontendRoute enc = renderObeliskRoute enc . hoistR (FullRoute_Frontend . ObeliskRoute_App)
863-- | Renders a route of the form typically found in an Obelisk project
1002-- | Renders a route of the form typically found in an Obelisk project
864renderObeliskRoute
1003renderObeliskRoute
865 :: forall a b.
1004 :: forall a b.
866− Encoder Identity Identity (R (Sum a b)) PageName
1005+ Encoder Identity Identity (R (FullRoute a b)) PageName
867− -> R (Sum a b)
1006+ -> R (FullRoute a b)
869renderObeliskRoute e r =
1008renderObeliskRoute e r =
870− let enc :: Encoder Identity (Either Text) (R (Sum a b)) PathQuery
1009+ let enc :: Encoder Identity (Either Text) (R (FullRoute a b)) PathQuery
871 enc = (pageNameEncoder . hoistParse (pure . runIdentity) e)
1010 enc = (pageNameEncoder . hoistParse (pure . runIdentity) e)
872 in (T.pack . uncurry (<>)) $ encode enc r
1011 in (T.pack . uncurry (<>)) $ encode enc r
@@ -877,14 +1016,9 @@ readShowEncoder = singlePathSegmentEncoder . unsafeTshowEncoder
877integralEncoder :: (MonadError Text parse, Applicative check, Integral a) => Encoder check parse a Integer
1016integralEncoder :: (MonadError Text parse, Applicative check, Integral a) => Encoder check parse a Integer
878integralEncoder = prismEncoder (Numeric.Lens.integral)
1017integralEncoder = prismEncoder (Numeric.Lens.integral)
880−pathSegmentEncoder :: (MonadError Text parse, Applicative check) =>
1019+pathSegmentEncoder :: (MonadError Text parse, Applicative check, Cons as as a a) =>
881− Encoder check parse (Text, PageName) PageName
1020+ Encoder check parse (a, (as, b)) (as, b)
882−pathSegmentEncoder = unsafeMkEncoder EncoderImpl
1021+pathSegmentEncoder = first (prismEncoder _Cons) . disassociate
883− { _encoderImpl_encode = \(x, (y, z)) -> (x:y, z)
884− , _encoderImpl_decode = \(xss, y) -> case xss of
885− [] -> throwError "not enough path segments"
886− (x:xs) -> pure (x, (xs, y))
889newtype Decoder check parse b a = Decoder { toEncoder :: Encoder check parse a b }
1023newtype Decoder check parse b a = Decoder { toEncoder :: Encoder check parse a b }
@@ -938,6 +1072,32 @@ fieldMapEncoder = unsafeEncoder $ do
938 Just (Identity v) -> return v
1072 Just (Identity v) -> return v
1075+-- this is in base 4.12 (GHC 8.6);
1076+newtype Ap f a = Ap {getAp :: f a}
1078+instance (Applicative f, Semigroup a) => Semigroup (Ap f a) where
1079+ Ap x <> Ap y = Ap (liftA2 (<>) x y)
1081+instance (Applicative f, Monoid a) => Monoid (Ap f a) where
1083+ mempty = Ap (pure mempty)
1085+pathFieldEncoder :: forall a p check parse . (HasFields a, Monad check, MonadError Text parse, GCompare (Field a)) => (forall x. Field a x -> Encoder check parse x p) -> Encoder check parse (a, [p]) [p]
1086+pathFieldEncoder fieldEncoder = unsafeEncoder $ do
1087+ fieldEncoderPureMap :: DMap.DMap (Field a) (Decoder Identity parse p) <- getAp $ getConst $ tabulateFieldsA @a $ \f -> Const (Ap $ fmap (DMap.singleton f . Decoder) $ (checkEncoder @Identity) $ fieldEncoder f)
1088+ let fieldEncoderPure :: forall x. Field a x -> Encoder Identity parse x p
1089+ fieldEncoderPure f = toEncoder (DMap.findWithDefault (error "bad") f fieldEncoderPureMap)
1091+ { _encoderImpl_encode = \(x, rest) -> execWriter $ do
1092+ _ <- traverseWithField (\f x_i -> tell (pure $ encode (fieldEncoderPure f) x_i) *> pure x_i) x
1094+ , _encoderImpl_decode = State.runStateT $ tabulateFieldsA $ \f -> State.get >>= \case
1095+ [] -> throwError $ T.pack "not enough path components"
1098+ lift $ tryDecode (fieldEncoderPure f) p
941-- | Use ToJSON/FromJSON to encode to Text. The correctness of this encoder is dependent on the encoding being injective and round-tripping correctly.
1101-- | Use ToJSON/FromJSON to encode to Text. The correctness of this encoder is dependent on the encoding being injective and round-tripping correctly.
942jsonEncoder :: forall check parse r.
1102jsonEncoder :: forall check parse r.
943 ( ToJSON r
1103 ( ToJSON r
@@ -954,4 +1114,14 @@ jsonEncoder = unsafeEncoder $ do
954 Right x -> return x
1114 Right x -> return x
1117+-- Useful for app server integration.
1118+-- p must not start with slashes
1119+byteStringsToPageName :: BS.ByteString -> BS.ByteString -> PageName
1120+byteStringsToPageName p q =
1121+ let pageNameEncoder' :: Encoder Identity Identity PageName (String, String)
1122+ pageNameEncoder' = bimap
1123+ (unpackTextEncoder . pathSegmentsTextEncoder . listToNonEmptyEncoder)
1124+ (unpackTextEncoder . queryParametersTextEncoder . toListMapEncoder)
1125+ in decode pageNameEncoder' (T.unpack (T.decodeUtf8 p), T.unpack (T.decodeUtf8 q))
957--TODO: decodeURIComponent as appropriate
1127--TODO: decodeURIComponent as appropriate