From ce74e23d872943834bde77c6b5f759f2b025bf72 Mon Sep 17 00:00:00 2001 From: Maximilian Tagher Date: Fri, 28 Nov 2014 15:57:01 -0500 Subject: [PATCH 01/43] `timeField` now uses `type="time"` * Also removes deprecation from `timeField` * Also mildly discourages using `timeFieldTypeText` --- yesod-form/Yesod/Form/Fields.hs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/yesod-form/Yesod/Form/Fields.hs b/yesod-form/Yesod/Form/Fields.hs index 84c42c8b..3c3a0d2b 100644 --- a/yesod-form/Yesod/Form/Fields.hs +++ b/yesod-form/Yesod/Form/Fields.hs @@ -154,10 +154,9 @@ $newline never } where showVal = either id (pack . show) --- | An alias for 'timeFieldTypeText'. +-- | An alias for 'timeFieldTypeTime'. timeField :: Monad m => RenderMessage (HandlerSite m) FormMessage => Field m TimeOfDay -timeField = timeFieldTypeText -{-# DEPRECATED timeField "'timeField' currently defaults to an input of type=\"text\". In the next major release, it will default to type=\"time\". To opt in to the new functionality, use 'timeFieldTypeTime'. To keep the existing behavior, use 'timeFieldTypeText'. See 'https://github.com/yesodweb/yesod/pull/874' for details." #-} +timeField = timeFieldTypeTime -- | Creates an input with @type="time"@. will fallback to a text field, and Yesod will parse the time as described in 'timeFieldTypeText'. -- @@ -168,6 +167,8 @@ timeFieldTypeTime :: Monad m => RenderMessage (HandlerSite m) FormMessage => Fie timeFieldTypeTime = timeFieldOfType "time" -- | Creates an input with @type="text"@, parsing the time from an [H]H:MM[:SS] format, with an optional AM or PM (if not given, AM is assumed for compatibility with the 24 hour clock system). +-- +-- This function exists for backwards compatibility with the old implementation of 'timeField', which used to use @type="text"@. Consider using 'timeField' or 'timeFieldTypeTime' for improved UX and validation from the browser. -- -- Add the @time@ package and import the "Data.Time.LocalTime" module to use this function. -- From 79aefc694ae2e3eb176dfd26f4e3d83def3d1e12 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Tue, 13 Oct 2015 10:34:31 +0000 Subject: [PATCH 02/43] Make guessApproot the default (for yesod-core1.5) --- yesod-core/Yesod/Core/Class/Yesod.hs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/yesod-core/Yesod/Core/Class/Yesod.hs b/yesod-core/Yesod/Core/Class/Yesod.hs index 47f4fb49..184d6721 100644 --- a/yesod-core/Yesod/Core/Class/Yesod.hs +++ b/yesod-core/Yesod/Core/Class/Yesod.hs @@ -64,18 +64,14 @@ class RenderRoute site => Yesod site where -- | An absolute URL to the root of the application. Do not include -- trailing slash. -- - -- Default value: 'ApprootRelative'. This is valid under the following - -- conditions: + -- Default value: 'guessApproot'. If you know your application root + -- statically, it will be more efficient and more reliable to instead use + -- 'ApprootStatic' or 'ApprootMaster'. If you do not need full absolute + -- URLs, you can use 'ApprootRelative' instead. -- - -- * Your application is served from the root of the domain. - -- - -- * You do not use any features that require absolute URLs, such as Atom - -- feeds and XML sitemaps. - -- - -- If this is not true, you should override with a different - -- implementation. + -- Note: Prior to yesod-core 1.5, the default value was 'ApprootRelative'. approot :: Approot site - approot = ApprootRelative + approot = guessApproot -- | Output error response pages. -- From c5ac8211151d99ed6730e3b4c3deaeea8c90c8ad Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Tue, 12 Dec 2017 12:08:06 +0200 Subject: [PATCH 03/43] Remove some conditionals for old versions --- yesod-core/Yesod/Core/Class/Handler.hs | 6 ----- yesod-core/Yesod/Core/Content.hs | 24 -------------------- yesod-core/Yesod/Core/Handler.hs | 4 ---- yesod-core/Yesod/Core/Internal/Response.hs | 3 --- yesod-core/Yesod/Core/Internal/Util.hs | 7 +----- yesod-core/Yesod/Core/Json.hs | 16 ------------- yesod-core/Yesod/Core/Types.hs | 26 ---------------------- yesod-core/test/RouteSpec.hs | 8 ------- yesod-core/yesod-core.cabal | 10 ++++----- 9 files changed, 6 insertions(+), 98 deletions(-) diff --git a/yesod-core/Yesod/Core/Class/Handler.hs b/yesod-core/Yesod/Core/Class/Handler.hs index 1d711900..409b867b 100644 --- a/yesod-core/Yesod/Core/Class/Handler.hs +++ b/yesod-core/Yesod/Core/Class/Handler.hs @@ -25,9 +25,7 @@ import Control.Monad.Trans.Identity ( IdentityT) import Control.Monad.Trans.List ( ListT ) import Control.Monad.Trans.Maybe ( MaybeT ) import Control.Monad.Trans.Error ( ErrorT, Error) -#if MIN_VERSION_transformers(0,4,0) import Control.Monad.Trans.Except ( ExceptT ) -#endif import Control.Monad.Trans.Reader ( ReaderT ) import Control.Monad.Trans.State ( StateT ) import Control.Monad.Trans.Writer ( WriterT ) @@ -59,9 +57,7 @@ GO(IdentityT) GO(ListT) GO(MaybeT) GOX(Error e, ErrorT e) -#if MIN_VERSION_transformers(0,4,0) GO(ExceptT e) -#endif GO(ReaderT r) GO(StateT s) GOX(Monoid w, WriterT w) @@ -85,9 +81,7 @@ GO(IdentityT) GO(ListT) GO(MaybeT) GOX(Error e, ErrorT e) -#if MIN_VERSION_transformers(0,4,0) GO(ExceptT e) -#endif GO(ReaderT r) GO(StateT s) GOX(Monoid w, WriterT w) diff --git a/yesod-core/Yesod/Core/Content.hs b/yesod-core/Yesod/Core/Content.hs index 1313a1ea..d98d967e 100644 --- a/yesod-core/Yesod/Core/Content.hs +++ b/yesod-core/Yesod/Core/Content.hs @@ -66,12 +66,6 @@ import Data.Conduit.Internal (ResumableSource (ResumableSource)) import qualified Data.Conduit.Internal as CI import qualified Data.Aeson as J -#if MIN_VERSION_aeson(1, 0, 0) -#elif MIN_VERSION_aeson(0, 7, 0) -import Data.Aeson.Encode (encodeToTextBuilder) -#else -import Data.Aeson.Encode (fromValue) -#endif import qualified Blaze.ByteString.Builder.Char.Utf8 as Blaze import Data.Text.Lazy.Builder (toLazyText) import Yesod.Core.Types @@ -243,34 +237,18 @@ instance ToContent a => ToContent (DontFullyEvaluate a) where toContent (DontFullyEvaluate a) = ContentDontEvaluate $ toContent a instance ToContent J.Value where -#if MIN_VERSION_aeson(1, 0, 0) toContent = flip ContentBuilder Nothing . J.fromEncoding . J.toEncoding -#else - toContent = flip ContentBuilder Nothing - . Blaze.fromLazyText - . toLazyText -#if MIN_VERSION_aeson(0, 7, 0) - . encodeToTextBuilder -#else - . fromValue -#endif -#endif - -#if MIN_VERSION_aeson(0, 11, 0) instance ToContent J.Encoding where toContent = flip ContentBuilder Nothing . J.fromEncoding -#endif instance HasContentType J.Value where getContentType _ = typeJson -#if MIN_VERSION_aeson(0, 11, 0) instance HasContentType J.Encoding where getContentType _ = typeJson -#endif instance HasContentType Html where getContentType _ = typeHtml @@ -307,10 +285,8 @@ instance ToTypedContent RepXml where toTypedContent (RepXml c) = TypedContent typeXml c instance ToTypedContent J.Value where toTypedContent v = TypedContent typeJson (toContent v) -#if MIN_VERSION_aeson(0, 11, 0) instance ToTypedContent J.Encoding where toTypedContent e = TypedContent typeJson (toContent e) -#endif instance ToTypedContent Html where toTypedContent h = TypedContent typeHtml (toContent h) instance ToTypedContent T.Text where diff --git a/yesod-core/Yesod/Core/Handler.hs b/yesod-core/Yesod/Core/Handler.hs index 9a340803..bfd254d0 100644 --- a/yesod-core/Yesod/Core/Handler.hs +++ b/yesod-core/Yesod/Core/Handler.hs @@ -635,11 +635,7 @@ sendResponseStatus s = handlerError . HCContent s . toTypedContent -- -- @since 1.4.18 sendStatusJSON :: (MonadHandler m, ToJSON c) => H.Status -> c -> m a -#if MIN_VERSION_aeson(0, 11, 0) sendStatusJSON s v = sendResponseStatus s (toEncoding v) -#else -sendStatusJSON s v = sendResponseStatus s (toJSON v) -#endif -- | Send a 201 "Created" response with the given route as the Location -- response header. diff --git a/yesod-core/Yesod/Core/Internal/Response.hs b/yesod-core/Yesod/Core/Internal/Response.hs index 7b2f2dde..a7263b61 100644 --- a/yesod-core/Yesod/Core/Internal/Response.hs +++ b/yesod-core/Yesod/Core/Internal/Response.hs @@ -12,9 +12,6 @@ import Network.Wai import Control.Monad (mplus) import Control.Monad.Trans.Resource (runInternalState, InternalState) import Network.Wai.Internal -#if !MIN_VERSION_base(4, 6, 0) -import Prelude hiding (catch) -#endif import Web.Cookie (renderSetCookie) import Yesod.Core.Content import Yesod.Core.Types diff --git a/yesod-core/Yesod/Core/Internal/Util.hs b/yesod-core/Yesod/Core/Internal/Util.hs index 204602c7..5f747855 100644 --- a/yesod-core/Yesod/Core/Internal/Util.hs +++ b/yesod-core/Yesod/Core/Internal/Util.hs @@ -13,12 +13,7 @@ import Data.Serialize (Get, Put, Serialize (..)) import qualified Data.Text as T import Data.Time (Day (ModifiedJulianDay, toModifiedJulianDay), DiffTime, UTCTime (..), formatTime, - getCurrentTime, addUTCTime) -#if MIN_VERSION_time(1,5,0) -import Data.Time (defaultTimeLocale) -#else -import System.Locale (defaultTimeLocale) -#endif + getCurrentTime, addUTCTime, defaultTimeLocale) putTime :: UTCTime -> Put putTime (UTCTime d t) = diff --git a/yesod-core/Yesod/Core/Json.hs b/yesod-core/Yesod/Core/Json.hs index dc1116ba..245aad90 100644 --- a/yesod-core/Yesod/Core/Json.hs +++ b/yesod-core/Yesod/Core/Json.hs @@ -6,9 +6,7 @@ module Yesod.Core.Json defaultLayoutJson , jsonToRepJson , returnJson -#if MIN_VERSION_aeson(0, 11, 0) , returnJsonEncoding -#endif , provideJson -- * Convert to a JSON value @@ -29,9 +27,7 @@ module Yesod.Core.Json -- * Convenience functions , jsonOrRedirect -#if MIN_VERSION_aeson(0, 11, 0) , jsonEncodingOrRedirect -#endif , acceptsJson ) where @@ -67,11 +63,7 @@ defaultLayoutJson :: (Yesod site, J.ToJSON a) -> HandlerT site IO TypedContent defaultLayoutJson w json = selectRep $ do provideRep $ defaultLayout w -#if MIN_VERSION_aeson(0, 11, 0) provideRep $ fmap J.toEncoding json -#else - provideRep $ fmap J.toJSON json -#endif -- | Wraps a data type in a 'RepJson'. The data type must -- support conversion to JSON via 'J.ToJSON'. @@ -87,24 +79,18 @@ jsonToRepJson = return . J.toJSON returnJson :: (Monad m, J.ToJSON a) => a -> m J.Value returnJson = return . J.toJSON -#if MIN_VERSION_aeson(0, 11, 0) -- | Convert a value to a JSON representation via aeson\'s 'J.toEncoding' function. -- -- @since 1.4.21 returnJsonEncoding :: (Monad m, J.ToJSON a) => a -> m J.Encoding returnJsonEncoding = return . J.toEncoding -#endif -- | Provide a JSON representation for usage with 'selectReps', using aeson\'s -- 'J.toJSON' (aeson >= 0.11: 'J.toEncoding') function to perform the conversion. -- -- @since 1.2.1 provideJson :: (Monad m, J.ToJSON a) => a -> Writer (Endo [ProvidedRep m]) () -#if MIN_VERSION_aeson(0, 11, 0) provideJson = provideRep . return . J.toEncoding -#else -provideJson = provideRep . return . J.toJSON -#endif -- | Parse the request body to a data type as a JSON value. The -- data type must support conversion from JSON via 'J.FromJSON'. @@ -173,7 +159,6 @@ jsonOrRedirect :: (MonadHandler m, J.ToJSON a) -> m J.Value jsonOrRedirect = jsonOrRedirect' J.toJSON -#if MIN_VERSION_aeson(0, 11, 0) -- | jsonEncodingOrRedirect simplifies the scenario where a POST handler sends a different -- response based on Accept headers: -- @@ -187,7 +172,6 @@ jsonEncodingOrRedirect :: (MonadHandler m, J.ToJSON a) -> a -- ^ Data to send via JSON -> m J.Encoding jsonEncodingOrRedirect = jsonOrRedirect' J.toEncoding -#endif jsonOrRedirect' :: MonadHandler m => (a -> b) diff --git a/yesod-core/Yesod/Core/Types.hs b/yesod-core/Yesod/Core/Types.hs index 5067c480..a1c960de 100644 --- a/yesod-core/Yesod/Core/Types.hs +++ b/yesod-core/Yesod/Core/Types.hs @@ -59,16 +59,11 @@ import Yesod.Core.Internal.Util (getTime, putTime) import Control.Monad.Trans.Class (MonadTrans (..)) import Yesod.Routes.Class (RenderRoute (..), ParseRoute (..)) import Control.Monad.Reader (MonadReader (..)) -#if !MIN_VERSION_base(4, 6, 0) -import Prelude hiding (catch) -#endif import Control.DeepSeq (NFData (rnf)) import Control.DeepSeq.Generics (genericRnf) import Data.Conduit.Lazy (MonadActive, monadActive) import Yesod.Core.TypeCache (TypeMap, KeyedTypeMap) -#if MIN_VERSION_monad_logger(0, 3, 10) import Control.Monad.Logger (MonadLoggerIO (..)) -#endif import Data.Semigroup (Semigroup) -- Sessions @@ -422,21 +417,12 @@ instance MonadIO m => MonadIO (WidgetT site m) where instance MonadBase b m => MonadBase b (WidgetT site m) where liftBase = WidgetT . const . liftBase . fmap (, mempty) instance MonadBaseControl b m => MonadBaseControl b (WidgetT site m) where -#if MIN_VERSION_monad_control(1,0,0) type StM (WidgetT site m) a = StM m (a, GWData (Route site)) liftBaseWith f = WidgetT $ \reader' -> liftBaseWith $ \runInBase -> fmap (\x -> (x, mempty)) (f $ runInBase . flip unWidgetT reader') restoreM = WidgetT . const . restoreM -#else - data StM (WidgetT site m) a = StW (StM m (a, GWData (Route site))) - liftBaseWith f = WidgetT $ \reader' -> - liftBaseWith $ \runInBase -> - fmap (\x -> (x, mempty)) - (f $ fmap StW . runInBase . flip unWidgetT reader') - restoreM (StW base) = WidgetT $ const $ restoreM base -#endif instance Monad m => MonadReader site (WidgetT site m) where ask = WidgetT $ \hd -> return (rheSite $ handlerEnv hd, mempty) local f (WidgetT g) = WidgetT $ \hd -> g hd @@ -479,10 +465,8 @@ instance MonadIO m => MonadLogger (WidgetT site m) where monadLoggerLog a b c d = WidgetT $ \hd -> liftIO $ (, mempty) <$> rheLog (handlerEnv hd) a b c (toLogStr d) -#if MIN_VERSION_monad_logger(0, 3, 10) instance MonadIO m => MonadLoggerIO (WidgetT site m) where askLoggerIO = WidgetT $ \hd -> return (rheLog (handlerEnv hd), mempty) -#endif instance MonadActive m => MonadActive (WidgetT site m) where monadActive = lift monadActive @@ -521,19 +505,11 @@ instance Monad m => MonadReader site (HandlerT site m) where -- \"Control.Monad.Trans.Resource.register\': The mutable state is being accessed -- after cleanup. Please contact the maintainers.\" instance MonadBaseControl b m => MonadBaseControl b (HandlerT site m) where -#if MIN_VERSION_monad_control(1,0,0) type StM (HandlerT site m) a = StM m a liftBaseWith f = HandlerT $ \reader' -> liftBaseWith $ \runInBase -> f $ runInBase . (\(HandlerT r) -> r reader') restoreM = HandlerT . const . restoreM -#else - data StM (HandlerT site m) a = StH (StM m a) - liftBaseWith f = HandlerT $ \reader' -> - liftBaseWith $ \runInBase -> - f $ fmap StH . runInBase . (\(HandlerT r) -> r reader') - restoreM (StH base) = HandlerT $ const $ restoreM base -#endif instance MonadThrow m => MonadThrow (HandlerT site m) where throwM = lift . monadThrow @@ -545,10 +521,8 @@ instance MonadIO m => MonadLogger (HandlerT site m) where monadLoggerLog a b c d = HandlerT $ \hd -> liftIO $ rheLog (handlerEnv hd) a b c (toLogStr d) -#if MIN_VERSION_monad_logger(0, 3, 10) instance MonadIO m => MonadLoggerIO (HandlerT site m) where askLoggerIO = HandlerT $ \hd -> return (rheLog (handlerEnv hd)) -#endif instance Monoid (UniqueList x) where mempty = UniqueList id diff --git a/yesod-core/test/RouteSpec.hs b/yesod-core/test/RouteSpec.hs index 283119e2..5766a4cf 100644 --- a/yesod-core/test/RouteSpec.hs +++ b/yesod-core/test/RouteSpec.hs @@ -30,11 +30,7 @@ data MyApp = MyApp data MySub = MySub instance RenderRoute MySub where data -#if MIN_VERSION_base(4,5,0) Route -#else - YRC.Route -#endif MySub = MySubRoute ([Text], [(Text, Text)]) deriving (Show, Eq, Read) renderRoute (MySubRoute x) = x @@ -47,11 +43,7 @@ getMySub MyApp = MySub data MySubParam = MySubParam Int instance RenderRoute MySubParam where data -#if MIN_VERSION_base(4,5,0) Route -#else - YRC.Route -#endif MySubParam = ParamRoute Char deriving (Show, Eq, Read) renderRoute (ParamRoute x) = ([singleton x], []) diff --git a/yesod-core/yesod-core.cabal b/yesod-core/yesod-core.cabal index c1d6f8a8..fe3165a4 100644 --- a/yesod-core/yesod-core.cabal +++ b/yesod-core/yesod-core.cabal @@ -22,7 +22,7 @@ extra-source-files: library build-depends: base >= 4.7 && < 5 - , time >= 1.1.4 + , time >= 1.5 , wai >= 3.0 , wai-extra >= 3.0.7 , bytestring >= 0.10 @@ -31,7 +31,7 @@ library , path-pieces >= 0.1.2 && < 0.3 , shakespeare >= 2.0 , blaze-builder >= 0.2.1.4 && < 0.5 - , transformers >= 0.2.2 + , transformers >= 0.4 , mtl , clientsession >= 0.9.1 && < 0.10 , random >= 1.0.0.2 && < 1.2 @@ -39,7 +39,7 @@ library , old-locale >= 1.0.0.2 && < 1.1 , containers >= 0.2 , unordered-containers >= 0.2 - , monad-control >= 0.3 && < 1.1 + , monad-control >= 1.0 && < 1.1 , transformers-base >= 0.4 , cookie >= 0.4.2 && < 0.5 , http-types >= 0.7 @@ -47,10 +47,10 @@ library , parsec >= 2 && < 3.2 , directory >= 1 , vector >= 0.9 && < 0.13 - , aeson >= 0.5 + , aeson >= 1.0 , fast-logger >= 2.2 , wai-logger >= 0.2 - , monad-logger >= 0.3.1 && < 0.4 + , monad-logger >= 0.3.10 && < 0.4 , conduit >= 1.2 , resourcet >= 0.4.9 && < 1.2 , lifted-base >= 0.1.2 From 5c8b1b542a24437b9014f87713df4dc4210d2e44 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Tue, 12 Dec 2017 12:46:35 +0200 Subject: [PATCH 04/43] WidgetT uses IORef --- yesod-core/ChangeLog.md | 7 ++++ yesod-core/Yesod/Core/Class/Handler.hs | 9 ++-- yesod-core/Yesod/Core/Class/Yesod.hs | 5 ++- yesod-core/Yesod/Core/Types.hs | 57 +++++++++++++------------- yesod-core/Yesod/Core/Widget.hs | 16 +++++--- yesod-core/yesod-core.cabal | 2 +- 6 files changed, 55 insertions(+), 41 deletions(-) diff --git a/yesod-core/ChangeLog.md b/yesod-core/ChangeLog.md index 43266e77..88a0dcb2 100644 --- a/yesod-core/ChangeLog.md +++ b/yesod-core/ChangeLog.md @@ -1,3 +1,10 @@ +## 1.4.38 + +* Internal only change, users of stable API are unaffected: `WidgetT` + holds its data in an `IORef` so that it is isomorphic to `ReaderT`, + avoiding state-loss issues.. +* Instances for `MonadUnliftIO` + ## 1.4.37.2 * Improve error messages for the CSRF checking functions [#1455](https://github.com/yesodweb/yesod/issues/1455) diff --git a/yesod-core/Yesod/Core/Class/Handler.hs b/yesod-core/Yesod/Core/Class/Handler.hs index 409b867b..8e447eab 100644 --- a/yesod-core/Yesod/Core/Class/Handler.hs +++ b/yesod-core/Yesod/Core/Class/Handler.hs @@ -12,12 +12,11 @@ module Yesod.Core.Class.Handler ) where import Yesod.Core.Types -import Control.Monad (liftM) import Control.Monad.IO.Class (liftIO) import Control.Monad.Trans.Resource (MonadResource, MonadResourceBase) import Control.Monad.Trans.Class (lift) #if __GLASGOW_HASKELL__ < 710 -import Data.Monoid (Monoid, mempty) +import Data.Monoid (Monoid) #endif import Data.Conduit.Internal (Pipe, ConduitM) @@ -48,8 +47,8 @@ instance MonadResourceBase m => MonadHandler (HandlerT site m) where instance MonadResourceBase m => MonadHandler (WidgetT site m) where type HandlerSite (WidgetT site m) = site - liftHandlerT (HandlerT f) = WidgetT $ liftIO . liftM (, mempty) . f . replaceToParent -{-# RULES "liftHandlerT (WidgetT site IO)" forall f. liftHandlerT (HandlerT f) = WidgetT $ liftM (, mempty) . f #-} + liftHandlerT (HandlerT f) = WidgetT $ \_ref env -> liftIO $ f $ replaceToParent env +{-# RULES "liftHandlerT (WidgetT site IO)" forall f. liftHandlerT (HandlerT f) = WidgetT $ const f #-} #define GO(T) instance MonadHandler m => MonadHandler (T m) where type HandlerSite (T m) = HandlerSite m; liftHandlerT = lift . liftHandlerT #define GOX(X, T) instance (X, MonadHandler m) => MonadHandler (T m) where type HandlerSite (T m) = HandlerSite m; liftHandlerT = lift . liftHandlerT @@ -73,7 +72,7 @@ GO(ConduitM i o) class MonadHandler m => MonadWidget m where liftWidgetT :: WidgetT (HandlerSite m) IO a -> m a instance MonadResourceBase m => MonadWidget (WidgetT site m) where - liftWidgetT (WidgetT f) = WidgetT $ liftIO . f . replaceToParent + liftWidgetT (WidgetT f) = WidgetT $ \ref env -> liftIO $ f ref $ replaceToParent env #define GO(T) instance MonadWidget m => MonadWidget (T m) where liftWidgetT = lift . liftWidgetT #define GOX(X, T) instance (X, MonadWidget m) => MonadWidget (T m) where liftWidgetT = lift . liftWidgetT diff --git a/yesod-core/Yesod/Core/Class/Yesod.hs b/yesod-core/Yesod/Core/Class/Yesod.hs index 385ebe7a..5e169d7e 100644 --- a/yesod-core/Yesod/Core/Class/Yesod.hs +++ b/yesod-core/Yesod/Core/Class/Yesod.hs @@ -59,6 +59,7 @@ import Yesod.Core.Widget import Control.Monad.Trans.Class (lift) import Data.CaseInsensitive (CI) import qualified Network.Wai.Request +import Data.IORef -- | Define settings for a Yesod applications. All methods have intelligent -- defaults, and therefore no implementation is required. @@ -550,7 +551,9 @@ widgetToPageContent :: Yesod site widgetToPageContent w = do master <- getYesod hd <- HandlerT return - ((), GWData (Body body) (Last mTitle) scripts' stylesheets' style jscript (Head head')) <- lift $ unWidgetT w hd + ref <- lift $ newIORef mempty + lift $ unWidgetT w ref hd + GWData (Body body) (Last mTitle) scripts' stylesheets' style jscript (Head head') <- lift $ readIORef ref let title = maybe mempty unTitle mTitle scripts = runUniqueList scripts' stylesheets = runUniqueList stylesheets' diff --git a/yesod-core/Yesod/Core/Types.hs b/yesod-core/Yesod/Core/Types.hs index a1c960de..607dde01 100644 --- a/yesod-core/Yesod/Core/Types.hs +++ b/yesod-core/Yesod/Core/Types.hs @@ -29,7 +29,7 @@ import Control.Monad.Trans.Resource (MonadResource (..), Interna import Data.ByteString (ByteString) import qualified Data.ByteString.Lazy as L import Data.Conduit (Flush, Source) -import Data.IORef (IORef) +import Data.IORef (IORef, modifyIORef') import Data.Map (Map, unionWith) import qualified Data.Map as Map import Data.Monoid (Endo (..), Last (..)) @@ -59,6 +59,7 @@ import Yesod.Core.Internal.Util (getTime, putTime) import Control.Monad.Trans.Class (MonadTrans (..)) import Yesod.Routes.Class (RenderRoute (..), ParseRoute (..)) import Control.Monad.Reader (MonadReader (..)) +import Data.Monoid ((<>)) import Control.DeepSeq (NFData (rnf)) import Control.DeepSeq.Generics (genericRnf) import Data.Conduit.Lazy (MonadActive, monadActive) @@ -248,7 +249,7 @@ type YesodApp = YesodRequest -> ResourceT IO YesodResponse -- site datatypes. While this is simply a @WriterT@, we define a newtype for -- better error messages. newtype WidgetT site m a = WidgetT - { unWidgetT :: HandlerData site (MonadRoute m) -> m (a, GWData (Route site)) + { unWidgetT :: IORef (GWData (Route site)) -> HandlerData site (MonadRoute m) -> m a } instance (a ~ (), Monad m) => Monoid (WidgetT site m a) where @@ -261,10 +262,12 @@ instance (a ~ (), Monad m) => Semigroup (WidgetT site m a) -- For example, in a yesod-scaffold site you could use: -- -- @getHomeR = do defaultLayout "Widget text"@ -instance (Monad m, a ~ ()) => IsString (WidgetT site m a) where +instance (MonadIO m, a ~ ()) => IsString (WidgetT site m a) where -- FIXME turn it into WidgetFor? fromString = toWidget . toHtml . T.pack - where toWidget x = WidgetT $ const $ return ((), GWData (Body (const x)) - mempty mempty mempty mempty mempty mempty) + where toWidget x = tellWidget mempty { gwdBody = Body (const x) } + +tellWidget :: MonadIO m => GWData (Route site) -> WidgetT site m () +tellWidget d = WidgetT $ \ref _ -> liftIO $ modifyIORef' ref (<> d) type RY master = Route master -> [(Text, Text)] -> Text @@ -407,32 +410,30 @@ instance Monad m => Applicative (WidgetT site m) where pure = return (<*>) = ap instance Monad m => Monad (WidgetT site m) where - return a = WidgetT $ const $ return (a, mempty) - WidgetT x >>= f = WidgetT $ \r -> do - (a, wa) <- x r - (b, wb) <- unWidgetT (f a) r - return (b, wa `mappend` wb) + return a = WidgetT $ \_ _ -> return a + WidgetT x >>= f = WidgetT $ \ref r -> do + a <- x ref r + unWidgetT (f a) ref r instance MonadIO m => MonadIO (WidgetT site m) where liftIO = lift . liftIO instance MonadBase b m => MonadBase b (WidgetT site m) where - liftBase = WidgetT . const . liftBase . fmap (, mempty) + liftBase = WidgetT . const . const . liftBase instance MonadBaseControl b m => MonadBaseControl b (WidgetT site m) where - type StM (WidgetT site m) a = StM m (a, GWData (Route site)) - liftBaseWith f = WidgetT $ \reader' -> + type StM (WidgetT site m) a = StM m a + liftBaseWith f = WidgetT $ \ref reader' -> liftBaseWith $ \runInBase -> - fmap (\x -> (x, mempty)) - (f $ runInBase . flip unWidgetT reader') - restoreM = WidgetT . const . restoreM + f $ runInBase . (\(WidgetT w) -> w ref reader') + restoreM = WidgetT . const . const . restoreM instance Monad m => MonadReader site (WidgetT site m) where - ask = WidgetT $ \hd -> return (rheSite $ handlerEnv hd, mempty) - local f (WidgetT g) = WidgetT $ \hd -> g hd + ask = WidgetT $ \_ hd -> return (rheSite $ handlerEnv hd) + local f (WidgetT g) = WidgetT $ \ref hd -> g ref hd { handlerEnv = (handlerEnv hd) { rheSite = f $ rheSite $ handlerEnv hd } } instance MonadTrans (WidgetT site) where - lift = WidgetT . const . liftM (, mempty) + lift = WidgetT . const . const instance MonadThrow m => MonadThrow (WidgetT site m) where throwM = lift . throwM @@ -445,13 +446,13 @@ instance MonadMask m => MonadMask (HandlerT site m) where HandlerT $ \e -> uninterruptibleMask $ \u -> unHandlerT (a $ q u) e where q u (HandlerT b) = HandlerT (u . b) instance MonadCatch m => MonadCatch (WidgetT site m) where - catch (WidgetT m) c = WidgetT $ \r -> m r `catch` \e -> unWidgetT (c e) r + catch (WidgetT m) c = WidgetT $ \ref r -> m ref r `catch` \e -> unWidgetT (c e) ref r instance MonadMask m => MonadMask (WidgetT site m) where - mask a = WidgetT $ \e -> mask $ \u -> unWidgetT (a $ q u) e - where q u (WidgetT b) = WidgetT (u . b) + mask a = WidgetT $ \ref e -> mask $ \u -> unWidgetT (a $ q u) ref e + where q u (WidgetT b) = WidgetT (\ref e -> u $ b ref e) uninterruptibleMask a = - WidgetT $ \e -> uninterruptibleMask $ \u -> unWidgetT (a $ q u) e - where q u (WidgetT b) = WidgetT (u . b) + WidgetT $ \ref e -> uninterruptibleMask $ \u -> unWidgetT (a $ q u) ref e + where q u (WidgetT b) = WidgetT (\ref e -> u $ b ref e) -- CPP to avoid a redundant constraints warning #if MIN_VERSION_base(4,9,0) @@ -459,14 +460,14 @@ instance (MonadIO m, MonadBase IO m, MonadThrow m) => MonadResource (WidgetT sit #else instance (Applicative m, MonadIO m, MonadBase IO m, MonadThrow m) => MonadResource (WidgetT site m) where #endif - liftResourceT f = WidgetT $ \hd -> liftIO $ (, mempty) <$> runInternalState f (handlerResource hd) + liftResourceT f = WidgetT $ \_ hd -> liftIO $ runInternalState f (handlerResource hd) instance MonadIO m => MonadLogger (WidgetT site m) where - monadLoggerLog a b c d = WidgetT $ \hd -> - liftIO $ (, mempty) <$> rheLog (handlerEnv hd) a b c (toLogStr d) + monadLoggerLog a b c d = WidgetT $ \_ hd -> + liftIO $ rheLog (handlerEnv hd) a b c (toLogStr d) instance MonadIO m => MonadLoggerIO (WidgetT site m) where - askLoggerIO = WidgetT $ \hd -> return (rheLog (handlerEnv hd), mempty) + askLoggerIO = WidgetT $ \_ hd -> return $ rheLog $ handlerEnv hd instance MonadActive m => MonadActive (WidgetT site m) where monadActive = lift monadActive diff --git a/yesod-core/Yesod/Core/Widget.hs b/yesod-core/Yesod/Core/Widget.hs index 4f4a119a..f9e1eeb3 100644 --- a/yesod-core/Yesod/Core/Widget.hs +++ b/yesod-core/Yesod/Core/Widget.hs @@ -60,7 +60,6 @@ import Yesod.Core.Handler (getMessageRender, getUrlRenderParams) #if __GLASGOW_HASKELL__ < 710 import Control.Applicative ((<$>)) #endif -import Control.Monad (liftM) import Control.Monad.IO.Class (MonadIO, liftIO) import Text.Shakespeare.I18N (RenderMessage) import Data.Text (Text) @@ -73,6 +72,7 @@ import Data.Text.Lazy.Builder (fromLazyText) import Text.Blaze.Html (toHtml, preEscapedToMarkup) import qualified Data.Text.Lazy as TL import qualified Data.Text.Lazy.Builder as TB +import Data.IORef import Yesod.Core.Types import Yesod.Core.Class.Handler @@ -268,20 +268,24 @@ ihamletToHtml ih = do return $ ih (toHtml . mrender) urender tell :: MonadWidget m => GWData (Route (HandlerSite m)) -> m () -tell w = liftWidgetT $ WidgetT $ const $ return ((), w) +tell = liftWidgetT . tellWidget toUnique :: x -> UniqueList x toUnique = UniqueList . (:) handlerToWidget :: Monad m => HandlerT site m a -> WidgetT site m a -handlerToWidget (HandlerT f) = WidgetT $ liftM (, mempty) . f +handlerToWidget (HandlerT f) = WidgetT $ const f widgetToParentWidget :: MonadIO m => WidgetT child IO a -> HandlerT child (HandlerT parent m) (WidgetT parent m a) -widgetToParentWidget (WidgetT f) = HandlerT $ \hd -> do - (a, gwd) <- liftIO $ f hd { handlerToParent = const () } - return $ WidgetT $ const $ return (a, liftGWD (handlerToParent hd) gwd) +widgetToParentWidget (WidgetT f) = HandlerT $ \hdChild -> do + return $ WidgetT $ \ref _hdParent -> liftIO $ do + tmp <- newIORef mempty + a <- f tmp hdChild { handlerToParent = const () } + gwd <- readIORef tmp + modifyIORef' ref (<> liftGWD (handlerToParent hdChild) gwd) + return a liftGWD :: (child -> parent) -> GWData child -> GWData parent liftGWD tp gwd = GWData diff --git a/yesod-core/yesod-core.cabal b/yesod-core/yesod-core.cabal index fe3165a4..3dc85c18 100644 --- a/yesod-core/yesod-core.cabal +++ b/yesod-core/yesod-core.cabal @@ -1,5 +1,5 @@ name: yesod-core -version: 1.4.37.2 +version: 1.4.38 license: MIT license-file: LICENSE author: Michael Snoyman From 1c2914eded9fee8674e3ad2402cf42f4da8025ef Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Tue, 12 Dec 2017 12:46:49 +0200 Subject: [PATCH 05/43] MonadUnliftIO instances --- stack.yaml | 2 ++ yesod-core/Yesod/Core/Types.hs | 13 +++++++++++++ yesod-core/yesod-core.cabal | 1 + 3 files changed, 16 insertions(+) diff --git a/stack.yaml b/stack.yaml index 213e9db3..b53c2b2e 100644 --- a/stack.yaml +++ b/stack.yaml @@ -13,3 +13,5 @@ packages: - ./yesod - ./yesod-eventsource - ./yesod-websockets +extra-deps: +- unliftio-core-0.1.0.0 diff --git a/yesod-core/Yesod/Core/Types.hs b/yesod-core/Yesod/Core/Types.hs index 607dde01..fd4572de 100644 --- a/yesod-core/Yesod/Core/Types.hs +++ b/yesod-core/Yesod/Core/Types.hs @@ -66,6 +66,7 @@ import Data.Conduit.Lazy (MonadActive, monadActive) import Yesod.Core.TypeCache (TypeMap, KeyedTypeMap) import Control.Monad.Logger (MonadLoggerIO (..)) import Data.Semigroup (Semigroup) +import Control.Monad.IO.Unlift (MonadUnliftIO (..), UnliftIO (..), withUnliftIO) -- Sessions type SessionMap = Map Text ByteString @@ -424,6 +425,12 @@ instance MonadBaseControl b m => MonadBaseControl b (WidgetT site m) where liftBaseWith $ \runInBase -> f $ runInBase . (\(WidgetT w) -> w ref reader') restoreM = WidgetT . const . const . restoreM +-- | @since 1.4.38 +instance MonadUnliftIO m => MonadUnliftIO (WidgetT site m) where + {-# INLINE askUnliftIO #-} + askUnliftIO = WidgetT $ \ref r -> + withUnliftIO $ \u -> + return (UnliftIO (\(WidgetT w) -> unliftIO u $ w ref r)) instance Monad m => MonadReader site (WidgetT site m) where ask = WidgetT $ \_ hd -> return (rheSite $ handlerEnv hd) local f (WidgetT g) = WidgetT $ \ref hd -> g ref hd @@ -511,6 +518,12 @@ instance MonadBaseControl b m => MonadBaseControl b (HandlerT site m) where liftBaseWith $ \runInBase -> f $ runInBase . (\(HandlerT r) -> r reader') restoreM = HandlerT . const . restoreM +-- | @since 1.4.38 +instance MonadUnliftIO m => MonadUnliftIO (HandlerT site m) where + {-# INLINE askUnliftIO #-} + askUnliftIO = HandlerT $ \r -> + withUnliftIO $ \u -> + return (UnliftIO (unliftIO u . flip unHandlerT r)) instance MonadThrow m => MonadThrow (HandlerT site m) where throwM = lift . monadThrow diff --git a/yesod-core/yesod-core.cabal b/yesod-core/yesod-core.cabal index 3dc85c18..84ae5ab0 100644 --- a/yesod-core/yesod-core.cabal +++ b/yesod-core/yesod-core.cabal @@ -70,6 +70,7 @@ library , auto-update , semigroups , byteable + , unliftio-core exposed-modules: Yesod.Core Yesod.Core.Content From 47ee7384ea123135d090c4e931657cb11c583b94 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Wed, 13 Dec 2017 09:53:14 +0200 Subject: [PATCH 06/43] Be gone with ye HandlerT! --- yesod-core/ChangeLog.md | 4 + yesod-core/Yesod/Core.hs | 14 +- yesod-core/Yesod/Core/Class/Breadcrumbs.hs | 4 +- yesod-core/Yesod/Core/Class/Dispatch.hs | 77 ++++-- yesod-core/Yesod/Core/Class/Handler.hs | 48 ++-- yesod-core/Yesod/Core/Class/Yesod.hs | 64 ++--- yesod-core/Yesod/Core/Dispatch.hs | 1 - yesod-core/Yesod/Core/Handler.hs | 57 ++--- yesod-core/Yesod/Core/Internal/LiteApp.hs | 4 +- yesod-core/Yesod/Core/Internal/Run.hs | 11 +- yesod-core/Yesod/Core/Internal/TH.hs | 9 +- yesod-core/Yesod/Core/Json.hs | 10 +- yesod-core/Yesod/Core/Types.hs | 226 ++++++++---------- yesod-core/Yesod/Core/Unsafe.hs | 5 +- yesod-core/Yesod/Core/Widget.hs | 56 +---- .../test/YesodCoreTest/NoOverloadedStrings.hs | 15 +- .../YesodCoreTest/NoOverloadedStringsSub.hs | 4 +- 17 files changed, 290 insertions(+), 319 deletions(-) diff --git a/yesod-core/ChangeLog.md b/yesod-core/ChangeLog.md index 88a0dcb2..521e68fc 100644 --- a/yesod-core/ChangeLog.md +++ b/yesod-core/ChangeLog.md @@ -1,3 +1,7 @@ +## 1.5.0 + +* Overhaul of `HandlerT`/`WidgetT` to no longer be transformers. + ## 1.4.38 * Internal only change, users of stable API are unaffected: `WidgetT` diff --git a/yesod-core/Yesod/Core.hs b/yesod-core/Yesod/Core.hs index 1d5de1d1..d13d5cf4 100644 --- a/yesod-core/Yesod/Core.hs +++ b/yesod-core/Yesod/Core.hs @@ -67,15 +67,15 @@ module Yesod.Core -- * JS loaders , ScriptLoadPosition (..) , BottomOfHeadAsync - -- * Subsites + -- * Generalizing type classes , MonadHandler (..) , MonadWidget (..) - , getRouteToParent - , defaultLayoutSub -- * Approot , guessApproot , guessApprootOr , getApprootText + -- * Subsites + , MonadSubHandler (..) -- * Misc , yesodVersion , yesodRender @@ -185,14 +185,6 @@ maybeAuthorized r isWrite = do x <- isAuthorized r isWrite return $ if x == Authorized then Just r else Nothing -getRouteToParent :: Monad m => HandlerT child (HandlerT parent m) (Route child -> Route parent) -getRouteToParent = HandlerT $ return . handlerToParent - -defaultLayoutSub :: Yesod parent - => WidgetT child IO () - -> HandlerT child (HandlerT parent IO) Html -defaultLayoutSub cwidget = widgetToParentWidget cwidget >>= lift . defaultLayout - showIntegral :: Integral a => a -> String showIntegral x = show (fromIntegral x :: Integer) diff --git a/yesod-core/Yesod/Core/Class/Breadcrumbs.hs b/yesod-core/Yesod/Core/Class/Breadcrumbs.hs index 84586055..1e956ff2 100644 --- a/yesod-core/Yesod/Core/Class/Breadcrumbs.hs +++ b/yesod-core/Yesod/Core/Class/Breadcrumbs.hs @@ -11,11 +11,11 @@ import Data.Text (Text) class YesodBreadcrumbs site where -- | Returns the title and the parent resource, if available. If you return -- a 'Nothing', then this is considered a top-level page. - breadcrumb :: Route site -> HandlerT site IO (Text , Maybe (Route site)) + breadcrumb :: Route site -> HandlerFor site (Text , Maybe (Route site)) -- | Gets the title of the current page and the hierarchy of parent pages, -- along with their respective titles. -breadcrumbs :: YesodBreadcrumbs site => HandlerT site IO (Text, [(Route site, Text)]) +breadcrumbs :: YesodBreadcrumbs site => HandlerFor site (Text, [(Route site, Text)]) breadcrumbs = do x <- getCurrentRoute case x of diff --git a/yesod-core/Yesod/Core/Class/Dispatch.hs b/yesod-core/Yesod/Core/Class/Dispatch.hs index b68340ea..16910ecf 100644 --- a/yesod-core/Yesod/Core/Class/Dispatch.hs +++ b/yesod-core/Yesod/Core/Class/Dispatch.hs @@ -6,13 +6,13 @@ {-# LANGUAGE FlexibleContexts #-} module Yesod.Core.Class.Dispatch where -import Yesod.Routes.Class import qualified Network.Wai as W import Yesod.Core.Types -import Yesod.Core.Content -import Yesod.Core.Handler (sendWaiApplication, stripHandlerT) -import Yesod.Core.Class.Yesod +import Yesod.Core.Content (ToTypedContent (..)) +import Yesod.Core.Handler (sendWaiApplication, getYesod, getCurrentRoute) import Yesod.Core.Class.Handler +import Yesod.Core.Class.Yesod +import Control.Monad.Trans.Reader (ReaderT (..), ask) -- | This class is automatically instantiated when you use the template haskell -- mkYesod function. You should never need to deal with it directly. @@ -28,24 +28,63 @@ instance YesodSubDispatch WaiSubsite master where where WaiSubsite app = ysreGetSub $ yreSite ysreParentEnv -instance YesodSubDispatch WaiSubsiteWithAuth (HandlerT master IO) where +instance MonadHandler m => YesodSubDispatch WaiSubsiteWithAuth m where yesodSubDispatch YesodSubRunnerEnv {..} req = - ysreParentRunner base ysreParentEnv (fmap ysreToParentRoute route) req + ysreParentRunner handlert ysreParentEnv (fmap ysreToParentRoute route) req where - base = stripHandlerT handlert ysreGetSub ysreToParentRoute route route = Just $ WaiSubsiteWithAuthRoute (W.pathInfo req) [] WaiSubsiteWithAuth set = ysreGetSub $ yreSite $ ysreParentEnv - handlert = sendWaiApplication $ set + handlert = sendWaiApplication set --- | A helper function for creating YesodSubDispatch instances, used by the --- internal generated code. This function has been exported since 1.4.11. --- It promotes a subsite handler to a wai application. -subHelper :: Monad m -- NOTE: This is incredibly similar in type signature to yesodRunner, should probably be pointed out/explained. - => HandlerT child (HandlerT parent m) TypedContent - -> YesodSubRunnerEnv child parent (HandlerT parent m) - -> Maybe (Route child) - -> W.Application -subHelper handlert YesodSubRunnerEnv {..} route = - ysreParentRunner base ysreParentEnv (fmap ysreToParentRoute route) +type SubHandler child parent a = ReaderT (SubsiteData child parent) (HandlerFor parent) a + +data SubsiteData child parent = SubsiteData + { sdToParentRoute :: !(Route child -> Route parent) + , sdCurrentRoute :: !(Maybe (Route child)) + , sdSubsiteData :: !child + } + +class MonadHandler m => MonadSubHandler m where + type SubHandlerSite m + + getSubYesod :: m (SubHandlerSite m) + getToParentRoute :: m (Route (SubHandlerSite m) -> Route (HandlerSite m)) + getSubCurrentRoute :: m (Maybe (Route (SubHandlerSite m))) + +instance MonadSubHandler (HandlerFor site) where + type SubHandlerSite (HandlerFor site) = site + + getSubYesod = getYesod + getToParentRoute = return id + getSubCurrentRoute = getCurrentRoute + +instance MonadSubHandler (WidgetFor site) where + type SubHandlerSite (WidgetFor site) = site + + getSubYesod = getYesod + getToParentRoute = return id + getSubCurrentRoute = getCurrentRoute + +instance (MonadSubHandler m, parent ~ SubHandlerSite m) => MonadSubHandler (ReaderT (SubsiteData child parent) m) where + type SubHandlerSite (ReaderT (SubsiteData child parent) m) = child + + getSubYesod = fmap sdSubsiteData ask + getSubCurrentRoute = fmap sdCurrentRoute ask + getToParentRoute = ReaderT $ \sd -> do + toParent' <- getToParentRoute + return $ toParent' . sdToParentRoute sd + +subHelper + :: (ToTypedContent content, MonadSubHandler m, parent ~ HandlerSite m) + => ReaderT (SubsiteData child parent) m content + -> YesodSubRunnerEnv child parent m + -> Maybe (Route child) + -> W.Application +subHelper (ReaderT f) YesodSubRunnerEnv {..} mroute = + ysreParentRunner handler ysreParentEnv (fmap ysreToParentRoute mroute) where - base = stripHandlerT (fmap toTypedContent handlert) ysreGetSub ysreToParentRoute route + handler = fmap toTypedContent $ f SubsiteData + { sdToParentRoute = ysreToParentRoute + , sdCurrentRoute = mroute + , sdSubsiteData = ysreGetSub $ yreSite ysreParentEnv + } diff --git a/yesod-core/Yesod/Core/Class/Handler.hs b/yesod-core/Yesod/Core/Class/Handler.hs index 8e447eab..7373d261 100644 --- a/yesod-core/Yesod/Core/Class/Handler.hs +++ b/yesod-core/Yesod/Core/Class/Handler.hs @@ -9,11 +9,12 @@ module Yesod.Core.Class.Handler ( MonadHandler (..) , MonadWidget (..) + , liftHandlerT + , liftWidgetT ) where import Yesod.Core.Types -import Control.Monad.IO.Class (liftIO) -import Control.Monad.Trans.Resource (MonadResource, MonadResourceBase) +import Control.Monad.Trans.Resource (MonadResource) import Control.Monad.Trans.Class (lift) #if __GLASGOW_HASKELL__ < 710 import Data.Monoid (Monoid) @@ -33,25 +34,27 @@ import qualified Control.Monad.Trans.RWS.Strict as Strict ( RWST ) import qualified Control.Monad.Trans.State.Strict as Strict ( StateT ) import qualified Control.Monad.Trans.Writer.Strict as Strict ( WriterT ) +-- FIXME should we just use MonadReader instances instead? class MonadResource m => MonadHandler m where type HandlerSite m - liftHandlerT :: HandlerT (HandlerSite m) IO a -> m a + liftHandler :: HandlerFor (HandlerSite m) a -> m a -replaceToParent :: HandlerData site route -> HandlerData site () -replaceToParent hd = hd { handlerToParent = const () } +liftHandlerT :: MonadHandler m => HandlerFor (HandlerSite m) a -> m a +liftHandlerT = liftHandler +{-# DEPRECATED liftHandlerT "Use liftHandler instead" #-} -instance MonadResourceBase m => MonadHandler (HandlerT site m) where - type HandlerSite (HandlerT site m) = site - liftHandlerT (HandlerT f) = HandlerT $ liftIO . f . replaceToParent -{-# RULES "liftHandlerT (HandlerT site IO)" liftHandlerT = id #-} +instance MonadHandler (HandlerFor site) where + type HandlerSite (HandlerFor site) = site + liftHandler = id + {-# INLINE liftHandler #-} -instance MonadResourceBase m => MonadHandler (WidgetT site m) where - type HandlerSite (WidgetT site m) = site - liftHandlerT (HandlerT f) = WidgetT $ \_ref env -> liftIO $ f $ replaceToParent env -{-# RULES "liftHandlerT (WidgetT site IO)" forall f. liftHandlerT (HandlerT f) = WidgetT $ const f #-} +instance MonadHandler (WidgetFor site) where + type HandlerSite (WidgetFor site) = site + liftHandler (HandlerFor f) = WidgetFor $ f . wdHandler + {-# INLINE liftHandler #-} -#define GO(T) instance MonadHandler m => MonadHandler (T m) where type HandlerSite (T m) = HandlerSite m; liftHandlerT = lift . liftHandlerT -#define GOX(X, T) instance (X, MonadHandler m) => MonadHandler (T m) where type HandlerSite (T m) = HandlerSite m; liftHandlerT = lift . liftHandlerT +#define GO(T) instance MonadHandler m => MonadHandler (T m) where type HandlerSite (T m) = HandlerSite m; liftHandler = lift . liftHandler +#define GOX(X, T) instance (X, MonadHandler m) => MonadHandler (T m) where type HandlerSite (T m) = HandlerSite m; liftHandler = lift . liftHandler GO(IdentityT) GO(ListT) GO(MaybeT) @@ -70,12 +73,17 @@ GO(ConduitM i o) #undef GOX class MonadHandler m => MonadWidget m where - liftWidgetT :: WidgetT (HandlerSite m) IO a -> m a -instance MonadResourceBase m => MonadWidget (WidgetT site m) where - liftWidgetT (WidgetT f) = WidgetT $ \ref env -> liftIO $ f ref $ replaceToParent env + liftWidget :: WidgetFor (HandlerSite m) a -> m a +instance MonadWidget (WidgetFor site) where + liftWidget = id + {-# INLINE liftWidget #-} -#define GO(T) instance MonadWidget m => MonadWidget (T m) where liftWidgetT = lift . liftWidgetT -#define GOX(X, T) instance (X, MonadWidget m) => MonadWidget (T m) where liftWidgetT = lift . liftWidgetT +liftWidgetT :: MonadWidget m => WidgetFor (HandlerSite m) a -> m a +liftWidgetT = liftWidget +{-# DEPRECATED liftWidgetT "Use liftWidget instead" #-} + +#define GO(T) instance MonadWidget m => MonadWidget (T m) where liftWidget = lift . liftWidget +#define GOX(X, T) instance (X, MonadWidget m) => MonadWidget (T m) where liftWidget = lift . liftWidget GO(IdentityT) GO(ListT) GO(MaybeT) diff --git a/yesod-core/Yesod/Core/Class/Yesod.hs b/yesod-core/Yesod/Core/Class/Yesod.hs index 5e169d7e..eafd1b34 100644 --- a/yesod-core/Yesod/Core/Class/Yesod.hs +++ b/yesod-core/Yesod/Core/Class/Yesod.hs @@ -56,7 +56,6 @@ import Web.Cookie (SetCookie (..), parseCookie import Yesod.Core.Types import Yesod.Core.Internal.Session import Yesod.Core.Widget -import Control.Monad.Trans.Class (lift) import Data.CaseInsensitive (CI) import qualified Network.Wai.Request import Data.IORef @@ -83,11 +82,11 @@ class RenderRoute site => Yesod site where -- | Output error response pages. -- -- Default value: 'defaultErrorHandler'. - errorHandler :: ErrorResponse -> HandlerT site IO TypedContent + errorHandler :: ErrorResponse -> HandlerFor site TypedContent errorHandler = defaultErrorHandler -- | Applies some form of layout to the contents of a page. - defaultLayout :: WidgetT site IO () -> HandlerT site IO Html + defaultLayout :: WidgetFor site () -> HandlerFor site Html defaultLayout w = do p <- widgetToPageContent w msgs <- getMessages @@ -139,7 +138,7 @@ class RenderRoute site => Yesod site where -- If authentication is required, return 'AuthenticationRequired'. isAuthorized :: Route site -> Bool -- ^ is this a write request? - -> HandlerT site IO AuthResult + -> HandlerFor site AuthResult isAuthorized _ _ = return Authorized -- | Determines whether the current request is a write request. By default, @@ -149,7 +148,7 @@ class RenderRoute site => Yesod site where -- -- This function is used to determine if a request is authorized; see -- 'isAuthorized'. - isWriteRequest :: Route site -> HandlerT site IO Bool + isWriteRequest :: Route site -> HandlerFor site Bool isWriteRequest _ = do wai <- waiRequest return $ W.requestMethod wai `notElem` @@ -215,7 +214,7 @@ class RenderRoute site => Yesod site where addStaticContent :: Text -- ^ filename extension -> Text -- ^ mime-type -> L.ByteString -- ^ content - -> HandlerT site IO (Maybe (Either Text (Route site, [(Text, Text)]))) + -> HandlerFor site (Maybe (Either Text (Route site, [(Text, Text)]))) addStaticContent _ _ _ = return Nothing -- | Maximum allowed length of the request body, in bytes. @@ -304,7 +303,7 @@ class RenderRoute site => Yesod site where -- Default: the 'defaultYesodMiddleware' function. -- -- Since: 1.1.6 - yesodMiddleware :: ToTypedContent res => HandlerT site IO res -> HandlerT site IO res + yesodMiddleware :: ToTypedContent res => HandlerFor site res -> HandlerFor site res yesodMiddleware = defaultYesodMiddleware -- | How to allocate an @InternalState@ for each request. @@ -325,7 +324,7 @@ class RenderRoute site => Yesod site where -- primarily for wrapping up error messages for better display. -- -- @since 1.4.30 - defaultMessageWidget :: Html -> HtmlUrl (Route site) -> WidgetT site IO () + defaultMessageWidget :: Html -> HtmlUrl (Route site) -> WidgetFor site () defaultMessageWidget title body = do setTitle title toWidget @@ -384,7 +383,7 @@ defaultShouldLogIO a b = return $ defaultShouldLog a b -- \"Vary: Accept, Accept-Language\" and performs authorization checks. -- -- Since 1.2.0 -defaultYesodMiddleware :: Yesod site => HandlerT site IO res -> HandlerT site IO res +defaultYesodMiddleware :: Yesod site => HandlerFor site res -> HandlerFor site res defaultYesodMiddleware handler = do addHeader "Vary" "Accept, Accept-Language" authorizationCheck @@ -444,8 +443,8 @@ sameSiteSession s = (fmap . fmap) secureSessionCookies -- -- Since 1.4.7 sslOnlyMiddleware :: Int -- ^ minutes - -> HandlerT site IO res - -> HandlerT site IO res + -> HandlerFor site res + -> HandlerFor site res sslOnlyMiddleware timeout handler = do addHeader "Strict-Transport-Security" $ T.pack $ concat [ "max-age=" @@ -458,7 +457,7 @@ sslOnlyMiddleware timeout handler = do -- 'isWriteRequest'. -- -- Since 1.2.0 -authorizationCheck :: Yesod site => HandlerT site IO () +authorizationCheck :: Yesod site => HandlerFor site () authorizationCheck = getCurrentRoute >>= maybe (return ()) checkUrl where checkUrl url = do @@ -482,7 +481,7 @@ authorizationCheck = getCurrentRoute >>= maybe (return ()) checkUrl -- | Calls 'csrfCheckMiddleware' with 'isWriteRequest', 'defaultCsrfHeaderName', and 'defaultCsrfParamName' as parameters. -- -- Since 1.4.14 -defaultCsrfCheckMiddleware :: Yesod site => HandlerT site IO res -> HandlerT site IO res +defaultCsrfCheckMiddleware :: Yesod site => HandlerFor site res -> HandlerFor site res defaultCsrfCheckMiddleware handler = csrfCheckMiddleware handler @@ -496,11 +495,11 @@ defaultCsrfCheckMiddleware handler = -- For details, see the "AJAX CSRF protection" section of "Yesod.Core.Handler". -- -- Since 1.4.14 -csrfCheckMiddleware :: HandlerT site IO res - -> HandlerT site IO Bool -- ^ Whether or not to perform the CSRF check. +csrfCheckMiddleware :: HandlerFor site res + -> HandlerFor site Bool -- ^ Whether or not to perform the CSRF check. -> CI S8.ByteString -- ^ The header name to lookup the CSRF token from. -> Text -- ^ The POST parameter name to lookup the CSRF token from. - -> HandlerT site IO res + -> HandlerFor site res csrfCheckMiddleware handler shouldCheckFn headerName paramName = do shouldCheck <- shouldCheckFn when shouldCheck (checkCsrfHeaderOrParam headerName paramName) @@ -511,7 +510,7 @@ csrfCheckMiddleware handler shouldCheckFn headerName paramName = do -- The cookie's path is set to @/@, making it valid for your whole website. -- -- Since 1.4.14 -defaultCsrfSetCookieMiddleware :: HandlerT site IO res -> HandlerT site IO res +defaultCsrfSetCookieMiddleware :: HandlerFor site res -> HandlerFor site res defaultCsrfSetCookieMiddleware handler = setCsrfCookie >> handler -- | Takes a 'SetCookie' and overrides its value with a CSRF token, then sets the cookie. See 'setCsrfCookieWithCookie'. @@ -521,7 +520,7 @@ defaultCsrfSetCookieMiddleware handler = setCsrfCookie >> handler -- Make sure to set the 'setCookiePath' to the root path of your application, otherwise you'll generate a new CSRF token for every path of your app. If your app is run from from e.g. www.example.com\/app1, use @app1@. The vast majority of sites will just use @/@. -- -- Since 1.4.14 -csrfSetCookieMiddleware :: HandlerT site IO res -> SetCookie -> HandlerT site IO res +csrfSetCookieMiddleware :: HandlerFor site res -> SetCookie -> HandlerFor site res csrfSetCookieMiddleware handler cookie = setCsrfCookieWithCookie cookie >> handler -- | Calls 'defaultCsrfSetCookieMiddleware' and 'defaultCsrfCheckMiddleware'. @@ -541,23 +540,26 @@ csrfSetCookieMiddleware handler cookie = setCsrfCookieWithCookie cookie >> handl -- @ -- -- Since 1.4.14 -defaultCsrfMiddleware :: Yesod site => HandlerT site IO res -> HandlerT site IO res +defaultCsrfMiddleware :: Yesod site => HandlerFor site res -> HandlerFor site res defaultCsrfMiddleware = defaultCsrfSetCookieMiddleware . defaultCsrfCheckMiddleware -- | Convert a widget to a 'PageContent'. widgetToPageContent :: Yesod site - => WidgetT site IO () - -> HandlerT site IO (PageContent (Route site)) -widgetToPageContent w = do - master <- getYesod - hd <- HandlerT return - ref <- lift $ newIORef mempty - lift $ unWidgetT w ref hd - GWData (Body body) (Last mTitle) scripts' stylesheets' style jscript (Head head') <- lift $ readIORef ref - let title = maybe mempty unTitle mTitle - scripts = runUniqueList scripts' - stylesheets = runUniqueList stylesheets' + => WidgetFor site () + -> HandlerFor site (PageContent (Route site)) +widgetToPageContent w = HandlerFor $ \hd -> do + master <- unHandlerFor getYesod hd + ref <- newIORef mempty + unWidgetFor w WidgetData + { wdRef = ref + , wdHandler = hd + } + GWData (Body body) (Last mTitle) scripts' stylesheets' style jscript (Head head') <- readIORef ref + let title = maybe mempty unTitle mTitle + scripts = runUniqueList scripts' + stylesheets = runUniqueList stylesheets' + flip unHandlerFor hd $ do render <- getUrlRenderParams let renderLoc x = case x of @@ -645,7 +647,7 @@ widgetToPageContent w = do runUniqueList (UniqueList x) = nub $ x [] -- | The default error handler for 'errorHandler'. -defaultErrorHandler :: Yesod site => ErrorResponse -> HandlerT site IO TypedContent +defaultErrorHandler :: Yesod site => ErrorResponse -> HandlerFor site TypedContent defaultErrorHandler NotFound = selectRep $ do provideRep $ defaultLayout $ do r <- waiRequest diff --git a/yesod-core/Yesod/Core/Dispatch.hs b/yesod-core/Yesod/Core/Dispatch.hs index d13a154d..bd3c41c6 100644 --- a/yesod-core/Yesod/Core/Dispatch.hs +++ b/yesod-core/Yesod/Core/Dispatch.hs @@ -35,7 +35,6 @@ module Yesod.Core.Dispatch -- * WAI subsites , WaiSubsite (..) , WaiSubsiteWithAuth (..) - , subHelper ) where import Prelude hiding (exp) diff --git a/yesod-core/Yesod/Core/Handler.hs b/yesod-core/Yesod/Core/Handler.hs index bfd254d0..f8cd1666 100644 --- a/yesod-core/Yesod/Core/Handler.hs +++ b/yesod-core/Yesod/Core/Handler.hs @@ -27,6 +27,7 @@ module Yesod.Core.Handler ( -- * Handler monad HandlerT + , HandlerFor -- ** Read information from handler , getYesod , getsYesod @@ -161,7 +162,6 @@ module Yesod.Core.Handler -- * Per-request caching , cached , cachedBy - , stripHandlerT -- * AJAX CSRF protection -- $ajaxCSRFOverview @@ -254,14 +254,17 @@ import qualified Data.Foldable as Fold import Data.Default import Control.Monad.Logger (MonadLogger, logWarnS) +type HandlerT site (m :: * -> *) = HandlerFor site +{-# DEPRECATED HandlerT "Use HandlerFor directly" #-} + get :: MonadHandler m => m GHState -get = liftHandlerT $ HandlerT $ I.readIORef . handlerState +get = liftHandler $ HandlerFor $ I.readIORef . handlerState put :: MonadHandler m => GHState -> m () -put x = liftHandlerT $ HandlerT $ flip I.writeIORef x . handlerState +put x = liftHandler $ HandlerFor $ flip I.writeIORef x . handlerState modify :: MonadHandler m => (GHState -> GHState) -> m () -modify f = liftHandlerT $ HandlerT $ flip I.modifyIORef f . handlerState +modify f = liftHandler $ HandlerFor $ flip I.modifyIORef f . handlerState tell :: MonadHandler m => Endo [Header] -> m () tell hs = modify $ \g -> g { ghsHeaders = ghsHeaders g `mappend` hs } @@ -273,14 +276,14 @@ hcError :: MonadHandler m => ErrorResponse -> m a hcError = handlerError . HCError getRequest :: MonadHandler m => m YesodRequest -getRequest = liftHandlerT $ HandlerT $ return . handlerRequest +getRequest = liftHandler $ HandlerFor $ return . handlerRequest runRequestBody :: MonadHandler m => m RequestBodyContents runRequestBody = do HandlerData { handlerEnv = RunHandlerEnv {..} , handlerRequest = req - } <- liftHandlerT $ HandlerT return + } <- liftHandler $ HandlerFor return let len = W.requestBodyLength $ reqWaiRequest req upload = rheUpload len x <- get @@ -320,7 +323,7 @@ rbHelper' backend mkFI req = go = decodeUtf8With lenientDecode askHandlerEnv :: MonadHandler m => m (RunHandlerEnv (HandlerSite m)) -askHandlerEnv = liftHandlerT $ HandlerT $ return . handlerEnv +askHandlerEnv = liftHandler $ HandlerFor $ return . handlerEnv -- | Get the master site application argument. getYesod :: MonadHandler m => m (HandlerSite m) @@ -396,9 +399,9 @@ getCurrentRoute = rheRoute <$> askHandlerEnv -- This allows the inner 'GHandler' to outlive the outer -- 'GHandler' (e.g., on the @forkIO@ example above, a response -- may be sent to the client without killing the new thread). -handlerToIO :: (MonadIO m1, MonadIO m2) => HandlerT site m1 (HandlerT site IO a -> m2 a) +handlerToIO :: MonadIO m => HandlerFor site (HandlerFor site a -> m a) handlerToIO = - HandlerT $ \oldHandlerData -> do + HandlerFor $ \oldHandlerData -> do -- Take just the bits we need from oldHandlerData. let newReq = oldReq { reqWaiRequest = newWaiReq } where @@ -420,7 +423,7 @@ handlerToIO = liftIO $ evaluate (newReq `seq` oldEnv `seq` newState `seq` ()) -- Return GHandler running function. - return $ \(HandlerT f) -> + return $ \(HandlerFor f) -> liftIO $ runResourceT $ withInternalState $ \resState -> do -- The state IORef needs to be created here, otherwise it @@ -431,7 +434,6 @@ handlerToIO = { handlerRequest = newReq , handlerEnv = oldEnv , handlerState = newStateIORef - , handlerToParent = const () , handlerResource = resState } liftIO (f newHandlerData) @@ -442,9 +444,9 @@ handlerToIO = -- for correctness and efficiency -- -- @since 1.2.8 -forkHandler :: (SomeException -> HandlerT site IO ()) -- ^ error handler - -> HandlerT site IO () - -> HandlerT site IO () +forkHandler :: (SomeException -> HandlerFor site ()) -- ^ error handler + -> HandlerFor site () + -> HandlerFor site () forkHandler onErr handler = do yesRunner <- handlerToIO void $ liftResourceT $ resourceForkIO $ yesRunner $ handle onErr handler @@ -1370,14 +1372,14 @@ respond ct = return . TypedContent ct . toContent -- -- @since 1.2.0 respondSource :: ContentType - -> Source (HandlerT site IO) (Flush Builder) - -> HandlerT site IO TypedContent -respondSource ctype src = HandlerT $ \hd -> + -> Source (HandlerFor site) (Flush Builder) + -> HandlerFor site TypedContent +respondSource ctype src = HandlerFor $ \hd -> -- Note that this implementation relies on the fact that the ResourceT -- environment provided by the server is the same one used in HandlerT. -- This is a safe assumption assuming the HandlerT is run correctly. return $ TypedContent ctype $ ContentSource - $ transPipe (lift . flip unHandlerT hd) src + $ transPipe (lift . flip unHandlerFor hd) src -- | In a streaming response, send a single chunk of data. This function works -- on most datatypes, such as @ByteString@ and @Html@. @@ -1423,25 +1425,6 @@ sendChunkLazyText = sendChunk sendChunkHtml :: Monad m => Html -> Producer m (Flush Builder) sendChunkHtml = sendChunk --- | Converts a child handler to a parent handler --- --- Exported since 1.4.11 -stripHandlerT :: HandlerT child (HandlerT parent m) a - -> (parent -> child) - -> (Route child -> Route parent) - -> Maybe (Route child) - -> HandlerT parent m a -stripHandlerT (HandlerT f) getSub toMaster newRoute = HandlerT $ \hd -> do - let env = handlerEnv hd - ($ hd) $ unHandlerT $ f hd - { handlerEnv = env - { rheSite = getSub $ rheSite env - , rheRoute = newRoute - , rheRender = \url params -> rheRender env (toMaster url) params - } - , handlerToParent = toMaster - } - -- $ajaxCSRFOverview -- When a user has authenticated with your site, all requests made from the browser to your server will include the session information that you use to verify that the user is logged in. -- Unfortunately, this allows attackers to make unwanted requests on behalf of the user by e.g. submitting an HTTP request to your site when the user visits theirs. diff --git a/yesod-core/Yesod/Core/Internal/LiteApp.hs b/yesod-core/Yesod/Core/Internal/LiteApp.hs index b09217c6..c9a6f51d 100644 --- a/yesod-core/Yesod/Core/Internal/LiteApp.hs +++ b/yesod-core/Yesod/Core/Internal/LiteApp.hs @@ -46,8 +46,8 @@ instance Monoid LiteApp where mempty = LiteApp $ \_ _ -> Nothing mappend (LiteApp x) (LiteApp y) = LiteApp $ \m ps -> x m ps <|> y m ps -type LiteHandler = HandlerT LiteApp IO -type LiteWidget = WidgetT LiteApp IO +type LiteHandler = HandlerFor LiteApp +type LiteWidget = WidgetFor LiteApp liteApp :: Writer LiteApp () -> LiteApp liteApp = execWriter diff --git a/yesod-core/Yesod/Core/Internal/Run.hs b/yesod-core/Yesod/Core/Internal/Run.hs index 668e8604..4f8e69a1 100644 --- a/yesod-core/Yesod/Core/Internal/Run.hs +++ b/yesod-core/Yesod/Core/Internal/Run.hs @@ -83,7 +83,7 @@ errFromShow x = evaluate $!! InternalError $! T.pack $! show x -- represented by the @HandlerContents@. basicRunHandler :: ToTypedContent c => RunHandlerEnv site - -> HandlerT site IO c + -> HandlerFor site c -> YesodRequest -> InternalState -> IO (GHState, HandlerContents) @@ -96,7 +96,7 @@ basicRunHandler rhe handler yreq resState = do -- converting them into a @HandlerContents@ contents' <- catchSync (do - res <- unHandlerT handler (hd istate) + res <- unHandlerFor handler (hd istate) tc <- evaluate (toTypedContent res) -- Success! Wrap it up in an @HCContent@ return (HCContent defaultStatus tc)) @@ -121,7 +121,6 @@ basicRunHandler rhe handler yreq resState = do { handlerRequest = yreq , handlerEnv = rhe , handlerState = istate - , handlerToParent = const () , handlerResource = resState } @@ -208,7 +207,7 @@ evalFallback contents val = catchSync -- 'HandlerT' into an 'Application'. Should not be needed by users. runHandler :: ToTypedContent c => RunHandlerEnv site - -> HandlerT site IO c + -> HandlerFor site c -> YesodApp runHandler rhe@RunHandlerEnv {..} handler yreq = withInternalState $ \resState -> do -- Get the raw state and original contents @@ -263,7 +262,7 @@ runFakeHandler :: (Yesod site, MonadIO m) => SessionMap -> (site -> Logger) -> site - -> HandlerT site IO a + -> HandlerFor site a -> m (Either ErrorResponse a) runFakeHandler fakeSessionMap logger site handler = liftIO $ do ret <- I.newIORef (Left $ InternalError "runFakeHandler: no result") @@ -322,7 +321,7 @@ runFakeHandler fakeSessionMap logger site handler = liftIO $ do I.readIORef ret yesodRunner :: (ToTypedContent res, Yesod site) - => HandlerT site IO res + => HandlerFor site res -> YesodRunnerEnv site -> Maybe (Route site) -> Application diff --git a/yesod-core/Yesod/Core/Internal/TH.hs b/yesod-core/Yesod/Core/Internal/TH.hs index 8ee5b4e0..fbfee2ed 100644 --- a/yesod-core/Yesod/Core/Internal/TH.hs +++ b/yesod-core/Yesod/Core/Internal/TH.hs @@ -32,7 +32,6 @@ import Text.ParserCombinators.Parsec.Char (alphaNum, spaces, string, char) import Yesod.Routes.TH import Yesod.Routes.Parse import Yesod.Core.Types -import Yesod.Core.Content import Yesod.Core.Class.Dispatch import Yesod.Core.Internal.Run @@ -102,12 +101,12 @@ mkYesodDispatch :: String -> [ResourceTree String] -> Q [Dec] mkYesodDispatch name = fmap snd . mkYesodGeneral name [] False return -- | Get the Handler and Widget type synonyms for the given site. -masterTypeSyns :: [Name] -> Type -> [Dec] +masterTypeSyns :: [Name] -> Type -> [Dec] -- FIXME remove from here, put into the scaffolding itself? masterTypeSyns vs site = [ TySynD (mkName "Handler") (fmap PlainTV vs) - $ ConT ''HandlerT `AppT` site `AppT` ConT ''IO + $ ConT ''HandlerFor `AppT` site , TySynD (mkName "Widget") (fmap PlainTV vs) - $ ConT ''WidgetT `AppT` site `AppT` ConT ''IO `AppT` ConT ''() + $ ConT ''WidgetFor `AppT` site `AppT` ConT ''() ] -- | 'Left' arguments indicate a monomorphic type, a 'Right' argument @@ -242,7 +241,7 @@ mkDispatchInstance master cxt f res = do mkYesodSubDispatch :: [ResourceTree a] -> Q Exp mkYesodSubDispatch res = do - clause' <- mkDispatchClause (mkMDS return [|subHelper . fmap toTypedContent|]) res + clause' <- mkDispatchClause (mkMDS return [|subHelper|]) res inner <- newName "inner" let innerFun = FunD inner [clause'] helper <- newName "helper" diff --git a/yesod-core/Yesod/Core/Json.hs b/yesod-core/Yesod/Core/Json.hs index 245aad90..441b3e92 100644 --- a/yesod-core/Yesod/Core/Json.hs +++ b/yesod-core/Yesod/Core/Json.hs @@ -31,14 +31,14 @@ module Yesod.Core.Json , acceptsJson ) where -import Yesod.Core.Handler (HandlerT, getRequest, invalidArgs, redirect, selectRep, provideRep, rawRequestBody, ProvidedRep, lookupHeader) +import Yesod.Core.Handler (HandlerFor, getRequest, invalidArgs, redirect, selectRep, provideRep, rawRequestBody, ProvidedRep, lookupHeader) import Control.Monad.Trans.Writer (Writer) import Data.Monoid (Endo) import Yesod.Core.Content (TypedContent) import Yesod.Core.Types (reqAccept) import Yesod.Core.Class.Yesod (defaultLayout, Yesod) import Yesod.Core.Class.Handler -import Yesod.Core.Widget (WidgetT) +import Yesod.Core.Widget (WidgetFor) import Yesod.Routes.Class import qualified Data.Aeson as J import qualified Data.Aeson.Parser as JP @@ -58,9 +58,9 @@ import Control.Monad (liftM) -- -- @since 0.3.0 defaultLayoutJson :: (Yesod site, J.ToJSON a) - => WidgetT site IO () -- ^ HTML - -> HandlerT site IO a -- ^ JSON - -> HandlerT site IO TypedContent + => WidgetFor site () -- ^ HTML + -> HandlerFor site a -- ^ JSON + -> HandlerFor site TypedContent defaultLayoutJson w json = selectRep $ do provideRep $ defaultLayout w provideRep $ fmap J.toEncoding json diff --git a/yesod-core/Yesod/Core/Types.hs b/yesod-core/Yesod/Core/Types.hs index fd4572de..4211ba43 100644 --- a/yesod-core/Yesod/Core/Types.hs +++ b/yesod-core/Yesod/Core/Types.hs @@ -1,4 +1,5 @@ {-# LANGUAGE CPP #-} +{-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE TupleSections #-} @@ -18,14 +19,14 @@ import Data.Monoid (Monoid (..)) #endif import Control.Arrow (first) import Control.Exception (Exception) -import Control.Monad (liftM, ap) +import Control.Monad (ap) import Control.Monad.Base (MonadBase (liftBase)) import Control.Monad.Catch (MonadMask (..), MonadCatch (..)) import Control.Monad.IO.Class (MonadIO (liftIO)) import Control.Monad.Logger (LogLevel, LogSource, MonadLogger (..)) import Control.Monad.Trans.Control (MonadBaseControl (..)) -import Control.Monad.Trans.Resource (MonadResource (..), InternalState, runInternalState, MonadThrow (..), monadThrow, ResourceT) +import Control.Monad.Trans.Resource (MonadResource (..), InternalState, runInternalState, MonadThrow (..), ResourceT) import Data.ByteString (ByteString) import qualified Data.ByteString.Lazy as L import Data.Conduit (Flush, Source) @@ -56,7 +57,6 @@ import Text.Hamlet (HtmlUrl) import Text.Julius (JavascriptUrl) import Web.Cookie (SetCookie) import Yesod.Core.Internal.Util (getTime, putTime) -import Control.Monad.Trans.Class (MonadTrans (..)) import Yesod.Routes.Class (RenderRoute (..), ParseRoute (..)) import Control.Monad.Reader (MonadReader (..)) import Data.Monoid ((<>)) @@ -66,7 +66,7 @@ import Data.Conduit.Lazy (MonadActive, monadActive) import Yesod.Core.TypeCache (TypeMap, KeyedTypeMap) import Control.Monad.Logger (MonadLoggerIO (..)) import Data.Semigroup (Semigroup) -import Control.Monad.IO.Unlift (MonadUnliftIO (..), UnliftIO (..), withUnliftIO) +import Control.Monad.IO.Unlift (MonadUnliftIO (..), UnliftIO (..)) -- Sessions type SessionMap = Map Text ByteString @@ -193,11 +193,10 @@ data RunHandlerEnv site = RunHandlerEnv , rheMaxExpires :: !Text } -data HandlerData site parentRoute = HandlerData +data HandlerData site = HandlerData { handlerRequest :: !YesodRequest , handlerEnv :: !(RunHandlerEnv site) , handlerState :: !(IORef GHState) - , handlerToParent :: !(Route site -> parentRoute) , handlerResource :: !InternalState } @@ -224,16 +223,13 @@ type ParentRunner parent m -- | A generic handler monad, which can have a different subsite and master -- site. We define a newtype for better error message. -newtype HandlerT site m a = HandlerT - { unHandlerT :: HandlerData site (MonadRoute m) -> m a +newtype HandlerFor site a = HandlerFor + { unHandlerFor :: HandlerData site -> IO a } - -type family MonadRoute (m :: * -> *) -type instance MonadRoute IO = () -type instance MonadRoute (HandlerT site m) = (Route site) + deriving Functor data GHState = GHState - { ghsSession :: SessionMap + { ghsSession :: !SessionMap , ghsRBC :: Maybe RequestBodyContents , ghsIdent :: Int , ghsCache :: TypeMap @@ -249,26 +245,32 @@ type YesodApp = YesodRequest -> ResourceT IO YesodResponse -- | A generic widget, allowing specification of both the subsite and master -- site datatypes. While this is simply a @WriterT@, we define a newtype for -- better error messages. -newtype WidgetT site m a = WidgetT - { unWidgetT :: IORef (GWData (Route site)) -> HandlerData site (MonadRoute m) -> m a +newtype WidgetFor site a = WidgetFor + { unWidgetFor :: WidgetData site -> IO a } + deriving Functor -instance (a ~ (), Monad m) => Monoid (WidgetT site m a) where +data WidgetData site = WidgetData + { wdRef :: {-# UNPACK #-} !(IORef (GWData (Route site))) + , wdHandler :: {-# UNPACK #-} !(HandlerData site) + } + +instance a ~ () => Monoid (WidgetFor site a) where mempty = return () mappend x y = x >> y -instance (a ~ (), Monad m) => Semigroup (WidgetT site m a) +instance a ~ () => Semigroup (WidgetFor site a) -- | A 'String' can be trivially promoted to a widget. -- -- For example, in a yesod-scaffold site you could use: -- -- @getHomeR = do defaultLayout "Widget text"@ -instance (MonadIO m, a ~ ()) => IsString (WidgetT site m a) where -- FIXME turn it into WidgetFor? +instance a ~ () => IsString (WidgetFor site a) where fromString = toWidget . toHtml . T.pack where toWidget x = tellWidget mempty { gwdBody = Body (const x) } -tellWidget :: MonadIO m => GWData (Route site) -> WidgetT site m () -tellWidget d = WidgetT $ \ref _ -> liftIO $ modifyIORef' ref (<> d) +tellWidget :: GWData (Route site) -> WidgetFor site () +tellWidget d = WidgetFor $ \wd -> modifyIORef' (wdRef wd) (<> d) type RY master = Route master -> [(Text, Text)] -> Text @@ -404,106 +406,85 @@ instance Show HandlerContents where show (HCWaiApp _) = "HCWaiApp" instance Exception HandlerContents --- Instances for WidgetT -instance Monad m => Functor (WidgetT site m) where - fmap = liftM -instance Monad m => Applicative (WidgetT site m) where - pure = return +-- Instances for WidgetFor +instance Applicative (WidgetFor site) where + pure = WidgetFor . const . pure (<*>) = ap -instance Monad m => Monad (WidgetT site m) where - return a = WidgetT $ \_ _ -> return a - WidgetT x >>= f = WidgetT $ \ref r -> do - a <- x ref r - unWidgetT (f a) ref r -instance MonadIO m => MonadIO (WidgetT site m) where - liftIO = lift . liftIO -instance MonadBase b m => MonadBase b (WidgetT site m) where - liftBase = WidgetT . const . const . liftBase -instance MonadBaseControl b m => MonadBaseControl b (WidgetT site m) where - type StM (WidgetT site m) a = StM m a - liftBaseWith f = WidgetT $ \ref reader' -> +instance Monad (WidgetFor site) where + return = pure + WidgetFor x >>= f = WidgetFor $ \wd -> do + a <- x wd + unWidgetFor (f a) wd +instance MonadIO (WidgetFor site) where + liftIO = WidgetFor . const +instance b ~ IO => MonadBase b (WidgetFor site) where + liftBase = WidgetFor . const +instance b ~ IO => MonadBaseControl b (WidgetFor site) where + type StM (WidgetFor site) a = a + liftBaseWith f = WidgetFor $ \wd -> liftBaseWith $ \runInBase -> - f $ runInBase . (\(WidgetT w) -> w ref reader') - restoreM = WidgetT . const . const . restoreM + f $ runInBase . (flip unWidgetFor wd) + restoreM = WidgetFor . const . return -- | @since 1.4.38 -instance MonadUnliftIO m => MonadUnliftIO (WidgetT site m) where +instance MonadUnliftIO (WidgetFor site) where {-# INLINE askUnliftIO #-} - askUnliftIO = WidgetT $ \ref r -> - withUnliftIO $ \u -> - return (UnliftIO (\(WidgetT w) -> unliftIO u $ w ref r)) -instance Monad m => MonadReader site (WidgetT site m) where - ask = WidgetT $ \_ hd -> return (rheSite $ handlerEnv hd) - local f (WidgetT g) = WidgetT $ \ref hd -> g ref hd - { handlerEnv = (handlerEnv hd) - { rheSite = f $ rheSite $ handlerEnv hd - } - } + askUnliftIO = WidgetFor $ \wd -> + return (UnliftIO (flip unWidgetFor wd)) +instance MonadReader (WidgetData site) (WidgetFor site) where + ask = WidgetFor return + local f (WidgetFor g) = WidgetFor $ g . f -instance MonadTrans (WidgetT site) where - lift = WidgetT . const . const -instance MonadThrow m => MonadThrow (WidgetT site m) where - throwM = lift . throwM +instance MonadThrow (WidgetFor site) where + throwM = liftIO . throwM -instance MonadCatch m => MonadCatch (HandlerT site m) where - catch (HandlerT m) c = HandlerT $ \r -> m r `catch` \e -> unHandlerT (c e) r -instance MonadMask m => MonadMask (HandlerT site m) where - mask a = HandlerT $ \e -> mask $ \u -> unHandlerT (a $ q u) e - where q u (HandlerT b) = HandlerT (u . b) +instance MonadCatch (HandlerFor site) where + catch (HandlerFor m) c = HandlerFor $ \r -> m r `catch` \e -> unHandlerFor (c e) r +instance MonadMask (HandlerFor site) where + mask a = HandlerFor $ \e -> mask $ \u -> unHandlerFor (a $ q u) e + where q u (HandlerFor b) = HandlerFor (u . b) uninterruptibleMask a = - HandlerT $ \e -> uninterruptibleMask $ \u -> unHandlerT (a $ q u) e - where q u (HandlerT b) = HandlerT (u . b) -instance MonadCatch m => MonadCatch (WidgetT site m) where - catch (WidgetT m) c = WidgetT $ \ref r -> m ref r `catch` \e -> unWidgetT (c e) ref r -instance MonadMask m => MonadMask (WidgetT site m) where - mask a = WidgetT $ \ref e -> mask $ \u -> unWidgetT (a $ q u) ref e - where q u (WidgetT b) = WidgetT (\ref e -> u $ b ref e) + HandlerFor $ \e -> uninterruptibleMask $ \u -> unHandlerFor (a $ q u) e + where q u (HandlerFor b) = HandlerFor (u . b) +instance MonadCatch (WidgetFor site) where + catch (WidgetFor m) c = WidgetFor $ \r -> m r `catch` \e -> unWidgetFor (c e) r +instance MonadMask (WidgetFor site) where + mask a = WidgetFor $ \e -> mask $ \u -> unWidgetFor (a $ q u) e + where q u (WidgetFor b) = WidgetFor (u . b) uninterruptibleMask a = - WidgetT $ \ref e -> uninterruptibleMask $ \u -> unWidgetT (a $ q u) ref e - where q u (WidgetT b) = WidgetT (\ref e -> u $ b ref e) + WidgetFor $ \e -> uninterruptibleMask $ \u -> unWidgetFor (a $ q u) e + where q u (WidgetFor b) = WidgetFor (u . b) --- CPP to avoid a redundant constraints warning -#if MIN_VERSION_base(4,9,0) -instance (MonadIO m, MonadBase IO m, MonadThrow m) => MonadResource (WidgetT site m) where -#else -instance (Applicative m, MonadIO m, MonadBase IO m, MonadThrow m) => MonadResource (WidgetT site m) where -#endif - liftResourceT f = WidgetT $ \_ hd -> liftIO $ runInternalState f (handlerResource hd) +instance MonadResource (WidgetFor site) where + liftResourceT f = WidgetFor $ runInternalState f . handlerResource . wdHandler -instance MonadIO m => MonadLogger (WidgetT site m) where - monadLoggerLog a b c d = WidgetT $ \_ hd -> - liftIO $ rheLog (handlerEnv hd) a b c (toLogStr d) +instance MonadLogger (WidgetFor site) where + monadLoggerLog a b c d = WidgetFor $ \wd -> + rheLog (handlerEnv $ wdHandler wd) a b c (toLogStr d) -instance MonadIO m => MonadLoggerIO (WidgetT site m) where - askLoggerIO = WidgetT $ \_ hd -> return $ rheLog $ handlerEnv hd +instance MonadLoggerIO (WidgetFor site) where + askLoggerIO = WidgetFor $ return . rheLog . handlerEnv . wdHandler -instance MonadActive m => MonadActive (WidgetT site m) where - monadActive = lift monadActive -instance MonadActive m => MonadActive (HandlerT site m) where - monadActive = lift monadActive - -instance MonadTrans (HandlerT site) where - lift = HandlerT . const +-- FIXME look at implementation of ResourceT +instance MonadActive (WidgetFor site) where + monadActive = liftIO monadActive +instance MonadActive (HandlerFor site) where + monadActive = liftIO monadActive -- Instances for HandlerT -instance Monad m => Functor (HandlerT site m) where - fmap = liftM -instance Monad m => Applicative (HandlerT site m) where - pure = return +instance Applicative (HandlerFor site) where + pure = HandlerFor . const . return (<*>) = ap -instance Monad m => Monad (HandlerT site m) where - return = HandlerT . const . return - HandlerT x >>= f = HandlerT $ \r -> x r >>= \x' -> unHandlerT (f x') r -instance MonadIO m => MonadIO (HandlerT site m) where - liftIO = lift . liftIO -instance MonadBase b m => MonadBase b (HandlerT site m) where - liftBase = lift . liftBase -instance Monad m => MonadReader site (HandlerT site m) where - ask = HandlerT $ return . rheSite . handlerEnv - local f (HandlerT g) = HandlerT $ \hd -> g hd - { handlerEnv = (handlerEnv hd) - { rheSite = f $ rheSite $ handlerEnv hd - } - } +instance Monad (HandlerFor site) where + return = pure + HandlerFor x >>= f = HandlerFor $ \r -> x r >>= \x' -> unHandlerFor (f x') r +instance MonadIO (HandlerFor site) where + liftIO = HandlerFor . const +instance b ~ IO => MonadBase b (HandlerFor site) where + liftBase = liftIO +instance MonadReader (HandlerData site) (HandlerFor site) where + ask = HandlerFor return + local f (HandlerFor g) = HandlerFor $ g . f + -- | Note: although we provide a @MonadBaseControl@ instance, @lifted-base@'s -- @fork@ function is incompatible with the underlying @ResourceT@ system. -- Instead, if you must fork a separate thread, you should use @@ -512,31 +493,30 @@ instance Monad m => MonadReader site (HandlerT site m) where -- Using fork usually leads to an exception that says -- \"Control.Monad.Trans.Resource.register\': The mutable state is being accessed -- after cleanup. Please contact the maintainers.\" -instance MonadBaseControl b m => MonadBaseControl b (HandlerT site m) where - type StM (HandlerT site m) a = StM m a - liftBaseWith f = HandlerT $ \reader' -> +instance b ~ IO => MonadBaseControl b (HandlerFor site) where + type StM (HandlerFor site) a = a + liftBaseWith f = HandlerFor $ \reader' -> liftBaseWith $ \runInBase -> - f $ runInBase . (\(HandlerT r) -> r reader') - restoreM = HandlerT . const . restoreM + f $ runInBase . (flip unHandlerFor reader') + restoreM = HandlerFor . const . return -- | @since 1.4.38 -instance MonadUnliftIO m => MonadUnliftIO (HandlerT site m) where +instance MonadUnliftIO (HandlerFor site) where {-# INLINE askUnliftIO #-} - askUnliftIO = HandlerT $ \r -> - withUnliftIO $ \u -> - return (UnliftIO (unliftIO u . flip unHandlerT r)) + askUnliftIO = HandlerFor $ \r -> + return (UnliftIO (flip unHandlerFor r)) -instance MonadThrow m => MonadThrow (HandlerT site m) where - throwM = lift . monadThrow +instance MonadThrow (HandlerFor site) where + throwM = liftIO . throwM -instance (MonadIO m, MonadBase IO m, MonadThrow m) => MonadResource (HandlerT site m) where - liftResourceT f = HandlerT $ \hd -> liftIO $ runInternalState f (handlerResource hd) +instance MonadResource (HandlerFor site) where + liftResourceT f = HandlerFor $ runInternalState f . handlerResource -instance MonadIO m => MonadLogger (HandlerT site m) where - monadLoggerLog a b c d = HandlerT $ \hd -> - liftIO $ rheLog (handlerEnv hd) a b c (toLogStr d) +instance MonadLogger (HandlerFor site) where + monadLoggerLog a b c d = HandlerFor $ \hd -> + rheLog (handlerEnv hd) a b c (toLogStr d) -instance MonadIO m => MonadLoggerIO (HandlerT site m) where - askLoggerIO = HandlerT $ \hd -> return (rheLog (handlerEnv hd)) +instance MonadLoggerIO (HandlerFor site) where + askLoggerIO = HandlerFor $ \hd -> return (rheLog (handlerEnv hd)) instance Monoid (UniqueList x) where mempty = UniqueList id diff --git a/yesod-core/Yesod/Core/Unsafe.hs b/yesod-core/Yesod/Core/Unsafe.hs index ea15e7b3..c4d75b1e 100644 --- a/yesod-core/Yesod/Core/Unsafe.hs +++ b/yesod-core/Yesod/Core/Unsafe.hs @@ -19,7 +19,10 @@ import Control.Monad.IO.Class (MonadIO) -- -- > unsafeHandler = Unsafe.fakeHandlerGetLogger appLogger fakeHandlerGetLogger :: (Yesod site, MonadIO m) - => (site -> Logger) -> site -> HandlerT site IO a -> m a + => (site -> Logger) + -> site + -> HandlerFor site a + -> m a fakeHandlerGetLogger getLogger app f = runFakeHandler mempty getLogger app f >>= either (error . ("runFakeHandler issue: " `mappend`) . show) diff --git a/yesod-core/Yesod/Core/Widget.hs b/yesod-core/Yesod/Core/Widget.hs index f9e1eeb3..67ac6380 100644 --- a/yesod-core/Yesod/Core/Widget.hs +++ b/yesod-core/Yesod/Core/Widget.hs @@ -14,6 +14,7 @@ module Yesod.Core.Widget ( -- * Datatype WidgetT + , WidgetFor , PageContent (..) -- * Special Hamlet quasiquoter/TH for Widgets , whamlet @@ -43,7 +44,6 @@ module Yesod.Core.Widget , addScriptRemoteAttrs , addScriptEither -- * Subsites - , widgetToParentWidget , handlerToWidget -- * Internal , whamletFileWithSettings @@ -60,7 +60,6 @@ import Yesod.Core.Handler (getMessageRender, getUrlRenderParams) #if __GLASGOW_HASKELL__ < 710 import Control.Applicative ((<$>)) #endif -import Control.Monad.IO.Class (MonadIO, liftIO) import Text.Shakespeare.I18N (RenderMessage) import Data.Text (Text) import qualified Data.Map as Map @@ -72,11 +71,13 @@ import Data.Text.Lazy.Builder (fromLazyText) import Text.Blaze.Html (toHtml, preEscapedToMarkup) import qualified Data.Text.Lazy as TL import qualified Data.Text.Lazy.Builder as TB -import Data.IORef import Yesod.Core.Types import Yesod.Core.Class.Handler +type WidgetT site (m :: * -> *) = WidgetFor site +{-# DEPRECATED WidgetT "Use WidgetFor directly" #-} + preEscapedLazyText :: TL.Text -> Html preEscapedLazyText = preEscapedToMarkup @@ -97,8 +98,8 @@ instance render ~ RY site => ToWidget site (render -> Javascript) where toWidget x = tell $ GWData mempty mempty mempty mempty mempty (Just x) mempty instance ToWidget site Javascript where toWidget x = tell $ GWData mempty mempty mempty mempty mempty (Just $ const x) mempty -instance (site' ~ site, IO ~ m, a ~ ()) => ToWidget site' (WidgetT site m a) where - toWidget = liftWidgetT +instance (site' ~ site, a ~ ()) => ToWidget site' (WidgetFor site a) where + toWidget = liftWidget instance ToWidget site Html where toWidget = toWidget . const -- | @since 1.4.28 @@ -268,49 +269,10 @@ ihamletToHtml ih = do return $ ih (toHtml . mrender) urender tell :: MonadWidget m => GWData (Route (HandlerSite m)) -> m () -tell = liftWidgetT . tellWidget +tell = liftWidget . tellWidget toUnique :: x -> UniqueList x toUnique = UniqueList . (:) -handlerToWidget :: Monad m => HandlerT site m a -> WidgetT site m a -handlerToWidget (HandlerT f) = WidgetT $ const f - -widgetToParentWidget :: MonadIO m - => WidgetT child IO a - -> HandlerT child (HandlerT parent m) (WidgetT parent m a) -widgetToParentWidget (WidgetT f) = HandlerT $ \hdChild -> do - return $ WidgetT $ \ref _hdParent -> liftIO $ do - tmp <- newIORef mempty - a <- f tmp hdChild { handlerToParent = const () } - gwd <- readIORef tmp - modifyIORef' ref (<> liftGWD (handlerToParent hdChild) gwd) - return a - -liftGWD :: (child -> parent) -> GWData child -> GWData parent -liftGWD tp gwd = GWData - { gwdBody = fixBody $ gwdBody gwd - , gwdTitle = gwdTitle gwd - , gwdScripts = fixUnique fixScript $ gwdScripts gwd - , gwdStylesheets = fixUnique fixStyle $ gwdStylesheets gwd - , gwdCss = fixCss <$> gwdCss gwd - , gwdJavascript = fixJS <$> gwdJavascript gwd - , gwdHead = fixHead $ gwdHead gwd - } - where - fixRender f route = f (tp route) - - fixBody (Body h) = Body $ h . fixRender - fixHead (Head h) = Head $ h . fixRender - - fixUnique go (UniqueList f) = UniqueList (map go (f []) ++) - - fixScript (Script loc attrs) = Script (fixLoc loc) attrs - fixStyle (Stylesheet loc attrs) = Stylesheet (fixLoc loc) attrs - - fixLoc (Local url) = Local $ tp url - fixLoc (Remote t) = Remote t - - fixCss f = f . fixRender - - fixJS f = f . fixRender +handlerToWidget :: HandlerFor site a -> WidgetFor site a +handlerToWidget (HandlerFor f) = WidgetFor $ f . wdHandler diff --git a/yesod-core/test/YesodCoreTest/NoOverloadedStrings.hs b/yesod-core/test/YesodCoreTest/NoOverloadedStrings.hs index 0785df0b..c01af705 100644 --- a/yesod-core/test/YesodCoreTest/NoOverloadedStrings.hs +++ b/yesod-core/test/YesodCoreTest/NoOverloadedStrings.hs @@ -1,5 +1,6 @@ {-# LANGUAGE QuasiQuotes, TypeFamilies, TemplateHaskell, MultiParamTypeClasses #-} {-# LANGUAGE FlexibleInstances, ViewPatterns #-} +{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} -- the module name is a lie!!! module YesodCoreTest.NoOverloadedStrings ( noOverloadedTest @@ -20,19 +21,19 @@ import qualified Data.ByteString.Lazy.Char8 as L8 getSubsite :: a -> Subsite getSubsite _ = Subsite $(mkYesodSubDispatch resourcesSubsite) -getBarR :: Monad m => m T.Text +getBarR :: MonadSubHandler m => m T.Text getBarR = return $ T.pack "BarR" -getBazR :: Yesod master => HandlerT Subsite (HandlerT master IO) Html -getBazR = lift $ defaultLayout [whamlet|Used Default Layout|] +getBazR :: (MonadSubHandler m, Yesod (HandlerSite m)) => m Html +getBazR = liftHandler $ defaultLayout [whamlet|Used Default Layout|] -getBinR :: Yesod master => HandlerT Subsite (HandlerT master IO) Html +getBinR :: (MonadSubHandler m, Yesod (HandlerSite m), SubHandlerSite m ~ Subsite) => m Html getBinR = do - widget <- widgetToParentWidget [whamlet| + toParentRoute <- getToParentRoute + liftHandler $ defaultLayout [whamlet|

Used defaultLayoutT - Baz + Baz |] - lift $ defaultLayout widget getOnePiecesR :: Monad m => Int -> m () getOnePiecesR _ = return () diff --git a/yesod-core/test/YesodCoreTest/NoOverloadedStringsSub.hs b/yesod-core/test/YesodCoreTest/NoOverloadedStringsSub.hs index e8be9c14..170fd711 100644 --- a/yesod-core/test/YesodCoreTest/NoOverloadedStringsSub.hs +++ b/yesod-core/test/YesodCoreTest/NoOverloadedStringsSub.hs @@ -10,7 +10,7 @@ module YesodCoreTest.NoOverloadedStringsSub where import Yesod.Core import Yesod.Core.Types -data Subsite = Subsite (forall master. Yesod master => YesodSubRunnerEnv Subsite master (HandlerT master IO) -> Application) +data Subsite = Subsite (forall master. Yesod master => YesodSubRunnerEnv Subsite master (HandlerFor master) -> Application) mkYesodSubData "Subsite" [parseRoutes| /bar BarR GET @@ -21,7 +21,7 @@ mkYesodSubData "Subsite" [parseRoutes| /has-three-pieces/#Int/#Int/#Int ThreePiecesR GET |] -instance Yesod master => YesodSubDispatch Subsite (HandlerT master IO) where +instance Yesod master => YesodSubDispatch Subsite (HandlerFor master) where yesodSubDispatch ysre = f ysre where From 61c887f5014f3090ce5237609237edfa26eafcb2 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Wed, 13 Dec 2017 13:44:59 +0200 Subject: [PATCH 07/43] Start converting yesod-auth over --- yesod-auth/Yesod/Auth.hs | 113 +++++++++--------- yesod-auth/Yesod/Auth/BrowserId.hs | 11 +- yesod-core/Yesod/Core/Class/Dispatch.hs | 32 ++--- yesod-core/Yesod/Core/Class/Handler.hs | 3 +- .../test/YesodCoreTest/NoOverloadedStrings.hs | 4 +- 5 files changed, 83 insertions(+), 80 deletions(-) diff --git a/yesod-auth/Yesod/Auth.hs b/yesod-auth/Yesod/Auth.hs index 4a0eb390..c5b1c163 100644 --- a/yesod-auth/Yesod/Auth.hs +++ b/yesod-auth/Yesod/Auth.hs @@ -64,7 +64,7 @@ import Network.HTTP.Client (Manager, Request, withResponse, Response, BodyReader import qualified Network.Wai as W import Yesod.Core -import Yesod.Core.Types (HandlerT(..), unHandlerT) +import Yesod.Core.Types (HandlerFor(..)) import Yesod.Persist import Yesod.Auth.Message (AuthMessage, defaultMessage) import qualified Yesod.Auth.Message as Msg @@ -72,13 +72,12 @@ import Yesod.Form (FormMessage) import Data.Typeable (Typeable) import Control.Exception (Exception) import Network.HTTP.Types (Status, internalServerError500, unauthorized401) -import Control.Monad.Trans.Resource (MonadResourceBase) import qualified Control.Monad.Trans.Writer as Writer import Control.Monad (void) type AuthRoute = Route Auth -type AuthHandler master a = YesodAuth master => HandlerT Auth (HandlerT master IO) a +type AuthHandler master a = forall m. (MonadSubHandler m, YesodAuth master, master ~ HandlerSite m, Auth ~ SubHandlerSite m) => m a type Method = Text type Piece = Text @@ -94,7 +93,7 @@ data AuthenticationResult master data AuthPlugin master = AuthPlugin { apName :: Text , apDispatch :: Method -> [Piece] -> AuthHandler master TypedContent - , apLogin :: (Route Auth -> Route master) -> WidgetT master IO () + , apLogin :: (Route Auth -> Route master) -> WidgetFor master () } getAuth :: a -> Auth @@ -111,7 +110,7 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage type AuthId master -- | specify the layout. Uses defaultLayout by default - authLayout :: WidgetT master IO () -> HandlerT master IO Html + authLayout :: WidgetFor master () -> HandlerFor master Html authLayout = defaultLayout -- | Default destination on successful login, if no other @@ -127,7 +126,7 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage -- Default implementation is in terms of @'getAuthId'@ -- -- Since: 1.4.4 - authenticate :: Creds master -> HandlerT master IO (AuthenticationResult master) + authenticate :: Creds master -> HandlerFor master (AuthenticationResult master) authenticate creds = do muid <- getAuthId creds @@ -137,7 +136,7 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage -- -- Default implementation is in terms of @'authenticate'@ -- - getAuthId :: Creds master -> HandlerT master IO (Maybe (AuthId master)) + getAuthId :: Creds master -> HandlerFor master (Maybe (AuthId master)) getAuthId creds = do auth <- authenticate creds @@ -167,7 +166,7 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage -- > lift $ redirect HomeR -- or any other Handler code you want -- > defaultLoginHandler -- - loginHandler :: HandlerT Auth (HandlerT master IO) Html + loginHandler :: AuthHandler master Html loginHandler = defaultLoginHandler -- | Used for i18n of messages provided by this package. @@ -196,11 +195,11 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage -- | Called on a successful login. By default, calls -- @addMessageI "success" NowLoggedIn@. - onLogin :: HandlerT master IO () + onLogin :: HandlerFor master () onLogin = addMessageI "success" Msg.NowLoggedIn -- | Called on logout. By default, does nothing - onLogout :: HandlerT master IO () + onLogout :: HandlerFor master () onLogout = return () -- | Retrieves user credentials, if user is authenticated. @@ -212,16 +211,16 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage -- other than a browser. -- -- Since 1.2.0 - maybeAuthId :: HandlerT master IO (Maybe (AuthId master)) + maybeAuthId :: HandlerFor master (Maybe (AuthId master)) default maybeAuthId :: (YesodAuthPersist master, Typeable (AuthEntity master)) - => HandlerT master IO (Maybe (AuthId master)) + => HandlerFor master (Maybe (AuthId master)) maybeAuthId = defaultMaybeAuthId -- | Called on login error for HTTP requests. By default, calls -- @addMessage@ with "error" as status and redirects to @dest@. - onErrorHtml :: (MonadResourceBase m) => Route master -> Text -> HandlerT master m Html + onErrorHtml :: Route master -> Text -> HandlerFor master Html onErrorHtml dest msg = do addMessage "error" $ toHtml msg fmap asHtml $ redirect dest @@ -231,10 +230,10 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage -- -- The HTTP 'Request' is given in case it is useful to change behavior based on inspecting the request. -- This is an experimental API that is not broadly used throughout the yesod-auth code base - runHttpRequest :: Request -> (Response BodyReader -> HandlerT master IO a) -> HandlerT master IO a + runHttpRequest :: Request -> (Response BodyReader -> HandlerFor master a) -> HandlerFor master a runHttpRequest req inner = do man <- authHttpManager Control.Applicative.<$> getYesod - HandlerT $ \t -> withResponse req man $ \res -> unHandlerT (inner res) t + HandlerFor $ \t -> withResponse req man $ \res -> unHandlerFor (inner res) t {-# MINIMAL loginDest, logoutDest, (authenticate | getAuthId), authPlugins, authHttpManager #-} @@ -255,7 +254,7 @@ credsKey = "_ID" -- Since 1.1.2 defaultMaybeAuthId :: (YesodAuthPersist master, Typeable (AuthEntity master)) - => HandlerT master IO (Maybe (AuthId master)) + => HandlerFor master (Maybe (AuthId master)) defaultMaybeAuthId = runMaybeT $ do s <- MaybeT $ lookupSession credsKey aid <- MaybeT $ return $ fromPathPiece s @@ -264,7 +263,7 @@ defaultMaybeAuthId = runMaybeT $ do cachedAuth :: (YesodAuthPersist master, Typeable (AuthEntity master)) - => AuthId master -> HandlerT master IO (Maybe (AuthEntity master)) + => AuthId master -> HandlerFor master (Maybe (AuthEntity master)) cachedAuth = fmap unCachedMaybeAuth . cached @@ -281,48 +280,47 @@ cachedAuth defaultLoginHandler :: AuthHandler master Html defaultLoginHandler = do tp <- getRouteToParent - lift $ authLayout $ do + liftHandler $ authLayout $ do setTitleI Msg.LoginTitle master <- getYesod mapM_ (flip apLogin tp) (authPlugins master) -loginErrorMessageI :: (MonadResourceBase m, YesodAuth master) - => Route child +loginErrorMessageI :: (YesodAuth (HandlerSite m), MonadSubHandler m) + => Route (SubHandlerSite m) -> AuthMessage - -> HandlerT child (HandlerT master m) TypedContent + -> m TypedContent loginErrorMessageI dest msg = do toParent <- getRouteToParent - lift $ loginErrorMessageMasterI (toParent dest) msg + liftHandler $ loginErrorMessageMasterI (toParent dest) msg -loginErrorMessageMasterI :: (YesodAuth master, MonadResourceBase m, RenderMessage master AuthMessage) +loginErrorMessageMasterI :: (YesodAuth master, RenderMessage master AuthMessage) => Route master -> AuthMessage - -> HandlerT master m TypedContent + -> HandlerFor master TypedContent loginErrorMessageMasterI dest msg = do mr <- getMessageRender loginErrorMessage dest (mr msg) -- | For HTML, set the message and redirect to the route. -- For JSON, send the message and a 401 status -loginErrorMessage :: (YesodAuth master, MonadResourceBase m) +loginErrorMessage :: YesodAuth master => Route master -> Text - -> HandlerT master m TypedContent + -> HandlerFor master TypedContent loginErrorMessage dest msg = messageJson401 msg (onErrorHtml dest msg) -messageJson401 :: MonadResourceBase m => Text -> HandlerT master m Html -> HandlerT master m TypedContent +messageJson401 :: Text -> HandlerFor master Html -> HandlerFor master TypedContent messageJson401 = messageJsonStatus unauthorized401 -messageJson500 :: MonadResourceBase m => Text -> HandlerT master m Html -> HandlerT master m TypedContent +messageJson500 :: Text -> HandlerFor master Html -> HandlerFor master TypedContent messageJson500 = messageJsonStatus internalServerError500 -messageJsonStatus :: MonadResourceBase m - => Status +messageJsonStatus :: Status -> Text - -> HandlerT master m Html - -> HandlerT master m TypedContent + -> HandlerFor master Html + -> HandlerFor master TypedContent messageJsonStatus status msg html = selectRep $ do provideRep html provideRep $ do @@ -336,7 +334,7 @@ provideJsonMessage msg = provideRep $ return $ object ["message" .= msg] setCredsRedirect :: YesodAuth master => Creds master -- ^ new credentials - -> HandlerT master IO TypedContent + -> HandlerFor master TypedContent setCredsRedirect creds = do y <- getYesod auth <- authenticate creds @@ -378,7 +376,7 @@ setCredsRedirect creds = do setCreds :: YesodAuth master => Bool -- ^ if HTTP redirects should be done -> Creds master -- ^ new credentials - -> HandlerT master IO () + -> HandlerFor master () setCreds doRedirects creds = if doRedirects then void $ setCredsRedirect creds @@ -389,9 +387,9 @@ setCreds doRedirects creds = -- | same as defaultLayoutJson, but uses authLayout authLayoutJson :: (YesodAuth site, ToJSON j) - => WidgetT site IO () -- ^ HTML - -> HandlerT site IO j -- ^ JSON - -> HandlerT site IO TypedContent + => WidgetFor site () -- ^ HTML + -> HandlerFor site j -- ^ JSON + -> HandlerFor site TypedContent authLayoutJson w json = selectRep $ do provideRep $ authLayout w provideRep $ fmap toJSON json @@ -399,18 +397,18 @@ authLayoutJson w json = selectRep $ do -- | Clears current user credentials for the session. -- -- Since 1.1.7 -clearCreds :: YesodAuth master +clearCreds :: (MonadHandler m, YesodAuth (HandlerSite m)) => Bool -- ^ if HTTP redirect to 'logoutDest' should be done - -> HandlerT master IO () + -> m () clearCreds doRedirects = do y <- getYesod - onLogout + liftHandler onLogout deleteSession credsKey when doRedirects $ do redirectUltDest $ logoutDest y getCheckR :: AuthHandler master TypedContent -getCheckR = lift $ do +getCheckR = liftHandler $ do creds <- maybeAuthId authLayoutJson (do setTitle "Authentication Status" @@ -431,7 +429,7 @@ $nothing ] setUltDestReferer' :: AuthHandler master () -setUltDestReferer' = lift $ do +setUltDestReferer' = liftHandler $ do master <- getYesod when (redirectToReferer master) setUltDestReferer @@ -439,14 +437,16 @@ getLoginR :: AuthHandler master Html getLoginR = setUltDestReferer' >> loginHandler getLogoutR :: AuthHandler master () -getLogoutR = setUltDestReferer' >> redirectToPost LogoutR +getLogoutR = do + tp <- getRouteToParent + setUltDestReferer' >> redirectToPost (tp LogoutR) postLogoutR :: AuthHandler master () -postLogoutR = lift $ clearCreds True +postLogoutR = clearCreds True handlePluginR :: Text -> [Text] -> AuthHandler master TypedContent handlePluginR plugin pieces = do - master <- lift getYesod + master <- getYesod env <- waiRequest let method = decodeUtf8With lenientDecode $ W.requestMethod env case filter (\x -> apName x == plugin) (authPlugins master) of @@ -463,7 +463,7 @@ maybeAuth :: ( YesodAuthPersist master , Key val ~ AuthId master , PersistEntity val , Typeable val - ) => HandlerT master IO (Maybe (Entity val)) + ) => HandlerFor master (Maybe (Entity val)) maybeAuth = runMaybeT $ do (aid, ae) <- MaybeT maybeAuthPair return $ Entity aid ae @@ -473,7 +473,7 @@ maybeAuth = runMaybeT $ do -- -- Since 1.4.0 maybeAuthPair :: (YesodAuthPersist master, Typeable (AuthEntity master)) - => HandlerT master IO (Maybe (AuthId master, AuthEntity master)) + => HandlerFor master (Maybe (AuthId master, AuthEntity master)) maybeAuthPair = runMaybeT $ do aid <- MaybeT maybeAuthId ae <- MaybeT $ cachedAuth aid @@ -504,7 +504,7 @@ class (YesodAuth master, YesodPersist master) => YesodAuthPersist master where type AuthEntity master :: * type AuthEntity master = KeyEntity (AuthId master) - getAuthEntity :: AuthId master -> HandlerT master IO (Maybe (AuthEntity master)) + getAuthEntity :: AuthId master -> HandlerFor master (Maybe (AuthEntity master)) #if MIN_VERSION_persistent(2,5,0) default getAuthEntity @@ -513,7 +513,7 @@ class (YesodAuth master, YesodPersist master) => YesodAuthPersist master where , Key (AuthEntity master) ~ AuthId master , PersistStore backend ) - => AuthId master -> HandlerT master IO (Maybe (AuthEntity master)) + => AuthId master -> HandlerFor master (Maybe (AuthEntity master)) #else default getAuthEntity :: ( YesodPersistBackend master @@ -522,7 +522,7 @@ class (YesodAuth master, YesodPersist master) => YesodAuthPersist master where , PersistStore (YesodPersistBackend master) , PersistEntity (AuthEntity master) ) - => AuthId master -> HandlerT master IO (Maybe (AuthEntity master)) + => AuthId master -> HandlerFor master (Maybe (AuthEntity master)) #endif getAuthEntity = runDB . get @@ -534,7 +534,7 @@ type instance KeyEntity (Key x) = x -- authenticated or responds with error 401 if this is an API client (expecting JSON). -- -- Since 1.1.0 -requireAuthId :: YesodAuth master => HandlerT master IO (AuthId master) +requireAuthId :: YesodAuth master => HandlerFor master (AuthId master) requireAuthId = maybeAuthId >>= maybe handleAuthLack return -- | Similar to 'maybeAuth', but redirects to a login page if user is not @@ -546,7 +546,7 @@ requireAuth :: ( YesodAuthPersist master , Key val ~ AuthId master , PersistEntity val , Typeable val - ) => HandlerT master IO (Entity val) + ) => HandlerFor master (Entity val) requireAuth = maybeAuth >>= maybe handleAuthLack return -- | Similar to 'requireAuth', but not tied to Persistent's 'Entity' type. @@ -554,15 +554,15 @@ requireAuth = maybeAuth >>= maybe handleAuthLack return -- -- Since 1.4.0 requireAuthPair :: (YesodAuthPersist master, Typeable (AuthEntity master)) - => HandlerT master IO (AuthId master, AuthEntity master) + => HandlerFor master (AuthId master, AuthEntity master) requireAuthPair = maybeAuthPair >>= maybe handleAuthLack return -handleAuthLack :: YesodAuth master => HandlerT master IO a +handleAuthLack :: YesodAuth master => HandlerFor master a handleAuthLack = do aj <- acceptsJson if aj then notAuthenticated else redirectLogin -redirectLogin :: YesodAuth master => HandlerT master IO a +redirectLogin :: YesodAuth master => HandlerFor master a redirectLogin = do y <- getYesod when (redirectToCurrent y) setUltDestCurrent @@ -577,7 +577,8 @@ data AuthException = InvalidFacebookResponse deriving (Show, Typeable) instance Exception AuthException -instance YesodAuth master => YesodSubDispatch Auth (HandlerT master IO) where +-- FIXME this is ugly, and I probably want to ditch the MonadSubHandler typeclass anyway +instance (YesodAuth (HandlerSite m), MonadSubHandler m) => YesodSubDispatch Auth m where yesodSubDispatch = $(mkYesodSubDispatch resourcesAuth) asHtml :: Html -> Html diff --git a/yesod-auth/Yesod/Auth/BrowserId.hs b/yesod-auth/Yesod/Auth/BrowserId.hs index a63ed0e1..802cba03 100644 --- a/yesod-auth/Yesod/Auth/BrowserId.hs +++ b/yesod-auth/Yesod/Auth/BrowserId.hs @@ -70,20 +70,21 @@ authBrowserId bis@BrowserIdSettings {..} = AuthPlugin , apDispatch = \m ps -> case (m, ps) of ("GET", [assertion]) -> do - master <- lift getYesod + master <- getYesod audience <- case bisAudience of Just a -> return a Nothing -> do r <- getUrlRender - return $ T.takeWhile (/= '/') $ stripScheme $ r LoginR - memail <- lift $ checkAssertion audience assertion (authHttpManager master) + tm <- getRouteToParent + return $ T.takeWhile (/= '/') $ stripScheme $ r $ tm LoginR + memail <- liftHandler $ checkAssertion audience assertion (authHttpManager master) case memail of Nothing -> do $logErrorS "yesod-auth" "BrowserID assertion failure" tm <- getRouteToParent - lift $ loginErrorMessage (tm LoginR) "BrowserID login error." - Just email -> lift $ setCredsRedirect Creds + liftHandler $ loginErrorMessage (tm LoginR) "BrowserID login error." + Just email -> liftHandler $ setCredsRedirect Creds { credsPlugin = pid , credsIdent = email , credsExtra = [] diff --git a/yesod-core/Yesod/Core/Class/Dispatch.hs b/yesod-core/Yesod/Core/Class/Dispatch.hs index 16910ecf..7c4d6bd8 100644 --- a/yesod-core/Yesod/Core/Class/Dispatch.hs +++ b/yesod-core/Yesod/Core/Class/Dispatch.hs @@ -36,10 +36,8 @@ instance MonadHandler m => YesodSubDispatch WaiSubsiteWithAuth m where WaiSubsiteWithAuth set = ysreGetSub $ yreSite $ ysreParentEnv handlert = sendWaiApplication set -type SubHandler child parent a = ReaderT (SubsiteData child parent) (HandlerFor parent) a - data SubsiteData child parent = SubsiteData - { sdToParentRoute :: !(Route child -> Route parent) + { sdRouteToParent :: !(Route child -> Route parent) , sdCurrentRoute :: !(Maybe (Route child)) , sdSubsiteData :: !child } @@ -48,21 +46,21 @@ class MonadHandler m => MonadSubHandler m where type SubHandlerSite m getSubYesod :: m (SubHandlerSite m) - getToParentRoute :: m (Route (SubHandlerSite m) -> Route (HandlerSite m)) + getRouteToParent :: m (Route (SubHandlerSite m) -> Route (HandlerSite m)) getSubCurrentRoute :: m (Maybe (Route (SubHandlerSite m))) instance MonadSubHandler (HandlerFor site) where type SubHandlerSite (HandlerFor site) = site getSubYesod = getYesod - getToParentRoute = return id + getRouteToParent = return id getSubCurrentRoute = getCurrentRoute instance MonadSubHandler (WidgetFor site) where type SubHandlerSite (WidgetFor site) = site getSubYesod = getYesod - getToParentRoute = return id + getRouteToParent = return id getSubCurrentRoute = getCurrentRoute instance (MonadSubHandler m, parent ~ SubHandlerSite m) => MonadSubHandler (ReaderT (SubsiteData child parent) m) where @@ -70,21 +68,23 @@ instance (MonadSubHandler m, parent ~ SubHandlerSite m) => MonadSubHandler (Read getSubYesod = fmap sdSubsiteData ask getSubCurrentRoute = fmap sdCurrentRoute ask - getToParentRoute = ReaderT $ \sd -> do - toParent' <- getToParentRoute - return $ toParent' . sdToParentRoute sd + getRouteToParent = ReaderT $ \sd -> do + toParent' <- getRouteToParent + return $ toParent' . sdRouteToParent sd subHelper - :: (ToTypedContent content, MonadSubHandler m, parent ~ HandlerSite m) - => ReaderT (SubsiteData child parent) m content + :: (ToTypedContent content, MonadSubHandler m, master ~ HandlerSite m, parent ~ SubHandlerSite m) + => ReaderT (SubsiteData child master) m content -> YesodSubRunnerEnv child parent m -> Maybe (Route child) -> W.Application subHelper (ReaderT f) YesodSubRunnerEnv {..} mroute = ysreParentRunner handler ysreParentEnv (fmap ysreToParentRoute mroute) where - handler = fmap toTypedContent $ f SubsiteData - { sdToParentRoute = ysreToParentRoute - , sdCurrentRoute = mroute - , sdSubsiteData = ysreGetSub $ yreSite ysreParentEnv - } + handler = fmap toTypedContent $ do + tm <- getRouteToParent + f SubsiteData + { sdRouteToParent = tm . ysreToParentRoute + , sdCurrentRoute = mroute + , sdSubsiteData = ysreGetSub $ yreSite ysreParentEnv + } diff --git a/yesod-core/Yesod/Core/Class/Handler.hs b/yesod-core/Yesod/Core/Class/Handler.hs index 7373d261..f32e1477 100644 --- a/yesod-core/Yesod/Core/Class/Handler.hs +++ b/yesod-core/Yesod/Core/Class/Handler.hs @@ -14,6 +14,7 @@ module Yesod.Core.Class.Handler ) where import Yesod.Core.Types +import Control.Monad.Logger (MonadLogger) import Control.Monad.Trans.Resource (MonadResource) import Control.Monad.Trans.Class (lift) #if __GLASGOW_HASKELL__ < 710 @@ -35,7 +36,7 @@ import qualified Control.Monad.Trans.State.Strict as Strict ( StateT ) import qualified Control.Monad.Trans.Writer.Strict as Strict ( WriterT ) -- FIXME should we just use MonadReader instances instead? -class MonadResource m => MonadHandler m where +class (MonadResource m, MonadLogger m) => MonadHandler m where type HandlerSite m liftHandler :: HandlerFor (HandlerSite m) a -> m a diff --git a/yesod-core/test/YesodCoreTest/NoOverloadedStrings.hs b/yesod-core/test/YesodCoreTest/NoOverloadedStrings.hs index c01af705..11d3a145 100644 --- a/yesod-core/test/YesodCoreTest/NoOverloadedStrings.hs +++ b/yesod-core/test/YesodCoreTest/NoOverloadedStrings.hs @@ -29,10 +29,10 @@ getBazR = liftHandler $ defaultLayout [whamlet|Used Default Layout|] getBinR :: (MonadSubHandler m, Yesod (HandlerSite m), SubHandlerSite m ~ Subsite) => m Html getBinR = do - toParentRoute <- getToParentRoute + routeToParent <- getRouteToParent liftHandler $ defaultLayout [whamlet|

Used defaultLayoutT - Baz + Baz |] getOnePiecesR :: Monad m => Int -> m () From aed10fc84a69745f849d26fa033bae5ed9a28ec2 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Wed, 13 Dec 2017 14:39:59 +0200 Subject: [PATCH 08/43] WIP --- yesod-auth/Yesod/Auth.hs | 52 ++++++++++++++++++++-------------- yesod-auth/Yesod/Auth/Dummy.hs | 7 +++-- yesod-auth/Yesod/Auth/Email.hs | 36 +++++++++++------------ yesod-auth/yesod-auth.cabal | 2 ++ yesod-core/Yesod/Core.hs | 1 + 5 files changed, 55 insertions(+), 43 deletions(-) diff --git a/yesod-auth/Yesod/Auth.hs b/yesod-auth/Yesod/Auth.hs index c5b1c163..8c2a4f4c 100644 --- a/yesod-auth/Yesod/Auth.hs +++ b/yesod-auth/Yesod/Auth.hs @@ -47,9 +47,10 @@ module Yesod.Auth , asHtml ) where -import Control.Applicative ((<$>)) import Control.Monad (when) import Control.Monad.Trans.Maybe +import Control.Monad.Trans.Reader (ReaderT) +import Control.Monad.IO.Unlift (withRunInIO) import Yesod.Auth.Routes import Data.Aeson hiding (json) @@ -60,11 +61,11 @@ import qualified Data.Text as T import qualified Data.HashMap.Lazy as Map import Data.Monoid (Endo) import Network.HTTP.Client (Manager, Request, withResponse, Response, BodyReader) +import Network.HTTP.Client.TLS (getGlobalManager) import qualified Network.Wai as W import Yesod.Core -import Yesod.Core.Types (HandlerFor(..)) import Yesod.Persist import Yesod.Auth.Message (AuthMessage, defaultMessage) import qualified Yesod.Auth.Message as Msg @@ -110,8 +111,8 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage type AuthId master -- | specify the layout. Uses defaultLayout by default - authLayout :: WidgetFor master () -> HandlerFor master Html - authLayout = defaultLayout + authLayout :: WidgetFor master () -> AuthHandler master Html + authLayout = liftHandler . defaultLayout -- | Default destination on successful login, if no other -- destination exists. @@ -126,7 +127,7 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage -- Default implementation is in terms of @'getAuthId'@ -- -- Since: 1.4.4 - authenticate :: Creds master -> HandlerFor master (AuthenticationResult master) + authenticate :: Creds master -> AuthHandler master (AuthenticationResult master) authenticate creds = do muid <- getAuthId creds @@ -136,7 +137,7 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage -- -- Default implementation is in terms of @'authenticate'@ -- - getAuthId :: Creds master -> HandlerFor master (Maybe (AuthId master)) + getAuthId :: Creds master -> AuthHandler master (Maybe (AuthId master)) getAuthId creds = do auth <- authenticate creds @@ -191,15 +192,16 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage -- type. This allows backends to reuse persistent connections. If none of -- the backends you're using use HTTP connections, you can safely return -- @error \"authHttpManager\"@ here. - authHttpManager :: master -> Manager + authHttpManager :: master -> IO Manager + authHttpManager _ = getGlobalManager -- | Called on a successful login. By default, calls -- @addMessageI "success" NowLoggedIn@. - onLogin :: HandlerFor master () + onLogin :: AuthHandler master () onLogin = addMessageI "success" Msg.NowLoggedIn -- | Called on logout. By default, does nothing - onLogout :: HandlerFor master () + onLogout :: AuthHandler master () onLogout = return () -- | Retrieves user credentials, if user is authenticated. @@ -211,16 +213,16 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage -- other than a browser. -- -- Since 1.2.0 - maybeAuthId :: HandlerFor master (Maybe (AuthId master)) + maybeAuthId :: AuthHandler master (Maybe (AuthId master)) default maybeAuthId :: (YesodAuthPersist master, Typeable (AuthEntity master)) - => HandlerFor master (Maybe (AuthId master)) + => AuthHandler master (Maybe (AuthId master)) maybeAuthId = defaultMaybeAuthId -- | Called on login error for HTTP requests. By default, calls -- @addMessage@ with "error" as status and redirects to @dest@. - onErrorHtml :: Route master -> Text -> HandlerFor master Html + onErrorHtml :: Route master -> Text -> AuthHandler master Html onErrorHtml dest msg = do addMessage "error" $ toHtml msg fmap asHtml $ redirect dest @@ -230,10 +232,13 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage -- -- The HTTP 'Request' is given in case it is useful to change behavior based on inspecting the request. -- This is an experimental API that is not broadly used throughout the yesod-auth code base - runHttpRequest :: Request -> (Response BodyReader -> HandlerFor master a) -> HandlerFor master a + runHttpRequest :: (MonadSubHandler m, HandlerSite m ~ master, SubHandlerSite m ~ Auth) + => Request + -> (Response BodyReader -> ReaderT (SubsiteData Auth master) (HandlerFor master) a) + -> m a runHttpRequest req inner = do - man <- authHttpManager Control.Applicative.<$> getYesod - HandlerFor $ \t -> withResponse req man $ \res -> unHandlerFor (inner res) t + man <- getYesod >>= liftIO . authHttpManager + lift $ withRunInIO $ \run -> withResponse req man $ run . inner {-# MINIMAL loginDest, logoutDest, (authenticate | getAuthId), authPlugins, authHttpManager #-} @@ -254,7 +259,7 @@ credsKey = "_ID" -- Since 1.1.2 defaultMaybeAuthId :: (YesodAuthPersist master, Typeable (AuthEntity master)) - => HandlerFor master (Maybe (AuthId master)) + => AuthHandler master (Maybe (AuthId master)) defaultMaybeAuthId = runMaybeT $ do s <- MaybeT $ lookupSession credsKey aid <- MaybeT $ return $ fromPathPiece s @@ -263,7 +268,7 @@ defaultMaybeAuthId = runMaybeT $ do cachedAuth :: (YesodAuthPersist master, Typeable (AuthEntity master)) - => AuthId master -> HandlerFor master (Maybe (AuthEntity master)) + => AuthId master -> AuthHandler master (Maybe (AuthEntity master)) cachedAuth = fmap unCachedMaybeAuth . cached @@ -298,7 +303,7 @@ loginErrorMessageI dest msg = do loginErrorMessageMasterI :: (YesodAuth master, RenderMessage master AuthMessage) => Route master -> AuthMessage - -> HandlerFor master TypedContent + -> AuthHandler master TypedContent loginErrorMessageMasterI dest msg = do mr <- getMessageRender loginErrorMessage dest (mr msg) @@ -308,10 +313,13 @@ loginErrorMessageMasterI dest msg = do loginErrorMessage :: YesodAuth master => Route master -> Text - -> HandlerFor master TypedContent + -> AuthHandler master TypedContent loginErrorMessage dest msg = messageJson401 msg (onErrorHtml dest msg) -messageJson401 :: Text -> HandlerFor master Html -> HandlerFor master TypedContent +messageJson401 :: (MonadSubHandler m, HandlerSite m ~ master, SubHandlerSite m ~ Auth) + => Text + -> m Html + -> m TypedContent messageJson401 = messageJsonStatus unauthorized401 messageJson500 :: Text -> HandlerFor master Html -> HandlerFor master TypedContent @@ -577,8 +585,8 @@ data AuthException = InvalidFacebookResponse deriving (Show, Typeable) instance Exception AuthException --- FIXME this is ugly, and I probably want to ditch the MonadSubHandler typeclass anyway -instance (YesodAuth (HandlerSite m), MonadSubHandler m) => YesodSubDispatch Auth m where +-- FIXME HandlerSite m ~ SubHandlerSite m should be unnecessary +instance (YesodAuth (HandlerSite m), HandlerSite m ~ SubHandlerSite m, MonadSubHandler m) => YesodSubDispatch Auth m where yesodSubDispatch = $(mkYesodSubDispatch resourcesAuth) asHtml :: Html -> Html diff --git a/yesod-auth/Yesod/Auth/Dummy.hs b/yesod-auth/Yesod/Auth/Dummy.hs index 9e4611d6..4899f99d 100644 --- a/yesod-auth/Yesod/Auth/Dummy.hs +++ b/yesod-auth/Yesod/Auth/Dummy.hs @@ -1,5 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE FlexibleContexts #-} -- | Provides a dummy authentication module that simply lets a user specify -- his/her identifier. This is not intended for real world use, just for -- testing. @@ -15,9 +16,9 @@ authDummy :: YesodAuth m => AuthPlugin m authDummy = AuthPlugin "dummy" dispatch login where - dispatch "POST" [] = do - ident <- lift $ runInputPost $ ireq textField "ident" - lift $ setCredsRedirect $ Creds "dummy" ident [] + dispatch "POST" [] = liftHandler $ do + ident <- runInputPost $ ireq textField "ident" + setCredsRedirect $ Creds "dummy" ident [] dispatch _ _ = notFound url = PluginR "dummy" [] login authToMaster = do diff --git a/yesod-auth/Yesod/Auth/Email.hs b/yesod-auth/Yesod/Auth/Email.hs index 51cbea7c..60b65fb7 100644 --- a/yesod-auth/Yesod/Auth/Email.hs +++ b/yesod-auth/Yesod/Auth/Email.hs @@ -325,7 +325,7 @@ class ( YesodAuth site -- Default: 'defaultRegisterHandler'. -- -- @since: 1.2.6 - registerHandler :: HandlerT Auth (HandlerT site IO) Html + registerHandler :: AuthHandler site Html registerHandler = defaultRegisterHandler -- | Handler called to render the \"forgot password\" page. @@ -335,7 +335,7 @@ class ( YesodAuth site -- Default: 'defaultForgotPasswordHandler'. -- -- @since: 1.2.6 - forgotPasswordHandler :: HandlerT Auth (HandlerT site IO) Html + forgotPasswordHandler :: AuthHandler site Html forgotPasswordHandler = defaultForgotPasswordHandler -- | Handler called to render the \"set password\" page. The @@ -351,7 +351,7 @@ class ( YesodAuth site -- field for the old password should be presented. -- Otherwise, just two fields for the new password are -- needed. - -> HandlerT Auth (HandlerT site IO) TypedContent + -> AuthHandler site TypedContent setPasswordHandler = defaultSetPasswordHandler authEmail :: (YesodAuthEmail m) => AuthPlugin m @@ -371,7 +371,7 @@ authEmail = dispatch "POST" ["set-password"] = postPasswordR >>= sendResponse dispatch _ _ = notFound -getRegisterR :: YesodAuthEmail master => HandlerT Auth (HandlerT master IO) Html +getRegisterR :: YesodAuthEmail master => AuthHandler master Html getRegisterR = registerHandler -- | Default implementation of 'emailLoginHandler'. @@ -437,7 +437,7 @@ defaultEmailLoginHandler toParent = do -- | Default implementation of 'registerHandler'. -- -- @since 1.2.6 -defaultRegisterHandler :: YesodAuthEmail master => HandlerT Auth (HandlerT master IO) Html +defaultRegisterHandler :: YesodAuthEmail master => AuthHandler master Html defaultRegisterHandler = do (widget, enctype) <- lift $ generateFormPost registrationForm toParentRoute <- getRouteToParent @@ -480,7 +480,7 @@ parseEmail = withObject "email" (\obj -> do registerHelper :: YesodAuthEmail master => Bool -- ^ allow usernames? -> Route Auth - -> HandlerT Auth (HandlerT master IO) TypedContent + -> AuthHandler master TypedContent registerHelper allowUsername dest = do y <- lift getYesod checkCsrfHeaderOrParam defaultCsrfHeaderName defaultCsrfParamName @@ -525,16 +525,16 @@ registerHelper allowUsername dest = do lift $ sendVerifyEmail email verKey verUrl lift $ confirmationEmailSentResponse identifier -postRegisterR :: YesodAuthEmail master => HandlerT Auth (HandlerT master IO) TypedContent +postRegisterR :: YesodAuthEmail master => AuthHandler master TypedContent postRegisterR = registerHelper False registerR -getForgotPasswordR :: YesodAuthEmail master => HandlerT Auth (HandlerT master IO) Html +getForgotPasswordR :: YesodAuthEmail master => AuthHandler master Html getForgotPasswordR = forgotPasswordHandler -- | Default implementation of 'forgotPasswordHandler'. -- -- @since 1.2.6 -defaultForgotPasswordHandler :: YesodAuthEmail master => HandlerT Auth (HandlerT master IO) Html +defaultForgotPasswordHandler :: YesodAuthEmail master => AuthHandler master Html defaultForgotPasswordHandler = do (widget, enctype) <- lift $ generateFormPost forgotPasswordForm toParent <- getRouteToParent @@ -569,13 +569,13 @@ defaultForgotPasswordHandler = do fsAttrs = [("autofocus", "")] } -postForgotPasswordR :: YesodAuthEmail master => HandlerT Auth (HandlerT master IO) TypedContent +postForgotPasswordR :: YesodAuthEmail master => AuthHandler master TypedContent postForgotPasswordR = registerHelper True forgotPasswordR getVerifyR :: YesodAuthEmail site => AuthEmailId site -> Text - -> HandlerT Auth (HandlerT site IO) TypedContent + -> AuthHandler site TypedContent getVerifyR lid key = do realKey <- lift $ getVerifyKey lid memail <- lift $ getEmail lid @@ -612,7 +612,7 @@ parseCreds = withObject "creds" (\obj -> do return (email', pass)) -postLoginR :: YesodAuthEmail master => HandlerT Auth (HandlerT master IO) TypedContent +postLoginR :: YesodAuthEmail master => AuthHandler master TypedContent postLoginR = do result <- lift $ runInputPostResult $ (,) <$> ireq textField "email" @@ -658,7 +658,7 @@ postLoginR = do then Msg.InvalidEmailPass else Msg.InvalidUsernamePass -getPasswordR :: YesodAuthEmail master => HandlerT Auth (HandlerT master IO) TypedContent +getPasswordR :: YesodAuthEmail master => AuthHandler master TypedContent getPasswordR = do maid <- lift maybeAuthId case maid of @@ -670,7 +670,7 @@ getPasswordR = do -- | Default implementation of 'setPasswordHandler'. -- -- @since 1.2.6 -defaultSetPasswordHandler :: YesodAuthEmail master => Bool -> HandlerT Auth (HandlerT master IO) TypedContent +defaultSetPasswordHandler :: YesodAuthEmail master => Bool -> AuthHandler master TypedContent defaultSetPasswordHandler needOld = do messageRender <- lift getMessageRender toParent <- getRouteToParent @@ -749,7 +749,7 @@ parsePassword = withObject "password" (\obj -> do curr <- obj .:? "current" return (email', pass, curr)) -postPasswordR :: YesodAuthEmail master => HandlerT Auth (HandlerT master IO) TypedContent +postPasswordR :: YesodAuthEmail master => AuthHandler master TypedContent postPasswordR = do maid <- lift maybeAuthId (creds :: Result Value) <- lift parseCheckJsonBody @@ -773,14 +773,14 @@ postPasswordR = do mrealpass <- lift $ getPassword aid case (mrealpass, current) of (Nothing, _) -> - lift $ loginErrorMessage (tm setpassR) "You do not currently have a password set on your account" + liftHandler $ loginErrorMessage (tm setpassR) "You do not currently have a password set on your account" (_, Nothing) -> loginErrorMessageI LoginR Msg.BadSetPass (Just realpass, Just current') -> do - passValid <- lift $ verifyPassword current' realpass + passValid <- liftHandler $ verifyPassword current' realpass if passValid then confirmPassword aid tm jcreds - else lift $ loginErrorMessage (tm setpassR) "Invalid current password, please try again" + else liftHandler $ loginErrorMessage (tm setpassR) "Invalid current password, please try again" where msgOk = Msg.PassUpdated diff --git a/yesod-auth/yesod-auth.cabal b/yesod-auth/yesod-auth.cabal index 1de994a2..2adcfa49 100644 --- a/yesod-auth/yesod-auth.cabal +++ b/yesod-auth/yesod-auth.cabal @@ -41,6 +41,7 @@ library , persistent >= 2.1 && < 2.8 , persistent-template >= 2.1 && < 2.8 , http-client + , http-client-tls , http-conduit >= 2.1 , aeson >= 0.7 , lifted-base >= 0.1 @@ -61,6 +62,7 @@ library , conduit , conduit-extra , nonce >= 1.0.2 && < 1.1 + , unliftio-core if flag(network-uri) build-depends: network-uri >= 2.6 diff --git a/yesod-core/Yesod/Core.hs b/yesod-core/Yesod/Core.hs index d13d5cf4..fdcb7c43 100644 --- a/yesod-core/Yesod/Core.hs +++ b/yesod-core/Yesod/Core.hs @@ -76,6 +76,7 @@ module Yesod.Core , getApprootText -- * Subsites , MonadSubHandler (..) + , SubsiteData -- * Misc , yesodVersion , yesodRender From 8e265f6ebc500d2eff2169ea0727c0dbb8b23404 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Mon, 18 Dec 2017 15:04:45 +0200 Subject: [PATCH 09/43] It all compiles --- yesod-auth-oauth/Yesod/Auth/OAuth.hs | 33 ++++-- yesod-auth-oauth/yesod-auth-oauth.cabal | 1 + yesod-auth/Yesod/Auth.hs | 140 ++++++++++++------------ yesod-auth/Yesod/Auth/BrowserId.hs | 12 +- yesod-auth/Yesod/Auth/Dummy.hs | 3 +- yesod-auth/Yesod/Auth/Email.hs | 133 +++++++++++----------- yesod-auth/Yesod/Auth/GoogleEmail.hs | 89 --------------- yesod-auth/Yesod/Auth/GoogleEmail2.hs | 43 +++----- yesod-auth/Yesod/Auth/Hardcoded.hs | 23 ++-- yesod-auth/Yesod/Auth/OpenId.hs | 26 +++-- yesod-auth/Yesod/Auth/Rpxnow.hs | 14 ++- yesod-auth/yesod-auth.cabal | 5 +- yesod-static/Yesod/Static.hs | 7 +- 13 files changed, 228 insertions(+), 301 deletions(-) delete mode 100644 yesod-auth/Yesod/Auth/GoogleEmail.hs diff --git a/yesod-auth-oauth/Yesod/Auth/OAuth.hs b/yesod-auth-oauth/Yesod/Auth/OAuth.hs index 9a5d3a0e..6e723c08 100644 --- a/yesod-auth-oauth/Yesod/Auth/OAuth.hs +++ b/yesod-auth-oauth/Yesod/Auth/OAuth.hs @@ -1,5 +1,8 @@ {-# LANGUAGE DeriveDataTypeable, OverloadedStrings, QuasiQuotes #-} {-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE ScopedTypeVariables #-} module Yesod.Auth.OAuth ( authOAuth , oauthUrl @@ -14,6 +17,7 @@ import Control.Applicative as A ((<$>), (<*>)) import Control.Arrow ((***)) import Control.Exception.Lifted import Control.Monad.IO.Class +import Control.Monad.IO.Unlift (MonadUnliftIO) import Data.ByteString (ByteString) import Data.Maybe import Data.Text (Text) @@ -35,26 +39,37 @@ instance Exception YesodOAuthException oauthUrl :: Text -> AuthRoute oauthUrl name = PluginR name ["forward"] -authOAuth :: YesodAuth m +authOAuth :: forall master. YesodAuth master => OAuth -- ^ 'OAuth' data-type for signing. - -> (Credential -> IO (Creds m)) -- ^ How to extract ident. - -> AuthPlugin m + -> (Credential -> IO (Creds master)) -- ^ How to extract ident. + -> AuthPlugin master authOAuth oauth mkCreds = AuthPlugin name dispatch login where name = T.pack $ oauthServerName oauth url = PluginR name [] lookupTokenSecret = bsToText . fromMaybe "" . lookup "oauth_token_secret" . unCredential + + oauthSessionName :: Text oauthSessionName = "__oauth_token_secret" + dispatch + :: ( MonadSubHandler m + , master ~ HandlerSite m + , Auth ~ SubHandlerSite m + , MonadUnliftIO m + ) + => Text + -> [Text] + -> m TypedContent dispatch "GET" ["forward"] = do - render <- lift getUrlRender + render <- getUrlRender tm <- getRouteToParent let oauth' = oauth { oauthCallback = Just $ encodeUtf8 $ render $ tm url } - master <- lift getYesod - tok <- lift $ getTemporaryCredential oauth' (authHttpManager master) + manager <- authHttpManager + tok <- getTemporaryCredential oauth' manager setSession oauthSessionName $ lookupTokenSecret tok redirect $ authorizeUrl oauth' tok - dispatch "GET" [] = lift $ do + dispatch "GET" [] = do Just tokSec <- lookupSession oauthSessionName deleteSession oauthSessionName reqTok <- @@ -72,8 +87,8 @@ authOAuth oauth mkCreds = AuthPlugin name dispatch login , ("oauth_token", encodeUtf8 oaTok) , ("oauth_token_secret", encodeUtf8 tokSec) ] - master <- getYesod - accTok <- getAccessToken oauth reqTok (authHttpManager master) + manager <- authHttpManager + accTok <- getAccessToken oauth reqTok manager creds <- liftIO $ mkCreds accTok setCredsRedirect creds dispatch _ _ = notFound diff --git a/yesod-auth-oauth/yesod-auth-oauth.cabal b/yesod-auth-oauth/yesod-auth-oauth.cabal index c21ac9e1..7c9c55a5 100644 --- a/yesod-auth-oauth/yesod-auth-oauth.cabal +++ b/yesod-auth-oauth/yesod-auth-oauth.cabal @@ -29,6 +29,7 @@ library , yesod-form >= 1.4 && < 1.5 , transformers >= 0.2.2 && < 0.6 , lifted-base >= 0.2 && < 0.3 + , unliftio-core exposed-modules: Yesod.Auth.OAuth ghc-options: -Wall diff --git a/yesod-auth/Yesod/Auth.hs b/yesod-auth/Yesod/Auth.hs index 8c2a4f4c..c5d137e8 100644 --- a/yesod-auth/Yesod/Auth.hs +++ b/yesod-auth/Yesod/Auth.hs @@ -39,6 +39,7 @@ module Yesod.Auth -- * Exception , AuthException (..) -- * Helper + , MonadAuthHandler , AuthHandler -- * Internal , credsKey @@ -49,8 +50,7 @@ module Yesod.Auth import Control.Monad (when) import Control.Monad.Trans.Maybe -import Control.Monad.Trans.Reader (ReaderT) -import Control.Monad.IO.Unlift (withRunInIO) +import Control.Monad.IO.Unlift (withRunInIO, MonadUnliftIO) import Yesod.Auth.Routes import Data.Aeson hiding (json) @@ -78,7 +78,8 @@ import Control.Monad (void) type AuthRoute = Route Auth -type AuthHandler master a = forall m. (MonadSubHandler m, YesodAuth master, master ~ HandlerSite m, Auth ~ SubHandlerSite m) => m a +type MonadAuthHandler master m = (MonadSubHandler m, YesodAuth master, master ~ HandlerSite m, Auth ~ SubHandlerSite m, MonadUnliftIO m) +type AuthHandler master a = forall m. MonadAuthHandler master m => m a type Method = Text type Piece = Text @@ -192,8 +193,8 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage -- type. This allows backends to reuse persistent connections. If none of -- the backends you're using use HTTP connections, you can safely return -- @error \"authHttpManager\"@ here. - authHttpManager :: master -> IO Manager - authHttpManager _ = getGlobalManager + authHttpManager :: AuthHandler master Manager + authHttpManager = liftIO getGlobalManager -- | Called on a successful login. By default, calls -- @addMessageI "success" NowLoggedIn@. @@ -232,13 +233,14 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage -- -- The HTTP 'Request' is given in case it is useful to change behavior based on inspecting the request. -- This is an experimental API that is not broadly used throughout the yesod-auth code base - runHttpRequest :: (MonadSubHandler m, HandlerSite m ~ master, SubHandlerSite m ~ Auth) - => Request - -> (Response BodyReader -> ReaderT (SubsiteData Auth master) (HandlerFor master) a) - -> m a + runHttpRequest + :: MonadAuthHandler master m + => Request + -> (Response BodyReader -> m a) + -> m a runHttpRequest req inner = do - man <- getYesod >>= liftIO . authHttpManager - lift $ withRunInIO $ \run -> withResponse req man $ run . inner + man <- authHttpManager + withRunInIO $ \run -> withResponse req man $ run . inner {-# MINIMAL loginDest, logoutDest, (authenticate | getAuthId), authPlugins, authHttpManager #-} @@ -268,7 +270,8 @@ defaultMaybeAuthId = runMaybeT $ do cachedAuth :: (YesodAuthPersist master, Typeable (AuthEntity master)) - => AuthId master -> AuthHandler master (Maybe (AuthEntity master)) + => AuthId master + -> AuthHandler master (Maybe (AuthEntity master)) cachedAuth = fmap unCachedMaybeAuth . cached @@ -285,25 +288,25 @@ cachedAuth defaultLoginHandler :: AuthHandler master Html defaultLoginHandler = do tp <- getRouteToParent - liftHandler $ authLayout $ do + authLayout $ do setTitleI Msg.LoginTitle master <- getYesod mapM_ (flip apLogin tp) (authPlugins master) -loginErrorMessageI :: (YesodAuth (HandlerSite m), MonadSubHandler m) - => Route (SubHandlerSite m) - -> AuthMessage - -> m TypedContent +loginErrorMessageI + :: Route Auth + -> AuthMessage + -> AuthHandler master TypedContent loginErrorMessageI dest msg = do toParent <- getRouteToParent - liftHandler $ loginErrorMessageMasterI (toParent dest) msg + loginErrorMessageMasterI (toParent dest) msg -loginErrorMessageMasterI :: (YesodAuth master, RenderMessage master AuthMessage) - => Route master - -> AuthMessage - -> AuthHandler master TypedContent +loginErrorMessageMasterI + :: Route master + -> AuthMessage + -> AuthHandler master TypedContent loginErrorMessageMasterI dest msg = do mr <- getMessageRender loginErrorMessage dest (mr msg) @@ -316,19 +319,22 @@ loginErrorMessage :: YesodAuth master -> AuthHandler master TypedContent loginErrorMessage dest msg = messageJson401 msg (onErrorHtml dest msg) -messageJson401 :: (MonadSubHandler m, HandlerSite m ~ master, SubHandlerSite m ~ Auth) - => Text - -> m Html - -> m TypedContent +messageJson401 + :: MonadAuthHandler master m + => Text + -> m Html + -> m TypedContent messageJson401 = messageJsonStatus unauthorized401 -messageJson500 :: Text -> HandlerFor master Html -> HandlerFor master TypedContent +messageJson500 :: MonadAuthHandler master m => Text -> m Html -> m TypedContent messageJson500 = messageJsonStatus internalServerError500 -messageJsonStatus :: Status - -> Text - -> HandlerFor master Html - -> HandlerFor master TypedContent +messageJsonStatus + :: MonadAuthHandler master m + => Status + -> Text + -> m Html + -> m TypedContent messageJsonStatus status msg html = selectRep $ do provideRep html provideRep $ do @@ -340,9 +346,9 @@ provideJsonMessage :: Monad m => Text -> Writer.Writer (Endo [ProvidedRep m]) () provideJsonMessage msg = provideRep $ return $ object ["message" .= msg] -setCredsRedirect :: YesodAuth master - => Creds master -- ^ new credentials - -> HandlerFor master TypedContent +setCredsRedirect + :: Creds master -- ^ new credentials + -> AuthHandler master TypedContent setCredsRedirect creds = do y <- getYesod auth <- authenticate creds @@ -381,10 +387,9 @@ setCredsRedirect creds = do return $ renderAuthMessage master langs msg -- | Sets user credentials for the session after checking them with authentication backends. -setCreds :: YesodAuth master - => Bool -- ^ if HTTP redirects should be done +setCreds :: Bool -- ^ if HTTP redirects should be done -> Creds master -- ^ new credentials - -> HandlerFor master () + -> AuthHandler master () setCreds doRedirects creds = if doRedirects then void $ setCredsRedirect creds @@ -394,10 +399,11 @@ setCreds doRedirects creds = _ -> return () -- | same as defaultLayoutJson, but uses authLayout -authLayoutJson :: (YesodAuth site, ToJSON j) - => WidgetFor site () -- ^ HTML - -> HandlerFor site j -- ^ JSON - -> HandlerFor site TypedContent +authLayoutJson + :: (ToJSON j, MonadAuthHandler master m) + => WidgetFor master () -- ^ HTML + -> m j -- ^ JSON + -> m TypedContent authLayoutJson w json = selectRep $ do provideRep $ authLayout w provideRep $ fmap toJSON json @@ -405,18 +411,17 @@ authLayoutJson w json = selectRep $ do -- | Clears current user credentials for the session. -- -- Since 1.1.7 -clearCreds :: (MonadHandler m, YesodAuth (HandlerSite m)) - => Bool -- ^ if HTTP redirect to 'logoutDest' should be done - -> m () +clearCreds :: Bool -- ^ if HTTP redirect to 'logoutDest' should be done + -> AuthHandler master () clearCreds doRedirects = do y <- getYesod - liftHandler onLogout + onLogout deleteSession credsKey when doRedirects $ do redirectUltDest $ logoutDest y getCheckR :: AuthHandler master TypedContent -getCheckR = liftHandler $ do +getCheckR = do creds <- maybeAuthId authLayoutJson (do setTitle "Authentication Status" @@ -437,7 +442,7 @@ $nothing ] setUltDestReferer' :: AuthHandler master () -setUltDestReferer' = liftHandler $ do +setUltDestReferer' = do master <- getYesod when (redirectToReferer master) setUltDestReferer @@ -471,17 +476,16 @@ maybeAuth :: ( YesodAuthPersist master , Key val ~ AuthId master , PersistEntity val , Typeable val - ) => HandlerFor master (Maybe (Entity val)) -maybeAuth = runMaybeT $ do - (aid, ae) <- MaybeT maybeAuthPair - return $ Entity aid ae + ) => AuthHandler master (Maybe (Entity val)) +maybeAuth = fmap (fmap (uncurry Entity)) maybeAuthPair -- | Similar to 'maybeAuth', but doesn’t assume that you are using a -- Persistent database. -- -- Since 1.4.0 -maybeAuthPair :: (YesodAuthPersist master, Typeable (AuthEntity master)) - => HandlerFor master (Maybe (AuthId master, AuthEntity master)) +maybeAuthPair + :: (YesodAuthPersist master, Typeable (AuthEntity master)) + => AuthHandler master (Maybe (AuthId master, AuthEntity master)) maybeAuthPair = runMaybeT $ do aid <- MaybeT maybeAuthId ae <- MaybeT $ cachedAuth aid @@ -512,9 +516,8 @@ class (YesodAuth master, YesodPersist master) => YesodAuthPersist master where type AuthEntity master :: * type AuthEntity master = KeyEntity (AuthId master) - getAuthEntity :: AuthId master -> HandlerFor master (Maybe (AuthEntity master)) + getAuthEntity :: AuthId master -> AuthHandler master (Maybe (AuthEntity master)) -#if MIN_VERSION_persistent(2,5,0) default getAuthEntity :: ( YesodPersistBackend master ~ backend , PersistRecordBackend (AuthEntity master) backend @@ -522,16 +525,6 @@ class (YesodAuth master, YesodPersist master) => YesodAuthPersist master where , PersistStore backend ) => AuthId master -> HandlerFor master (Maybe (AuthEntity master)) -#else - default getAuthEntity - :: ( YesodPersistBackend master - ~ PersistEntityBackend (AuthEntity master) - , Key (AuthEntity master) ~ AuthId master - , PersistStore (YesodPersistBackend master) - , PersistEntity (AuthEntity master) - ) - => AuthId master -> HandlerFor master (Maybe (AuthEntity master)) -#endif getAuthEntity = runDB . get @@ -542,7 +535,7 @@ type instance KeyEntity (Key x) = x -- authenticated or responds with error 401 if this is an API client (expecting JSON). -- -- Since 1.1.0 -requireAuthId :: YesodAuth master => HandlerFor master (AuthId master) +requireAuthId :: AuthHandler master (AuthId master) requireAuthId = maybeAuthId >>= maybe handleAuthLack return -- | Similar to 'maybeAuth', but redirects to a login page if user is not @@ -554,23 +547,26 @@ requireAuth :: ( YesodAuthPersist master , Key val ~ AuthId master , PersistEntity val , Typeable val - ) => HandlerFor master (Entity val) + ) => AuthHandler master (Entity val) requireAuth = maybeAuth >>= maybe handleAuthLack return -- | Similar to 'requireAuth', but not tied to Persistent's 'Entity' type. -- Instead, the 'AuthId' and 'AuthEntity' are returned in a tuple. -- -- Since 1.4.0 -requireAuthPair :: (YesodAuthPersist master, Typeable (AuthEntity master)) - => HandlerFor master (AuthId master, AuthEntity master) +requireAuthPair + :: ( YesodAuthPersist master + , Typeable (AuthEntity master) + ) + => AuthHandler master (AuthId master, AuthEntity master) requireAuthPair = maybeAuthPair >>= maybe handleAuthLack return -handleAuthLack :: YesodAuth master => HandlerFor master a +handleAuthLack :: AuthHandler master a handleAuthLack = do aj <- acceptsJson if aj then notAuthenticated else redirectLogin -redirectLogin :: YesodAuth master => HandlerFor master a +redirectLogin :: AuthHandler master a redirectLogin = do y <- getYesod when (redirectToCurrent y) setUltDestCurrent @@ -586,7 +582,7 @@ data AuthException = InvalidFacebookResponse instance Exception AuthException -- FIXME HandlerSite m ~ SubHandlerSite m should be unnecessary -instance (YesodAuth (HandlerSite m), HandlerSite m ~ SubHandlerSite m, MonadSubHandler m) => YesodSubDispatch Auth m where +instance (YesodAuth (HandlerSite m), HandlerSite m ~ SubHandlerSite m, MonadSubHandler m, MonadUnliftIO m) => YesodSubDispatch Auth m where yesodSubDispatch = $(mkYesodSubDispatch resourcesAuth) asHtml :: Html -> Html diff --git a/yesod-auth/Yesod/Auth/BrowserId.hs b/yesod-auth/Yesod/Auth/BrowserId.hs index 802cba03..87dce2dc 100644 --- a/yesod-auth/Yesod/Auth/BrowserId.hs +++ b/yesod-auth/Yesod/Auth/BrowserId.hs @@ -70,7 +70,6 @@ authBrowserId bis@BrowserIdSettings {..} = AuthPlugin , apDispatch = \m ps -> case (m, ps) of ("GET", [assertion]) -> do - master <- getYesod audience <- case bisAudience of Just a -> return a @@ -78,13 +77,14 @@ authBrowserId bis@BrowserIdSettings {..} = AuthPlugin r <- getUrlRender tm <- getRouteToParent return $ T.takeWhile (/= '/') $ stripScheme $ r $ tm LoginR - memail <- liftHandler $ checkAssertion audience assertion (authHttpManager master) + manager <- authHttpManager + memail <- liftResourceT $ checkAssertion audience assertion manager case memail of Nothing -> do $logErrorS "yesod-auth" "BrowserID assertion failure" tm <- getRouteToParent - liftHandler $ loginErrorMessage (tm LoginR) "BrowserID login error." - Just email -> liftHandler $ setCredsRedirect Creds + loginErrorMessage (tm LoginR) "BrowserID login error." + Just email -> setCredsRedirect Creds { credsPlugin = pid , credsIdent = email , credsExtra = [] @@ -117,7 +117,7 @@ $newline never createOnClickOverride :: BrowserIdSettings -> (Route Auth -> Route master) -> Maybe (Route master) - -> WidgetT master IO Text + -> WidgetFor master Text createOnClickOverride BrowserIdSettings {..} toMaster mOnRegistration = do unless bisLazyLoad $ addScriptRemote browserIdJs onclick <- newIdent @@ -166,5 +166,5 @@ createOnClickOverride BrowserIdSettings {..} toMaster mOnRegistration = do -- name. createOnClick :: BrowserIdSettings -> (Route Auth -> Route master) - -> WidgetT master IO Text + -> WidgetFor master Text createOnClick bidSettings toMaster = createOnClickOverride bidSettings toMaster Nothing diff --git a/yesod-auth/Yesod/Auth/Dummy.hs b/yesod-auth/Yesod/Auth/Dummy.hs index 4899f99d..721d6311 100644 --- a/yesod-auth/Yesod/Auth/Dummy.hs +++ b/yesod-auth/Yesod/Auth/Dummy.hs @@ -1,6 +1,7 @@ {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE TypeFamilies #-} -- | Provides a dummy authentication module that simply lets a user specify -- his/her identifier. This is not intended for real world use, just for -- testing. @@ -16,7 +17,7 @@ authDummy :: YesodAuth m => AuthPlugin m authDummy = AuthPlugin "dummy" dispatch login where - dispatch "POST" [] = liftHandler $ do + dispatch "POST" [] = do ident <- runInputPost $ ireq textField "ident" setCredsRedirect $ Creds "dummy" ident [] dispatch _ _ = notFound diff --git a/yesod-auth/Yesod/Auth/Email.hs b/yesod-auth/Yesod/Auth/Email.hs index 60b65fb7..38afc51e 100644 --- a/yesod-auth/Yesod/Auth/Email.hs +++ b/yesod-auth/Yesod/Auth/Email.hs @@ -186,29 +186,29 @@ class ( YesodAuth site -- has not yet been verified. -- -- @since 1.1.0 - addUnverified :: Email -> VerKey -> HandlerT site IO (AuthEmailId site) + addUnverified :: Email -> VerKey -> AuthHandler site (AuthEmailId site) -- | Send an email to the given address to verify ownership. -- -- @since 1.1.0 - sendVerifyEmail :: Email -> VerKey -> VerUrl -> HandlerT site IO () + sendVerifyEmail :: Email -> VerKey -> VerUrl -> AuthHandler site () -- | Get the verification key for the given email ID. -- -- @since 1.1.0 - getVerifyKey :: AuthEmailId site -> HandlerT site IO (Maybe VerKey) + getVerifyKey :: AuthEmailId site -> AuthHandler site (Maybe VerKey) -- | Set the verification key for the given email ID. -- -- @since 1.1.0 - setVerifyKey :: AuthEmailId site -> VerKey -> HandlerT site IO () + setVerifyKey :: AuthEmailId site -> VerKey -> AuthHandler site () -- | Hash and salt a password -- -- Default: 'saltPass'. -- -- @since 1.4.20 - hashAndSaltPassword :: Text -> HandlerT site IO SaltedPass + hashAndSaltPassword :: Text -> AuthHandler site SaltedPass hashAndSaltPassword = liftIO . saltPass -- | Verify a password matches the stored password for the given account. @@ -216,7 +216,7 @@ class ( YesodAuth site -- Default: Fetch a password with 'getPassword' and match using 'Yesod.Auth.Util.PasswordStore.verifyPassword'. -- -- @since 1.4.20 - verifyPassword :: Text -> SaltedPass -> HandlerT site IO Bool + verifyPassword :: Text -> SaltedPass -> AuthHandler site Bool verifyPassword plain salted = return $ isValidPass plain salted -- | Verify the email address on the given account. @@ -228,28 +228,28 @@ class ( YesodAuth site -- See . -- -- @since 1.1.0 - verifyAccount :: AuthEmailId site -> HandlerT site IO (Maybe (AuthId site)) + verifyAccount :: AuthEmailId site -> AuthHandler site (Maybe (AuthId site)) -- | Get the salted password for the given account. -- -- @since 1.1.0 - getPassword :: AuthId site -> HandlerT site IO (Maybe SaltedPass) + getPassword :: AuthId site -> AuthHandler site (Maybe SaltedPass) -- | Set the salted password for the given account. -- -- @since 1.1.0 - setPassword :: AuthId site -> SaltedPass -> HandlerT site IO () + setPassword :: AuthId site -> SaltedPass -> AuthHandler site () -- | Get the credentials for the given @Identifier@, which may be either an -- email address or some other identification (e.g., username). -- -- @since 1.2.0 - getEmailCreds :: Identifier -> HandlerT site IO (Maybe (EmailCreds site)) + getEmailCreds :: Identifier -> AuthHandler site (Maybe (EmailCreds site)) -- | Get the email address for the given email ID. -- -- @since 1.1.0 - getEmail :: AuthEmailId site -> HandlerT site IO (Maybe Email) + getEmail :: AuthEmailId site -> AuthHandler site (Maybe Email) -- | Generate a random alphanumeric string. -- @@ -268,7 +268,7 @@ class ( YesodAuth site -- Default: if the user logged in via an email link do not require a password. -- -- @since 1.2.1 - needOldPassword :: AuthId site -> HandlerT site IO Bool + needOldPassword :: AuthId site -> AuthHandler site Bool needOldPassword aid' = do mkey <- lookupSession loginLinkKey case mkey >>= readMay . TS.unpack of @@ -280,7 +280,7 @@ class ( YesodAuth site -- | Check that the given plain-text password meets minimum security standards. -- -- Default: password is at least three characters. - checkPasswordSecurity :: AuthId site -> Text -> HandlerT site IO (Either Text ()) + checkPasswordSecurity :: AuthId site -> Text -> AuthHandler site (Either Text ()) checkPasswordSecurity _ x | TS.length x >= 3 = return $ Right () | otherwise = return $ Left "Password must be at least three characters" @@ -288,7 +288,7 @@ class ( YesodAuth site -- | Response after sending a confirmation email. -- -- @since 1.2.2 - confirmationEmailSentResponse :: Text -> HandlerT site IO TypedContent + confirmationEmailSentResponse :: Text -> AuthHandler site TypedContent confirmationEmailSentResponse identifier = do mr <- getMessageRender selectRep $ do @@ -314,7 +314,7 @@ class ( YesodAuth site -- Default: 'defaultEmailLoginHandler'. -- -- @since 1.4.17 - emailLoginHandler :: (Route Auth -> Route site) -> WidgetT site IO () + emailLoginHandler :: (Route Auth -> Route site) -> WidgetFor site () emailLoginHandler = defaultEmailLoginHandler @@ -377,9 +377,12 @@ getRegisterR = registerHandler -- | Default implementation of 'emailLoginHandler'. -- -- @since 1.4.17 -defaultEmailLoginHandler :: YesodAuthEmail master => (Route Auth -> Route master) -> WidgetT master IO () +defaultEmailLoginHandler + :: YesodAuthEmail master + => (Route Auth -> Route master) + -> WidgetFor master () defaultEmailLoginHandler toParent = do - (widget, enctype) <- liftWidgetT $ generateFormPost loginForm + (widget, enctype) <- generateFormPost loginForm [whamlet|

@@ -439,9 +442,9 @@ defaultEmailLoginHandler toParent = do -- @since 1.2.6 defaultRegisterHandler :: YesodAuthEmail master => AuthHandler master Html defaultRegisterHandler = do - (widget, enctype) <- lift $ generateFormPost registrationForm + (widget, enctype) <- generateFormPost registrationForm toParentRoute <- getRouteToParent - lift $ authLayout $ do + authLayout $ do setTitleI Msg.RegisterLong [whamlet|

_{Msg.EnterEmail} @@ -482,12 +485,12 @@ registerHelper :: YesodAuthEmail master -> Route Auth -> AuthHandler master TypedContent registerHelper allowUsername dest = do - y <- lift getYesod + y <- getYesod checkCsrfHeaderOrParam defaultCsrfHeaderName defaultCsrfParamName pidentifier <- lookupPostParam "email" midentifier <- case pidentifier of Nothing -> do - (jidentifier :: Result Value) <- lift parseCheckJsonBody + (jidentifier :: Result Value) <- parseCheckJsonBody case jidentifier of Error _ -> return Nothing Success val -> return $ parseMaybe parseEmail val @@ -502,28 +505,29 @@ registerHelper allowUsername dest = do case eidentifier of Left route -> loginErrorMessageI dest route Right identifier -> do - mecreds <- lift $ getEmailCreds identifier + mecreds <- getEmailCreds identifier registerCreds <- case mecreds of Just (EmailCreds lid _ _ (Just key) email) -> return $ Just (lid, key, email) Just (EmailCreds lid _ _ Nothing email) -> do key <- liftIO $ randomKey y - lift $ setVerifyKey lid key + setVerifyKey lid key return $ Just (lid, key, email) Nothing | allowUsername -> return Nothing | otherwise -> do key <- liftIO $ randomKey y - lid <- lift $ addUnverified identifier key + lid <- addUnverified identifier key return $ Just (lid, key, identifier) case registerCreds of Nothing -> loginErrorMessageI dest (Msg.IdentifierNotFound identifier) Just (lid, verKey, email) -> do render <- getUrlRender - let verUrl = render $ verifyR (toPathPiece lid) verKey - lift $ sendVerifyEmail email verKey verUrl - lift $ confirmationEmailSentResponse identifier + tp <- getRouteToParent + let verUrl = render $ tp $ verifyR (toPathPiece lid) verKey + sendVerifyEmail email verKey verUrl + confirmationEmailSentResponse identifier postRegisterR :: YesodAuthEmail master => AuthHandler master TypedContent postRegisterR = registerHelper False registerR @@ -536,9 +540,9 @@ getForgotPasswordR = forgotPasswordHandler -- @since 1.2.6 defaultForgotPasswordHandler :: YesodAuthEmail master => AuthHandler master Html defaultForgotPasswordHandler = do - (widget, enctype) <- lift $ generateFormPost forgotPasswordForm + (widget, enctype) <- generateFormPost forgotPasswordForm toParent <- getRouteToParent - lift $ authLayout $ do + authLayout $ do setTitleI Msg.PasswordResetTitle [whamlet|

_{Msg.PasswordResetPrompt} @@ -577,27 +581,28 @@ getVerifyR :: YesodAuthEmail site -> Text -> AuthHandler site TypedContent getVerifyR lid key = do - realKey <- lift $ getVerifyKey lid - memail <- lift $ getEmail lid - mr <- lift getMessageRender + realKey <- getVerifyKey lid + memail <- getEmail lid + mr <- getMessageRender case (realKey == Just key, memail) of (True, Just email) -> do - muid <- lift $ verifyAccount lid + muid <- verifyAccount lid case muid of Nothing -> invalidKey mr Just uid -> do - lift $ setCreds False $ Creds "email-verify" email [("verifiedEmail", email)] -- FIXME uid? - lift $ setLoginLinkKey uid + setCreds False $ Creds "email-verify" email [("verifiedEmail", email)] -- FIXME uid? + setLoginLinkKey uid let msgAv = Msg.AddressVerified selectRep $ do provideRep $ do - lift $ addMessageI "success" msgAv - fmap asHtml $ redirect setpassR + addMessageI "success" msgAv + tp <- getRouteToParent + fmap asHtml $ redirect $ tp setpassR provideJsonMessage $ mr msgAv _ -> invalidKey mr where msgIk = Msg.InvalidKey - invalidKey mr = messageJson401 (mr msgIk) $ lift $ authLayout $ do + invalidKey mr = messageJson401 (mr msgIk) $ authLayout $ do setTitleI msgIk [whamlet| $newline never @@ -614,14 +619,14 @@ parseCreds = withObject "creds" (\obj -> do postLoginR :: YesodAuthEmail master => AuthHandler master TypedContent postLoginR = do - result <- lift $ runInputPostResult $ (,) + result <- runInputPostResult $ (,) <$> ireq textField "email" <*> ireq textField "password" midentifier <- case result of FormSuccess (iden, pass) -> return $ Just (iden, pass) _ -> do - (creds :: Result Value) <- lift parseCheckJsonBody + (creds :: Result Value) <- parseCheckJsonBody case creds of Error _ -> return Nothing Success val -> return $ parseMaybe parseCreds val @@ -629,18 +634,18 @@ postLoginR = do case midentifier of Nothing -> loginErrorMessageI LoginR Msg.NoIdentifierProvided Just (identifier, pass) -> do - mecreds <- lift $ getEmailCreds identifier + mecreds <- getEmailCreds identifier maid <- case ( mecreds >>= emailCredsAuthId , emailCredsEmail <$> mecreds , emailCredsStatus <$> mecreds ) of (Just aid, Just email', Just True) -> do - mrealpass <- lift $ getPassword aid + mrealpass <- getPassword aid case mrealpass of Nothing -> return Nothing Just realpass -> do - passValid <- lift $ verifyPassword pass realpass + passValid <- verifyPassword pass realpass return $ if passValid then Just email' else Nothing @@ -648,7 +653,7 @@ postLoginR = do let isEmail = Text.Email.Validate.isValid $ encodeUtf8 identifier case maid of Just email' -> - lift $ setCredsRedirect $ Creds + setCredsRedirect $ Creds (if isEmail then "email" else "username") email' [("verifiedEmail", email')] @@ -660,11 +665,11 @@ postLoginR = do getPasswordR :: YesodAuthEmail master => AuthHandler master TypedContent getPasswordR = do - maid <- lift maybeAuthId + maid <- maybeAuthId case maid of Nothing -> loginErrorMessageI LoginR Msg.BadSetPass Just _ -> do - needOld <- maybe (return True) (lift . needOldPassword) maid + needOld <- maybe (return True) needOldPassword maid setPasswordHandler needOld -- | Default implementation of 'setPasswordHandler'. @@ -672,12 +677,12 @@ getPasswordR = do -- @since 1.2.6 defaultSetPasswordHandler :: YesodAuthEmail master => Bool -> AuthHandler master TypedContent defaultSetPasswordHandler needOld = do - messageRender <- lift getMessageRender + messageRender <- getMessageRender toParent <- getRouteToParent selectRep $ do provideJsonMessage $ messageRender Msg.SetPass - provideRep $ lift $ authLayout $ do - (widget, enctype) <- liftWidgetT $ generateFormPost setPasswordForm + provideRep $ authLayout $ do + (widget, enctype) <- generateFormPost setPasswordForm setTitleI Msg.SetPassTitle [whamlet|

_{Msg.SetPass} @@ -751,8 +756,8 @@ parsePassword = withObject "password" (\obj -> do postPasswordR :: YesodAuthEmail master => AuthHandler master TypedContent postPasswordR = do - maid <- lift maybeAuthId - (creds :: Result Value) <- lift parseCheckJsonBody + maid <- maybeAuthId + (creds :: Result Value) <- parseCheckJsonBody let jcreds = case creds of Error _ -> Nothing Success val -> parseMaybe parsePassword val @@ -761,26 +766,26 @@ postPasswordR = do Nothing -> loginErrorMessageI LoginR Msg.BadSetPass Just aid -> do tm <- getRouteToParent - needOld <- lift $ needOldPassword aid + needOld <- needOldPassword aid if not needOld then confirmPassword aid tm jcreds else do - res <- lift $ runInputPostResult $ ireq textField "current" + res <- runInputPostResult $ ireq textField "current" let fcurrent = case res of FormSuccess currentPass -> Just currentPass _ -> Nothing let current = if doJsonParsing then getThird jcreds else fcurrent - mrealpass <- lift $ getPassword aid + mrealpass <- getPassword aid case (mrealpass, current) of (Nothing, _) -> - liftHandler $ loginErrorMessage (tm setpassR) "You do not currently have a password set on your account" + loginErrorMessage (tm setpassR) "You do not currently have a password set on your account" (_, Nothing) -> loginErrorMessageI LoginR Msg.BadSetPass (Just realpass, Just current') -> do - passValid <- liftHandler $ verifyPassword current' realpass + passValid <- verifyPassword current' realpass if passValid then confirmPassword aid tm jcreds - else liftHandler $ loginErrorMessage (tm setpassR) "Invalid current password, please try again" + else loginErrorMessage (tm setpassR) "Invalid current password, please try again" where msgOk = Msg.PassUpdated @@ -789,7 +794,7 @@ postPasswordR = do getNewConfirm (Just (a,b,_)) = Just (a,b) getNewConfirm _ = Nothing confirmPassword aid tm jcreds = do - res <- lift $ runInputPostResult $ (,) + res <- runInputPostResult $ (,) <$> ireq textField "new" <*> ireq textField "confirm" let creds = if (isJust jcreds) @@ -803,21 +808,21 @@ postPasswordR = do if new /= confirm then loginErrorMessageI setpassR Msg.PassMismatch else do - isSecure <- lift $ checkPasswordSecurity aid new + isSecure <- checkPasswordSecurity aid new case isSecure of - Left e -> lift $ loginErrorMessage (tm setpassR) e + Left e -> loginErrorMessage (tm setpassR) e Right () -> do - salted <- lift $ hashAndSaltPassword new - y <- lift $ do + salted <- hashAndSaltPassword new + y <- do setPassword aid salted deleteSession loginLinkKey addMessageI "success" msgOk getYesod - mr <- lift getMessageRender + mr <- getMessageRender selectRep $ do provideRep $ - fmap asHtml $ lift $ redirect $ afterPasswordRoute y + fmap asHtml $ redirect $ afterPasswordRoute y provideJsonMessage (mr msgOk) saltLength :: Int diff --git a/yesod-auth/Yesod/Auth/GoogleEmail.hs b/yesod-auth/Yesod/Auth/GoogleEmail.hs deleted file mode 100644 index eb0b6cee..00000000 --- a/yesod-auth/Yesod/Auth/GoogleEmail.hs +++ /dev/null @@ -1,89 +0,0 @@ -{-# LANGUAGE QuasiQuotes #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE CPP #-} -{-# LANGUAGE RankNTypes #-} --- | Use an email address as an identifier via Google's OpenID login system. --- --- This backend will not use the OpenID identifier at all. It only uses OpenID --- as a login system. By using this plugin, you are trusting Google to validate --- an email address, and requiring users to have a Google account. On the plus --- side, you get to use email addresses as the identifier, many users have --- existing Google accounts, the login system has been long tested (as opposed --- to BrowserID), and it requires no credential managing or setup (as opposed --- to Email). -module Yesod.Auth.GoogleEmail - {-# DEPRECATED "Google no longer provides OpenID support, please use Yesod.Auth.GoogleEmail2" #-} - ( authGoogleEmail - , forwardUrl - ) where - -import Yesod.Auth -import qualified Web.Authenticate.OpenId as OpenId - -import Yesod.Core -import Data.Text (Text) -import qualified Yesod.Auth.Message as Msg -import qualified Data.Text as T -import Control.Exception.Lifted (try, SomeException) - -pid :: Text -pid = "googleemail" - -forwardUrl :: AuthRoute -forwardUrl = PluginR pid ["forward"] - -googleIdent :: Text -googleIdent = "https://www.google.com/accounts/o8/id" - -authGoogleEmail :: YesodAuth m => AuthPlugin m -authGoogleEmail = - AuthPlugin pid dispatch login - where - complete = PluginR pid ["complete"] - login tm = - [whamlet|_{Msg.LoginGoogle}|] - dispatch "GET" ["forward"] = do - render <- getUrlRender - let complete' = render complete - master <- lift getYesod - eres <- lift $ try $ OpenId.getForwardUrl googleIdent complete' Nothing - [ ("openid.ax.type.email", "http://schema.openid.net/contact/email") - , ("openid.ns.ax", "http://openid.net/srv/ax/1.0") - , ("openid.ns.ax.required", "email") - , ("openid.ax.mode", "fetch_request") - , ("openid.ax.required", "email") - , ("openid.ui.icon", "true") - ] (authHttpManager master) - either - (\err -> do - tm <- getRouteToParent - lift $ loginErrorMessage (tm LoginR) $ T.pack $ show (err :: SomeException)) - redirect - eres - dispatch "GET" ["complete", ""] = dispatch "GET" ["complete"] -- compatibility issues - dispatch "GET" ["complete"] = do - rr <- getRequest - completeHelper $ reqGetParams rr - dispatch "POST" ["complete", ""] = dispatch "POST" ["complete"] -- compatibility issues - dispatch "POST" ["complete"] = do - (posts, _) <- runRequestBody - completeHelper posts - dispatch _ _ = notFound - -completeHelper :: [(Text, Text)] -> AuthHandler master TypedContent -completeHelper gets' = do - master <- lift getYesod - eres <- lift $ try $ OpenId.authenticateClaimed gets' (authHttpManager master) - tm <- getRouteToParent - either (onFailure tm) (onSuccess tm) eres - where - onFailure tm err = - lift $ loginErrorMessage (tm LoginR) $ T.pack $ show (err :: SomeException) - onSuccess tm oir = do - let OpenId.Identifier ident = OpenId.oirOpLocal oir - memail <- lookupGetParam "openid.ext1.value.email" - case (memail, "https://www.google.com/accounts/o8/id" `T.isPrefixOf` ident) of - (Just email, True) -> lift $ setCredsRedirect $ Creds pid email [] - (_, False) -> lift $ loginErrorMessage (tm LoginR) "Only Google login is supported" - (Nothing, _) -> lift $ loginErrorMessage (tm LoginR) "No email address provided" diff --git a/yesod-auth/Yesod/Auth/GoogleEmail2.hs b/yesod-auth/Yesod/Auth/GoogleEmail2.hs index 577e86a7..01a00e3c 100644 --- a/yesod-auth/Yesod/Auth/GoogleEmail2.hs +++ b/yesod-auth/Yesod/Auth/GoogleEmail2.hs @@ -2,6 +2,8 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE TypeFamilies #-} -- | Use an email address as an identifier via Google's login system. -- -- Note that this is a replacement for "Yesod.Auth.GoogleEmail", which depends @@ -54,12 +56,12 @@ import Yesod.Auth (Auth, AuthPlugin (AuthPlugin), AuthRoute, Creds (Creds), Route (PluginR), YesodAuth, runHttpRequest, setCredsRedirect, - logoutDest) + logoutDest, AuthHandler) import qualified Yesod.Auth.Message as Msg -import Yesod.Core (HandlerSite, HandlerT, MonadHandler, +import Yesod.Core (HandlerSite, MonadHandler, TypedContent, getRouteToParent, getUrlRender, invalidArgs, - lift, liftIO, lookupGetParam, + liftIO, lookupGetParam, lookupSession, notFound, redirect, setSession, whamlet, (.:), addMessage, getYesod, @@ -187,10 +189,10 @@ authPlugin storeToken clientID clientSecret = dispatch :: YesodAuth site => Text -> [Text] - -> HandlerT Auth (HandlerT site IO) TypedContent + -> AuthHandler site TypedContent dispatch "GET" ["forward"] = do tm <- getRouteToParent - lift (getDest tm) >>= redirect + getDest tm >>= redirect dispatch "GET" ["complete"] = do mstate <- lookupGetParam "state" @@ -207,30 +209,27 @@ authPlugin storeToken clientID clientSecret = case merr of Nothing -> invalidArgs ["Missing code paramter"] Just err -> do - master <- lift getYesod + master <- getYesod let msg = case err of "access_denied" -> "Access denied" _ -> "Unknown error occurred: " `T.append` err addMessage "error" $ toHtml msg - lift $ redirect $ logoutDest master + redirect $ logoutDest master Just c -> return c render <- getUrlRender + tm <- getRouteToParent req' <- liftIO $ -#if MIN_VERSION_http_client(0,4,30) HTTP.parseUrlThrow -#else - HTTP.parseUrl -#endif "https://accounts.google.com/o/oauth2/token" -- FIXME don't hardcode, use: https://accounts.google.com/.well-known/openid-configuration let req = urlEncodedBody [ ("code", encodeUtf8 code) , ("client_id", encodeUtf8 clientID) , ("client_secret", encodeUtf8 clientSecret) - , ("redirect_uri", encodeUtf8 $ render complete) + , ("redirect_uri", encodeUtf8 $ render $ tm complete) , ("grant_type", "authorization_code") ] req' @@ -257,15 +256,12 @@ authPlugin storeToken clientID clientSecret = [e] -> return e [] -> error "No account email" x -> error $ "Too many account emails: " ++ show x - lift $ setCredsRedirect $ Creds pid email $ allPersonInfo personValue + setCredsRedirect $ Creds pid email $ allPersonInfo personValue dispatch _ _ = notFound -makeHttpRequest - :: (YesodAuth site) - => Request - -> HandlerT Auth (HandlerT site IO) A.Value -makeHttpRequest req = lift $ +makeHttpRequest :: Request -> AuthHandler site A.Value +makeHttpRequest req = runHttpRequest req $ \res -> bodyReaderSource (responseBody res) $$ sinkParser json' -- | Allows to fetch information about a user from Google's API. @@ -273,7 +269,7 @@ makeHttpRequest req = lift $ -- Will throw 'HttpException' in case of network problems or error response code. -- -- @since 1.4.3 -getPerson :: Manager -> Token -> HandlerT site IO (Maybe Person) +getPerson :: Manager -> Token -> AuthHandler site (Maybe Person) getPerson manager token = parseMaybe parseJSON <$> (do req <- personValueRequest token res <- http req manager @@ -282,13 +278,8 @@ getPerson manager token = parseMaybe parseJSON <$> (do personValueRequest :: MonadIO m => Token -> m Request personValueRequest token = do - req2' <- liftIO $ -#if MIN_VERSION_http_client(0,4,30) - HTTP.parseUrlThrow -#else - HTTP.parseUrl -#endif - "https://www.googleapis.com/plus/v1/people/me" + req2' <- liftIO + $ HTTP.parseUrlThrow "https://www.googleapis.com/plus/v1/people/me" return req2' { requestHeaders = [ ("Authorization", encodeUtf8 $ "Bearer " `mappend` accessToken token) diff --git a/yesod-auth/Yesod/Auth/Hardcoded.hs b/yesod-auth/Yesod/Auth/Hardcoded.hs index 9421feb4..cb5ec199 100644 --- a/yesod-auth/Yesod/Auth/Hardcoded.hs +++ b/yesod-auth/Yesod/Auth/Hardcoded.hs @@ -131,9 +131,10 @@ module Yesod.Auth.Hardcoded , loginR ) where -import Yesod.Auth (Auth, AuthPlugin (..), AuthRoute, +import Yesod.Auth (AuthPlugin (..), AuthRoute, Creds (..), Route (..), YesodAuth, - loginErrorMessageI, setCredsRedirect) + loginErrorMessageI, setCredsRedirect, + AuthHandler) import qualified Yesod.Auth.Message as Msg import Yesod.Core import Yesod.Form (ireq, runInputPost, textField) @@ -148,10 +149,10 @@ loginR = PluginR "hardcoded" ["login"] class (YesodAuth site) => YesodAuthHardcoded site where -- | Check whether given user name exists among hardcoded names. - doesUserNameExist :: Text -> HandlerT site IO Bool + doesUserNameExist :: Text -> AuthHandler site Bool -- | Validate given user name with given password. - validatePassword :: Text -> Text -> HandlerT site IO Bool + validatePassword :: Text -> Text -> AuthHandler site Bool authHardcoded :: YesodAuthHardcoded m => AuthPlugin m @@ -182,16 +183,16 @@ authHardcoded = |] -postLoginR :: (YesodAuthHardcoded master) - => HandlerT Auth (HandlerT master IO) TypedContent +postLoginR :: YesodAuthHardcoded site + => AuthHandler site TypedContent postLoginR = - do (username, password) <- lift (runInputPost + do (username, password) <- runInputPost ((,) Control.Applicative.<$> ireq textField "username" - Control.Applicative.<*> ireq textField "password")) - isValid <- lift (validatePassword username password) + Control.Applicative.<*> ireq textField "password") + isValid <- validatePassword username password if isValid - then lift (setCredsRedirect (Creds "hardcoded" username [])) - else do isExists <- lift (doesUserNameExist username) + then setCredsRedirect (Creds "hardcoded" username []) + else do isExists <- doesUserNameExist username loginErrorMessageI LoginR (if isExists then Msg.InvalidUsernamePass diff --git a/yesod-auth/Yesod/Auth/OpenId.hs b/yesod-auth/Yesod/Auth/OpenId.hs index f32ff747..f65bed7c 100644 --- a/yesod-auth/Yesod/Auth/OpenId.hs +++ b/yesod-auth/Yesod/Auth/OpenId.hs @@ -3,6 +3,7 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE CPP #-} {-# LANGUAGE RankNTypes #-} +{-# LANGUAGE GADTs #-} module Yesod.Auth.OpenId ( authOpenId , forwardUrl @@ -36,7 +37,10 @@ authOpenId idType extensionFields = AuthPlugin "openid" dispatch login where complete = PluginR "openid" ["complete"] + + name :: Text name = "openid_identifier" + login tm = do ident <- newIdent -- FIXME this is a hack to get GHC 7.6's type checker to allow the @@ -57,18 +61,20 @@ $newline never |] + + dispatch :: Text -> [Text] -> AuthHandler master TypedContent dispatch "GET" ["forward"] = do - roid <- lift $ runInputGet $ iopt textField name + roid <- runInputGet $ iopt textField name case roid of Just oid -> do + tm <- getRouteToParent render <- getUrlRender - let complete' = render complete - master <- lift getYesod - eres <- lift $ try $ OpenId.getForwardUrl oid complete' Nothing extensionFields (authHttpManager master) + let complete' = render $ tm complete + manager <- authHttpManager + eres <- liftResourceT $ try $ OpenId.getForwardUrl oid complete' Nothing extensionFields manager case eres of Left err -> do - tm <- getRouteToParent - lift $ loginErrorMessage (tm LoginR) $ T.pack $ + loginErrorMessage (tm LoginR) $ T.pack $ show (err :: SomeException) Right x -> redirect x Nothing -> loginErrorMessageI LoginR Msg.NoOpenID @@ -84,13 +90,13 @@ $newline never completeHelper :: IdentifierType -> [(Text, Text)] -> AuthHandler master TypedContent completeHelper idType gets' = do - master <- lift getYesod - eres <- try $ OpenId.authenticateClaimed gets' (authHttpManager master) + manager <- authHttpManager + eres <- liftResourceT $ try $ OpenId.authenticateClaimed gets' manager either onFailure onSuccess eres where onFailure err = do tm <- getRouteToParent - lift $ loginErrorMessage (tm LoginR) $ T.pack $ + loginErrorMessage (tm LoginR) $ T.pack $ show (err :: SomeException) onSuccess oir = do let claimed = @@ -105,7 +111,7 @@ completeHelper idType gets' = do case idType of OPLocal -> OpenId.oirOpLocal oir Claimed -> fromMaybe (OpenId.oirOpLocal oir) $ OpenId.oirClaimed oir - lift $ setCredsRedirect $ Creds "openid" i gets'' + setCredsRedirect $ Creds "openid" i gets'' -- | The main identifier provided by the OpenID authentication plugin is the -- \"OP-local identifier\". There is also sometimes a \"claimed\" identifier diff --git a/yesod-auth/Yesod/Auth/Rpxnow.hs b/yesod-auth/Yesod/Auth/Rpxnow.hs index 58456cda..8ff663e5 100644 --- a/yesod-auth/Yesod/Auth/Rpxnow.hs +++ b/yesod-auth/Yesod/Auth/Rpxnow.hs @@ -2,6 +2,7 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE GADTs #-} module Yesod.Auth.Rpxnow ( authRpxnow ) where @@ -17,10 +18,10 @@ import Data.Text.Encoding.Error (lenientDecode) import Control.Arrow ((***)) import Network.HTTP.Types (renderQuery) -authRpxnow :: YesodAuth m +authRpxnow :: YesodAuth master => String -- ^ app name -> String -- ^ key - -> AuthPlugin m + -> AuthPlugin master authRpxnow app apiKey = AuthPlugin "rpxnow" dispatch login where @@ -32,14 +33,17 @@ authRpxnow app apiKey = $newline never