Compare commits

...

8 Commits
master ... rio

Author SHA1 Message Date
Michael Snoyman
576bfb7ff9
Merge remote-tracking branch 'origin/master' into rio 2019-03-17 11:19:38 +02:00
Michael Snoyman
eccbe4acbe
It all compiles 2019-03-12 13:14:27 +02:00
Michael Snoyman
cd76b34497
yesod package compiles (still want to clean it up) 2019-02-27 05:32:36 +02:00
Michael Snoyman
53d7cf0959
src subdir 2019-02-27 05:27:11 +02:00
Michael Snoyman
6bc5feced9
Use a Deque 2019-02-27 05:26:30 +02:00
Michael Snoyman
9d47aa24da
More things work with rio 2019-02-26 11:33:11 +02:00
Michael Snoyman
2c246486e7
Remove some older stuff 2019-02-21 07:05:31 +02:00
Michael Snoyman
950c8e5a77
yesod-core moved over to rio 2019-02-19 13:03:29 +02:00
53 changed files with 1053 additions and 2204 deletions

View File

@ -35,12 +35,6 @@ matrix:
include: include:
# We grab the appropriate GHC and cabal-install versions from hvr's PPA. See: # We grab the appropriate GHC and cabal-install versions from hvr's PPA. See:
# https://github.com/hvr/multi-ghc-travis # https://github.com/hvr/multi-ghc-travis
- env: BUILD=cabal GHCVER=8.0.2 CABALVER=1.24 HAPPYVER=1.19.5 ALEXVER=3.1.7
compiler: ": #GHC 8.0.2"
addons: {apt: {packages: [cabal-install-1.24,ghc-8.0.2,happy-1.19.5,alex-3.1.7], sources: [hvr-ghc]}}
- env: BUILD=cabal GHCVER=8.2.2 CABALVER=2.0 HAPPYVER=1.19.5 ALEXVER=3.1.7
compiler: ": #GHC 8.2.2"
addons: {apt: {packages: [cabal-install-2.0,ghc-8.2.2,happy-1.19.5,alex-3.1.7], sources: [hvr-ghc]}}
- env: BUILD=cabal GHCVER=8.4.4 CABALVER=2.2 HAPPYVER=1.19.5 ALEXVER=3.1.7 - env: BUILD=cabal GHCVER=8.4.4 CABALVER=2.2 HAPPYVER=1.19.5 ALEXVER=3.1.7
compiler: ": #GHC 8.4.4" compiler: ": #GHC 8.4.4"
addons: {apt: {packages: [cabal-install-2.2,ghc-8.4.4,happy-1.19.5,alex-3.1.7], sources: [hvr-ghc]}} addons: {apt: {packages: [cabal-install-2.2,ghc-8.4.4,happy-1.19.5,alex-3.1.7], sources: [hvr-ghc]}}
@ -60,14 +54,6 @@ matrix:
compiler: ": #stack default" compiler: ": #stack default"
addons: {apt: {packages: [libgmp-dev]}} addons: {apt: {packages: [libgmp-dev]}}
- env: BUILD=stack ARGS="--stack-yaml stack-lts-9.yaml --resolver lts-9"
compiler: ": #stack 8.0.2"
addons: {apt: {packages: [libgmp-dev]}}
- env: BUILD=stack ARGS="--resolver lts-11"
compiler: ": #stack 8.2.2"
addons: {apt: {packages: [libgmp-dev]}}
- env: BUILD=stack ARGS="--resolver lts-12" - env: BUILD=stack ARGS="--resolver lts-12"
compiler: ": #stack 8.4.4" compiler: ": #stack 8.4.4"
addons: {apt: {packages: [libgmp-dev]}} addons: {apt: {packages: [libgmp-dev]}}
@ -76,10 +62,6 @@ matrix:
compiler: ": #stack 8.6.3" compiler: ": #stack 8.6.3"
addons: {apt: {packages: [libgmp-dev]}} addons: {apt: {packages: [libgmp-dev]}}
- env: BUILD=stack ARGS="--stack-yaml stack-persistent-2-9.yaml"
compiler: ": #stack/persistent 2.9"
addons: {apt: {packages: [libgmp-dev]}}
# Nightly builds are allowed to fail # Nightly builds are allowed to fail
- env: BUILD=stack ARGS="--resolver nightly" - env: BUILD=stack ARGS="--resolver nightly"
compiler: ": #stack nightly" compiler: ": #stack nightly"
@ -90,14 +72,6 @@ matrix:
compiler: ": #stack default osx" compiler: ": #stack default osx"
os: osx os: osx
- env: BUILD=stack ARGS="--stack-yaml stack-lts-9.yaml --resolver lts-9"
compiler: ": #stack 8.0.2 osx"
os: osx
- env: BUILD=stack ARGS="--resolver lts-11"
compiler: ": #stack 8.2.2 osx"
os: osx
- env: BUILD=stack ARGS="--resolver lts-12" - env: BUILD=stack ARGS="--resolver lts-12"
compiler: ": #stack 8.4.4 osx" compiler: ": #stack 8.4.4 osx"
os: osx os: osx
@ -106,10 +80,6 @@ matrix:
compiler: ": #stack 8.6.3 osx" compiler: ": #stack 8.6.3 osx"
os: osx os: osx
- env: BUILD=stack ARGS="--stack-yaml stack-persistent-2-9.yaml"
compiler: ": #stack/persistent 2.9"
os: osx
- env: BUILD=stack ARGS="--resolver nightly" - env: BUILD=stack ARGS="--resolver nightly"
compiler: ": #stack nightly osx" compiler: ": #stack nightly osx"
os: osx os: osx

View File

@ -9,7 +9,7 @@ install:
build: off build: off
build_script: build_script:
- stack --no-terminal test --no-run-tests --resolver lts-11 - stack --no-terminal test --no-run-tests
test_script: test_script:
- stack --jobs 1 --no-terminal test --resolver lts-11 - stack --jobs 1 --no-terminal test

View File

@ -1,13 +0,0 @@
./yesod-core
./yesod-static
./yesod-persistent
./yesod-newsfeed
./yesod-form
./yesod-auth
./yesod-auth-oauth
./yesod-sitemap
./yesod-test
./yesod-bin
./yesod
./yesod-eventsource
./yesod-websockets

View File

@ -1,37 +0,0 @@
resolver: lts-9.21
packages:
- ./yesod-core
- ./yesod-static
- ./yesod-persistent
- ./yesod-newsfeed
- ./yesod-form
- ./yesod-auth
- ./yesod-auth-oauth
- ./yesod-sitemap
- ./yesod-test
- ./yesod-bin
- ./yesod
- ./yesod-eventsource
- ./yesod-websockets
extra-deps:
- bsb-http-chunked-0.0.0.2@rev:0
- conduit-1.3.0.2@rev:0
- conduit-extra-1.3.0@rev:0
- html-conduit-1.3.0@rev:0
- http-client-0.5.12.1@rev:0
- http-conduit-2.3.1@rev:0
- monad-logger-0.3.28.5@rev:0
- mono-traversable-1.0.8.1@rev:0
- persistent-2.8.2@rev:0
- persistent-sqlite-2.8.1.2@rev:0
- project-template-0.2.0.1@rev:0
- resourcet-1.2.1@rev:0
- streaming-commons-0.2.0.0@rev:0
- typed-process-0.2.2.0@rev:0
- unliftio-0.2.7.0@rev:0
- unliftio-core-0.1.1.0@rev:0
- wai-extra-3.0.22.1@rev:0
- wai-logger-2.3.2@rev:0
- warp-3.2.22@rev:0
- xml-conduit-1.8.0@rev:0
- yaml-0.8.30@rev:0

View File

@ -1,18 +0,0 @@
resolver: lts-11.10
packages:
- ./yesod-core
- ./yesod-static
- ./yesod-persistent
- ./yesod-newsfeed
- ./yesod-form
- ./yesod-auth
- ./yesod-auth-oauth
- ./yesod-sitemap
- ./yesod-test
- ./yesod-bin
- ./yesod
- ./yesod-eventsource
- ./yesod-websockets
extra-deps:
- persistent-2.9.0@rev:0
- persistent-sqlite-2.9.0@rev:0

View File

@ -4,6 +4,7 @@
{-# LANGUAGE RankNTypes #-} {-# LANGUAGE RankNTypes #-}
{-# LANGUAGE GADTs #-} {-# LANGUAGE GADTs #-}
{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Yesod.Auth.OAuth module Yesod.Auth.OAuth
( authOAuth ( authOAuth
, oauthUrl , oauthUrl
@ -14,14 +15,8 @@ module Yesod.Auth.OAuth
, tumblrUrl , tumblrUrl
, module Web.Authenticate.OAuth , module Web.Authenticate.OAuth
) where ) where
import Control.Applicative as A ((<$>), (<*>))
import Control.Arrow ((***)) import Control.Arrow ((***))
import UnliftIO.Exception import RIO
import Control.Monad.IO.Class
import UnliftIO (MonadUnliftIO)
import Data.ByteString (ByteString)
import Data.Maybe
import Data.Text (Text)
import qualified Data.Text as T import qualified Data.Text as T
import Data.Text.Encoding (decodeUtf8With, encodeUtf8) import Data.Text.Encoding (decodeUtf8With, encodeUtf8)
import Data.Text.Encoding.Error (lenientDecode) import Data.Text.Encoding.Error (lenientDecode)
@ -53,14 +48,9 @@ authOAuth oauth mkCreds = AuthPlugin name dispatch login
oauthSessionName = "__oauth_token_secret" oauthSessionName = "__oauth_token_secret"
dispatch dispatch
:: ( MonadHandler m :: Text
, master ~ HandlerSite m
, Auth ~ SubHandlerSite m
, MonadUnliftIO m
)
=> Text
-> [Text] -> [Text]
-> m TypedContent -> SubHandlerFor Auth master TypedContent
dispatch "GET" ["forward"] = do dispatch "GET" ["forward"] = do
render <- getUrlRender render <- getUrlRender
tm <- getRouteToParent tm <- getRouteToParent
@ -83,8 +73,8 @@ authOAuth oauth mkCreds = AuthPlugin name dispatch login
] ]
else do else do
(verifier, oaTok) <- (verifier, oaTok) <-
runInputGet $ (,) A.<$> ireq textField "oauth_verifier" runInputGet $ (,) <$> ireq textField "oauth_verifier"
A.<*> ireq textField "oauth_token" <*> ireq textField "oauth_token"
return $ Credential [ ("oauth_verifier", encodeUtf8 verifier) return $ Credential [ ("oauth_verifier", encodeUtf8 verifier)
, ("oauth_token", encodeUtf8 oaTok) , ("oauth_token", encodeUtf8 oaTok)
, ("oauth_token_secret", encodeUtf8 tokSec) , ("oauth_token_secret", encodeUtf8 tokSec)

View File

@ -24,7 +24,7 @@ library
build-depends: authenticate-oauth >= 1.5 && < 1.7 build-depends: authenticate-oauth >= 1.5 && < 1.7
, bytestring >= 0.9.1.4 , bytestring >= 0.9.1.4
, text >= 0.7 , text >= 0.7
, unliftio , rio
, yesod-auth >= 1.6 && < 1.7 , yesod-auth >= 1.6 && < 1.7
, yesod-core >= 1.6 && < 1.7 , yesod-core >= 1.6 && < 1.7
, yesod-form >= 1.6 && < 1.7 , yesod-form >= 1.6 && < 1.7

View File

@ -6,6 +6,7 @@
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE RankNTypes #-} {-# LANGUAGE RankNTypes #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveDataTypeable #-}
@ -15,6 +16,7 @@ module Yesod.Auth
( -- * Subsite ( -- * Subsite
Auth Auth
, AuthRoute , AuthRoute
, AuthHandler
, Route (..) , Route (..)
, AuthPlugin (..) , AuthPlugin (..)
, getAuth , getAuth
@ -38,9 +40,6 @@ module Yesod.Auth
, requireAuth , requireAuth
-- * Exception -- * Exception
, AuthException (..) , AuthException (..)
-- * Helper
, MonadAuthHandler
, AuthHandler
-- * Internal -- * Internal
, credsKey , credsKey
, provideJsonMessage , provideJsonMessage
@ -48,9 +47,8 @@ module Yesod.Auth
, asHtml , asHtml
) where ) where
import Control.Monad (when) import RIO
import Control.Monad.Trans.Maybe import Control.Monad.Trans.Maybe
import UnliftIO (withRunInIO, MonadUnliftIO)
import Yesod.Auth.Routes import Yesod.Auth.Routes
import Data.Aeson hiding (json) import Data.Aeson hiding (json)
@ -76,10 +74,9 @@ import Network.HTTP.Types (Status, internalServerError500, unauthorized401)
import qualified Control.Monad.Trans.Writer as Writer import qualified Control.Monad.Trans.Writer as Writer
import Control.Monad (void) import Control.Monad (void)
type AuthRoute = Route Auth type AuthHandler site = SubHandlerFor Auth site
type MonadAuthHandler master m = (MonadHandler m, YesodAuth master, master ~ HandlerSite m, Auth ~ SubHandlerSite m, MonadUnliftIO m) type AuthRoute = Route Auth
type AuthHandler master a = forall m. MonadAuthHandler master m => m a
type Method = Text type Method = Text
type Piece = Text type Piece = Text
@ -94,7 +91,7 @@ data AuthenticationResult master
data AuthPlugin master = AuthPlugin data AuthPlugin master = AuthPlugin
{ apName :: Text { apName :: Text
, apDispatch :: Method -> [Piece] -> AuthHandler master TypedContent , apDispatch :: Method -> [Piece] -> SubHandlerFor Auth master TypedContent
, apLogin :: (Route Auth -> Route master) -> WidgetFor master () , apLogin :: (Route Auth -> Route master) -> WidgetFor master ()
} }
@ -112,7 +109,7 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage
type AuthId master type AuthId master
-- | specify the layout. Uses defaultLayout by default -- | specify the layout. Uses defaultLayout by default
authLayout :: (MonadHandler m, HandlerSite m ~ master) => WidgetFor master () -> m Html authLayout :: (HasHandlerData env, HandlerSite env ~ master) => WidgetFor master () -> RIO env Html
authLayout = liftHandler . defaultLayout authLayout = liftHandler . defaultLayout
-- | Default destination on successful login, if no other -- | Default destination on successful login, if no other
@ -128,7 +125,7 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage
-- Default implementation is in terms of @'getAuthId'@ -- Default implementation is in terms of @'getAuthId'@
-- --
-- @since: 1.4.4 -- @since: 1.4.4
authenticate :: (MonadHandler m, HandlerSite m ~ master) => Creds master -> m (AuthenticationResult master) authenticate :: (HasHandlerData env, HandlerSite env ~ master) => Creds master -> RIO env (AuthenticationResult master)
authenticate creds = do authenticate creds = do
muid <- getAuthId creds muid <- getAuthId creds
@ -138,7 +135,7 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage
-- --
-- Default implementation is in terms of @'authenticate'@ -- Default implementation is in terms of @'authenticate'@
-- --
getAuthId :: (MonadHandler m, HandlerSite m ~ master) => Creds master -> m (Maybe (AuthId master)) getAuthId :: (HasHandlerData env, HandlerSite env ~ master) => Creds master -> RIO env (Maybe (AuthId master))
getAuthId creds = do getAuthId creds = do
auth <- authenticate creds auth <- authenticate creds
@ -168,7 +165,9 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage
-- > lift $ redirect HomeR -- or any other Handler code you want -- > lift $ redirect HomeR -- or any other Handler code you want
-- > defaultLoginHandler -- > defaultLoginHandler
-- --
loginHandler :: AuthHandler master Html loginHandler
:: (HasHandlerData env, SubHandlerSite env ~ Auth, HandlerSite env ~ master)
=> RIO env Html
loginHandler = defaultLoginHandler loginHandler = defaultLoginHandler
-- | Used for i18n of messages provided by this package. -- | Used for i18n of messages provided by this package.
@ -194,16 +193,16 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage
-- type. This allows backends to reuse persistent connections. If none of -- type. This allows backends to reuse persistent connections. If none of
-- the backends you're using use HTTP connections, you can safely return -- the backends you're using use HTTP connections, you can safely return
-- @error \"authHttpManager\"@ here. -- @error \"authHttpManager\"@ here.
authHttpManager :: (MonadHandler m, HandlerSite m ~ master) => m Manager authHttpManager :: (HasHandlerData env, HandlerSite env ~ master) => RIO env Manager
authHttpManager = liftIO getGlobalManager authHttpManager = liftIO getGlobalManager
-- | Called on a successful login. By default, calls -- | Called on a successful login. By default, calls
-- @addMessageI "success" NowLoggedIn@. -- @addMessageI "success" NowLoggedIn@.
onLogin :: (MonadHandler m, master ~ HandlerSite m) => m () onLogin :: (HasHandlerData env, master ~ HandlerSite env) => RIO env ()
onLogin = addMessageI "success" Msg.NowLoggedIn onLogin = addMessageI "success" Msg.NowLoggedIn
-- | Called on logout. By default, does nothing -- | Called on logout. By default, does nothing
onLogout :: (MonadHandler m, master ~ HandlerSite m) => m () onLogout :: (HasHandlerData env, master ~ HandlerSite env) => RIO env ()
onLogout = return () onLogout = return ()
-- | Retrieves user credentials, if user is authenticated. -- | Retrieves user credentials, if user is authenticated.
@ -215,16 +214,20 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage
-- other than a browser. -- other than a browser.
-- --
-- @since 1.2.0 -- @since 1.2.0
maybeAuthId :: (MonadHandler m, master ~ HandlerSite m) => m (Maybe (AuthId master)) maybeAuthId :: (HasHandlerData env, master ~ HandlerSite env) => RIO env (Maybe (AuthId master))
default maybeAuthId default maybeAuthId
:: (MonadHandler m, master ~ HandlerSite m, YesodAuthPersist master, Typeable (AuthEntity master)) :: (HasHandlerData env, master ~ HandlerSite env, YesodAuthPersist master, Typeable (AuthEntity master))
=> m (Maybe (AuthId master)) => RIO env (Maybe (AuthId master))
maybeAuthId = defaultMaybeAuthId maybeAuthId = defaultMaybeAuthId
-- | Called on login error for HTTP requests. By default, calls -- | Called on login error for HTTP requests. By default, calls
-- @addMessage@ with "error" as status and redirects to @dest@. -- @addMessage@ with "error" as status and redirects to @dest@.
onErrorHtml :: (MonadHandler m, HandlerSite m ~ master) => Route master -> Text -> m Html onErrorHtml
:: (HasHandlerData env, HandlerSite env ~ master)
=> Route master
-> Text
-> RIO env Html
onErrorHtml dest msg = do onErrorHtml dest msg = do
addMessage "error" $ toHtml msg addMessage "error" $ toHtml msg
fmap asHtml $ redirect dest fmap asHtml $ redirect dest
@ -235,10 +238,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. -- 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 -- This is an experimental API that is not broadly used throughout the yesod-auth code base
runHttpRequest runHttpRequest
:: (MonadHandler m, HandlerSite m ~ master, MonadUnliftIO m) :: (HasHandlerData env, HandlerSite env ~ master)
=> Request => Request
-> (Response BodyReader -> m a) -> (Response BodyReader -> RIO env a)
-> m a -> RIO env a
runHttpRequest req inner = do runHttpRequest req inner = do
man <- authHttpManager man <- authHttpManager
withRunInIO $ \run -> withResponse req man $ run . inner withRunInIO $ \run -> withResponse req man $ run . inner
@ -261,8 +264,8 @@ credsKey = "_ID"
-- --
-- @since 1.1.2 -- @since 1.1.2
defaultMaybeAuthId defaultMaybeAuthId
:: (MonadHandler m, HandlerSite m ~ master, YesodAuthPersist master, Typeable (AuthEntity master)) :: (HasHandlerData env, HandlerSite env ~ master, YesodAuthPersist master, Typeable (AuthEntity master))
=> m (Maybe (AuthId master)) => RIO env (Maybe (AuthId master))
defaultMaybeAuthId = runMaybeT $ do defaultMaybeAuthId = runMaybeT $ do
s <- MaybeT $ lookupSession credsKey s <- MaybeT $ lookupSession credsKey
aid <- MaybeT $ return $ fromPathPiece s aid <- MaybeT $ return $ fromPathPiece s
@ -270,13 +273,13 @@ defaultMaybeAuthId = runMaybeT $ do
return aid return aid
cachedAuth cachedAuth
:: ( MonadHandler m :: ( HasHandlerData env
, YesodAuthPersist master , YesodAuthPersist master
, Typeable (AuthEntity master) , Typeable (AuthEntity master)
, HandlerSite m ~ master , HandlerSite env ~ master
) )
=> AuthId master => AuthId master
-> m (Maybe (AuthEntity master)) -> RIO env (Maybe (AuthEntity master))
cachedAuth cachedAuth
= fmap unCachedMaybeAuth = fmap unCachedMaybeAuth
. cached . cached
@ -290,7 +293,9 @@ cachedAuth
-- wraps the result in 'authLayout'. See 'loginHandler' for more details. -- wraps the result in 'authLayout'. See 'loginHandler' for more details.
-- --
-- @since 1.4.9 -- @since 1.4.9
defaultLoginHandler :: AuthHandler master Html defaultLoginHandler
:: (HasHandlerData env, SubHandlerSite env ~ Auth, YesodAuth (HandlerSite env))
=> RIO env Html
defaultLoginHandler = do defaultLoginHandler = do
tp <- getRouteToParent tp <- getRouteToParent
authLayout $ do authLayout $ do
@ -298,21 +303,21 @@ defaultLoginHandler = do
master <- getYesod master <- getYesod
mapM_ (flip apLogin tp) (authPlugins master) mapM_ (flip apLogin tp) (authPlugins master)
loginErrorMessageI loginErrorMessageI
:: Route Auth :: (HasHandlerData env, SubHandlerSite env ~ Auth, YesodAuth (HandlerSite env))
=> Route Auth
-> AuthMessage -> AuthMessage
-> AuthHandler master TypedContent -> RIO env TypedContent
loginErrorMessageI dest msg = do loginErrorMessageI dest msg = do
toParent <- getRouteToParent toParent <- getRouteToParent
loginErrorMessageMasterI (toParent dest) msg loginErrorMessageMasterI (toParent dest) msg
loginErrorMessageMasterI loginErrorMessageMasterI
:: (MonadHandler m, HandlerSite m ~ master, YesodAuth master) :: (HasHandlerData env, HandlerSite env ~ master, YesodAuth master)
=> Route master => Route master
-> AuthMessage -> AuthMessage
-> m TypedContent -> RIO env TypedContent
loginErrorMessageMasterI dest msg = do loginErrorMessageMasterI dest msg = do
mr <- getMessageRender mr <- getMessageRender
loginErrorMessage dest (mr msg) loginErrorMessage dest (mr msg)
@ -320,28 +325,28 @@ loginErrorMessageMasterI dest msg = do
-- | For HTML, set the message and redirect to the route. -- | For HTML, set the message and redirect to the route.
-- For JSON, send the message and a 401 status -- For JSON, send the message and a 401 status
loginErrorMessage loginErrorMessage
:: (MonadHandler m, YesodAuth (HandlerSite m)) :: (HasHandlerData env, YesodAuth (HandlerSite env))
=> Route (HandlerSite m) => Route (HandlerSite env)
-> Text -> Text
-> m TypedContent -> RIO env TypedContent
loginErrorMessage dest msg = messageJson401 msg (onErrorHtml dest msg) loginErrorMessage dest msg = messageJson401 msg (onErrorHtml dest msg)
messageJson401 messageJson401
:: MonadHandler m :: HasHandlerData env
=> Text => Text
-> m Html -> RIO env Html
-> m TypedContent -> RIO env TypedContent
messageJson401 = messageJsonStatus unauthorized401 messageJson401 = messageJsonStatus unauthorized401
messageJson500 :: MonadHandler m => Text -> m Html -> m TypedContent messageJson500 :: HasHandlerData env => Text -> RIO env Html -> RIO env TypedContent
messageJson500 = messageJsonStatus internalServerError500 messageJson500 = messageJsonStatus internalServerError500
messageJsonStatus messageJsonStatus
:: MonadHandler m :: HasHandlerData env
=> Status => Status
-> Text -> Text
-> m Html -> RIO env Html
-> m TypedContent -> RIO env TypedContent
messageJsonStatus status msg html = selectRep $ do messageJsonStatus status msg html = selectRep $ do
provideRep html provideRep html
provideRep $ do provideRep $ do
@ -354,9 +359,9 @@ provideJsonMessage msg = provideRep $ return $ object ["message" .= msg]
setCredsRedirect setCredsRedirect
:: (MonadHandler m, YesodAuth (HandlerSite m)) :: (HasHandlerData env, YesodAuth (HandlerSite env))
=> Creds (HandlerSite m) -- ^ new credentials => Creds (HandlerSite env) -- ^ new credentials
-> m TypedContent -> RIO env TypedContent
setCredsRedirect creds = do setCredsRedirect creds = do
y <- getYesod y <- getYesod
auth <- authenticate creds auth <- authenticate creds
@ -379,7 +384,7 @@ setCredsRedirect creds = do
Just ar -> loginErrorMessageMasterI ar msg Just ar -> loginErrorMessageMasterI ar msg
ServerError msg -> do ServerError msg -> do
$(logError) msg logError $ display msg
case authRoute y of case authRoute y of
Nothing -> do Nothing -> do
@ -395,10 +400,10 @@ setCredsRedirect creds = do
return $ renderAuthMessage master langs msg return $ renderAuthMessage master langs msg
-- | Sets user credentials for the session after checking them with authentication backends. -- | Sets user credentials for the session after checking them with authentication backends.
setCreds :: (MonadHandler m, YesodAuth (HandlerSite m)) setCreds :: (HasHandlerData env, YesodAuth (HandlerSite env))
=> Bool -- ^ if HTTP redirects should be done => Bool -- ^ if HTTP redirects should be done
-> Creds (HandlerSite m) -- ^ new credentials -> Creds (HandlerSite env) -- ^ new credentials
-> m () -> RIO env ()
setCreds doRedirects creds = setCreds doRedirects creds =
if doRedirects if doRedirects
then void $ setCredsRedirect creds then void $ setCredsRedirect creds
@ -409,10 +414,10 @@ setCreds doRedirects creds =
-- | same as defaultLayoutJson, but uses authLayout -- | same as defaultLayoutJson, but uses authLayout
authLayoutJson authLayoutJson
:: (ToJSON j, MonadAuthHandler master m) :: (ToJSON j, HasHandlerData env, YesodAuth (HandlerSite env))
=> WidgetFor master () -- ^ HTML => WidgetFor (HandlerSite env) () -- ^ HTML
-> m j -- ^ JSON -> RIO env j -- ^ JSON
-> m TypedContent -> RIO env TypedContent
authLayoutJson w json = selectRep $ do authLayoutJson w json = selectRep $ do
provideRep $ authLayout w provideRep $ authLayout w
provideRep $ fmap toJSON json provideRep $ fmap toJSON json
@ -420,9 +425,9 @@ authLayoutJson w json = selectRep $ do
-- | Clears current user credentials for the session. -- | Clears current user credentials for the session.
-- --
-- @since 1.1.7 -- @since 1.1.7
clearCreds :: (MonadHandler m, YesodAuth (HandlerSite m)) clearCreds :: (HasHandlerData env, YesodAuth (HandlerSite env))
=> Bool -- ^ if HTTP redirect to 'logoutDest' should be done => Bool -- ^ if HTTP redirect to 'logoutDest' should be done
-> m () -> RIO env ()
clearCreds doRedirects = do clearCreds doRedirects = do
y <- getYesod y <- getYesod
onLogout onLogout
@ -430,7 +435,7 @@ clearCreds doRedirects = do
when doRedirects $ do when doRedirects $ do
redirectUltDest $ logoutDest y redirectUltDest $ logoutDest y
getCheckR :: AuthHandler master TypedContent getCheckR :: (YesodAuth (HandlerSite env), HasHandlerData env) => RIO env TypedContent
getCheckR = do getCheckR = do
creds <- maybeAuthId creds <- maybeAuthId
authLayoutJson (do authLayoutJson (do
@ -451,23 +456,27 @@ $nothing
[ (T.pack "logged_in", Bool $ maybe False (const True) creds) [ (T.pack "logged_in", Bool $ maybe False (const True) creds)
] ]
setUltDestReferer' :: (MonadHandler m, YesodAuth (HandlerSite m)) => m () setUltDestReferer' :: (HasHandlerData env, YesodAuth (HandlerSite env)) => RIO env ()
setUltDestReferer' = do setUltDestReferer' = do
master <- getYesod master <- getYesod
when (redirectToReferer master) setUltDestReferer when (redirectToReferer master) setUltDestReferer
getLoginR :: AuthHandler master Html getLoginR :: (HasHandlerData env, YesodAuth (HandlerSite env), SubHandlerSite env ~ Auth) => RIO env Html
getLoginR = setUltDestReferer' >> loginHandler getLoginR = setUltDestReferer' >> loginHandler
getLogoutR :: AuthHandler master () getLogoutR :: (HasHandlerData env, YesodAuth (HandlerSite env), SubHandlerSite env ~ Auth) => RIO env ()
getLogoutR = do getLogoutR = do
tp <- getRouteToParent tp <- getRouteToParent
setUltDestReferer' >> redirectToPost (tp LogoutR) setUltDestReferer' >> redirectToPost (tp LogoutR)
postLogoutR :: AuthHandler master () postLogoutR :: (HasHandlerData env, YesodAuth (HandlerSite env)) => RIO env ()
postLogoutR = clearCreds True postLogoutR = clearCreds True
handlePluginR :: Text -> [Text] -> AuthHandler master TypedContent handlePluginR
:: YesodAuth site
=> Text
-> [Text]
-> SubHandlerFor Auth site TypedContent
handlePluginR plugin pieces = do handlePluginR plugin pieces = do
master <- getYesod master <- getYesod
env <- waiRequest env <- waiRequest
@ -486,9 +495,9 @@ maybeAuth :: ( YesodAuthPersist master
, Key val ~ AuthId master , Key val ~ AuthId master
, PersistEntity val , PersistEntity val
, Typeable val , Typeable val
, MonadHandler m , HasHandlerData env
, HandlerSite m ~ master , HandlerSite env ~ master
) => m (Maybe (Entity val)) ) => RIO env (Maybe (Entity val))
maybeAuth = fmap (fmap (uncurry Entity)) maybeAuthPair maybeAuth = fmap (fmap (uncurry Entity)) maybeAuthPair
-- | Similar to 'maybeAuth', but doesnt assume that you are using a -- | Similar to 'maybeAuth', but doesnt assume that you are using a
@ -498,10 +507,10 @@ maybeAuth = fmap (fmap (uncurry Entity)) maybeAuthPair
maybeAuthPair maybeAuthPair
:: ( YesodAuthPersist master :: ( YesodAuthPersist master
, Typeable (AuthEntity master) , Typeable (AuthEntity master)
, MonadHandler m , HasHandlerData env
, HandlerSite m ~ master , HandlerSite env ~ master
) )
=> m (Maybe (AuthId master, AuthEntity master)) => RIO env (Maybe (AuthId master, AuthEntity master))
maybeAuthPair = runMaybeT $ do maybeAuthPair = runMaybeT $ do
aid <- MaybeT maybeAuthId aid <- MaybeT maybeAuthId
ae <- MaybeT $ cachedAuth aid ae <- MaybeT $ cachedAuth aid
@ -532,18 +541,21 @@ class (YesodAuth master, YesodPersist master) => YesodAuthPersist master where
type AuthEntity master :: * type AuthEntity master :: *
type AuthEntity master = KeyEntity (AuthId master) type AuthEntity master = KeyEntity (AuthId master)
getAuthEntity :: (MonadHandler m, HandlerSite m ~ master) getAuthEntity
=> AuthId master -> m (Maybe (AuthEntity master)) :: (HasHandlerData env, HandlerSite env ~ master)
=> AuthId master
-> RIO env (Maybe (AuthEntity master))
default getAuthEntity default getAuthEntity
:: ( YesodPersistBackend master ~ backend :: ( YesodPersistBackend master ~ backend
, PersistRecordBackend (AuthEntity master) backend , PersistRecordBackend (AuthEntity master) backend
, Key (AuthEntity master) ~ AuthId master , Key (AuthEntity master) ~ AuthId master
, PersistStore backend , PersistStore backend
, MonadHandler m , HasHandlerData env
, HandlerSite m ~ master , HandlerSite env ~ master
) )
=> AuthId master -> m (Maybe (AuthEntity master)) => AuthId master
-> RIO env (Maybe (AuthEntity master))
getAuthEntity = liftHandler . runDB . get getAuthEntity = liftHandler . runDB . get
@ -554,7 +566,7 @@ type instance KeyEntity (Key x) = x
-- authenticated or responds with error 401 if this is an API client (expecting JSON). -- authenticated or responds with error 401 if this is an API client (expecting JSON).
-- --
-- @since 1.1.0 -- @since 1.1.0
requireAuthId :: (MonadHandler m, YesodAuth (HandlerSite m)) => m (AuthId (HandlerSite m)) requireAuthId :: (HasHandlerData env, YesodAuth (HandlerSite env)) => RIO env (AuthId (HandlerSite env))
requireAuthId = maybeAuthId >>= maybe handleAuthLack return requireAuthId = maybeAuthId >>= maybe handleAuthLack return
-- | Similar to 'maybeAuth', but redirects to a login page if user is not -- | Similar to 'maybeAuth', but redirects to a login page if user is not
@ -566,9 +578,9 @@ requireAuth :: ( YesodAuthPersist master
, Key val ~ AuthId master , Key val ~ AuthId master
, PersistEntity val , PersistEntity val
, Typeable val , Typeable val
, MonadHandler m , HasHandlerData env
, HandlerSite m ~ master , HandlerSite env ~ master
) => m (Entity val) ) => RIO env (Entity val)
requireAuth = maybeAuth >>= maybe handleAuthLack return requireAuth = maybeAuth >>= maybe handleAuthLack return
-- | Similar to 'requireAuth', but not tied to Persistent's 'Entity' type. -- | Similar to 'requireAuth', but not tied to Persistent's 'Entity' type.
@ -578,18 +590,18 @@ requireAuth = maybeAuth >>= maybe handleAuthLack return
requireAuthPair requireAuthPair
:: ( YesodAuthPersist master :: ( YesodAuthPersist master
, Typeable (AuthEntity master) , Typeable (AuthEntity master)
, MonadHandler m , HasHandlerData env
, HandlerSite m ~ master , HandlerSite env ~ master
) )
=> m (AuthId master, AuthEntity master) => RIO env (AuthId master, AuthEntity master)
requireAuthPair = maybeAuthPair >>= maybe handleAuthLack return requireAuthPair = maybeAuthPair >>= maybe handleAuthLack return
handleAuthLack :: (YesodAuth (HandlerSite m), MonadHandler m) => m a handleAuthLack :: (YesodAuth (HandlerSite env), HasHandlerData env) => RIO env a
handleAuthLack = do handleAuthLack = do
aj <- acceptsJson aj <- acceptsJson
if aj then notAuthenticated else redirectLogin if aj then notAuthenticated else redirectLogin
redirectLogin :: (YesodAuth (HandlerSite m), MonadHandler m) => m a redirectLogin :: (YesodAuth (HandlerSite env), HasHandlerData env) => RIO env a
redirectLogin = do redirectLogin = do
y <- getYesod y <- getYesod
when (redirectToCurrent y) setUltDestCurrent when (redirectToCurrent y) setUltDestCurrent

View File

@ -1,170 +0,0 @@
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE RecordWildCards #-}
-- | NOTE: Mozilla Persona will be shut down by the end of 2016, therefore this
-- module is no longer recommended for use.
module Yesod.Auth.BrowserId
{-# DEPRECATED "Mozilla Persona will be shut down by the end of 2016" #-}
( authBrowserId
, createOnClick, createOnClickOverride
, def
, BrowserIdSettings
, bisAudience
, bisLazyLoad
, forwardUrl
) where
import Yesod.Auth
import Web.Authenticate.BrowserId
import Data.Text (Text)
import Yesod.Core
import qualified Data.Text as T
import Data.Maybe (fromMaybe)
import Control.Monad (when, unless)
import Text.Julius (rawJS)
import Network.URI (uriPath, parseURI)
import Data.FileEmbed (embedFile)
import Data.ByteString (ByteString)
import Data.Default
pid :: Text
pid = "browserid"
forwardUrl :: AuthRoute
forwardUrl = PluginR pid []
complete :: AuthRoute
complete = forwardUrl
-- | A settings type for various configuration options relevant to BrowserID.
--
-- See: <http://www.yesodweb.com/book/settings-types>
--
-- Since 1.2.0
data BrowserIdSettings = BrowserIdSettings
{ bisAudience :: Maybe Text
-- ^ BrowserID audience value. If @Nothing@, will be extracted based on the
-- approot.
--
-- Default: @Nothing@
--
-- Since 1.2.0
, bisLazyLoad :: Bool
-- ^ Use asynchronous Javascript loading for the BrowserID JS file.
--
-- Default: @True@.
--
-- Since 1.2.0
}
instance Default BrowserIdSettings where
def = BrowserIdSettings
{ bisAudience = Nothing
, bisLazyLoad = True
}
authBrowserId :: YesodAuth m => BrowserIdSettings -> AuthPlugin m
authBrowserId bis@BrowserIdSettings {..} = AuthPlugin
{ apName = pid
, apDispatch = \m ps ->
case (m, ps) of
("GET", [assertion]) -> do
audience <-
case bisAudience of
Just a -> return a
Nothing -> do
r <- getUrlRender
tm <- getRouteToParent
return $ T.takeWhile (/= '/') $ stripScheme $ r $ tm LoginR
manager <- authHttpManager
memail <- checkAssertion audience assertion manager
case memail of
Nothing -> do
$logErrorS "yesod-auth" "BrowserID assertion failure"
tm <- getRouteToParent
loginErrorMessage (tm LoginR) "BrowserID login error."
Just email -> setCredsRedirect Creds
{ credsPlugin = pid
, credsIdent = email
, credsExtra = []
}
("GET", ["static", "sign-in.png"]) -> sendResponse
( "image/png" :: ByteString
, toContent $(embedFile "persona_sign_in_blue.png")
)
(_, []) -> badMethod
_ -> notFound
, apLogin = \toMaster -> do
onclick <- createOnClick bis toMaster
autologin <- fmap (== Just "true") $ lookupGetParam "autologin"
when autologin $ toWidget [julius|#{rawJS onclick}();|]
toWidget [hamlet|
$newline never
<p>
<a href="javascript:#{onclick}()">
<img src=@{toMaster loginIcon}>
|]
}
where
loginIcon = PluginR pid ["static", "sign-in.png"]
stripScheme t = fromMaybe t $ T.stripPrefix "//" $ snd $ T.breakOn "//" t
-- | Generates a function to handle on-click events, and returns that function
-- name.
createOnClickOverride :: BrowserIdSettings
-> (Route Auth -> Route master)
-> Maybe (Route master)
-> WidgetFor master Text
createOnClickOverride BrowserIdSettings {..} toMaster mOnRegistration = do
unless bisLazyLoad $ addScriptRemote browserIdJs
onclick <- newIdent
render <- getUrlRender
let login = toJSON $ getPath $ render loginRoute -- (toMaster LoginR)
loginRoute = maybe (toMaster LoginR) id mOnRegistration
toWidget [julius|
function #{rawJS onclick}() {
if (navigator.id) {
navigator.id.watch({
onlogin: function (assertion) {
if (assertion) {
document.location = "@{toMaster complete}/" + assertion;
}
},
onlogout: function () {}
});
navigator.id.request({
returnTo: #{login} + "?autologin=true"
});
}
else {
alert("Loading, please try again");
}
}
|]
when bisLazyLoad $ toWidget [julius|
(function(){
var bid = document.createElement("script");
bid.async = true;
bid.src = #{toJSON browserIdJs};
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(bid, s);
})();
|]
autologin <- fmap (== Just "true") $ lookupGetParam "autologin"
when autologin $ toWidget [julius|#{rawJS onclick}();|]
return onclick
where
getPath t = fromMaybe t $ do
uri <- parseURI $ T.unpack t
return $ T.pack $ uriPath uri
-- | Generates a function to handle on-click events, and returns that function
-- name.
createOnClick :: BrowserIdSettings
-> (Route Auth -> Route master)
-> WidgetFor master Text
createOnClick bidSettings toMaster = createOnClickOverride bidSettings toMaster Nothing

View File

@ -327,7 +327,7 @@ class ( YesodAuth site
-- used. -- used.
-- --
-- @since 1.6.4 -- @since 1.6.4
emailPreviouslyRegisteredResponse :: MonadAuthHandler site m => Text -> Maybe (m TypedContent) emailPreviouslyRegisteredResponse :: Text -> Maybe (AuthHandler site TypedContent)
emailPreviouslyRegisteredResponse _ = Nothing emailPreviouslyRegisteredResponse _ = Nothing
-- | Additional normalization of email addresses, besides standard canonicalization. -- | Additional normalization of email addresses, besides standard canonicalization.
@ -376,8 +376,8 @@ class ( YesodAuth site
-- Default: 'defaultSetPasswordHandler'. -- Default: 'defaultSetPasswordHandler'.
-- --
-- @since: 1.2.6 -- @since: 1.2.6
setPasswordHandler :: setPasswordHandler
Bool :: Bool
-- ^ Whether the old password is needed. If @True@, a -- ^ Whether the old password is needed. If @True@, a
-- field for the old password should be presented. -- field for the old password should be presented.
-- Otherwise, just two fields for the new password are -- Otherwise, just two fields for the new password are
@ -571,12 +571,12 @@ registerHelper allowUsername forgotPassword dest = do
return $ Just (lid, False, key, identifier) return $ Just (lid, False, key, identifier)
case registerCreds of case registerCreds of
Nothing -> loginErrorMessageI dest (Msg.IdentifierNotFound identifier) Nothing -> loginErrorMessageI dest (Msg.IdentifierNotFound identifier)
Just creds@(_, False, _, _) -> sendConfirmationEmail creds Just creds'@(_, False, _, _) -> sendConfirmationEmail creds'
Just creds@(_, True, _, _) -> do Just creds'@(_, True, _, _) -> do
if forgotPassword then sendConfirmationEmail creds if forgotPassword then sendConfirmationEmail creds'
else case emailPreviouslyRegisteredResponse identifier of else case emailPreviouslyRegisteredResponse identifier of
Just response -> response Just response -> response
Nothing -> sendConfirmationEmail creds Nothing -> sendConfirmationEmail creds'
where sendConfirmationEmail (lid, _, verKey, email) = do where sendConfirmationEmail (lid, _, verKey, email) = do
render <- getUrlRender render <- getUrlRender
tp <- getRouteToParent tp <- getRouteToParent
@ -928,9 +928,9 @@ loginLinkKey = "_AUTH_EMAIL_LOGIN_LINK"
-- --
-- @since 1.2.1 -- @since 1.2.1
--setLoginLinkKey :: (MonadHandler m) => AuthId site -> m () --setLoginLinkKey :: (MonadHandler m) => AuthId site -> m ()
setLoginLinkKey :: (MonadHandler m, YesodAuthEmail (HandlerSite m)) setLoginLinkKey :: (HasHandlerData env, YesodAuthEmail (HandlerSite env))
=> AuthId (HandlerSite m) => AuthId (HandlerSite env)
-> m () -> RIO env ()
setLoginLinkKey aid = do setLoginLinkKey aid = do
now <- liftIO getCurrentTime now <- liftIO getCurrentTime
setSession loginLinkKey $ TS.pack $ show (toPathPiece aid, now) setSession loginLinkKey $ TS.pack $ show (toPathPiece aid, now)

View File

@ -1,598 +0,0 @@
{-# LANGUAGE CPP #-}
{-# 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
-- on Google's now deprecated OpenID system. For more information, see
-- <https://developers.google.com/+/api/auth-migration>.
--
-- 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).
--
-- In order to use this plugin:
--
-- * Create an application on the Google Developer Console <https://console.developers.google.com/>
--
-- * Create OAuth credentials. The redirect URI will be <http://yourdomain/auth/page/googleemail2/complete>. (If you have your authentication subsite at a different root than \/auth\/, please adjust accordingly.)
--
-- * Enable the Google+ API.
--
-- @since 1.3.1
module Yesod.Auth.GoogleEmail2
{-# DEPRECATED "Google+ is being shut down, please migrate to Google Sign-in https://pbrisbin.com/posts/googleemail2_deprecation/" #-}
( -- * Authentication handlers
authGoogleEmail
, authGoogleEmailSaveToken
, forwardUrl
-- * User authentication token
, Token(..)
, getUserAccessToken
-- * Person
, getPerson
, Person(..)
, Name(..)
, Gender(..)
, PersonImage(..)
, resizePersonImage
, RelationshipStatus(..)
, PersonURI(..)
, PersonURIType(..)
, Organization(..)
, OrganizationType(..)
, Place(..)
, Email(..)
, EmailType(..)
-- * Other functions
, pid
) where
import Yesod.Auth (Auth, AuthPlugin (AuthPlugin),
AuthRoute, Creds (Creds),
Route (PluginR), YesodAuth,
runHttpRequest, setCredsRedirect,
logoutDest, AuthHandler)
import qualified Yesod.Auth.Message as Msg
import Yesod.Core (HandlerSite, MonadHandler,
TypedContent, getRouteToParent,
getUrlRender, invalidArgs,
liftIO, lookupGetParam,
lookupSession, notFound, redirect,
setSession, whamlet, (.:),
addMessage, getYesod,
toHtml, liftSubHandler)
import Blaze.ByteString.Builder (fromByteString, toByteString)
import Control.Applicative ((<$>), (<*>))
import Control.Arrow (second)
import Control.Monad (unless, when)
import Control.Monad.IO.Class (MonadIO)
import qualified Crypto.Nonce as Nonce
import Data.Aeson ((.:?))
import qualified Data.Aeson as A
#if MIN_VERSION_aeson(1,0,0)
import qualified Data.Aeson.Text as A
#else
import qualified Data.Aeson.Encode as A
#endif
import Data.Aeson.Parser (json')
import Data.Aeson.Types (FromJSON (parseJSON), parseEither,
parseMaybe, withObject, withText)
import Data.Conduit
import Data.Conduit.Attoparsec (sinkParser)
import qualified Data.HashMap.Strict as M
import Data.Maybe (fromMaybe)
import Data.Monoid (mappend)
import Data.Text (Text)
import qualified Data.Text as T
import Data.Text.Encoding (decodeUtf8, encodeUtf8)
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Builder as TL
import Network.HTTP.Client (Manager, requestHeaders,
responseBody, urlEncodedBody)
import qualified Network.HTTP.Client as HTTP
import Network.HTTP.Client.Conduit (Request, bodyReaderSource)
import Network.HTTP.Conduit (http)
import Network.HTTP.Types (renderQueryText)
import System.IO.Unsafe (unsafePerformIO)
-- | Plugin identifier. This is used to identify the plugin used for
-- authentication. The 'credsPlugin' will contain this value when this
-- plugin is used for authentication.
-- @since 1.4.17
pid :: Text
pid = "googleemail2"
forwardUrl :: AuthRoute
forwardUrl = PluginR pid ["forward"]
csrfKey :: Text
csrfKey = "_GOOGLE_CSRF_TOKEN"
getCsrfToken :: MonadHandler m => m (Maybe Text)
getCsrfToken = lookupSession csrfKey
accessTokenKey :: Text
accessTokenKey = "_GOOGLE_ACCESS_TOKEN"
-- | Get user's access token from the session. Returns Nothing if it's not found
-- (probably because the user is not logged in via 'Yesod.Auth.GoogleEmail2'
-- or you are not using 'authGoogleEmailSaveToken')
getUserAccessToken :: MonadHandler m => m (Maybe Token)
getUserAccessToken = fmap (\t -> Token t "Bearer") <$> lookupSession accessTokenKey
getCreateCsrfToken :: MonadHandler m => m Text
getCreateCsrfToken = do
mtoken <- getCsrfToken
case mtoken of
Just token -> return token
Nothing -> do
token <- Nonce.nonce128urlT defaultNonceGen
setSession csrfKey token
return token
authGoogleEmail :: YesodAuth m
=> Text -- ^ client ID
-> Text -- ^ client secret
-> AuthPlugin m
authGoogleEmail = authPlugin False
-- | An alternative version which stores user access token in the session
-- variable. Use it if you want to request user's profile from your app.
--
-- @since 1.4.3
authGoogleEmailSaveToken :: YesodAuth m
=> Text -- ^ client ID
-> Text -- ^ client secret
-> AuthPlugin m
authGoogleEmailSaveToken = authPlugin True
authPlugin :: YesodAuth m
=> Bool -- ^ if the token should be stored
-> Text -- ^ client ID
-> Text -- ^ client secret
-> AuthPlugin m
authPlugin storeToken clientID clientSecret =
AuthPlugin pid dispatch login
where
complete = PluginR pid ["complete"]
getDest :: MonadHandler m
=> (Route Auth -> Route (HandlerSite m))
-> m Text
getDest tm = do
csrf <- getCreateCsrfToken
render <- getUrlRender
let qs = map (second Just)
[ ("scope", "email profile")
, ("state", csrf)
, ("redirect_uri", render $ tm complete)
, ("response_type", "code")
, ("client_id", clientID)
, ("access_type", "offline")
]
return $ decodeUtf8
$ toByteString
$ fromByteString "https://accounts.google.com/o/oauth2/auth"
`Data.Monoid.mappend` renderQueryText True qs
login tm = do
[whamlet|<a href=@{tm forwardUrl}>_{Msg.LoginGoogle}|]
dispatch :: YesodAuth site
=> Text
-> [Text]
-> AuthHandler site TypedContent
dispatch "GET" ["forward"] = do
tm <- getRouteToParent
getDest tm >>= redirect
dispatch "GET" ["complete"] = do
mstate <- lookupGetParam "state"
case mstate of
Nothing -> invalidArgs ["CSRF state from Google is missing"]
Just state -> do
mtoken <- getCsrfToken
unless (Just state == mtoken) $ invalidArgs ["Invalid CSRF token from Google"]
mcode <- lookupGetParam "code"
code <-
case mcode of
Nothing -> do
merr <- lookupGetParam "error"
case merr of
Nothing -> invalidArgs ["Missing code paramter"]
Just err -> do
master <- getYesod
let msg =
case err of
"access_denied" -> "Access denied"
_ -> "Unknown error occurred: " `T.append` err
addMessage "error" $ toHtml msg
redirect $ logoutDest master
Just c -> return c
render <- getUrlRender
tm <- getRouteToParent
req' <- liftIO $
HTTP.parseUrlThrow
"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 $ tm complete)
, ("grant_type", "authorization_code")
]
req'
{ requestHeaders = []
}
value <- makeHttpRequest req
token@(Token accessToken' tokenType') <-
case parseEither parseJSON value of
Left e -> error e
Right t -> return t
unless (tokenType' == "Bearer") $ error $ "Unknown token type: " ++ show tokenType'
-- User's access token is saved for further access to API
when storeToken $ setSession accessTokenKey accessToken'
personValue <- makeHttpRequest =<< personValueRequest token
person <- case parseEither parseJSON personValue of
Left e -> error e
Right x -> return x
email <-
case map emailValue $ filter (\e -> emailType e == EmailAccount) $ personEmails person of
[e] -> return e
[] -> error "No account email"
x -> error $ "Too many account emails: " ++ show x
setCredsRedirect $ Creds pid email $ allPersonInfo personValue
dispatch _ _ = notFound
makeHttpRequest :: Request -> AuthHandler site A.Value
makeHttpRequest req =
liftSubHandler $ runHttpRequest req $ \res ->
runConduit $ bodyReaderSource (responseBody res) .| sinkParser json'
-- | Allows to fetch information about a user from Google's API.
-- In case of parsing error returns 'Nothing'.
-- Will throw 'HttpException' in case of network problems or error response code.
--
-- @since 1.4.3
getPerson :: MonadHandler m => Manager -> Token -> m (Maybe Person)
getPerson manager token = liftSubHandler $ parseMaybe parseJSON <$> (do
req <- personValueRequest token
res <- http req manager
runConduit $ responseBody res .| sinkParser json'
)
personValueRequest :: MonadIO m => Token -> m Request
personValueRequest token = do
req2' <- liftIO
$ HTTP.parseUrlThrow "https://www.googleapis.com/plus/v1/people/me"
return req2'
{ requestHeaders =
[ ("Authorization", encodeUtf8 $ "Bearer " `mappend` accessToken token)
]
}
--------------------------------------------------------------------------------
-- | An authentication token which was acquired from OAuth callback.
-- The token gets saved into the session storage only if you use
-- 'authGoogleEmailSaveToken'.
-- You can acquire saved token with 'getUserAccessToken'.
--
-- @since 1.4.3
data Token = Token { accessToken :: Text
, tokenType :: Text
} deriving (Show, Eq)
instance FromJSON Token where
parseJSON = withObject "Tokens" $ \o -> Token
Control.Applicative.<$> o .: "access_token"
Control.Applicative.<*> o .: "token_type"
--------------------------------------------------------------------------------
-- | Gender of the person
--
-- @since 1.4.3
data Gender = Male | Female | OtherGender deriving (Show, Eq)
instance FromJSON Gender where
parseJSON = withText "Gender" $ \t -> return $ case t of
"male" -> Male
"female" -> Female
_ -> OtherGender
--------------------------------------------------------------------------------
-- | URIs specified in the person's profile
--
-- @since 1.4.3
data PersonURI =
PersonURI { uriLabel :: Maybe Text
, uriValue :: Maybe Text
, uriType :: Maybe PersonURIType
} deriving (Show, Eq)
instance FromJSON PersonURI where
parseJSON = withObject "PersonURI" $ \o -> PersonURI <$> o .:? "label"
<*> o .:? "value"
<*> o .:? "type"
--------------------------------------------------------------------------------
-- | The type of URI
--
-- @since 1.4.3
data PersonURIType = OtherProfile -- ^ URI for another profile
| Contributor -- ^ URI to a site for which this person is a contributor
| Website -- ^ URI for this Google+ Page's primary website
| OtherURI -- ^ Other URL
| PersonURIType Text -- ^ Something else
deriving (Show, Eq)
instance FromJSON PersonURIType where
parseJSON = withText "PersonURIType" $ \t -> return $ case t of
"otherProfile" -> OtherProfile
"contributor" -> Contributor
"website" -> Website
"other" -> OtherURI
_ -> PersonURIType t
--------------------------------------------------------------------------------
-- | Current or past organizations with which this person is associated
--
-- @since 1.4.3
data Organization =
Organization { orgName :: Maybe Text
-- ^ The person's job title or role within the organization
, orgTitle :: Maybe Text
, orgType :: Maybe OrganizationType
-- ^ The date that the person joined this organization.
, orgStartDate :: Maybe Text
-- ^ The date that the person left this organization.
, orgEndDate :: Maybe Text
-- ^ If @True@, indicates this organization is the person's
-- ^ primary one, which is typically interpreted as the current one.
, orgPrimary :: Maybe Bool
} deriving (Show, Eq)
instance FromJSON Organization where
parseJSON = withObject "Organization" $ \o ->
Organization <$> o .:? "name"
<*> o .:? "title"
<*> o .:? "type"
<*> o .:? "startDate"
<*> o .:? "endDate"
<*> o .:? "primary"
--------------------------------------------------------------------------------
-- | The type of an organization
--
-- @since 1.4.3
data OrganizationType = Work
| School
| OrganizationType Text -- ^ Something else
deriving (Show, Eq)
instance FromJSON OrganizationType where
parseJSON = withText "OrganizationType" $ \t -> return $ case t of
"work" -> Work
"school" -> School
_ -> OrganizationType t
--------------------------------------------------------------------------------
-- | A place where the person has lived or is living at the moment.
--
-- @since 1.4.3
data Place =
Place { -- | A place where this person has lived. For example: "Seattle, WA", "Near Toronto".
placeValue :: Maybe Text
-- | If @True@, this place of residence is this person's primary residence.
, placePrimary :: Maybe Bool
} deriving (Show, Eq)
instance FromJSON Place where
parseJSON = withObject "Place" $ \o -> Place <$> (o .:? "value") <*> (o .:? "primary")
--------------------------------------------------------------------------------
-- | Individual components of a name
--
-- @since 1.4.3
data Name =
Name { -- | The full name of this person, including middle names, suffixes, etc
nameFormatted :: Maybe Text
-- | The family name (last name) of this person
, nameFamily :: Maybe Text
-- | The given name (first name) of this person
, nameGiven :: Maybe Text
-- | The middle name of this person.
, nameMiddle :: Maybe Text
-- | The honorific prefixes (such as "Dr." or "Mrs.") for this person
, nameHonorificPrefix :: Maybe Text
-- | The honorific suffixes (such as "Jr.") for this person
, nameHonorificSuffix :: Maybe Text
} deriving (Show, Eq)
instance FromJSON Name where
parseJSON = withObject "Name" $ \o -> Name <$> o .:? "formatted"
<*> o .:? "familyName"
<*> o .:? "givenName"
<*> o .:? "middleName"
<*> o .:? "honorificPrefix"
<*> o .:? "honorificSuffix"
--------------------------------------------------------------------------------
-- | The person's relationship status.
--
-- @since 1.4.3
data RelationshipStatus = Single -- ^ Person is single
| InRelationship -- ^ Person is in a relationship
| Engaged -- ^ Person is engaged
| Married -- ^ Person is married
| Complicated -- ^ The relationship is complicated
| OpenRelationship -- ^ Person is in an open relationship
| Widowed -- ^ Person is widowed
| DomesticPartnership -- ^ Person is in a domestic partnership
| CivilUnion -- ^ Person is in a civil union
| RelationshipStatus Text -- ^ Something else
deriving (Show, Eq)
instance FromJSON RelationshipStatus where
parseJSON = withText "RelationshipStatus" $ \t -> return $ case t of
"single" -> Single
"in_a_relationship" -> InRelationship
"engaged" -> Engaged
"married" -> Married
"its_complicated" -> Complicated
"open_relationship" -> OpenRelationship
"widowed" -> Widowed
"in_domestic_partnership" -> DomesticPartnership
"in_civil_union" -> CivilUnion
_ -> RelationshipStatus t
--------------------------------------------------------------------------------
-- | The URI of the person's profile photo.
--
-- @since 1.4.3
newtype PersonImage = PersonImage { imageUri :: Text } deriving (Show, Eq)
instance FromJSON PersonImage where
parseJSON = withObject "PersonImage" $ \o -> PersonImage <$> o .: "url"
-- | @resizePersonImage img 30@ would set query part to @?sz=30@ which would resize
-- the image under the URI. If for some reason you need to modify the query
-- part, you should do it after resizing.
--
-- @since 1.4.3
resizePersonImage :: PersonImage -> Int -> PersonImage
resizePersonImage (PersonImage uri) size =
PersonImage $ uri `mappend` "?sz=" `mappend` T.pack (show size)
--------------------------------------------------------------------------------
-- | Information about the user
-- Full description of the resource https://developers.google.com/+/api/latest/people
--
-- @since 1.4.3
data Person = Person
{ personId :: Text
-- | The name of this person, which is suitable for display
, personDisplayName :: Maybe Text
, personName :: Maybe Name
, personNickname :: Maybe Text
, personBirthday :: Maybe Text -- ^ Birthday formatted as YYYY-MM-DD
, personGender :: Maybe Gender
, personProfileUri :: Maybe Text -- ^ The URI of this person's profile
, personImage :: Maybe PersonImage
, personAboutMe :: Maybe Text -- ^ A short biography for this person
, personRelationshipStatus :: Maybe RelationshipStatus
, personUris :: [PersonURI]
, personOrganizations :: [Organization]
, personPlacesLived :: [Place]
-- | The brief description of this person
, personTagline :: Maybe Text
-- | Whether this user has signed up for Google+
, personIsPlusUser :: Maybe Bool
-- | The "bragging rights" line of this person
, personBraggingRights :: Maybe Text
-- | if a Google+ page, the number of people who have +1'd this page
, personPlusOneCount :: Maybe Int
-- | For followers who are visible, the number of people who have added
-- this person or page to a circle.
, personCircledByCount :: Maybe Int
-- | Whether the person or Google+ Page has been verified. This is used only
-- for pages with a higher risk of being impersonated or similar. This
-- flag will not be present on most profiles.
, personVerified :: Maybe Bool
-- | The user's preferred language for rendering.
, personLanguage :: Maybe Text
, personEmails :: [Email]
, personDomain :: Maybe Text
, personOccupation :: Maybe Text -- ^ The occupation of this person
, personSkills :: Maybe Text -- ^ The person's skills
} deriving (Show, Eq)
instance FromJSON Person where
parseJSON = withObject "Person" $ \o ->
Person <$> o .: "id"
<*> o .: "displayName"
<*> o .:? "name"
<*> o .:? "nickname"
<*> o .:? "birthday"
<*> o .:? "gender"
<*> (o .:? "url")
<*> o .:? "image"
<*> o .:? "aboutMe"
<*> o .:? "relationshipStatus"
<*> ((fromMaybe []) <$> (o .:? "urls"))
<*> ((fromMaybe []) <$> (o .:? "organizations"))
<*> ((fromMaybe []) <$> (o .:? "placesLived"))
<*> o .:? "tagline"
<*> o .:? "isPlusUser"
<*> o .:? "braggingRights"
<*> o .:? "plusOneCount"
<*> o .:? "circledByCount"
<*> o .:? "verified"
<*> o .:? "language"
<*> ((fromMaybe []) <$> (o .:? "emails"))
<*> o .:? "domain"
<*> o .:? "occupation"
<*> o .:? "skills"
--------------------------------------------------------------------------------
-- | Person's email
--
-- @since 1.4.3
data Email = Email
{ emailValue :: Text
, emailType :: EmailType
}
deriving (Show, Eq)
instance FromJSON Email where
parseJSON = withObject "Email" $ \o -> Email
<$> o .: "value"
<*> o .: "type"
--------------------------------------------------------------------------------
-- | Type of email
--
-- @since 1.4.3
data EmailType = EmailAccount -- ^ Google account email address
| EmailHome -- ^ Home email address
| EmailWork -- ^ Work email adress
| EmailOther -- ^ Other email address
| EmailType Text -- ^ Something else
deriving (Show, Eq)
instance FromJSON EmailType where
parseJSON = withText "EmailType" $ \t -> return $ case t of
"account" -> EmailAccount
"home" -> EmailHome
"work" -> EmailWork
"other" -> EmailOther
_ -> EmailType t
allPersonInfo :: A.Value -> [(Text, Text)]
allPersonInfo (A.Object o) = map enc $ M.toList o
where enc (key, A.String s) = (key, s)
enc (key, v) = (key, TL.toStrict $ TL.toLazyText $ A.encodeToTextBuilder v)
allPersonInfo _ = []
-- See https://github.com/yesodweb/yesod/issues/1245 for discussion on this
-- use of unsafePerformIO.
defaultNonceGen :: Nonce.Generator
defaultNonceGen = unsafePerformIO (Nonce.new)
{-# NOINLINE defaultNonceGen #-}

View File

@ -4,6 +4,7 @@
{-# LANGUAGE CPP #-} {-# LANGUAGE CPP #-}
{-# LANGUAGE RankNTypes #-} {-# LANGUAGE RankNTypes #-}
{-# LANGUAGE GADTs #-} {-# LANGUAGE GADTs #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Yesod.Auth.OpenId module Yesod.Auth.OpenId
( authOpenId ( authOpenId
, forwardUrl , forwardUrl
@ -29,7 +30,7 @@ forwardUrl = PluginR "openid" ["forward"]
data IdentifierType = Claimed | OPLocal data IdentifierType = Claimed | OPLocal
authOpenId :: YesodAuth master authOpenId :: forall master. YesodAuth master
=> IdentifierType => IdentifierType
-> [(Text, Text)] -- ^ extension fields -> [(Text, Text)] -- ^ extension fields
-> AuthPlugin master -> AuthPlugin master
@ -41,16 +42,15 @@ authOpenId idType extensionFields =
name :: Text name :: Text
name = "openid_identifier" name = "openid_identifier"
login
:: (AuthRoute -> Route master)
-> WidgetFor master ()
login tm = do login tm = do
ident <- newIdent ident <- newIdent
-- FIXME this is a hack to get GHC 7.6's type checker to allow the toWidget [cassius|##{ident}
-- code, but it shouldn't be necessary
let y :: a -> [(Text, Text)] -> Text
y = undefined
toWidget (\x -> [cassius|##{ident}
background: #fff url(http://www.myopenid.com/static/openid-icon-small.gif) no-repeat scroll 0pt 50%; background: #fff url(http://www.myopenid.com/static/openid-icon-small.gif) no-repeat scroll 0pt 50%;
padding-left: 18px; padding-left: 18px;
|] $ x `asTypeOf` y) |]
[whamlet| [whamlet|
$newline never $newline never
<form method="get" action="@{tm forwardUrl}"> <form method="get" action="@{tm forwardUrl}">
@ -62,7 +62,10 @@ $newline never
<input type="submit" value="_{Msg.LoginOpenID}"> <input type="submit" value="_{Msg.LoginOpenID}">
|] |]
dispatch :: Text -> [Text] -> AuthHandler master TypedContent dispatch
:: Text
-> [Text]
-> SubHandlerFor Auth master TypedContent
dispatch "GET" ["forward"] = do dispatch "GET" ["forward"] = do
roid <- runInputGet $ iopt textField name roid <- runInputGet $ iopt textField name
case roid of case roid of
@ -86,7 +89,11 @@ $newline never
completeHelper idType posts completeHelper idType posts
dispatch _ _ = notFound dispatch _ _ = notFound
completeHelper :: IdentifierType -> [(Text, Text)] -> AuthHandler master TypedContent completeHelper
:: (HasHandlerData env, SubHandlerSite env ~ Auth, YesodAuth (HandlerSite env))
=> IdentifierType
-> [(Text, Text)]
-> RIO env TypedContent
completeHelper idType gets' = do completeHelper idType gets' = do
manager <- authHttpManager manager <- authHttpManager
eres <- tryAny $ OpenId.authenticateClaimed gets' manager eres <- tryAny $ OpenId.authenticateClaimed gets' manager

View File

@ -3,6 +3,7 @@
{-# LANGUAGE RankNTypes #-} {-# LANGUAGE RankNTypes #-}
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-} {-# LANGUAGE GADTs #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Yesod.Auth.Rpxnow module Yesod.Auth.Rpxnow
( authRpxnow ( authRpxnow
) where ) where
@ -18,7 +19,7 @@ import Data.Text.Encoding.Error (lenientDecode)
import Control.Arrow ((***)) import Control.Arrow ((***))
import Network.HTTP.Types (renderQuery) import Network.HTTP.Types (renderQuery)
authRpxnow :: YesodAuth master authRpxnow :: forall master. YesodAuth master
=> String -- ^ app name => String -- ^ app name
-> String -- ^ key -> String -- ^ key
-> AuthPlugin master -> AuthPlugin master

View File

@ -45,6 +45,7 @@ library
, nonce >= 1.0.2 && < 1.1 , nonce >= 1.0.2 && < 1.1
, persistent >= 2.8 && < 2.10 , persistent >= 2.8 && < 2.10
, random >= 1.0.0.2 , random >= 1.0.0.2
, rio
, safe , safe
, shakespeare , shakespeare
, template-haskell , template-haskell
@ -63,13 +64,11 @@ library
build-depends: network-uri >= 2.6 build-depends: network-uri >= 2.6
exposed-modules: Yesod.Auth exposed-modules: Yesod.Auth
Yesod.Auth.BrowserId
Yesod.Auth.Dummy Yesod.Auth.Dummy
Yesod.Auth.Email Yesod.Auth.Email
Yesod.Auth.OpenId Yesod.Auth.OpenId
Yesod.Auth.Rpxnow Yesod.Auth.Rpxnow
Yesod.Auth.Message Yesod.Auth.Message
Yesod.Auth.GoogleEmail2
Yesod.Auth.Hardcoded Yesod.Auth.Hardcoded
Yesod.Auth.Util.PasswordStore Yesod.Auth.Util.PasswordStore
other-modules: Yesod.Auth.Routes other-modules: Yesod.Auth.Routes

View File

@ -1,5 +1,9 @@
# ChangeLog for yesod-core # ChangeLog for yesod-core
## 2.0.0.0
* Switch over to using `rio`
## 1.6.13 ## 1.6.13
* Introduce `maxContentLengthIO`. [issue #1588](https://github.com/yesodweb/yesod/issues/1588) and [PR #1589](https://github.com/yesodweb/yesod/pull/1589) * Introduce `maxContentLengthIO`. [issue #1588](https://github.com/yesodweb/yesod/issues/1588) and [PR #1589](https://github.com/yesodweb/yesod/pull/1589)

View File

@ -2,6 +2,7 @@
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-} {-# OPTIONS_GHC -fno-warn-warnings-deprecations #-}
module Yesod.Core module Yesod.Core
( -- * Type classes ( -- * Type classes
@ -29,10 +30,6 @@ module Yesod.Core
, AuthResult (..) , AuthResult (..)
, unauthorizedI , unauthorizedI
-- * Logging -- * Logging
, defaultMakeLogger
, defaultMessageLoggerSource
, defaultShouldLogIO
, formatLogMessage
, LogLevel (..) , LogLevel (..)
, logDebug , logDebug
, logInfo , logInfo
@ -67,8 +64,10 @@ module Yesod.Core
, ScriptLoadPosition (..) , ScriptLoadPosition (..)
, BottomOfHeadAsync , BottomOfHeadAsync
-- * Generalizing type classes -- * Generalizing type classes
, MonadHandler (..) , HasHandlerData (..)
, MonadWidget (..) , HasWidgetData (..)
, liftHandler
, liftWidget
-- * Approot -- * Approot
, guessApproot , guessApproot
, guessApprootOr , guessApprootOr
@ -76,7 +75,6 @@ module Yesod.Core
-- * Misc -- * Misc
, yesodVersion , yesodVersion
, yesodRender , yesodRender
, Yesod.Core.runFakeHandler
-- * LiteApp -- * LiteApp
, module Yesod.Core.Internal.LiteApp , module Yesod.Core.Internal.LiteApp
-- * Low-level -- * Low-level
@ -94,12 +92,9 @@ module Yesod.Core
, MonadIO (..) , MonadIO (..)
, MonadUnliftIO (..) , MonadUnliftIO (..)
, MonadResource (..) , MonadResource (..)
, MonadLogger , RIO
-- * Commonly referenced functions/datatypes -- * Commonly referenced functions/datatypes
, Application , Application
-- * Utilities
, showIntegral
, readIntegral
-- * Shakespeare -- * Shakespeare
-- ** Hamlet -- ** Hamlet
, hamlet , hamlet
@ -120,7 +115,6 @@ module Yesod.Core
import Yesod.Core.Content import Yesod.Core.Content
import Yesod.Core.Dispatch import Yesod.Core.Dispatch
import Yesod.Core.Handler import Yesod.Core.Handler
import Yesod.Core.Class.Handler
import Yesod.Core.Widget import Yesod.Core.Widget
import Yesod.Core.Json import Yesod.Core.Json
import Yesod.Core.Types import Yesod.Core.Types
@ -128,18 +122,16 @@ import Text.Shakespeare.I18N
import Yesod.Core.Internal.Util (formatW3 , formatRFC1123 , formatRFC822) import Yesod.Core.Internal.Util (formatW3 , formatRFC1123 , formatRFC822)
import Text.Blaze.Html (Html, toHtml, preEscapedToMarkup) import Text.Blaze.Html (Html, toHtml, preEscapedToMarkup)
import Control.Monad.Logger
import Control.Monad.Trans.Class (MonadTrans (..)) import Control.Monad.Trans.Class (MonadTrans (..))
import Yesod.Core.Internal.Session import Yesod.Core.Internal.Session
import Yesod.Core.Internal.Run (yesodRunner, yesodRender) import Yesod.Core.Internal.Run (yesodRunner, yesodRender)
import Yesod.Core.Class.Yesod import Yesod.Core.Class.Yesod
import Yesod.Core.Class.Dispatch import Yesod.Core.Class.Dispatch
import Yesod.Core.Class.Breadcrumbs import Yesod.Core.Class.Breadcrumbs
import qualified Yesod.Core.Internal.Run
import qualified Paths_yesod_core import qualified Paths_yesod_core
import Data.Version (showVersion) import Data.Version (showVersion)
import Yesod.Routes.Class import Yesod.Routes.Class
import UnliftIO (MonadIO (..), MonadUnliftIO (..)) import RIO
import Control.Monad.Trans.Resource (MonadResource (..)) import Control.Monad.Trans.Resource (MonadResource (..))
import Yesod.Core.Internal.LiteApp import Yesod.Core.Internal.LiteApp
@ -149,17 +141,11 @@ import Text.Lucius
import Text.Julius import Text.Julius
import Network.Wai (Application) import Network.Wai (Application)
runFakeHandler :: (Yesod site, MonadIO m) =>
SessionMap
-> (site -> Logger)
-> site
-> HandlerT site IO a
-> m (Either ErrorResponse a)
runFakeHandler = Yesod.Core.Internal.Run.runFakeHandler
{-# DEPRECATED runFakeHandler "import runFakeHandler from Yesod.Core.Unsafe" #-}
-- | Return an 'Unauthorized' value, with the given i18n message. -- | Return an 'Unauthorized' value, with the given i18n message.
unauthorizedI :: (MonadHandler m, RenderMessage (HandlerSite m) msg) => msg -> m AuthResult unauthorizedI
:: (HasHandlerData env, RenderMessage (HandlerSite env) msg)
=> msg
-> RIO env AuthResult
unauthorizedI msg = do unauthorizedI msg = do
mr <- getMessageRender mr <- getMessageRender
return $ Unauthorized $ mr msg return $ Unauthorized $ mr msg
@ -178,12 +164,3 @@ maybeAuthorized :: Yesod site
maybeAuthorized r isWrite = do maybeAuthorized r isWrite = do
x <- isAuthorized r isWrite x <- isAuthorized r isWrite
return $ if x == Authorized then Just r else Nothing return $ if x == Authorized then Just r else Nothing
showIntegral :: Integral a => a -> String
showIntegral x = show (fromIntegral x :: Integer)
readIntegral :: Num a => String -> Maybe a
readIntegral s =
case reads s of
(i, _):_ -> Just $ fromInteger i
[] -> Nothing

View File

@ -4,8 +4,10 @@
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Yesod.Core.Class.Dispatch where module Yesod.Core.Class.Dispatch where
import RIO
import qualified Network.Wai as W import qualified Network.Wai as W
import Yesod.Core.Types import Yesod.Core.Types
import Yesod.Core.Content (ToTypedContent (..)) import Yesod.Core.Content (ToTypedContent (..))
@ -30,8 +32,8 @@ instance YesodSubDispatch WaiSubsiteWithAuth master where
ysreParentRunner handlert ysreParentEnv (fmap ysreToParentRoute route) req ysreParentRunner handlert ysreParentEnv (fmap ysreToParentRoute route) req
where where
route = Just $ WaiSubsiteWithAuthRoute (W.pathInfo req) [] route = Just $ WaiSubsiteWithAuthRoute (W.pathInfo req) []
WaiSubsiteWithAuth set = ysreGetSub $ yreSite $ ysreParentEnv WaiSubsiteWithAuth set' = ysreGetSub $ yreSite $ ysreParentEnv
handlert = sendWaiApplication set handlert = sendWaiApplication set'
subHelper subHelper
:: ToTypedContent content :: ToTypedContent content
@ -39,14 +41,15 @@ subHelper
-> YesodSubRunnerEnv child master -> YesodSubRunnerEnv child master
-> Maybe (Route child) -> Maybe (Route child)
-> W.Application -> W.Application
subHelper (SubHandlerFor f) YesodSubRunnerEnv {..} mroute = subHelper subHandler YesodSubRunnerEnv {..} mroute =
ysreParentRunner handler ysreParentEnv (fmap ysreToParentRoute mroute) ysreParentRunner handler ysreParentEnv (fmap ysreToParentRoute mroute)
where where
handler = fmap toTypedContent $ HandlerFor $ \hd -> handler = fmap toTypedContent $ do
hd <- view subHandlerDataL
let rhe = handlerEnv hd let rhe = handlerEnv hd
rhe' = rhe rhe' = rhe
{ rheRoute = mroute { rheRoute = mroute
, rheChild = ysreGetSub $ yreSite ysreParentEnv , rheChild = ysreGetSub $ yreSite ysreParentEnv
, rheRouteToMaster = ysreToParentRoute , rheRouteToMaster = ysreToParentRoute
} }
in f hd { handlerEnv = rhe' } runRIO hd { handlerEnv = rhe' } subHandler

View File

@ -1,120 +0,0 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE UndecidableInstances #-}
module Yesod.Core.Class.Handler
( MonadHandler (..)
, MonadWidget (..)
, liftHandlerT
, liftWidgetT
) where
import Yesod.Core.Types
import Control.Monad.Logger (MonadLogger)
import Control.Monad.Trans.Resource (MonadResource)
import Control.Monad.Trans.Class (lift)
import Data.Conduit.Internal (Pipe, ConduitM)
import Control.Monad.Trans.Identity ( IdentityT)
import Control.Monad.Trans.List ( ListT )
import Control.Monad.Trans.Maybe ( MaybeT )
import Control.Monad.Trans.Except ( ExceptT )
import Control.Monad.Trans.Reader ( ReaderT )
import Control.Monad.Trans.State ( StateT )
import Control.Monad.Trans.Writer ( WriterT )
import Control.Monad.Trans.RWS ( RWST )
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, MonadLogger m) => MonadHandler m where
type HandlerSite m
type SubHandlerSite m
liftHandler :: HandlerFor (HandlerSite m) a -> m a
liftSubHandler :: SubHandlerFor (SubHandlerSite m) (HandlerSite m) a -> m a
liftHandlerT :: MonadHandler m => HandlerFor (HandlerSite m) a -> m a
liftHandlerT = liftHandler
{-# DEPRECATED liftHandlerT "Use liftHandler instead" #-}
instance MonadHandler (HandlerFor site) where
type HandlerSite (HandlerFor site) = site
type SubHandlerSite (HandlerFor site) = site
liftHandler = id
{-# INLINE liftHandler #-}
liftSubHandler (SubHandlerFor f) = HandlerFor f
{-# INLINE liftSubHandler #-}
instance MonadHandler (SubHandlerFor sub master) where
type HandlerSite (SubHandlerFor sub master) = master
type SubHandlerSite (SubHandlerFor sub master) = sub
liftHandler (HandlerFor f) = SubHandlerFor $ \hd -> f hd
{ handlerEnv =
let rhe = handlerEnv hd
in rhe
{ rheRoute = fmap (rheRouteToMaster rhe) (rheRoute rhe)
, rheRouteToMaster = id
, rheChild = rheSite rhe
}
}
{-# INLINE liftHandler #-}
liftSubHandler = id
{-# INLINE liftSubHandler #-}
instance MonadHandler (WidgetFor site) where
type HandlerSite (WidgetFor site) = site
type SubHandlerSite (WidgetFor site) = site
liftHandler (HandlerFor f) = WidgetFor $ f . wdHandler
{-# INLINE liftHandler #-}
liftSubHandler (SubHandlerFor f) = WidgetFor $ f . wdHandler
{-# INLINE liftSubHandler #-}
#define GO(T) instance MonadHandler m => MonadHandler (T m) where type HandlerSite (T m) = HandlerSite m; type SubHandlerSite (T m) = SubHandlerSite m; liftHandler = lift . liftHandler; liftSubHandler = lift . liftSubHandler
#define GOX(X, T) instance (X, MonadHandler m) => MonadHandler (T m) where type HandlerSite (T m) = HandlerSite m; type SubHandlerSite (T m) = SubHandlerSite m; liftHandler = lift . liftHandler; liftSubHandler = lift . liftSubHandler
GO(IdentityT)
GO(ListT)
GO(MaybeT)
GO(ExceptT e)
GO(ReaderT r)
GO(StateT s)
GOX(Monoid w, WriterT w)
GOX(Monoid w, RWST r w s)
GOX(Monoid w, Strict.RWST r w s)
GO(Strict.StateT s)
GOX(Monoid w, Strict.WriterT w)
GO(Pipe l i o u)
GO(ConduitM i o)
#undef GO
#undef GOX
class MonadHandler m => MonadWidget m where
liftWidget :: WidgetFor (HandlerSite m) a -> m a
instance MonadWidget (WidgetFor site) where
liftWidget = id
{-# INLINE liftWidget #-}
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)
GO(ExceptT e)
GO(ReaderT r)
GO(StateT s)
GOX(Monoid w, WriterT w)
GOX(Monoid w, RWST r w s)
GOX(Monoid w, Strict.RWST r w s)
GO(Strict.StateT s)
GOX(Monoid w, Strict.WriterT w)
GO(Pipe l i o u)
GO(ConduitM i o)
#undef GO
#undef GOX

View File

@ -1,9 +1,12 @@
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TemplateHaskell #-}
module Yesod.Core.Class.Yesod where module Yesod.Core.Class.Yesod where
import RIO
import Yesod.Core.Content import Yesod.Core.Content
import Yesod.Core.Handler import Yesod.Core.Handler
@ -12,11 +15,6 @@ import Yesod.Routes.Class
import Data.ByteString.Builder (Builder) import Data.ByteString.Builder (Builder)
import Data.Text.Encoding (encodeUtf8Builder) import Data.Text.Encoding (encodeUtf8Builder)
import Control.Arrow ((***), second) import Control.Arrow ((***), second)
import Control.Exception (bracket)
import Control.Monad (forM, when, void)
import Control.Monad.IO.Class (MonadIO (liftIO))
import Control.Monad.Logger (LogLevel (LevelInfo, LevelOther),
LogSource, logErrorS)
import Control.Monad.Trans.Resource (InternalState, createInternalState, closeInternalState) import Control.Monad.Trans.Resource (InternalState, createInternalState, closeInternalState)
import qualified Data.ByteString.Char8 as S8 import qualified Data.ByteString.Char8 as S8
import qualified Data.ByteString.Lazy as L import qualified Data.ByteString.Lazy as L
@ -30,15 +28,12 @@ import qualified Data.Text as T
import qualified Data.Text.Encoding as TE import qualified Data.Text.Encoding as TE
import qualified Data.Text.Encoding.Error as TEE import qualified Data.Text.Encoding.Error as TEE
import Data.Text.Lazy.Builder (toLazyText) import Data.Text.Lazy.Builder (toLazyText)
import Data.Text.Lazy.Encoding (encodeUtf8) import qualified Data.Text.Lazy.Encoding as TLE (encodeUtf8)
import Data.Word (Word64)
import Language.Haskell.TH.Syntax (Loc (..)) import Language.Haskell.TH.Syntax (Loc (..))
import Network.HTTP.Types (encodePath) import Network.HTTP.Types (encodePath)
import qualified Network.Wai as W import qualified Network.Wai as W
import Network.Wai.Parse (lbsBackEnd, import Network.Wai.Parse (lbsBackEnd,
tempFileBackEnd) tempFileBackEnd)
import Network.Wai.Logger (ZonedDate, clockDateCacher)
import System.Log.FastLogger
import Text.Blaze (customAttribute, textTag, import Text.Blaze (customAttribute, textTag,
toValue, (!), toValue, (!),
preEscapedToMarkup) preEscapedToMarkup)
@ -53,7 +48,6 @@ import Yesod.Core.Internal.Session
import Yesod.Core.Widget import Yesod.Core.Widget
import Data.CaseInsensitive (CI) import Data.CaseInsensitive (CI)
import qualified Network.Wai.Request import qualified Network.Wai.Request
import Data.IORef
-- | Define settings for a Yesod applications. All methods have intelligent -- | Define settings for a Yesod applications. All methods have intelligent
-- defaults, and therefore no implementation is required. -- defaults, and therefore no implementation is required.
@ -215,29 +209,15 @@ class RenderRoute site => Yesod site where
maximumContentLengthIO :: site -> Maybe (Route site) -> IO (Maybe Word64) maximumContentLengthIO :: site -> Maybe (Route site) -> IO (Maybe Word64)
maximumContentLengthIO a b = pure $ maximumContentLength a b maximumContentLengthIO a b = pure $ maximumContentLength a b
-- | Creates a @Logger@ to use for log messages. -- | Get the 'LogFunc' from the foundation type.
-- --
-- Note that a common technique (endorsed by the scaffolding) is to create -- If this function returns a @Nothing@ (the default), the Yesod
-- a @Logger@ value and place it in your foundation datatype, and have this -- codebase itself will create a log function for you with some
-- method return that already created value. That way, you can use that -- default settings. Overriding this allows you to have more
-- same @Logger@ for printing messages during app initialization. -- control, and also to share your log function with code outside
-- -- of your handlers.
-- Default: the 'defaultMakeLogger' function. getLogFunc :: site -> Maybe LogFunc
makeLogger :: site -> IO Logger getLogFunc _ = Nothing
makeLogger _ = defaultMakeLogger
-- | Send a message to the @Logger@ provided by @getLogger@.
--
-- Default: the 'defaultMessageLoggerSource' function, using
-- 'shouldLogIO' to check whether we should log.
messageLoggerSource :: site
-> Logger
-> Loc -- ^ position in source code
-> LogSource
-> LogLevel
-> LogStr -- ^ message
-> IO ()
messageLoggerSource site = defaultMessageLoggerSource $ shouldLogIO site
-- | Where to Load sripts from. We recommend the default value, -- | Where to Load sripts from. We recommend the default value,
-- 'BottomOfBody'. -- 'BottomOfBody'.
@ -268,14 +248,6 @@ class RenderRoute site => Yesod site where
| size <= 50000 = FileUploadMemory lbsBackEnd | size <= 50000 = FileUploadMemory lbsBackEnd
fileUpload _ _ = FileUploadDisk tempFileBackEnd fileUpload _ _ = FileUploadDisk tempFileBackEnd
-- | Should we log the given log source/level combination.
--
-- Default: the 'defaultShouldLogIO' function.
--
-- Since 1.2.4
shouldLogIO :: site -> LogSource -> LogLevel -> IO Bool
shouldLogIO _ = defaultShouldLogIO
-- | A Yesod middleware, which will wrap every handler function. This -- | A Yesod middleware, which will wrap every handler function. This
-- allows you to run code before and after a normal handler. -- allows you to run code before and after a normal handler.
-- --
@ -312,44 +284,6 @@ class RenderRoute site => Yesod site where
^{body} ^{body}
|] |]
-- | Default implementation of 'makeLogger'. Sends to stdout and
-- automatically flushes on each write.
--
-- Since 1.4.10
defaultMakeLogger :: IO Logger
defaultMakeLogger = do
loggerSet' <- newStdoutLoggerSet defaultBufSize
(getter, _) <- clockDateCacher
return $! Logger loggerSet' getter
-- | Default implementation of 'messageLoggerSource'. Checks if the
-- message should be logged using the provided function, and if so,
-- formats using 'formatLogMessage'. You can use 'defaultShouldLogIO'
-- as the provided function.
--
-- Since 1.4.10
defaultMessageLoggerSource ::
(LogSource -> LogLevel -> IO Bool) -- ^ Check whether we should
-- log this
-> Logger
-> Loc -- ^ position in source code
-> LogSource
-> LogLevel
-> LogStr -- ^ message
-> IO ()
defaultMessageLoggerSource ckLoggable logger loc source level msg = do
loggable <- ckLoggable source level
when loggable $
formatLogMessage (loggerDate logger) loc source level msg >>=
loggerPutStr logger
-- | Default implementation of 'shouldLog'. Logs everything at or
-- above 'LevelInfo'.
--
-- Since 1.4.10
defaultShouldLogIO :: LogSource -> LogLevel -> IO Bool
defaultShouldLogIO _ level = return $ level >= LevelInfo
-- | Default implementation of 'yesodMiddleware'. Adds the response header -- | Default implementation of 'yesodMiddleware'. Adds the response header
-- \"Vary: Accept, Accept-Language\", \"X-XSS-Protection: 1; mode=block\", and -- \"Vary: Accept, Accept-Language\", \"X-XSS-Protection: 1; mode=block\", and
-- performs authorization checks. -- performs authorization checks.
@ -418,12 +352,10 @@ sameSiteSession s = (fmap . fmap) secureSessionCookies
sslOnlyMiddleware :: Int -- ^ minutes sslOnlyMiddleware :: Int -- ^ minutes
-> HandlerFor site res -> HandlerFor site res
-> HandlerFor site res -> HandlerFor site res
sslOnlyMiddleware timeout handler = do sslOnlyMiddleware timeout' handler = do
addHeader "Strict-Transport-Security" addHeader "Strict-Transport-Security"
$ T.pack $ concat [ "max-age=" $ utf8BuilderToText -- FIXME should we store headers as Utf8Builders?
, show $ timeout * 60 $ "max-age=" <> display (timeout' * 60) <> "; includeSubDomains"
, "; includeSubDomains"
]
handler handler
-- | Check if a given request is authorized via 'isAuthorized' and -- | Check if a given request is authorized via 'isAuthorized' and
@ -449,7 +381,7 @@ authorizationCheck = getCurrentRoute >>= maybe (return ()) checkUrl
void $ redirect url' void $ redirect url'
provideRepType typeJson $ provideRepType typeJson $
void notAuthenticated void notAuthenticated
Unauthorized s' -> permissionDenied s' Unauthorized s' -> permissionDenied $ display s'
-- | Calls 'csrfCheckMiddleware' with 'isWriteRequest', 'defaultCsrfHeaderName', and 'defaultCsrfParamName' as parameters. -- | Calls 'csrfCheckMiddleware' with 'isWriteRequest', 'defaultCsrfHeaderName', and 'defaultCsrfParamName' as parameters.
-- --
@ -520,19 +452,17 @@ defaultCsrfMiddleware = defaultCsrfSetCookieMiddleware . defaultCsrfCheckMiddlew
widgetToPageContent :: Yesod site widgetToPageContent :: Yesod site
=> WidgetFor site () => WidgetFor site ()
-> HandlerFor site (PageContent (Route site)) -> HandlerFor site (PageContent (Route site))
widgetToPageContent w = HandlerFor $ \hd -> do widgetToPageContent w = do
master <- unHandlerFor getYesod hd master <- getYesod
ref <- newIORef mempty ref <- newIORef mempty
unWidgetFor w WidgetData hd <- ask
{ wdRef = ref runRIO WidgetData { wdRef = ref, wdHandler = hd } w
, wdHandler = hd
}
GWData (Body body) (Last mTitle) scripts' stylesheets' style jscript (Head head') <- readIORef ref GWData (Body body) (Last mTitle) scripts' stylesheets' style jscript (Head head') <- readIORef ref
let title = maybe mempty unTitle mTitle let title = maybe mempty unTitle mTitle
scripts = runUniqueList scripts' scripts = runUniqueList scripts'
stylesheets = runUniqueList stylesheets' stylesheets = runUniqueList stylesheets'
flip unHandlerFor hd $ do do -- just to reduce whitespace diffs
render <- getUrlRenderParams render <- getUrlRenderParams
let renderLoc x = let renderLoc x =
case x of case x of
@ -542,7 +472,7 @@ widgetToPageContent w = HandlerFor $ \hd -> do
css <- forM (Map.toList style) $ \(mmedia, content) -> do css <- forM (Map.toList style) $ \(mmedia, content) -> do
let rendered = toLazyText $ content render let rendered = toLazyText $ content render
x <- addStaticContent "css" "text/css; charset=utf-8" x <- addStaticContent "css" "text/css; charset=utf-8"
$ encodeUtf8 rendered $ TLE.encodeUtf8 $ rendered
return (mmedia, return (mmedia,
case x of case x of
Nothing -> Left $ preEscapedToMarkup rendered Nothing -> Left $ preEscapedToMarkup rendered
@ -552,7 +482,7 @@ widgetToPageContent w = HandlerFor $ \hd -> do
Nothing -> return Nothing Nothing -> return Nothing
Just s -> do Just s -> do
x <- addStaticContent "js" "text/javascript; charset=utf-8" x <- addStaticContent "js" "text/javascript; charset=utf-8"
$ encodeUtf8 $ renderJavascriptUrl render s $ TLE.encodeUtf8 $ renderJavascriptUrl render s
return $ renderLoc x return $ renderLoc x
-- modernizr should be at the end of the <head> http://www.modernizr.com/docs/#installing -- modernizr should be at the end of the <head> http://www.modernizr.com/docs/#installing
@ -673,7 +603,7 @@ defaultErrorHandler (InvalidArgs ia) = selectRep $ do
provideRep $ return $ ("Invalid Arguments: " <> T.intercalate " " ia) provideRep $ return $ ("Invalid Arguments: " <> T.intercalate " " ia)
defaultErrorHandler (InternalError e) = do defaultErrorHandler (InternalError e) = do
$logErrorS "yesod-core" e logErrorS "yesod-core" $ display e
selectRep $ do selectRep $ do
provideRep $ defaultLayout $ defaultMessageWidget provideRep $ defaultLayout $ defaultMessageWidget
"Internal Server Error" "Internal Server Error"
@ -711,43 +641,6 @@ asyncHelper render scripts jscript jsLoc =
Nothing -> Nothing Nothing -> Nothing
Just j -> Just $ jelper j Just j -> Just $ jelper j
-- | Default formatting for log messages. When you use
-- the template haskell logging functions for to log with information
-- about the source location, that information will be appended to
-- the end of the log. When you use the non-TH logging functions,
-- like 'logDebugN', this function does not include source
-- information. This currently works by checking to see if the
-- package name is the string \"\<unknown\>\". This is a hack,
-- but it removes some of the visual clutter from non-TH logs.
--
-- Since 1.4.10
formatLogMessage :: IO ZonedDate
-> Loc
-> LogSource
-> LogLevel
-> LogStr -- ^ message
-> IO LogStr
formatLogMessage getdate loc src level msg = do
now <- getdate
return $ mempty
`mappend` toLogStr now
`mappend` " ["
`mappend` (case level of
LevelOther t -> toLogStr t
_ -> toLogStr $ drop 5 $ show level)
`mappend` (if T.null src
then mempty
else "#" `mappend` toLogStr src)
`mappend` "] "
`mappend` msg
`mappend` sourceSuffix
`mappend` "\n"
where
sourceSuffix = if loc_package loc == "<unknown>" then "" else mempty
`mappend` " @("
`mappend` toLogStr (fileLocationToString loc)
`mappend` ")"
-- | Customize the cookies used by the session backend. You may -- | Customize the cookies used by the session backend. You may
-- use this function on your definition of 'makeSessionBackend'. -- use this function on your definition of 'makeSessionBackend'.
-- --

View File

@ -3,6 +3,7 @@
{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Yesod.Core.Dispatch module Yesod.Core.Dispatch
( -- * Quasi-quoted routing ( -- * Quasi-quoted routing
parseRoutes parseRoutes
@ -38,7 +39,6 @@ module Yesod.Core.Dispatch
import Prelude hiding (exp) import Prelude hiding (exp)
import Yesod.Core.Internal.TH import Yesod.Core.Internal.TH
import Language.Haskell.TH.Syntax (qLocation)
import Web.PathPieces import Web.PathPieces
@ -68,28 +68,43 @@ import Network.Wai.Middleware.AcceptOverride
import Network.Wai.Middleware.RequestLogger import Network.Wai.Middleware.RequestLogger
import Network.Wai.Middleware.Gzip import Network.Wai.Middleware.Gzip
import Network.Wai.Middleware.MethodOverride import Network.Wai.Middleware.MethodOverride
import System.Log.FastLogger (fromLogStr)
import qualified Network.Wai.Handler.Warp import qualified Network.Wai.Handler.Warp
import System.Log.FastLogger
import Control.Monad.Logger
import Control.Monad (when) import Control.Monad (when)
import qualified Paths_yesod_core import qualified Paths_yesod_core
import Data.Version (showVersion) import Data.Version (showVersion)
import RIO
-- | Get a 'LogFunc' from the site, or create if needed. Returns an
-- @IORef@ with a finalizer to clean up when done.
makeLogFunc :: Yesod site => site -> IO (LogFunc, IORef ())
makeLogFunc site =
case getLogFunc site of
Just logFunc -> do
ref <- newIORef ()
pure (logFunc, ref)
Nothing -> do
(logFunc, cleanup) <- logOptionsHandle stderr False >>= newLogFunc
ref <- newIORef ()
_ <- mkWeakIORef ref cleanup
pure (logFunc, ref)
-- | Convert the given argument into a WAI application, executable with any WAI -- | Convert the given argument into a WAI application, executable with any WAI
-- handler. This function will provide no middlewares; if you want commonly -- handler. This function will provide no middlewares; if you want commonly
-- used middlewares, please use 'toWaiApp'. -- used middlewares, please use 'toWaiApp'.
toWaiAppPlain :: YesodDispatch site => site -> IO W.Application toWaiAppPlain :: YesodDispatch site => site -> IO W.Application
toWaiAppPlain site = do toWaiAppPlain site = do
logger <- makeLogger site (logFunc, cleanup) <- makeLogFunc site
sb <- makeSessionBackend site sb <- makeSessionBackend site
getMaxExpires <- getGetMaxExpires getMaxExpires <- getGetMaxExpires
return $ toWaiAppYre YesodRunnerEnv return $ toWaiAppYre YesodRunnerEnv
{ yreLogger = logger { yreLogFunc = logFunc
, yreSite = site , yreSite = site
, yreSessionBackend = sb , yreSessionBackend = sb
, yreGen = defaultGen , yreGen = defaultGen
, yreGetMaxExpires = getMaxExpires , yreGetMaxExpires = getMaxExpires
, yreCleanup = cleanup
} }
defaultGen :: IO Int defaultGen :: IO Int
@ -143,28 +158,28 @@ toWaiAppYre yre req =
-- * Accept header override with the _accept query string parameter -- * Accept header override with the _accept query string parameter
toWaiApp :: YesodDispatch site => site -> IO W.Application toWaiApp :: YesodDispatch site => site -> IO W.Application
toWaiApp site = do toWaiApp site = do
logger <- makeLogger site (logFunc, cleanup) <- makeLogFunc site
toWaiAppLogger logger site toWaiAppLogger logFunc cleanup site
toWaiAppLogger :: YesodDispatch site => Logger -> site -> IO W.Application toWaiAppLogger
toWaiAppLogger logger site = do :: YesodDispatch site
=> LogFunc
-> IORef () -- ^ cleanup
-> site
-> IO W.Application
toWaiAppLogger logFunc cleanup site = do
sb <- makeSessionBackend site sb <- makeSessionBackend site
getMaxExpires <- getGetMaxExpires getMaxExpires <- getGetMaxExpires
let yre = YesodRunnerEnv let yre = YesodRunnerEnv
{ yreLogger = logger { yreLogFunc = logFunc
, yreSite = site , yreSite = site
, yreSessionBackend = sb , yreSessionBackend = sb
, yreGen = defaultGen , yreGen = defaultGen
, yreGetMaxExpires = getMaxExpires , yreGetMaxExpires = getMaxExpires
, yreCleanup = cleanup
} }
messageLoggerSource runRIO logFunc $ logInfoS "yesod-core" "Application launched"
site middleware <- mkDefaultMiddlewares logFunc
logger
$(qLocation >>= liftLoc)
"yesod-core"
LevelInfo
(toLogStr ("Application launched" :: S.ByteString))
middleware <- mkDefaultMiddlewares logger
return $ middleware $ toWaiAppYre yre return $ middleware $ toWaiAppYre yre
-- | A convenience method to run an application using the Warp webserver on the -- | A convenience method to run an application using the Warp webserver on the
@ -178,19 +193,15 @@ toWaiAppLogger logger site = do
-- Since 1.2.0 -- Since 1.2.0
warp :: YesodDispatch site => Int -> site -> IO () warp :: YesodDispatch site => Int -> site -> IO ()
warp port site = do warp port site = do
logger <- makeLogger site (logFunc, cleanup) <- makeLogFunc site
toWaiAppLogger logger site >>= Network.Wai.Handler.Warp.runSettings ( toWaiAppLogger logFunc cleanup site >>= Network.Wai.Handler.Warp.runSettings (
Network.Wai.Handler.Warp.setPort port $ Network.Wai.Handler.Warp.setPort port $
Network.Wai.Handler.Warp.setServerName serverValue $ Network.Wai.Handler.Warp.setServerName serverValue $
Network.Wai.Handler.Warp.setOnException (\_ e -> Network.Wai.Handler.Warp.setOnException (\_ e ->
when (shouldLog' e) $ when (shouldLog' e) $
messageLoggerSource runRIO logFunc $
site logErrorS "yesod-core" $
logger "Exception from Warp: " <> displayShow e)
$(qLocation >>= liftLoc)
"yesod-core"
LevelError
(toLogStr $ "Exception from Warp: " ++ show e))
Network.Wai.Handler.Warp.defaultSettings) Network.Wai.Handler.Warp.defaultSettings)
where where
shouldLog' = Network.Wai.Handler.Warp.defaultShouldDisplayException shouldLog' = Network.Wai.Handler.Warp.defaultShouldDisplayException
@ -207,10 +218,14 @@ serverValue = S8.pack $ concat
-- | A default set of middlewares. -- | A default set of middlewares.
-- --
-- Since 1.2.0 -- Since 1.2.0
mkDefaultMiddlewares :: Logger -> IO W.Middleware mkDefaultMiddlewares :: LogFunc -> IO W.Middleware
mkDefaultMiddlewares logger = do mkDefaultMiddlewares logFunc = do
logWare <- mkRequestLogger def logWare <- mkRequestLogger def
{ destination = Network.Wai.Middleware.RequestLogger.Logger $ loggerSet logger { destination = Network.Wai.Middleware.RequestLogger.Callback $
runRIO logFunc .
logInfoS "yesod-core" .
displayBytesUtf8 .
fromLogStr
, outputFormat = Apache FromSocket , outputFormat = Apache FromSocket
} }
return $ logWare . defaultMiddlewaresNoLogging return $ logWare . defaultMiddlewaresNoLogging

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,4 @@
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternGuards #-} {-# LANGUAGE PatternGuards #-}
{-# LANGUAGE RankNTypes #-} {-# LANGUAGE RankNTypes #-}
@ -8,16 +9,13 @@
module Yesod.Core.Internal.Run where module Yesod.Core.Internal.Run where
import RIO
import Yesod.Core.Internal.Response import Yesod.Core.Internal.Response
import Data.ByteString.Builder (toLazyByteString) import Data.ByteString.Builder (toLazyByteString)
import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Lazy as BL
import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Monad.Logger (LogLevel (LevelError), LogSource,
liftLoc)
import Control.Monad.Trans.Resource (runResourceT, withInternalState, runInternalState, InternalState) import Control.Monad.Trans.Resource (runResourceT, withInternalState, runInternalState, InternalState)
import qualified Data.ByteString as S import qualified Data.ByteString as S
import qualified Data.ByteString.Char8 as S8 import qualified Data.ByteString.Char8 as S8
import qualified Data.IORef as I
import qualified Data.Map as Map import qualified Data.Map as Map
import Data.Maybe (isJust, fromMaybe) import Data.Maybe (isJust, fromMaybe)
import Data.Monoid (appEndo) import Data.Monoid (appEndo)
@ -25,11 +23,9 @@ import Data.Text (Text)
import qualified Data.Text as T import qualified Data.Text as T
import Data.Text.Encoding (encodeUtf8, decodeUtf8With) import Data.Text.Encoding (encodeUtf8, decodeUtf8With)
import Data.Text.Encoding.Error (lenientDecode) import Data.Text.Encoding.Error (lenientDecode)
import Language.Haskell.TH.Syntax (Loc, qLocation)
import qualified Network.HTTP.Types as H import qualified Network.HTTP.Types as H
import Network.Wai import Network.Wai
import Network.Wai.Internal import Network.Wai.Internal
import System.Log.FastLogger (LogStr, toLogStr)
import Yesod.Core.Content import Yesod.Core.Content
import Yesod.Core.Class.Yesod import Yesod.Core.Class.Yesod
import Yesod.Core.Types import Yesod.Core.Types
@ -38,7 +34,6 @@ import Yesod.Core.Internal.Request (parseWaiRequest,
import Yesod.Core.Internal.Util (getCurrentMaxExpiresRFC1123) import Yesod.Core.Internal.Util (getCurrentMaxExpiresRFC1123)
import Yesod.Routes.Class (Route, renderRoute) import Yesod.Routes.Class (Route, renderRoute)
import Control.DeepSeq (($!!), NFData) import Control.DeepSeq (($!!), NFData)
import UnliftIO.Exception
-- | Convert a synchronous exception into an ErrorResponse -- | Convert a synchronous exception into an ErrorResponse
toErrorHandler :: SomeException -> IO ErrorResponse toErrorHandler :: SomeException -> IO ErrorResponse
@ -67,13 +62,13 @@ basicRunHandler :: ToTypedContent c
basicRunHandler rhe handler yreq resState = do basicRunHandler rhe handler yreq resState = do
-- Create a mutable ref to hold the state. We use mutable refs so -- Create a mutable ref to hold the state. We use mutable refs so
-- that the updates will survive runtime exceptions. -- that the updates will survive runtime exceptions.
istate <- I.newIORef defState istate <- newIORef defState
-- Run the handler itself, capturing any runtime exceptions and -- Run the handler itself, capturing any runtime exceptions and
-- converting them into a @HandlerContents@ -- converting them into a @HandlerContents@
contents' <- catchAny contents' <- catchAny
(do (do
res <- unHandlerFor handler (hd istate) res <- runRIO (hd istate) handler
tc <- evaluate (toTypedContent res) tc <- evaluate (toTypedContent res)
-- Success! Wrap it up in an @HCContent@ -- Success! Wrap it up in an @HCContent@
return (HCContent defaultStatus tc)) return (HCContent defaultStatus tc))
@ -83,7 +78,7 @@ basicRunHandler rhe handler yreq resState = do
Nothing -> HCError <$> toErrorHandler e) Nothing -> HCError <$> toErrorHandler e)
-- Get the raw state and return -- Get the raw state and return
state <- I.readIORef istate state <- readIORef istate
return (state, contents') return (state, contents')
where where
defState = GHState defState = GHState
@ -94,7 +89,7 @@ basicRunHandler rhe handler yreq resState = do
, ghsCacheBy = mempty , ghsCacheBy = mempty
, ghsHeaders = mempty , ghsHeaders = mempty
} }
hd istate = HandlerData hd istate = HandlerData $ SubHandlerData
{ handlerRequest = yreq { handlerRequest = yreq
, handlerEnv = rhe , handlerEnv = rhe
, handlerState = istate , handlerState = istate
@ -203,12 +198,11 @@ runHandler rhe@RunHandlerEnv {..} handler yreq = withInternalState $ \resState -
headers headers
contents3 contents3
safeEh :: (Loc -> LogSource -> LogLevel -> LogStr -> IO ()) safeEh :: LogFunc -> ErrorResponse -> YesodApp
-> ErrorResponse safeEh logFunc er req = do
-> YesodApp runRIO logFunc $
safeEh log' er req = do logErrorS "yesod-core" $
liftIO $ log' $(qLocation >>= liftLoc) "yesod-core" LevelError "Error handler errored out: " <> displayShow er
$ toLogStr $ "Error handler errored out: " ++ show er
return $ YRPlain return $ YRPlain
H.status500 H.status500
[] []
@ -238,14 +232,14 @@ safeEh log' er req = do
-- @HandlerT@'s return value. -- @HandlerT@'s return value.
runFakeHandler :: (Yesod site, MonadIO m) => runFakeHandler :: (Yesod site, MonadIO m) =>
SessionMap SessionMap
-> (site -> Logger) -> LogFunc
-> site -> site
-> HandlerFor site a -> HandlerFor site a
-> m (Either ErrorResponse a) -> m (Either ErrorResponse a)
runFakeHandler fakeSessionMap logger site handler = liftIO $ do runFakeHandler fakeSessionMap logFunc site handler = liftIO $ do
ret <- I.newIORef (Left $ InternalError "runFakeHandler: no result") ret <- newIORef (Left $ InternalError "runFakeHandler: no result")
maxExpires <- getCurrentMaxExpiresRFC1123 maxExpires <- getCurrentMaxExpiresRFC1123
let handler' = liftIO . I.writeIORef ret . Right =<< handler let handler' = writeIORef ret . Right =<< handler
let yapp = runHandler let yapp = runHandler
RunHandlerEnv RunHandlerEnv
{ rheRender = yesodRender site $ resolveApproot site fakeWaiRequest { rheRender = yesodRender site $ resolveApproot site fakeWaiRequest
@ -254,13 +248,13 @@ runFakeHandler fakeSessionMap logger site handler = liftIO $ do
, rheChild = site , rheChild = site
, rheSite = site , rheSite = site
, rheUpload = fileUpload site , rheUpload = fileUpload site
, rheLog = messageLoggerSource site $ logger site , rheLogFunc = logFunc
, rheOnError = errHandler , rheOnError = errHandler
, rheMaxExpires = maxExpires , rheMaxExpires = maxExpires
} }
handler' handler'
errHandler err req = do errHandler err req = do
liftIO $ I.writeIORef ret (Left err) writeIORef ret (Left err)
return $ YRPlain return $ YRPlain
H.status500 H.status500
[] []
@ -296,7 +290,7 @@ runFakeHandler fakeSessionMap logger site handler = liftIO $ do
, reqSession = fakeSessionMap , reqSession = fakeSessionMap
} }
_ <- runResourceT $ yapp fakeRequest _ <- runResourceT $ yapp fakeRequest
I.readIORef ret readIORef ret
yesodRunner :: (ToTypedContent res, Yesod site) yesodRunner :: (ToTypedContent res, Yesod site)
=> HandlerFor site res => HandlerFor site res
@ -318,8 +312,7 @@ yesodRunner handler' YesodRunnerEnv {..} route req sendResponse = do
Left yreq' -> yreq' Left yreq' -> yreq'
Right needGen -> needGen yreGen Right needGen -> needGen yreGen
let ra = resolveApproot yreSite req let ra = resolveApproot yreSite req
let log' = messageLoggerSource yreSite yreLogger let -- We set up two environments: the first one has a "safe" error handler
-- We set up two environments: the first one has a "safe" error handler
-- which will never throw an exception. The second one uses the -- which will never throw an exception. The second one uses the
-- user-provided errorHandler function. If that errorHandler function -- user-provided errorHandler function. If that errorHandler function
-- errors out, it will use the safeEh below to recover. -- errors out, it will use the safeEh below to recover.
@ -330,8 +323,8 @@ yesodRunner handler' YesodRunnerEnv {..} route req sendResponse = do
, rheChild = yreSite , rheChild = yreSite
, rheSite = yreSite , rheSite = yreSite
, rheUpload = fileUpload yreSite , rheUpload = fileUpload yreSite
, rheLog = log' , rheLogFunc = yreLogFunc
, rheOnError = safeEh log' , rheOnError = safeEh yreLogFunc
, rheMaxExpires = maxExpires , rheMaxExpires = maxExpires
} }
rhe = rheSafe rhe = rheSafe

View File

@ -1,6 +1,5 @@
{-# LANGUAGE TypeSynonymInstances, OverloadedStrings #-} {-# LANGUAGE TypeSynonymInstances, OverloadedStrings #-}
{-# LANGUAGE CPP #-} {-# LANGUAGE NoImplicitPrelude #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Yesod.Core.Json module Yesod.Core.Json
( -- * Convert from a JSON value ( -- * Convert from a JSON value
defaultLayoutJson defaultLayoutJson
@ -34,13 +33,13 @@ module Yesod.Core.Json
, acceptsJson , acceptsJson
) where ) where
import RIO
import Yesod.Core.Handler (HandlerFor, 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 Control.Monad.Trans.Writer (Writer)
import Data.Monoid (Endo) import Data.Monoid (Endo)
import Yesod.Core.Content (TypedContent) import Yesod.Core.Content (TypedContent)
import Yesod.Core.Types (reqAccept) import Yesod.Core.Types (reqAccept, HasHandlerData (..))
import Yesod.Core.Class.Yesod (defaultLayout, Yesod) import Yesod.Core.Class.Yesod (defaultLayout, Yesod)
import Yesod.Core.Class.Handler
import Yesod.Core.Widget (WidgetFor) import Yesod.Core.Widget (WidgetFor)
import Yesod.Routes.Class import Yesod.Routes.Class
import qualified Data.Aeson as J import qualified Data.Aeson as J
@ -98,7 +97,7 @@ provideJson = provideRep . return . J.toEncoding
-- | Same as 'parseInsecureJsonBody' -- | Same as 'parseInsecureJsonBody'
-- --
-- @since 0.3.0 -- @since 0.3.0
parseJsonBody :: (MonadHandler m, J.FromJSON a) => m (J.Result a) parseJsonBody :: (HasHandlerData env, J.FromJSON a) => RIO env (J.Result a)
parseJsonBody = parseInsecureJsonBody parseJsonBody = parseInsecureJsonBody
{-# DEPRECATED parseJsonBody "Use parseCheckJsonBody or parseInsecureJsonBody instead" #-} {-# DEPRECATED parseJsonBody "Use parseCheckJsonBody or parseInsecureJsonBody instead" #-}
@ -108,7 +107,7 @@ parseJsonBody = parseInsecureJsonBody
-- Note: This function is vulnerable to CSRF attacks. -- Note: This function is vulnerable to CSRF attacks.
-- --
-- @since 1.6.11 -- @since 1.6.11
parseInsecureJsonBody :: (MonadHandler m, J.FromJSON a) => m (J.Result a) parseInsecureJsonBody :: (HasHandlerData env, J.FromJSON a) => RIO env (J.Result a)
parseInsecureJsonBody = do parseInsecureJsonBody = do
eValue <- runConduit $ rawRequestBody .| runCatchC (sinkParser JP.value') eValue <- runConduit $ rawRequestBody .| runCatchC (sinkParser JP.value')
return $ case eValue of return $ case eValue of
@ -131,7 +130,7 @@ parseInsecureJsonBody = do
-- body will no longer be available. -- body will no longer be available.
-- --
-- @since 0.3.0 -- @since 0.3.0
parseCheckJsonBody :: (MonadHandler m, J.FromJSON a) => m (J.Result a) parseCheckJsonBody :: (HasHandlerData env, J.FromJSON a) => RIO env (J.Result a)
parseCheckJsonBody = do parseCheckJsonBody = do
mct <- lookupHeader "content-type" mct <- lookupHeader "content-type"
case fmap (B8.takeWhile (/= ';')) mct of case fmap (B8.takeWhile (/= ';')) mct of
@ -140,13 +139,13 @@ parseCheckJsonBody = do
-- | Same as 'parseInsecureJsonBody', but return an invalid args response on a parse -- | Same as 'parseInsecureJsonBody', but return an invalid args response on a parse
-- error. -- error.
parseJsonBody_ :: (MonadHandler m, J.FromJSON a) => m a parseJsonBody_ :: (HasHandlerData env, J.FromJSON a) => RIO env a
parseJsonBody_ = requireInsecureJsonBody parseJsonBody_ = requireInsecureJsonBody
{-# DEPRECATED parseJsonBody_ "Use requireCheckJsonBody or requireInsecureJsonBody instead" #-} {-# DEPRECATED parseJsonBody_ "Use requireCheckJsonBody or requireInsecureJsonBody instead" #-}
-- | Same as 'parseInsecureJsonBody', but return an invalid args response on a parse -- | Same as 'parseInsecureJsonBody', but return an invalid args response on a parse
-- error. -- error.
requireJsonBody :: (MonadHandler m, J.FromJSON a) => m a requireJsonBody :: (HasHandlerData env, J.FromJSON a) => RIO env a
requireJsonBody = requireInsecureJsonBody requireJsonBody = requireInsecureJsonBody
{-# DEPRECATED requireJsonBody "Use requireCheckJsonBody or requireInsecureJsonBody instead" #-} {-# DEPRECATED requireJsonBody "Use requireCheckJsonBody or requireInsecureJsonBody instead" #-}
@ -154,7 +153,7 @@ requireJsonBody = requireInsecureJsonBody
-- error. -- error.
-- --
-- @since 1.6.11 -- @since 1.6.11
requireInsecureJsonBody :: (MonadHandler m, J.FromJSON a) => m a requireInsecureJsonBody :: (HasHandlerData env, J.FromJSON a) => RIO env a
requireInsecureJsonBody = do requireInsecureJsonBody = do
ra <- parseInsecureJsonBody ra <- parseInsecureJsonBody
case ra of case ra of
@ -163,7 +162,7 @@ requireInsecureJsonBody = do
-- | Same as 'parseCheckJsonBody', but return an invalid args response on a parse -- | Same as 'parseCheckJsonBody', but return an invalid args response on a parse
-- error. -- error.
requireCheckJsonBody :: (MonadHandler m, J.FromJSON a) => m a requireCheckJsonBody :: (HasHandlerData env, J.FromJSON a) => RIO env a
requireCheckJsonBody = do requireCheckJsonBody = do
ra <- parseCheckJsonBody ra <- parseCheckJsonBody
case ra of case ra of
@ -181,10 +180,10 @@ array = J.Array . V.fromList . map J.toJSON
-- @application\/json@ (e.g. AJAX, see 'acceptsJSON'). -- @application\/json@ (e.g. AJAX, see 'acceptsJSON').
-- --
-- 2. 3xx otherwise, following the PRG pattern. -- 2. 3xx otherwise, following the PRG pattern.
jsonOrRedirect :: (MonadHandler m, J.ToJSON a) jsonOrRedirect :: (HasHandlerData env, J.ToJSON a)
=> Route (HandlerSite m) -- ^ Redirect target => Route (HandlerSite env) -- ^ Redirect target
-> a -- ^ Data to send via JSON -> a -- ^ Data to send via JSON
-> m J.Value -> RIO env J.Value
jsonOrRedirect = jsonOrRedirect' J.toJSON jsonOrRedirect = jsonOrRedirect' J.toJSON
-- | jsonEncodingOrRedirect simplifies the scenario where a POST handler sends a different -- | jsonEncodingOrRedirect simplifies the scenario where a POST handler sends a different
@ -195,17 +194,17 @@ jsonOrRedirect = jsonOrRedirect' J.toJSON
-- --
-- 2. 3xx otherwise, following the PRG pattern. -- 2. 3xx otherwise, following the PRG pattern.
-- @since 1.4.21 -- @since 1.4.21
jsonEncodingOrRedirect :: (MonadHandler m, J.ToJSON a) jsonEncodingOrRedirect :: (HasHandlerData env, J.ToJSON a)
=> Route (HandlerSite m) -- ^ Redirect target => Route (HandlerSite env) -- ^ Redirect target
-> a -- ^ Data to send via JSON -> a -- ^ Data to send via JSON
-> m J.Encoding -> RIO env J.Encoding
jsonEncodingOrRedirect = jsonOrRedirect' J.toEncoding jsonEncodingOrRedirect = jsonOrRedirect' J.toEncoding
jsonOrRedirect' :: MonadHandler m jsonOrRedirect' :: HasHandlerData env
=> (a -> b) => (a -> b)
-> Route (HandlerSite m) -- ^ Redirect target -> Route (HandlerSite env) -- ^ Redirect target
-> a -- ^ Data to send via JSON -> a -- ^ Data to send via JSON
-> m b -> RIO env b
jsonOrRedirect' f r j = do jsonOrRedirect' f r j = do
q <- acceptsJson q <- acceptsJson
if q then return (f j) if q then return (f j)
@ -213,7 +212,7 @@ jsonOrRedirect' f r j = do
-- | Returns @True@ if the client prefers @application\/json@ as -- | Returns @True@ if the client prefers @application\/json@ as
-- indicated by the @Accept@ HTTP header. -- indicated by the @Accept@ HTTP header.
acceptsJson :: MonadHandler m => m Bool acceptsJson :: HasHandlerData env => RIO env Bool
acceptsJson = (maybe False ((== "application/json") . B8.takeWhile (/= ';')) acceptsJson = (maybe False ((== "application/json") . B8.takeWhile (/= ';'))
. listToMaybe . listToMaybe
. reqAccept) . reqAccept)

View File

@ -1,61 +1,51 @@
{-# LANGUAGE CPP #-} {-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE TupleSections #-} {-# LANGUAGE TupleSections #-}
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE QuantifiedConstraints #-}
-- FIXME rename to Internal
module Yesod.Core.Types where module Yesod.Core.Types where
import qualified Data.ByteString.Builder as BB import qualified Data.ByteString.Builder as BB
import Control.Arrow (first) import Control.Monad.Trans.Resource (ResourceT)
import Control.Exception (Exception)
import Control.Monad (ap)
import Control.Monad.IO.Class (MonadIO (liftIO))
import Control.Monad.Logger (LogLevel, LogSource,
MonadLogger (..))
import Control.Monad.Primitive (PrimMonad (..))
import Control.Monad.Trans.Resource (MonadResource (..), InternalState, runInternalState, MonadThrow (..), ResourceT)
import Data.ByteString (ByteString)
import qualified Data.ByteString.Lazy as L import qualified Data.ByteString.Lazy as L
import Data.CaseInsensitive (CI) import Data.CaseInsensitive (CI)
import Data.Conduit (Flush, ConduitT) import Conduit (Flush, ConduitT)
import Data.IORef (IORef, modifyIORef') import RIO.Map (unionWith)
import Data.Map (Map, unionWith) import qualified RIO.Map as Map
import qualified Data.Map as Map
import Data.Monoid (Endo (..), Last (..)) import Data.Monoid (Endo (..), Last (..))
import Data.Semigroup (Semigroup(..))
import Data.Serialize (Serialize (..), import Data.Serialize (Serialize (..),
putByteString) putByteString)
import Data.String (IsString (fromString)) import Data.String (IsString (fromString))
import Data.Text (Text)
import qualified Data.Text as T import qualified Data.Text as T
import qualified Data.Text.Lazy.Builder as TBuilder import qualified Data.Text.Lazy.Builder as TBuilder
import Data.Time (UTCTime) import Data.Time (UTCTime)
import Data.Typeable (Typeable) import Data.Typeable (Typeable)
import GHC.Generics (Generic) import GHC.Generics (Generic)
import Language.Haskell.TH.Syntax (Loc)
import qualified Network.HTTP.Types as H import qualified Network.HTTP.Types as H
import Network.Wai (FilePart, import Network.Wai (FilePart,
RequestBodyLength) RequestBodyLength)
import qualified Network.Wai as W import qualified Network.Wai as W
import qualified Network.Wai.Parse as NWP import qualified Network.Wai.Parse as NWP
import System.Log.FastLogger (LogStr, LoggerSet, toLogStr, pushLogStr)
import Network.Wai.Logger (DateCacheGetter)
import Text.Blaze.Html (Html, toHtml) import Text.Blaze.Html (Html, toHtml)
import Text.Hamlet (HtmlUrl) import Text.Hamlet (HtmlUrl)
import Text.Julius (JavascriptUrl) import Text.Julius (JavascriptUrl)
import Web.Cookie (SetCookie) import Web.Cookie (SetCookie)
import Yesod.Core.Internal.Util (getTime, putTime) import Yesod.Core.Internal.Util (getTime, putTime)
import Yesod.Routes.Class (RenderRoute (..), ParseRoute (..)) import Yesod.Routes.Class (RenderRoute (..), ParseRoute (..))
import Control.Monad.Reader (MonadReader (..))
import Control.DeepSeq (NFData (rnf)) import Control.DeepSeq (NFData (rnf))
import Yesod.Core.TypeCache (TypeMap, KeyedTypeMap) import Yesod.Core.TypeCache (TypeMap, KeyedTypeMap)
import Control.Monad.Logger (MonadLoggerIO (..))
import UnliftIO (MonadUnliftIO (..), UnliftIO (..)) import RIO
import RIO.Orphans
-- Sessions -- Sessions
type SessionMap = Map Text ByteString type SessionMap = Map Text ByteString
@ -131,7 +121,7 @@ data FileInfo = FileInfo
} }
data FileUpload = FileUploadMemory !(NWP.BackEnd L.ByteString) data FileUpload = FileUploadMemory !(NWP.BackEnd L.ByteString)
| FileUploadDisk !(InternalState -> NWP.BackEnd FilePath) | FileUploadDisk !(ResourceMap -> NWP.BackEnd FilePath)
| FileUploadSource !(NWP.BackEnd (ConduitT () ByteString (ResourceT IO) ())) | FileUploadSource !(NWP.BackEnd (ConduitT () ByteString (ResourceT IO) ()))
-- | How to determine the root of the application for constructing URLs. -- | How to determine the root of the application for constructing URLs.
@ -176,28 +166,73 @@ data RunHandlerEnv child site = RunHandlerEnv
, rheSite :: !site , rheSite :: !site
, rheChild :: !child , rheChild :: !child
, rheUpload :: !(RequestBodyLength -> FileUpload) , rheUpload :: !(RequestBodyLength -> FileUpload)
, rheLog :: !(Loc -> LogSource -> LogLevel -> LogStr -> IO ()) , rheLogFunc :: !LogFunc
, rheOnError :: !(ErrorResponse -> YesodApp) , rheOnError :: !(ErrorResponse -> YesodApp)
-- ^ How to respond when an error is thrown internally. -- ^ How to respond when an error is thrown internally.
-- --
-- Since 1.2.0 -- Since 1.2.0
, rheMaxExpires :: !Text , rheMaxExpires :: !Text
} }
instance HasLogFunc (RunHandlerEnv child site) where
logFuncL = lens rheLogFunc (\x y -> x { rheLogFunc = y })
data HandlerData child site = HandlerData data SubHandlerData child site = SubHandlerData
{ handlerRequest :: !YesodRequest { handlerRequest :: !YesodRequest
, handlerEnv :: !(RunHandlerEnv child site) , handlerEnv :: !(RunHandlerEnv child site)
, handlerState :: !(IORef GHState) , handlerState :: !(IORef GHState)
, handlerResource :: !InternalState , handlerResource :: !ResourceMap
} }
class (HasResourceMap env, HasLogFunc env) => HasHandlerData env where
type HandlerSite env
type SubHandlerSite env
subHandlerDataL :: Lens' env (SubHandlerData (SubHandlerSite env) (HandlerSite env))
class (HasHandlerData env, HandlerSite env ~ SubHandlerSite env) => HasWidgetData env where
widgetDataL :: Lens' env (WidgetData (HandlerSite env))
instance HasHandlerData (SubHandlerData child site) where
type HandlerSite (SubHandlerData child site) = site
type SubHandlerSite (SubHandlerData child site) = child
subHandlerDataL = id
instance HasLogFunc (SubHandlerData child site) where
logFuncL = lens handlerEnv (\x y -> x { handlerEnv = y }).logFuncL
instance HasResourceMap (SubHandlerData child site) where
resourceMapL = lens handlerResource (\x y -> x { handlerResource = y })
instance HasHandlerData (HandlerData site) where
type HandlerSite (HandlerData site) = site
type SubHandlerSite (HandlerData site) = site
subHandlerDataL = lens unHandlerData (\_ y -> HandlerData y)
instance HasLogFunc (HandlerData site) where
logFuncL = subHandlerDataL.logFuncL
instance HasResourceMap (HandlerData site) where
resourceMapL = subHandlerDataL.resourceMapL
instance HasHandlerData (WidgetData site) where
type HandlerSite (WidgetData site) = site
type SubHandlerSite (WidgetData site) = site
subHandlerDataL =
(lens wdHandler (\x y -> x { wdHandler = y })).subHandlerDataL
instance HasWidgetData (WidgetData site) where
widgetDataL = id
instance HasLogFunc (WidgetData site) where
logFuncL = subHandlerDataL.logFuncL
instance HasResourceMap (WidgetData site) where
resourceMapL = subHandlerDataL.resourceMapL
newtype HandlerData site = HandlerData { unHandlerData :: SubHandlerData site site }
data YesodRunnerEnv site = YesodRunnerEnv data YesodRunnerEnv site = YesodRunnerEnv
{ yreLogger :: !Logger { yreLogFunc :: !LogFunc
, yreSite :: !site , yreSite :: !site
, yreSessionBackend :: !(Maybe SessionBackend) , yreSessionBackend :: !(Maybe SessionBackend)
, yreGen :: !(IO Int) , yreGen :: !(IO Int)
-- ^ Generate a random number -- ^ Generate a random number
, yreGetMaxExpires :: !(IO Text) , yreGetMaxExpires :: !(IO Text)
, yreCleanup :: !(IORef ())
-- ^ Used to ensure some cleanup actions can be performed via
-- garbage collection.
} }
data YesodSubRunnerEnv sub parent = YesodSubRunnerEnv data YesodSubRunnerEnv sub parent = YesodSubRunnerEnv
@ -215,10 +250,7 @@ type ParentRunner parent
-- | A generic handler monad, which can have a different subsite and master -- | A generic handler monad, which can have a different subsite and master
-- site. We define a newtype for better error message. -- site. We define a newtype for better error message.
newtype HandlerFor site a = HandlerFor type HandlerFor site = RIO (HandlerData site)
{ unHandlerFor :: HandlerData site site -> IO a
}
deriving Functor
data GHState = GHState data GHState = GHState
{ ghsSession :: !SessionMap { ghsSession :: !SessionMap
@ -237,24 +269,13 @@ type YesodApp = YesodRequest -> ResourceT IO YesodResponse
-- | A generic widget, allowing specification of both the subsite and master -- | A generic widget, allowing specification of both the subsite and master
-- site datatypes. While this is simply a @WriterT@, we define a newtype for -- site datatypes. While this is simply a @WriterT@, we define a newtype for
-- better error messages. -- better error messages.
newtype WidgetFor site a = WidgetFor type WidgetFor site = RIO (WidgetData site)
{ unWidgetFor :: WidgetData site -> IO a
}
deriving Functor
data WidgetData site = WidgetData data WidgetData site = WidgetData
{ wdRef :: {-# UNPACK #-} !(IORef (GWData (Route site))) { wdRef :: {-# UNPACK #-} !(IORef (GWData (Route site)))
, wdHandler :: {-# UNPACK #-} !(HandlerData site site) , wdHandler :: {-# UNPACK #-} !(HandlerData site)
} }
instance a ~ () => Monoid (WidgetFor site a) where
mempty = return ()
#if !(MIN_VERSION_base(4,11,0))
mappend = (<>)
#endif
instance a ~ () => Semigroup (WidgetFor site a) where
x <> y = x >> y
-- | A 'String' can be trivially promoted to a widget. -- | A 'String' can be trivially promoted to a widget.
-- --
-- For example, in a yesod-scaffold site you could use: -- For example, in a yesod-scaffold site you could use:
@ -264,8 +285,10 @@ instance a ~ () => IsString (WidgetFor site a) where
fromString = toWidget . toHtml . T.pack fromString = toWidget . toHtml . T.pack
where toWidget x = tellWidget mempty { gwdBody = Body (const x) } where toWidget x = tellWidget mempty { gwdBody = Body (const x) }
tellWidget :: GWData (Route site) -> WidgetFor site () tellWidget :: HasWidgetData env => GWData (Route (HandlerSite env)) -> RIO env ()
tellWidget d = WidgetFor $ \wd -> modifyIORef' (wdRef wd) (<> d) tellWidget d = do
wd <- view widgetDataL
modifyIORef' (wdRef wd) (<> d)
type RY master = Route master -> [(Text, Text)] -> Text type RY master = Route master -> [(Text, Text)] -> Text
@ -288,8 +311,8 @@ data PageContent url = PageContent
, pageBody :: !(HtmlUrl url) , pageBody :: !(HtmlUrl url)
} }
data Content = ContentBuilder !BB.Builder !(Maybe Int) -- ^ The content and optional content length. data Content = ContentBuilder !Builder !(Maybe Int) -- ^ The content and optional content length.
| ContentSource !(ConduitT () (Flush BB.Builder) (ResourceT IO) ()) | ContentSource !(ConduitT () (Flush Builder) (ResourceT IO) ())
| ContentFile !FilePath !(Maybe FilePart) | ContentFile !FilePath !(Maybe FilePart)
| ContentDontEvaluate !Content | ContentDontEvaluate !Content
@ -330,9 +353,6 @@ data Header =
-- ^ key and value -- ^ key and value
deriving (Eq, Show) deriving (Eq, Show)
-- FIXME In the next major version bump, let's just add strictness annotations
-- to Header (and probably everywhere else). We can also add strictness
-- annotations to SetCookie in the cookie package.
instance NFData Header where instance NFData Header where
rnf (AddCookie x) = rnf x rnf (AddCookie x) = rnf x
rnf (DeleteCookie x y) = x `seq` y `seq` () rnf (DeleteCookie x y) = x `seq` y `seq` ()
@ -373,9 +393,7 @@ data GWData a = GWData
} }
instance Monoid (GWData a) where instance Monoid (GWData a) where
mempty = GWData mempty mempty mempty mempty mempty mempty mempty mempty = GWData mempty mempty mempty mempty mempty mempty mempty
#if !(MIN_VERSION_base(4,11,0))
mappend = (<>) mappend = (<>)
#endif
instance Semigroup (GWData a) where instance Semigroup (GWData a) where
GWData a1 a2 a3 a4 a5 a6 a7 <> GWData a1 a2 a3 a4 a5 a6 a7 <>
GWData b1 b2 b3 b4 b5 b6 b7 = GWData GWData b1 b2 b3 b4 b5 b6 b7 = GWData
@ -407,84 +425,9 @@ instance Show HandlerContents where
show (HCWaiApp _) = "HCWaiApp" show (HCWaiApp _) = "HCWaiApp"
instance Exception HandlerContents instance Exception HandlerContents
-- Instances for WidgetFor
instance Applicative (WidgetFor site) where
pure = WidgetFor . const . pure
(<*>) = ap
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
-- | @since 1.6.7
instance PrimMonad (WidgetFor site) where
type PrimState (WidgetFor site) = PrimState IO
primitive = liftIO . primitive
-- | @since 1.4.38
instance MonadUnliftIO (WidgetFor site) where
{-# INLINE askUnliftIO #-}
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 MonadThrow (WidgetFor site) where
throwM = liftIO . throwM
instance MonadResource (WidgetFor site) where
liftResourceT f = WidgetFor $ runInternalState f . handlerResource . wdHandler
instance MonadLogger (WidgetFor site) where
monadLoggerLog a b c d = WidgetFor $ \wd ->
rheLog (handlerEnv $ wdHandler wd) a b c (toLogStr d)
instance MonadLoggerIO (WidgetFor site) where
askLoggerIO = WidgetFor $ return . rheLog . handlerEnv . wdHandler
-- Instances for HandlerT
instance Applicative (HandlerFor site) where
pure = HandlerFor . const . return
(<*>) = ap
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
-- | @since 1.6.7
instance PrimMonad (HandlerFor site) where
type PrimState (HandlerFor site) = PrimState IO
primitive = liftIO . primitive
instance MonadReader (HandlerData site site) (HandlerFor site) where
ask = HandlerFor return
local f (HandlerFor g) = HandlerFor $ g . f
-- | @since 1.4.38
instance MonadUnliftIO (HandlerFor site) where
{-# INLINE askUnliftIO #-}
askUnliftIO = HandlerFor $ \r ->
return (UnliftIO (flip unHandlerFor r))
instance MonadThrow (HandlerFor site) where
throwM = liftIO . throwM
instance MonadResource (HandlerFor site) where
liftResourceT f = HandlerFor $ runInternalState f . handlerResource
instance MonadLogger (HandlerFor site) where
monadLoggerLog a b c d = HandlerFor $ \hd ->
rheLog (handlerEnv hd) a b c (toLogStr d)
instance MonadLoggerIO (HandlerFor site) where
askLoggerIO = HandlerFor $ \hd -> return (rheLog (handlerEnv hd))
instance Monoid (UniqueList x) where instance Monoid (UniqueList x) where
mempty = UniqueList id mempty = UniqueList id
#if !(MIN_VERSION_base(4,11,0))
mappend = (<>) mappend = (<>)
#endif
instance Semigroup (UniqueList x) where instance Semigroup (UniqueList x) where
UniqueList x <> UniqueList y = UniqueList $ x . y UniqueList x <> UniqueList y = UniqueList $ x . y
@ -506,49 +449,34 @@ instance RenderRoute WaiSubsiteWithAuth where
instance ParseRoute WaiSubsiteWithAuth where instance ParseRoute WaiSubsiteWithAuth where
parseRoute (x, y) = Just $ WaiSubsiteWithAuthRoute x y parseRoute (x, y) = Just $ WaiSubsiteWithAuthRoute x y
data Logger = Logger
{ loggerSet :: !LoggerSet
, loggerDate :: !DateCacheGetter
}
loggerPutStr :: Logger -> LogStr -> IO ()
loggerPutStr (Logger ls _) = pushLogStr ls
-- | A handler monad for subsite -- | A handler monad for subsite
-- --
-- @since 1.6.0 -- @since 1.6.0
newtype SubHandlerFor sub master a = SubHandlerFor type SubHandlerFor sub master = RIO (SubHandlerData sub master)
{ unSubHandlerFor :: HandlerData sub master -> IO a
}
deriving Functor
instance Applicative (SubHandlerFor child master) where -- | Convert a concrete 'HandlerFor' action into an arbitrary other monad.
pure = SubHandlerFor . const . return liftHandler
(<*>) = ap :: (MonadIO m, MonadReader env m, HasHandlerData env)
instance Monad (SubHandlerFor child master) where => HandlerFor (HandlerSite env) a
return = pure -> m a
SubHandlerFor x >>= f = SubHandlerFor $ \r -> x r >>= \x' -> unSubHandlerFor (f x') r liftHandler action = do
instance MonadIO (SubHandlerFor child master) where shd <- view subHandlerDataL
liftIO = SubHandlerFor . const let hd = HandlerData $ shd
instance MonadReader (HandlerData child master) (SubHandlerFor child master) where { handlerEnv =
ask = SubHandlerFor return let rhe = handlerEnv shd
local f (SubHandlerFor g) = SubHandlerFor $ g . f in rhe
{ rheRoute = rheRouteToMaster rhe <$> rheRoute rhe
, rheChild = rheSite rhe
, rheRouteToMaster = id
}
}
runRIO hd action
-- | @since 1.4.38 -- | Convert a concrete 'WidgetFor' action into an arbitrary other monad.
instance MonadUnliftIO (SubHandlerFor child master) where liftWidget
{-# INLINE askUnliftIO #-} :: (MonadIO m, MonadReader env m, HasWidgetData env)
askUnliftIO = SubHandlerFor $ \r -> => WidgetFor (HandlerSite env) a
return (UnliftIO (flip unSubHandlerFor r)) -> m a
liftWidget action = do
instance MonadThrow (SubHandlerFor child master) where hd <- view widgetDataL
throwM = liftIO . throwM runRIO hd action
instance MonadResource (SubHandlerFor child master) where
liftResourceT f = SubHandlerFor $ runInternalState f . handlerResource
instance MonadLogger (SubHandlerFor child master) where
monadLoggerLog a b c d = SubHandlerFor $ \sd ->
rheLog (handlerEnv sd) a b c (toLogStr d)
instance MonadLoggerIO (SubHandlerFor child master) where
askLoggerIO = SubHandlerFor $ return . rheLog . handlerEnv

View File

@ -1,3 +1,4 @@
{-# LANGUAGE NoImplicitPrelude #-}
-- | This is designed to be used as -- | This is designed to be used as
-- --
-- > import qualified Yesod.Core.Unsafe as Unsafe -- > import qualified Yesod.Core.Unsafe as Unsafe
@ -5,21 +6,21 @@
-- This serves as a reminder that the functions are unsafe to use in many situations. -- This serves as a reminder that the functions are unsafe to use in many situations.
module Yesod.Core.Unsafe (runFakeHandler, fakeHandlerGetLogger) where module Yesod.Core.Unsafe (runFakeHandler, fakeHandlerGetLogger) where
import RIO
import Yesod.Core.Internal.Run (runFakeHandler) import Yesod.Core.Internal.Run (runFakeHandler)
import Yesod.Core.Types import Yesod.Core.Types
import Yesod.Core.Class.Yesod import Yesod.Core.Class.Yesod
import Control.Monad.IO.Class (MonadIO)
-- | designed to be used as -- | designed to be used as
-- --
-- > unsafeHandler = Unsafe.fakeHandlerGetLogger appLogger -- > unsafeHandler = Unsafe.fakeHandlerGetLogger appLogger
fakeHandlerGetLogger :: (Yesod site, MonadIO m) fakeHandlerGetLogger :: (Yesod site, MonadIO m)
=> (site -> Logger) => LogFunc
-> site -> site
-> HandlerFor site a -> HandlerFor site a
-> m a -> m a
fakeHandlerGetLogger getLogger app f = fakeHandlerGetLogger logFunc app f =
runFakeHandler mempty getLogger app f runFakeHandler mempty logFunc app f
>>= either (error . ("runFakeHandler issue: " `mappend`) . show) >>= either (error . ("runFakeHandler issue: " `mappend`) . show)
return return

View File

@ -6,6 +6,7 @@
{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE UndecidableInstances #-}
-- | Widgets combine HTML with JS and CSS dependencies with a unique identifier -- | Widgets combine HTML with JS and CSS dependencies with a unique identifier
@ -57,8 +58,7 @@ import Text.Julius
import Yesod.Routes.Class import Yesod.Routes.Class
import Yesod.Core.Handler (getMessageRender, getUrlRenderParams) import Yesod.Core.Handler (getMessageRender, getUrlRenderParams)
import Text.Shakespeare.I18N (RenderMessage) import Text.Shakespeare.I18N (RenderMessage)
import Data.Text (Text) import qualified RIO.Map as Map
import qualified Data.Map as Map
import Language.Haskell.TH.Quote (QuasiQuoter) import Language.Haskell.TH.Quote (QuasiQuoter)
import Language.Haskell.TH.Syntax (Q, Exp (InfixE, VarE, LamE, AppE), Pat (VarP), newName) import Language.Haskell.TH.Syntax (Q, Exp (InfixE, VarE, LamE, AppE), Pat (VarP), newName)
@ -68,8 +68,8 @@ import Text.Blaze.Html (toHtml, preEscapedToMarkup)
import qualified Data.Text.Lazy as TL import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Builder as TB import qualified Data.Text.Lazy.Builder as TB
import RIO
import Yesod.Core.Types import Yesod.Core.Types
import Yesod.Core.Class.Handler
type WidgetT site (m :: * -> *) = WidgetFor site type WidgetT site (m :: * -> *) = WidgetFor site
{-# DEPRECATED WidgetT "Use WidgetFor directly" #-} {-# DEPRECATED WidgetT "Use WidgetFor directly" #-}
@ -78,7 +78,7 @@ preEscapedLazyText :: TL.Text -> Html
preEscapedLazyText = preEscapedToMarkup preEscapedLazyText = preEscapedToMarkup
class ToWidget site a where class ToWidget site a where
toWidget :: (MonadWidget m, HandlerSite m ~ site) => a -> m () toWidget :: (HasWidgetData env, HandlerSite env ~ site) => a -> RIO env ()
instance render ~ RY site => ToWidget site (render -> Html) where instance render ~ RY site => ToWidget site (render -> Html) where
toWidget x = tell $ GWData (Body x) mempty mempty mempty mempty mempty mempty toWidget x = tell $ GWData (Body x) mempty mempty mempty mempty mempty mempty
@ -115,10 +115,10 @@ class ToWidgetMedia site a where
-- | Add the given content to the page, but only for the given media type. -- | Add the given content to the page, but only for the given media type.
-- --
-- Since 1.2 -- Since 1.2
toWidgetMedia :: (MonadWidget m, HandlerSite m ~ site) toWidgetMedia :: (HasWidgetData env, HandlerSite env ~ site)
=> Text -- ^ media value => Text -- ^ media value
-> a -> a
-> m () -> RIO env ()
instance render ~ RY site => ToWidgetMedia site (render -> Css) where instance render ~ RY site => ToWidgetMedia site (render -> Css) where
toWidgetMedia media x = toWidgetMedia media $ CssBuilder . fromLazyText . renderCss . x toWidgetMedia media x = toWidgetMedia media $ CssBuilder . fromLazyText . renderCss . x
instance ToWidgetMedia site Css where instance ToWidgetMedia site Css where
@ -129,7 +129,7 @@ instance ToWidgetMedia site CssBuilder where
toWidgetMedia media x = tell $ GWData mempty mempty mempty mempty (Map.singleton (Just media) $ unCssBuilder . const x) mempty mempty toWidgetMedia media x = tell $ GWData mempty mempty mempty mempty (Map.singleton (Just media) $ unCssBuilder . const x) mempty mempty
class ToWidgetBody site a where class ToWidgetBody site a where
toWidgetBody :: (MonadWidget m, HandlerSite m ~ site) => a -> m () toWidgetBody :: (HasWidgetData env, HandlerSite env ~ site) => a -> RIO env ()
instance render ~ RY site => ToWidgetBody site (render -> Html) where instance render ~ RY site => ToWidgetBody site (render -> Html) where
toWidgetBody = toWidget toWidgetBody = toWidget
@ -141,7 +141,7 @@ instance ToWidgetBody site Html where
toWidgetBody = toWidget toWidgetBody = toWidget
class ToWidgetHead site a where class ToWidgetHead site a where
toWidgetHead :: (MonadWidget m, HandlerSite m ~ site) => a -> m () toWidgetHead :: (HasWidgetData env, HandlerSite env ~ site) => a -> RIO env ()
instance render ~ RY site => ToWidgetHead site (render -> Html) where instance render ~ RY site => ToWidgetHead site (render -> Html) where
toWidgetHead = tell . GWData mempty mempty mempty mempty mempty mempty . Head toWidgetHead = tell . GWData mempty mempty mempty mempty mempty mempty . Head
@ -162,59 +162,59 @@ instance ToWidgetHead site Html where
-- | Set the page title. Calling 'setTitle' multiple times overrides previously -- | Set the page title. Calling 'setTitle' multiple times overrides previously
-- set values. -- set values.
setTitle :: MonadWidget m => Html -> m () setTitle :: HasWidgetData env => Html -> RIO env ()
setTitle x = tell $ GWData mempty (Last $ Just $ Title x) mempty mempty mempty mempty mempty setTitle x = tell $ GWData mempty (Last $ Just $ Title x) mempty mempty mempty mempty mempty
-- | Set the page title. Calling 'setTitle' multiple times overrides previously -- | Set the page title. Calling 'setTitle' multiple times overrides previously
-- set values. -- set values.
setTitleI :: (MonadWidget m, RenderMessage (HandlerSite m) msg) => msg -> m () setTitleI :: (HasWidgetData env, RenderMessage (HandlerSite env) msg) => msg -> RIO env ()
setTitleI msg = do setTitleI msg = do
mr <- getMessageRender mr <- getMessageRender
setTitle $ toHtml $ mr msg setTitle $ toHtml $ mr msg
-- | Link to the specified local stylesheet. -- | Link to the specified local stylesheet.
addStylesheet :: MonadWidget m => Route (HandlerSite m) -> m () addStylesheet :: HasWidgetData env => Route (HandlerSite env) -> RIO env ()
addStylesheet = flip addStylesheetAttrs [] addStylesheet = flip addStylesheetAttrs []
-- | Link to the specified local stylesheet. -- | Link to the specified local stylesheet.
addStylesheetAttrs :: MonadWidget m addStylesheetAttrs :: HasWidgetData env
=> Route (HandlerSite m) => Route (HandlerSite env)
-> [(Text, Text)] -> [(Text, Text)]
-> m () -> RIO env ()
addStylesheetAttrs x y = tell $ GWData mempty mempty mempty (toUnique $ Stylesheet (Local x) y) mempty mempty mempty addStylesheetAttrs x y = tell $ GWData mempty mempty mempty (toUnique $ Stylesheet (Local x) y) mempty mempty mempty
-- | Link to the specified remote stylesheet. -- | Link to the specified remote stylesheet.
addStylesheetRemote :: MonadWidget m => Text -> m () addStylesheetRemote :: HasWidgetData env => Text -> RIO env ()
addStylesheetRemote = flip addStylesheetRemoteAttrs [] addStylesheetRemote = flip addStylesheetRemoteAttrs []
-- | Link to the specified remote stylesheet. -- | Link to the specified remote stylesheet.
addStylesheetRemoteAttrs :: MonadWidget m => Text -> [(Text, Text)] -> m () addStylesheetRemoteAttrs :: HasWidgetData env => Text -> [(Text, Text)] -> RIO env ()
addStylesheetRemoteAttrs x y = tell $ GWData mempty mempty mempty (toUnique $ Stylesheet (Remote x) y) mempty mempty mempty addStylesheetRemoteAttrs x y = tell $ GWData mempty mempty mempty (toUnique $ Stylesheet (Remote x) y) mempty mempty mempty
addStylesheetEither :: MonadWidget m addStylesheetEither :: HasWidgetData env
=> Either (Route (HandlerSite m)) Text => Either (Route (HandlerSite env)) Text
-> m () -> RIO env ()
addStylesheetEither = either addStylesheet addStylesheetRemote addStylesheetEither = either addStylesheet addStylesheetRemote
addScriptEither :: MonadWidget m addScriptEither :: HasWidgetData env
=> Either (Route (HandlerSite m)) Text => Either (Route (HandlerSite env)) Text
-> m () -> RIO env ()
addScriptEither = either addScript addScriptRemote addScriptEither = either addScript addScriptRemote
-- | Link to the specified local script. -- | Link to the specified local script.
addScript :: MonadWidget m => Route (HandlerSite m) -> m () addScript :: HasWidgetData env => Route (HandlerSite env) -> RIO env ()
addScript = flip addScriptAttrs [] addScript = flip addScriptAttrs []
-- | Link to the specified local script. -- | Link to the specified local script.
addScriptAttrs :: MonadWidget m => Route (HandlerSite m) -> [(Text, Text)] -> m () addScriptAttrs :: HasWidgetData env => Route (HandlerSite env) -> [(Text, Text)] -> RIO env ()
addScriptAttrs x y = tell $ GWData mempty mempty (toUnique $ Script (Local x) y) mempty mempty mempty mempty addScriptAttrs x y = tell $ GWData mempty mempty (toUnique $ Script (Local x) y) mempty mempty mempty mempty
-- | Link to the specified remote script. -- | Link to the specified remote script.
addScriptRemote :: MonadWidget m => Text -> m () addScriptRemote :: HasWidgetData env => Text -> RIO env ()
addScriptRemote = flip addScriptRemoteAttrs [] addScriptRemote = flip addScriptRemoteAttrs []
-- | Link to the specified remote script. -- | Link to the specified remote script.
addScriptRemoteAttrs :: MonadWidget m => Text -> [(Text, Text)] -> m () addScriptRemoteAttrs :: HasWidgetData env => Text -> [(Text, Text)] -> RIO env ()
addScriptRemoteAttrs x y = tell $ GWData mempty mempty (toUnique $ Script (Remote x) y) mempty mempty mempty mempty addScriptRemoteAttrs x y = tell $ GWData mempty mempty (toUnique $ Script (Remote x) y) mempty mempty mempty mempty
whamlet :: QuasiQuoter whamlet :: QuasiQuoter
@ -247,28 +247,28 @@ rules = do
return $ NP.HamletRules ah ur $ \_ b -> return $ ah `AppE` b return $ NP.HamletRules ah ur $ \_ b -> return $ ah `AppE` b
-- | Wraps the 'Content' generated by 'hamletToContent' in a 'RepHtml'. -- | Wraps the 'Content' generated by 'hamletToContent' in a 'RepHtml'.
ihamletToRepHtml :: (MonadHandler m, RenderMessage (HandlerSite m) message) ihamletToRepHtml :: (HasHandlerData env, RenderMessage (HandlerSite env) message)
=> HtmlUrlI18n message (Route (HandlerSite m)) => HtmlUrlI18n message (Route (HandlerSite env))
-> m Html -> RIO env Html
ihamletToRepHtml = ihamletToHtml ihamletToRepHtml = ihamletToHtml
{-# DEPRECATED ihamletToRepHtml "Please use ihamletToHtml instead" #-} {-# DEPRECATED ihamletToRepHtml "Please use ihamletToHtml instead" #-}
-- | Wraps the 'Content' generated by 'hamletToContent' in a 'RepHtml'. -- | Wraps the 'Content' generated by 'hamletToContent' in a 'RepHtml'.
-- --
-- Since 1.2.1 -- Since 1.2.1
ihamletToHtml :: (MonadHandler m, RenderMessage (HandlerSite m) message) ihamletToHtml :: (HasHandlerData env, RenderMessage (HandlerSite env) message)
=> HtmlUrlI18n message (Route (HandlerSite m)) => HtmlUrlI18n message (Route (HandlerSite env))
-> m Html -> RIO env Html
ihamletToHtml ih = do ihamletToHtml ih = do
urender <- getUrlRenderParams urender <- getUrlRenderParams
mrender <- getMessageRender mrender <- getMessageRender
return $ ih (toHtml . mrender) urender return $ ih (toHtml . mrender) urender
tell :: MonadWidget m => GWData (Route (HandlerSite m)) -> m () tell :: HasWidgetData env => GWData (Route (HandlerSite env)) -> RIO env ()
tell = liftWidget . tellWidget tell = liftWidget . tellWidget
toUnique :: x -> UniqueList x toUnique :: x -> UniqueList x
toUnique = UniqueList . (:) toUnique = UniqueList . (:)
handlerToWidget :: HandlerFor site a -> WidgetFor site a handlerToWidget :: HandlerFor site a -> WidgetFor site a
handlerToWidget (HandlerFor f) = WidgetFor $ f . wdHandler handlerToWidget = liftHandler

View File

@ -56,7 +56,7 @@ instance Yesod App where
getHomeR :: Handler Html getHomeR :: Handler Html
getHomeR = do getHomeR = do
$logDebug "Testing logging" logDebug "Testing logging"
defaultLayout $ toWidget [hamlet| defaultLayout $ toWidget [hamlet|
$doctype 5 $doctype 5

View File

@ -21,13 +21,13 @@ import qualified Data.ByteString.Lazy.Char8 as L8
getSubsite :: a -> Subsite getSubsite :: a -> Subsite
getSubsite _ = Subsite $(mkYesodSubDispatch resourcesSubsite) getSubsite _ = Subsite $(mkYesodSubDispatch resourcesSubsite)
getBarR :: MonadHandler m => m T.Text getBarR :: Monad m => m T.Text
getBarR = return $ T.pack "BarR" getBarR = return $ T.pack "BarR"
getBazR :: (MonadHandler m, Yesod (HandlerSite m)) => m Html getBazR :: (HasHandlerData env, Yesod (HandlerSite env)) => RIO env Html
getBazR = liftHandler $ defaultLayout [whamlet|Used Default Layout|] getBazR = liftHandler $ defaultLayout [whamlet|Used Default Layout|]
getBinR :: (MonadHandler m, Yesod (HandlerSite m), SubHandlerSite m ~ Subsite) => m Html getBinR :: (HasHandlerData env, Yesod (HandlerSite env), SubHandlerSite env ~ Subsite) => RIO env Html
getBinR = do getBinR = do
routeToParent <- getRouteToParent routeToParent <- getRouteToParent
liftHandler $ defaultLayout [whamlet| liftHandler $ defaultLayout [whamlet|

View File

@ -24,7 +24,7 @@ extra-source-files:
library library
hs-source-dirs: src hs-source-dirs: src
build-depends: base >= 4.9 && < 5 build-depends: base >= 4.11 && < 5
, aeson >= 1.0 , aeson >= 1.0
, auto-update , auto-update
, blaze-html >= 0.5 , blaze-html >= 0.5
@ -41,25 +41,24 @@ library
, fast-logger >= 2.2 , fast-logger >= 2.2
, http-types >= 0.7 , http-types >= 0.7
, memory , memory
, monad-logger >= 0.3.10 && < 0.4
, mtl , mtl
, parsec >= 2 && < 3.2 , parsec >= 2 && < 3.2
, path-pieces >= 0.1.2 && < 0.3 , path-pieces >= 0.1.2 && < 0.3
, primitive >= 0.6
, random >= 1.0.0.2 && < 1.2 , random >= 1.0.0.2 && < 1.2
, resourcet >= 1.2 , resourcet >= 1.2
, rio , rio >= 0.1.9
, rio-orphans
, shakespeare >= 2.0 , shakespeare >= 2.0
, template-haskell >= 2.11 , template-haskell >= 2.11
, text >= 0.7 , text >= 0.7
, time >= 1.5 , time >= 1.5
, transformers >= 0.4 , transformers >= 0.4
, unix-compat , unix-compat
, unliftio
, unordered-containers >= 0.2 , unordered-containers >= 0.2
, vector >= 0.9 && < 0.13 , vector >= 0.9 && < 0.13
, wai >= 3.2 , wai >= 3.2
, wai-extra >= 3.0.7 , wai-extra >= 3.0.7
-- FIXME remove?
, wai-logger >= 0.2 , wai-logger >= 0.2
, warp >= 3.0.2 , warp >= 3.0.2
, word8 , word8
@ -76,7 +75,6 @@ library
Yesod.Routes.TH.Types Yesod.Routes.TH.Types
other-modules: Yesod.Core.Internal.Session other-modules: Yesod.Core.Internal.Session
Yesod.Core.Internal.Request Yesod.Core.Internal.Request
Yesod.Core.Class.Handler
Yesod.Core.Internal.Util Yesod.Core.Internal.Util
Yesod.Core.Internal.Response Yesod.Core.Internal.Response
Yesod.Core.Internal.Run Yesod.Core.Internal.Run

View File

@ -22,7 +22,7 @@ import qualified Network.Wai.EventSource.EventStream as ES
-- | (Internal) Find out the request's 'EventSourcePolyfill' and -- | (Internal) Find out the request's 'EventSourcePolyfill' and
-- set any necessary headers. -- set any necessary headers.
prepareForEventSource :: MonadHandler m => m EventSourcePolyfill prepareForEventSource :: HasHandlerData env => RIO env EventSourcePolyfill
prepareForEventSource = do prepareForEventSource = do
reqWith <- lookup "X-Requested-With" . W.requestHeaders Data.Functor.<$> waiRequest reqWith <- lookup "X-Requested-With" . W.requestHeaders Data.Functor.<$> waiRequest
let polyfill | reqWith == Just "XMLHttpRequest" = Remy'sESPolyfill let polyfill | reqWith == Just "XMLHttpRequest" = Remy'sESPolyfill

View File

@ -140,7 +140,7 @@ data BootstrapFormLayout =
-- | Render the given form using Bootstrap v3 conventions. -- | Render the given form using Bootstrap v3 conventions.
-- --
-- Since: yesod-form 1.3.8 -- Since: yesod-form 1.3.8
renderBootstrap3 :: Monad m => BootstrapFormLayout -> FormRender m a renderBootstrap3 :: BootstrapFormLayout -> FormRender site a
renderBootstrap3 formLayout aform fragment = do renderBootstrap3 formLayout aform fragment = do
(res, views') <- aFormToForm aform (res, views') <- aFormToForm aform
let views = views' [] let views = views' []
@ -223,8 +223,8 @@ instance IsString msg => IsString (BootstrapSubmit msg) where
-- --
-- Since: yesod-form 1.3.8 -- Since: yesod-form 1.3.8
bootstrapSubmit bootstrapSubmit
:: (RenderMessage site msg, HandlerSite m ~ site, MonadHandler m) :: RenderMessage site msg
=> BootstrapSubmit msg -> AForm m () => BootstrapSubmit msg -> AForm site ()
bootstrapSubmit = formToAForm . liftM (second return) . mbootstrapSubmit bootstrapSubmit = formToAForm . liftM (second return) . mbootstrapSubmit
@ -234,8 +234,8 @@ bootstrapSubmit = formToAForm . liftM (second return) . mbootstrapSubmit
-- --
-- Since: yesod-form 1.3.8 -- Since: yesod-form 1.3.8
mbootstrapSubmit mbootstrapSubmit
:: (RenderMessage site msg, HandlerSite m ~ site, MonadHandler m) :: RenderMessage site msg
=> BootstrapSubmit msg -> MForm m (FormResult (), FieldView site) => BootstrapSubmit msg -> MForm site (FormResult (), FieldView site)
mbootstrapSubmit (BootstrapSubmit msg classes attrs) = mbootstrapSubmit (BootstrapSubmit msg classes attrs) =
let res = FormSuccess () let res = FormSuccess ()
widget = [whamlet|<button class="btn #{classes}" type=submit *{attrs}>_{msg}|] widget = [whamlet|<button class="btn #{classes}" type=submit *{attrs}>_{msg}|]

View File

@ -1,4 +1,5 @@
{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
@ -60,11 +61,13 @@ module Yesod.Form.Fields
, optionsEnum , optionsEnum
) where ) where
import RIO
import Yesod.Form.Types import Yesod.Form.Types
import Yesod.Form.I18n.English import Yesod.Form.I18n.English
import Yesod.Form.Functions (parseHelper) import Yesod.Form.Functions (parseHelper)
import Yesod.Core import Yesod.Core
import Text.Blaze (ToMarkup (toMarkup), unsafeByteString) import Text.Blaze (ToMarkup (toMarkup), unsafeByteString)
import Prelude (zipWith)
#define ToHtml ToMarkup #define ToHtml ToMarkup
#define toHtml toMarkup #define toHtml toMarkup
#define preEscapedText preEscapedToMarkup #define preEscapedText preEscapedToMarkup
@ -117,10 +120,10 @@ defaultFormMessage :: FormMessage -> Text
defaultFormMessage = englishFormMessage defaultFormMessage = englishFormMessage
-- | Creates a input with @type="number"@ and @step=1@. -- | Creates a input with @type="number"@ and @step=1@.
intField :: (Monad m, Integral i, RenderMessage (HandlerSite m) FormMessage) => Field m i intField :: (Integral i, RenderMessage site FormMessage) => Field site i
intField = Field intField = Field
{ fieldParse = parseHelper $ \s -> { fieldParse = parseHelper $ \s ->
case Data.Text.Read.signed Data.Text.Read.decimal s of case Data.Text.Read.signed Data.Text.Read.decimal s of -- FIXME it overflows
Right (a, "") -> Right a Right (a, "") -> Right a
_ -> Left $ MsgInvalidInteger s _ -> Left $ MsgInvalidInteger s
@ -135,7 +138,7 @@ $newline never
showI x = show (fromIntegral x :: Integer) showI x = show (fromIntegral x :: Integer)
-- | Creates a input with @type="number"@ and @step=any@. -- | Creates a input with @type="number"@ and @step=any@.
doubleField :: Monad m => RenderMessage (HandlerSite m) FormMessage => Field m Double doubleField :: RenderMessage site FormMessage => Field site Double
doubleField = Field doubleField = Field
{ fieldParse = parseHelper $ \s -> { fieldParse = parseHelper $ \s ->
case Data.Text.Read.double (prependZero s) of case Data.Text.Read.double (prependZero s) of
@ -153,7 +156,7 @@ $newline never
-- | Creates an input with @type="date"@, validating the input using the 'parseDate' function. -- | Creates an input with @type="date"@, validating the input using the 'parseDate' function.
-- --
-- Add the @time@ package and import the "Data.Time.Calendar" module to use this function. -- Add the @time@ package and import the "Data.Time.Calendar" module to use this function.
dayField :: Monad m => RenderMessage (HandlerSite m) FormMessage => Field m Day dayField :: RenderMessage site FormMessage => Field site Day
dayField = Field dayField = Field
{ fieldParse = parseHelper $ parseDate . unpack { fieldParse = parseHelper $ parseDate . unpack
, fieldView = \theId name attrs val isReq -> toWidget [hamlet| , fieldView = \theId name attrs val isReq -> toWidget [hamlet|
@ -165,7 +168,7 @@ $newline never
where showVal = either id (pack . show) where showVal = either id (pack . show)
-- | An alias for 'timeFieldTypeTime'. -- | An alias for 'timeFieldTypeTime'.
timeField :: Monad m => RenderMessage (HandlerSite m) FormMessage => Field m TimeOfDay timeField :: RenderMessage site FormMessage => Field site TimeOfDay
timeField = timeFieldTypeTime timeField = timeFieldTypeTime
-- | Creates an input with @type="time"@. <http://caniuse.com/#search=time%20input%20type Browsers not supporting this type> will fallback to a text field, and Yesod will parse the time as described in 'timeFieldTypeText'. -- | Creates an input with @type="time"@. <http://caniuse.com/#search=time%20input%20type Browsers not supporting this type> will fallback to a text field, and Yesod will parse the time as described in 'timeFieldTypeText'.
@ -173,7 +176,7 @@ timeField = timeFieldTypeTime
-- Add the @time@ package and import the "Data.Time.LocalTime" module to use this function. -- Add the @time@ package and import the "Data.Time.LocalTime" module to use this function.
-- --
-- Since 1.4.2 -- Since 1.4.2
timeFieldTypeTime :: Monad m => RenderMessage (HandlerSite m) FormMessage => Field m TimeOfDay timeFieldTypeTime :: RenderMessage site FormMessage => Field site TimeOfDay
timeFieldTypeTime = timeFieldOfType "time" 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). -- | 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).
@ -183,10 +186,10 @@ timeFieldTypeTime = timeFieldOfType "time"
-- Add the @time@ package and import the "Data.Time.LocalTime" module to use this function. -- Add the @time@ package and import the "Data.Time.LocalTime" module to use this function.
-- --
-- Since 1.4.2 -- Since 1.4.2
timeFieldTypeText :: Monad m => RenderMessage (HandlerSite m) FormMessage => Field m TimeOfDay timeFieldTypeText :: RenderMessage site FormMessage => Field site TimeOfDay
timeFieldTypeText = timeFieldOfType "text" timeFieldTypeText = timeFieldOfType "text"
timeFieldOfType :: Monad m => RenderMessage (HandlerSite m) FormMessage => Text -> Field m TimeOfDay timeFieldOfType :: RenderMessage site FormMessage => Text -> Field site TimeOfDay
timeFieldOfType inputType = Field timeFieldOfType inputType = Field
{ fieldParse = parseHelper parseTime { fieldParse = parseHelper parseTime
, fieldView = \theId name attrs val isReq -> toWidget [hamlet| , fieldView = \theId name attrs val isReq -> toWidget [hamlet|
@ -203,7 +206,7 @@ $newline never
fullSec = fromInteger $ floor $ todSec tod fullSec = fromInteger $ floor $ todSec tod
-- | Creates a @\<textarea>@ tag whose input is sanitized to prevent XSS attacks and is validated for having balanced tags. -- | Creates a @\<textarea>@ tag whose input is sanitized to prevent XSS attacks and is validated for having balanced tags.
htmlField :: Monad m => RenderMessage (HandlerSite m) FormMessage => Field m Html htmlField :: RenderMessage site FormMessage => Field site Html
htmlField = Field htmlField = Field
{ fieldParse = parseHelper $ Right . preEscapedText . sanitizeBalance { fieldParse = parseHelper $ Right . preEscapedText . sanitizeBalance
, fieldView = \theId name attrs val isReq -> toWidget [hamlet| , fieldView = \theId name attrs val isReq -> toWidget [hamlet|
@ -239,7 +242,7 @@ instance ToHtml Textarea where
writeHtmlEscapedChar c = B.writeHtmlEscapedChar c writeHtmlEscapedChar c = B.writeHtmlEscapedChar c
-- | Creates a @\<textarea>@ tag whose returned value is wrapped in a 'Textarea'; see 'Textarea' for details. -- | Creates a @\<textarea>@ tag whose returned value is wrapped in a 'Textarea'; see 'Textarea' for details.
textareaField :: Monad m => RenderMessage (HandlerSite m) FormMessage => Field m Textarea textareaField :: RenderMessage site FormMessage => Field site Textarea
textareaField = Field textareaField = Field
{ fieldParse = parseHelper $ Right . Textarea { fieldParse = parseHelper $ Right . Textarea
, fieldView = \theId name attrs val isReq -> toWidget [hamlet| , fieldView = \theId name attrs val isReq -> toWidget [hamlet|
@ -250,8 +253,8 @@ $newline never
} }
-- | Creates an input with @type="hidden"@; you can use this to store information in a form that users shouldn't see (for example, Yesod stores CSRF tokens in a hidden field). -- | Creates an input with @type="hidden"@; you can use this to store information in a form that users shouldn't see (for example, Yesod stores CSRF tokens in a hidden field).
hiddenField :: (Monad m, PathPiece p, RenderMessage (HandlerSite m) FormMessage) hiddenField :: (PathPiece p, RenderMessage site FormMessage)
=> Field m p => Field site p
hiddenField = Field hiddenField = Field
{ fieldParse = parseHelper $ maybe (Left MsgValueRequired) Right . fromPathPiece { fieldParse = parseHelper $ maybe (Left MsgValueRequired) Right . fromPathPiece
, fieldView = \theId name attrs val _isReq -> toWidget [hamlet| , fieldView = \theId name attrs val _isReq -> toWidget [hamlet|
@ -262,7 +265,7 @@ $newline never
} }
-- | Creates a input with @type="text"@. -- | Creates a input with @type="text"@.
textField :: Monad m => RenderMessage (HandlerSite m) FormMessage => Field m Text textField :: RenderMessage site FormMessage => Field site Text
textField = Field textField = Field
{ fieldParse = parseHelper $ Right { fieldParse = parseHelper $ Right
, fieldView = \theId name attrs val isReq -> , fieldView = \theId name attrs val isReq ->
@ -273,7 +276,7 @@ $newline never
, fieldEnctype = UrlEncoded , fieldEnctype = UrlEncoded
} }
-- | Creates an input with @type="password"@. -- | Creates an input with @type="password"@.
passwordField :: Monad m => RenderMessage (HandlerSite m) FormMessage => Field m Text passwordField :: RenderMessage site FormMessage => Field site Text
passwordField = Field passwordField = Field
{ fieldParse = parseHelper $ Right { fieldParse = parseHelper $ Right
, fieldView = \theId name attrs _ isReq -> toWidget [hamlet| , fieldView = \theId name attrs _ isReq -> toWidget [hamlet|
@ -283,15 +286,10 @@ $newline never
, fieldEnctype = UrlEncoded , fieldEnctype = UrlEncoded
} }
readMay :: Read a => String -> Maybe a
readMay s = case filter (Prelude.null . snd) $ reads s of
(x, _):_ -> Just x
[] -> Nothing
-- | Parses a 'Day' from a 'String'. -- | Parses a 'Day' from a 'String'.
parseDate :: String -> Either FormMessage Day parseDate :: String -> Either FormMessage Day
parseDate = maybe (Left MsgInvalidDay) Right parseDate = maybe (Left MsgInvalidDay) Right
. readMay . replace '/' '-' . readMaybe . replace '/' '-'
-- | Replaces all instances of a value in a list by another value. -- | Replaces all instances of a value in a list by another value.
-- from http://hackage.haskell.org/packages/archive/cgi/3001.1.7.1/doc/html/src/Network-CGI-Protocol.html#replace -- from http://hackage.haskell.org/packages/archive/cgi/3001.1.7.1/doc/html/src/Network-CGI-Protocol.html#replace
@ -299,7 +297,7 @@ replace :: Eq a => a -> a -> [a] -> [a]
replace x y = map (\z -> if z == x then y else z) replace x y = map (\z -> if z == x then y else z)
parseTime :: Text -> Either FormMessage TimeOfDay parseTime :: Text -> Either FormMessage TimeOfDay
parseTime = either (Left . fromMaybe MsgInvalidTimeFormat . readMay . drop 2 . dropWhile (/= ':')) Right . parseOnly timeParser parseTime = either (Left . fromMaybe MsgInvalidTimeFormat . readMaybe . drop 2 . dropWhile (/= ':')) Right . parseOnly timeParser
timeParser :: Parser TimeOfDay timeParser :: Parser TimeOfDay
timeParser = do timeParser = do
@ -331,7 +329,10 @@ timeParser = do
x <- digit x <- digit
y <- (return Control.Applicative.<$> digit) <|> return [] y <- (return Control.Applicative.<$> digit) <|> return []
let xy = x : y let xy = x : y
let i = read xy let i =
case readMaybe xy of
Just i' -> i'
Nothing -> error $ "The impossible happened parsing: " ++ show xy
if i < 0 || i >= 24 if i < 0 || i >= 24
then fail $ show $ MsgInvalidHour $ pack xy then fail $ show $ MsgInvalidHour $ pack xy
else return i else return i
@ -340,13 +341,16 @@ timeParser = do
x <- digit x <- digit
y <- digit <|> fail (show $ msg $ pack [x]) y <- digit <|> fail (show $ msg $ pack [x])
let xy = [x, y] let xy = [x, y]
let i = read xy let i =
case readMaybe xy of
Just i' -> i'
Nothing -> error $ "The impossible happened parsing: " ++ show xy
if i < 0 || i >= 60 if i < 0 || i >= 60
then fail $ show $ msg $ pack xy then fail $ show $ msg $ pack xy
else return $ fromIntegral (i :: Int) else return $ fromIntegral (i :: Int)
-- | Creates an input with @type="email"@. Yesod will validate the email's correctness according to RFC5322 and canonicalize it by removing comments and whitespace (see "Text.Email.Validate"). -- | Creates an input with @type="email"@. Yesod will validate the email's correctness according to RFC5322 and canonicalize it by removing comments and whitespace (see "Text.Email.Validate").
emailField :: Monad m => RenderMessage (HandlerSite m) FormMessage => Field m Text emailField :: RenderMessage site FormMessage => Field site Text
emailField = Field emailField = Field
{ fieldParse = parseHelper $ { fieldParse = parseHelper $
\s -> \s ->
@ -363,7 +367,7 @@ $newline never
-- | Creates an input with @type="email"@ with the <http://w3c.github.io/html/sec-forms.html#the-multiple-attribute multiple> attribute; browsers might implement this as taking a comma separated list of emails. Each email address is validated as described in 'emailField'. -- | Creates an input with @type="email"@ with the <http://w3c.github.io/html/sec-forms.html#the-multiple-attribute multiple> attribute; browsers might implement this as taking a comma separated list of emails. Each email address is validated as described in 'emailField'.
-- --
-- Since 1.3.7 -- Since 1.3.7
multiEmailField :: Monad m => RenderMessage (HandlerSite m) FormMessage => Field m [Text] multiEmailField :: RenderMessage site FormMessage => Field site [Text]
multiEmailField = Field multiEmailField = Field
{ fieldParse = parseHelper $ { fieldParse = parseHelper $
\s -> \s ->
@ -387,7 +391,7 @@ $newline never
type AutoFocus = Bool type AutoFocus = Bool
-- | Creates an input with @type="search"@. For <http://caniuse.com/#search=autofocus browsers without autofocus support>, a JS fallback is used if @AutoFocus@ is true. -- | Creates an input with @type="search"@. For <http://caniuse.com/#search=autofocus browsers without autofocus support>, a JS fallback is used if @AutoFocus@ is true.
searchField :: Monad m => RenderMessage (HandlerSite m) FormMessage => AutoFocus -> Field m Text searchField :: RenderMessage site FormMessage => AutoFocus -> Field site Text
searchField autoFocus = Field searchField autoFocus = Field
{ fieldParse = parseHelper Right { fieldParse = parseHelper Right
, fieldView = \theId name attrs val isReq -> do , fieldView = \theId name attrs val isReq -> do
@ -408,7 +412,7 @@ $newline never
, fieldEnctype = UrlEncoded , fieldEnctype = UrlEncoded
} }
-- | Creates an input with @type="url"@, validating the URL according to RFC3986. -- | Creates an input with @type="url"@, validating the URL according to RFC3986.
urlField :: Monad m => RenderMessage (HandlerSite m) FormMessage => Field m Text urlField :: RenderMessage site FormMessage => Field site Text
urlField = Field urlField = Field
{ fieldParse = parseHelper $ \s -> { fieldParse = parseHelper $ \s ->
case parseURI $ unpack s of case parseURI $ unpack s of
@ -424,7 +428,7 @@ urlField = Field
-- > areq (selectFieldList [("Value 1" :: Text, "value1"),("Value 2", "value2")]) "Which value?" Nothing -- > areq (selectFieldList [("Value 1" :: Text, "value1"),("Value 2", "value2")]) "Which value?" Nothing
selectFieldList :: (Eq a, RenderMessage site FormMessage, RenderMessage site msg) selectFieldList :: (Eq a, RenderMessage site FormMessage, RenderMessage site msg)
=> [(msg, a)] => [(msg, a)]
-> Field (HandlerFor site) a -> Field site a
selectFieldList = selectField . optionsPairs selectFieldList = selectField . optionsPairs
-- | Creates a @\<select>@ tag for selecting one option. Example usage: -- | Creates a @\<select>@ tag for selecting one option. Example usage:
@ -432,7 +436,7 @@ selectFieldList = selectField . optionsPairs
-- > areq (selectField $ optionsPairs [(MsgValue1, "value1"),(MsgValue2, "value2")]) "Which value?" Nothing -- > areq (selectField $ optionsPairs [(MsgValue1, "value1"),(MsgValue2, "value2")]) "Which value?" Nothing
selectField :: (Eq a, RenderMessage site FormMessage) selectField :: (Eq a, RenderMessage site FormMessage)
=> HandlerFor site (OptionList a) => HandlerFor site (OptionList a)
-> Field (HandlerFor site) a -> Field site a
selectField = selectFieldHelper selectField = selectFieldHelper
(\theId name attrs inside -> [whamlet| (\theId name attrs inside -> [whamlet|
$newline never $newline never
@ -450,15 +454,15 @@ $newline never
-- | Creates a @\<select>@ tag for selecting multiple options. -- | Creates a @\<select>@ tag for selecting multiple options.
multiSelectFieldList :: (Eq a, RenderMessage site msg) multiSelectFieldList :: (Eq a, RenderMessage site msg)
=> [(msg, a)] => [(msg, a)]
-> Field (HandlerFor site) [a] -> Field site [a]
multiSelectFieldList = multiSelectField . optionsPairs multiSelectFieldList = multiSelectField . optionsPairs
-- | Creates a @\<select>@ tag for selecting multiple options. -- | Creates a @\<select>@ tag for selecting multiple options.
multiSelectField :: Eq a multiSelectField :: Eq a
=> HandlerFor site (OptionList a) => HandlerFor site (OptionList a)
-> Field (HandlerFor site) [a] -> Field site [a]
multiSelectField ioptlist = multiSelectField ioptlist =
Field parse view UrlEncoded Field parse view' UrlEncoded
where where
parse [] _ = return $ Right Nothing parse [] _ = return $ Right Nothing
parse optlist _ = do parse optlist _ = do
@ -467,7 +471,7 @@ multiSelectField ioptlist =
Nothing -> return $ Left "Error parsing values" Nothing -> return $ Left "Error parsing values"
Just res -> return $ Right $ Just res Just res -> return $ Right $ Just res
view theId name attrs val isReq = do view' theId name attrs val isReq = do
opts <- fmap olOptions $ handlerToWidget ioptlist opts <- fmap olOptions $ handlerToWidget ioptlist
let selOpts = map (id &&& (optselected val)) opts let selOpts = map (id &&& (optselected val)) opts
[whamlet| [whamlet|
@ -482,18 +486,18 @@ multiSelectField ioptlist =
-- | Creates an input with @type="radio"@ for selecting one option. -- | Creates an input with @type="radio"@ for selecting one option.
radioFieldList :: (Eq a, RenderMessage site FormMessage, RenderMessage site msg) radioFieldList :: (Eq a, RenderMessage site FormMessage, RenderMessage site msg)
=> [(msg, a)] => [(msg, a)]
-> Field (HandlerFor site) a -> Field site a
radioFieldList = radioField . optionsPairs radioFieldList = radioField . optionsPairs
-- | Creates an input with @type="checkbox"@ for selecting multiple options. -- | Creates an input with @type="checkbox"@ for selecting multiple options.
checkboxesFieldList :: (Eq a, RenderMessage site msg) => [(msg, a)] checkboxesFieldList :: (Eq a, RenderMessage site msg) => [(msg, a)]
-> Field (HandlerFor site) [a] -> Field site [a]
checkboxesFieldList = checkboxesField . optionsPairs checkboxesFieldList = checkboxesField . optionsPairs
-- | Creates an input with @type="checkbox"@ for selecting multiple options. -- | Creates an input with @type="checkbox"@ for selecting multiple options.
checkboxesField :: Eq a checkboxesField :: Eq a
=> HandlerFor site (OptionList a) => HandlerFor site (OptionList a)
-> Field (HandlerFor site) [a] -> Field site [a]
checkboxesField ioptlist = (multiSelectField ioptlist) checkboxesField ioptlist = (multiSelectField ioptlist)
{ fieldView = { fieldView =
\theId name attrs val _isReq -> do \theId name attrs val _isReq -> do
@ -511,7 +515,7 @@ checkboxesField ioptlist = (multiSelectField ioptlist)
-- | Creates an input with @type="radio"@ for selecting one option. -- | Creates an input with @type="radio"@ for selecting one option.
radioField :: (Eq a, RenderMessage site FormMessage) radioField :: (Eq a, RenderMessage site FormMessage)
=> HandlerFor site (OptionList a) => HandlerFor site (OptionList a)
-> Field (HandlerFor site) a -> Field site a
radioField = selectFieldHelper radioField = selectFieldHelper
(\theId _name _attrs inside -> [whamlet| (\theId _name _attrs inside -> [whamlet|
$newline never $newline never
@ -539,7 +543,7 @@ $newline never
-- If this field is required, the first radio button is labeled \"Yes" and the second \"No". -- If this field is required, the first radio button is labeled \"Yes" and the second \"No".
-- --
-- (Exact label titles will depend on localization). -- (Exact label titles will depend on localization).
boolField :: Monad m => RenderMessage (HandlerSite m) FormMessage => Field m Bool boolField :: RenderMessage site FormMessage => Field site Bool
boolField = Field boolField = Field
{ fieldParse = \e _ -> return $ boolParser e { fieldParse = \e _ -> return $ boolParser e
, fieldView = \theId name attrs val isReq -> [whamlet| , fieldView = \theId name attrs val isReq -> [whamlet|
@ -578,7 +582,7 @@ $newline never
-- --
-- Note that this makes the field always optional. -- Note that this makes the field always optional.
-- --
checkBoxField :: Monad m => Field m Bool checkBoxField :: Field site Bool
checkBoxField = Field checkBoxField = Field
{ fieldParse = \e _ -> return $ checkBoxParser e { fieldParse = \e _ -> return $ checkBoxParser e
, fieldView = \theId name attrs val _ -> [whamlet| , fieldView = \theId name attrs val _ -> [whamlet|
@ -623,22 +627,21 @@ data Option a = Option
-- | Since 1.4.6 -- | Since 1.4.6
instance Functor Option where instance Functor Option where
fmap f (Option display internal external) = Option display (f internal) external fmap f (Option display' internal external) = Option display' (f internal) external
-- | Creates an 'OptionList' from a list of (display-value, internal value) pairs. -- | Creates an 'OptionList' from a list of (display-value, internal value) pairs.
optionsPairs :: (MonadHandler m, RenderMessage (HandlerSite m) msg) optionsPairs :: RenderMessage site msg => [(msg, a)] -> HandlerFor site (OptionList a)
=> [(msg, a)] -> m (OptionList a)
optionsPairs opts = do optionsPairs opts = do
mr <- getMessageRender mr <- getMessageRender
let mkOption external (display, internal) = let mkOption external (display', internal) =
Option { optionDisplay = mr display Option { optionDisplay = mr display'
, optionInternalValue = internal , optionInternalValue = internal
, optionExternalValue = pack $ show external , optionExternalValue = pack $ show external
} }
return $ mkOptionList (zipWith mkOption [1 :: Int ..] opts) return $ mkOptionList (zipWith mkOption [1 :: Int ..] opts)
-- | Creates an 'OptionList' from an 'Enum', using its 'Show' instance for the user-facing value. -- | Creates an 'OptionList' from an 'Enum', using its 'Show' instance for the user-facing value.
optionsEnum :: (MonadHandler m, Show a, Enum a, Bounded a) => m (OptionList a) optionsEnum :: (Show a, Enum a, Bounded a) => HandlerFor site (OptionList a)
optionsEnum = optionsPairs $ map (\x -> (pack $ show x, x)) [minBound..maxBound] optionsEnum = optionsPairs $ map (\x -> (pack $ show x, x)) [minBound..maxBound]
-- | Selects a list of 'Entity's with the given 'Filter' and 'SelectOpt's. The @(a -> msg)@ function is then used to derive the display value for an 'OptionList'. Example usage: -- | Selects a list of 'Entity's with the given 'Filter' and 'SelectOpt's. The @(a -> msg)@ function is then used to derive the display value for an 'OptionList'. Example usage:
@ -656,33 +659,22 @@ optionsEnum = optionsPairs $ map (\x -> (pack $ show x, x)) [minBound..maxBound]
-- > <$> areq (selectField countries) "Which country do you live in?" Nothing -- > <$> areq (selectField countries) "Which country do you live in?" Nothing
-- > where -- > where
-- > countries = optionsPersist [] [Asc CountryName] countryName -- > countries = optionsPersist [] [Asc CountryName] countryName
#if MIN_VERSION_persistent(2,5,0)
optionsPersist :: ( YesodPersist site optionsPersist :: ( YesodPersist site
, PersistQueryRead backend , PersistQueryRead backend
, PathPiece (Key a) , PathPiece (Key a)
, RenderMessage site msg , RenderMessage site msg
, YesodPersistBackend site ~ backend , YesodPersistBackend site ~ backend
, PersistRecordBackend a backend , PersistRecordBackend a backend
, site ~ HandlerSite env
, HasHandlerData env
) )
=> [Filter a] => [Filter a]
-> [SelectOpt a] -> [SelectOpt a]
-> (a -> msg) -> (a -> msg)
-> HandlerFor site (OptionList (Entity a)) -> RIO env (OptionList (Entity a))
#else
optionsPersist :: ( YesodPersist site, PersistEntity a
, PersistQuery (PersistEntityBackend a)
, PathPiece (Key a)
, RenderMessage site msg
, YesodPersistBackend site ~ PersistEntityBackend a
)
=> [Filter a]
-> [SelectOpt a]
-> (a -> msg)
-> HandlerFor site (OptionList (Entity a))
#endif
optionsPersist filts ords toDisplay = fmap mkOptionList $ do optionsPersist filts ords toDisplay = fmap mkOptionList $ do
mr <- getMessageRender mr <- getMessageRender
pairs <- runDB $ selectList filts ords pairs <- liftHandler $ runDB $ selectList filts ords
return $ map (\(Entity key value) -> Option return $ map (\(Entity key value) -> Option
{ optionDisplay = mr (toDisplay value) { optionDisplay = mr (toDisplay value)
, optionInternalValue = Entity key value , optionInternalValue = Entity key value
@ -693,35 +685,21 @@ optionsPersist filts ords toDisplay = fmap mkOptionList $ do
-- the entire 'Entity'. -- the entire 'Entity'.
-- --
-- Since 1.3.2 -- Since 1.3.2
#if MIN_VERSION_persistent(2,5,0)
optionsPersistKey optionsPersistKey
:: (YesodPersist site :: ( YesodPersist site
, PersistQueryRead backend , PersistQueryRead backend
, PathPiece (Key a) , PathPiece (Key a)
, RenderMessage site msg , RenderMessage site msg
, backend ~ YesodPersistBackend site , backend ~ YesodPersistBackend site
, site ~ HandlerSite env
, PersistRecordBackend a backend , PersistRecordBackend a backend
, HasHandlerData env
) )
=> [Filter a] => [Filter a]
-> [SelectOpt a] -> [SelectOpt a]
-> (a -> msg) -> (a -> msg)
-> HandlerFor site (OptionList (Key a)) -> RIO env (OptionList (Key a))
#else optionsPersistKey filts ords toDisplay = liftHandler $ fmap mkOptionList $ do
optionsPersistKey
:: (YesodPersist site
, PersistEntity a
, PersistQuery (PersistEntityBackend a)
, PathPiece (Key a)
, RenderMessage site msg
, YesodPersistBackend site ~ PersistEntityBackend a
)
=> [Filter a]
-> [SelectOpt a]
-> (a -> msg)
-> HandlerFor site (OptionList (Key a))
#endif
optionsPersistKey filts ords toDisplay = fmap mkOptionList $ do
mr <- getMessageRender mr <- getMessageRender
pairs <- runDB $ selectList filts ords pairs <- runDB $ selectList filts ords
return $ map (\(Entity key value) -> Option return $ map (\(Entity key value) -> Option
@ -740,7 +718,7 @@ selectFieldHelper
-> (Text -> Text -> Bool -> WidgetFor site ()) -- ^ An option for None if the field is optional -> (Text -> Text -> Bool -> WidgetFor site ()) -- ^ An option for None if the field is optional
-> (Text -> Text -> [(Text, Text)] -> Text -> Bool -> Text -> WidgetFor site ()) -- ^ Other options -> (Text -> Text -> [(Text, Text)] -> Text -> Bool -> Text -> WidgetFor site ()) -- ^ Other options
-> HandlerFor site (OptionList a) -> HandlerFor site (OptionList a)
-> Field (HandlerFor site) a -> Field site a
selectFieldHelper outside onOpt inside opts' = Field selectFieldHelper outside onOpt inside opts' = Field
{ fieldParse = \x _ -> do { fieldParse = \x _ -> do
opts <- opts' opts <- opts'
@ -770,8 +748,7 @@ selectFieldHelper outside onOpt inside opts' = Field
Just y -> Right $ Just y Just y -> Right $ Just y
-- | Creates an input with @type="file"@. -- | Creates an input with @type="file"@.
fileField :: Monad m fileField :: Field site FileInfo
=> Field m FileInfo
fileField = Field fileField = Field
{ fieldParse = \_ files -> return $ { fieldParse = \_ files -> return $
case files of case files of
@ -783,18 +760,23 @@ fileField = Field
, fieldEnctype = Multipart , fieldEnctype = Multipart
} }
fileAFormReq :: (MonadHandler m, RenderMessage (HandlerSite m) FormMessage) fileAFormReq :: RenderMessage site FormMessage
=> FieldSettings (HandlerSite m) -> AForm m FileInfo => FieldSettings site -> AForm site FileInfo
fileAFormReq fs = AForm $ \(site, langs) menvs ints -> do fileAFormReq fs = AForm $ do
site <- getYesod
langs <- reqLangs <$> getRequest
WFormData viewsDeque mfd <- view id
ints <- readIORef $ mfdInts mfd
let (name, ints') = let (name, ints') =
case fsName fs of case fsName fs of
Just x -> (x, ints) Just x -> (x, ints)
Nothing -> Nothing ->
let i' = incrInts ints let i' = incrInts ints
in (pack $ 'f' : show i', i') in (pack $ 'f' : show i', i')
writeIORef (mfdInts mfd) ints'
id' <- maybe newIdent return $ fsId fs id' <- maybe newIdent return $ fsId fs
let (res, errs) = let (res, errs) =
case menvs of case mfdParams mfd of
Nothing -> (FormMissing, Nothing) Nothing -> (FormMissing, Nothing)
Just (_, fenv) -> Just (_, fenv) ->
case Map.lookup name fenv of case Map.lookup name fenv of
@ -813,21 +795,26 @@ $newline never
, fvErrors = errs , fvErrors = errs
, fvRequired = True , fvRequired = True
} }
return (res, (fv :), ints', Multipart) writeIORef (mfdEnctype mfd) Multipart
pushBackDeque viewsDeque fv
return res
fileAFormOpt :: MonadHandler m fileAFormOpt :: FieldSettings site -> AForm site (Maybe FileInfo)
=> FieldSettings (HandlerSite m) fileAFormOpt fs = AForm $ do
-> AForm m (Maybe FileInfo) master <- getYesod
fileAFormOpt fs = AForm $ \(master, langs) menvs ints -> do langs <- reqLangs <$> getRequest
WFormData viewsDeque mfd <- view id
ints <- readIORef $ mfdInts mfd
let (name, ints') = let (name, ints') =
case fsName fs of case fsName fs of
Just x -> (x, ints) Just x -> (x, ints)
Nothing -> Nothing ->
let i' = incrInts ints let i' = incrInts ints
in (pack $ 'f' : show i', i') in (pack $ 'f' : show i', i')
writeIORef (mfdInts mfd) ints'
id' <- maybe newIdent return $ fsId fs id' <- maybe newIdent return $ fsId fs
let (res, errs) = let (res, errs) =
case menvs of case mfdParams mfd of
Nothing -> (FormMissing, Nothing) Nothing -> (FormMissing, Nothing)
Just (_, fenv) -> Just (_, fenv) ->
case Map.lookup name fenv of case Map.lookup name fenv of
@ -844,7 +831,9 @@ $newline never
, fvErrors = errs , fvErrors = errs
, fvRequired = False , fvRequired = False
} }
return (res, (fv :), ints', Multipart) writeIORef (mfdEnctype mfd) Multipart
pushBackDeque viewsDeque fv
return res
incrInts :: Ints -> Ints incrInts :: Ints -> Ints
incrInts (IntSingle i) = IntSingle $ i + 1 incrInts (IntSingle i) = IntSingle $ i + 1

View File

@ -1,3 +1,4 @@
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TupleSections #-} {-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeFamilies #-}
@ -38,7 +39,6 @@ module Yesod.Form.Functions
, renderTable , renderTable
, renderDivs , renderDivs
, renderDivsNoLabels , renderDivsNoLabels
, renderBootstrap
, renderBootstrap2 , renderBootstrap2
-- * Validation -- * Validation
, check , check
@ -55,13 +55,12 @@ module Yesod.Form.Functions
, removeClass , removeClass
) where ) where
import RIO hiding (ask, local)
import Yesod.Form.Types import Yesod.Form.Types
import Yesod.Core.Types (liftHandler)
import Data.Text (Text, pack) import Data.Text (Text, pack)
import qualified Data.Text as T import qualified Data.Text as T
import Control.Arrow (second) import Control.Arrow (second)
import Control.Monad.Trans.Class
import Control.Monad.Trans.RWS (ask, get, put, runRWST, tell, evalRWST, local, mapRWST)
import Control.Monad.Trans.Writer (runWriterT, writer)
import Control.Monad (liftM, join) import Control.Monad (liftM, join)
import Data.Byteable (constEqBytes) import Data.Byteable (constEqBytes)
import Text.Blaze (Markup, toMarkup) import Text.Blaze (Markup, toMarkup)
@ -75,8 +74,28 @@ import qualified Data.Map as Map
import qualified Data.Text.Encoding as TE import qualified Data.Text.Encoding as TE
import Control.Arrow (first) import Control.Arrow (first)
get :: MForm site Ints
get = view (to mfdInts) >>= readIORef
put :: Ints -> MForm site ()
put ints = view (to mfdInts) >>= (`writeIORef` ints)
tell :: Enctype -> MForm site ()
tell ec = view (to mfdEnctype) >>= (`writeIORef` ec)
local
:: ( Maybe (Env, FileEnv)
-> Maybe (Env, FileEnv)
)
-> MForm site a
-> MForm site a
local f inner = do
mfd <- view id
let mfd' = mfd { mfdParams = f $ mfdParams mfd }
runRIO mfd' inner
-- | Get a unique identifier. -- | Get a unique identifier.
newFormIdent :: Monad m => MForm m Text newFormIdent :: MForm site Text
newFormIdent = do newFormIdent = do
i <- get i <- get
let i' = incrInts i let i' = incrInts i
@ -86,43 +105,34 @@ newFormIdent = do
incrInts (IntSingle i) = IntSingle $ i + 1 incrInts (IntSingle i) = IntSingle $ i + 1
incrInts (IntCons i is) = (i + 1) `IntCons` is incrInts (IntCons i is) = (i + 1) `IntCons` is
formToAForm :: (HandlerSite m ~ site, Monad m) formToAForm :: MForm site (FormResult a, [FieldView site]) -> AForm site a
=> MForm m (FormResult a, [FieldView site]) formToAForm mform = AForm $ do
-> AForm m a WFormData viewsDeque mfd <- view id
formToAForm form = AForm $ \(site, langs) env ints -> do (a, views) <- runRIO mfd mform
((a, xmls), ints', enc) <- runRWST form (env, site, langs) ints for_ views $ pushBackDeque viewsDeque
return (a, (++) xmls, ints', enc) pure a
aFormToForm :: (Monad m, HandlerSite m ~ site) aFormToForm :: AForm site a
=> AForm m a -> MForm site (FormResult a, [FieldView site] -> [FieldView site])
-> MForm m (FormResult a, [FieldView site] -> [FieldView site]) aFormToForm (AForm wform) = do
aFormToForm (AForm aform) = do (res, views) <- wFormToMForm wform
ints <- get pure (res, (views++))
(env, site, langs) <- ask
(a, xml, ints', enc) <- lift $ aform (site, langs) env ints
put ints'
tell enc
return (a, xml)
askParams :: Monad m => MForm m (Maybe Env) askParams :: MForm site (Maybe Env)
askParams = do askParams = view $ to (fmap fst . mfdParams)
(x, _, _) <- ask
return $ liftM fst x
askFiles :: Monad m => MForm m (Maybe FileEnv) askFiles :: MForm site (Maybe FileEnv)
askFiles = do askFiles = view $ to (fmap snd . mfdParams)
(x, _, _) <- ask
return $ liftM snd x
-- | Converts a form field into monadic form 'WForm'. This field requires a -- | Converts a form field into monadic form 'WForm'. This field requires a
-- value and will return 'FormFailure' if left empty. -- value and will return 'FormFailure' if left empty.
-- --
-- @since 1.4.14 -- @since 1.4.14
wreq :: (RenderMessage site FormMessage, HandlerSite m ~ site, MonadHandler m) wreq :: RenderMessage site FormMessage
=> Field m a -- ^ form field => Field site a -- ^ form field
-> FieldSettings site -- ^ settings for this field -> FieldSettings site -- ^ settings for this field
-> Maybe a -- ^ optional default value -> Maybe a -- ^ optional default value
-> WForm m (FormResult a) -> WForm site (FormResult a)
wreq f fs = mFormToWForm . mreq f fs wreq f fs = mFormToWForm . mreq f fs
-- | Converts a form field into monadic form 'WForm'. This field is optional, -- | Converts a form field into monadic form 'WForm'. This field is optional,
@ -131,75 +141,78 @@ wreq f fs = mFormToWForm . mreq f fs
-- value). -- value).
-- --
-- @since 1.4.14 -- @since 1.4.14
wopt :: (MonadHandler m, HandlerSite m ~ site) wopt :: Field site a -- ^ form field
=> Field m a -- ^ form field
-> FieldSettings site -- ^ settings for this field -> FieldSettings site -- ^ settings for this field
-> Maybe (Maybe a) -- ^ optional default value -> Maybe (Maybe a) -- ^ optional default value
-> WForm m (FormResult (Maybe a)) -> WForm site (FormResult (Maybe a))
wopt f fs = mFormToWForm . mopt f fs wopt f fs = mFormToWForm . mopt f fs
-- | Converts a monadic form 'WForm' into an applicative form 'AForm'. -- | Converts a monadic form 'WForm' into an applicative form 'AForm'.
-- --
-- @since 1.4.14 -- @since 1.4.14
wFormToAForm :: MonadHandler m wFormToAForm
=> WForm m (FormResult a) -- ^ input form :: WForm site (FormResult a) -- ^ input form
-> AForm m a -- ^ output form -> AForm site a -- ^ output form
wFormToAForm = formToAForm . wFormToMForm wFormToAForm = formToAForm . wFormToMForm
-- | Converts a monadic form 'WForm' into another monadic form 'MForm'. -- | Converts a monadic form 'WForm' into another monadic form 'MForm'.
-- --
-- @since 1.4.14 -- @since 1.4.14
wFormToMForm :: (MonadHandler m, HandlerSite m ~ site) wFormToMForm
=> WForm m a -- ^ input form :: WForm site a -- ^ input form
-> MForm m (a, [FieldView site]) -- ^ output form -> MForm site (a, [FieldView site]) -- ^ output form
wFormToMForm = mapRWST (fmap group . runWriterT) wFormToMForm wform = do
where viewsDeque <- newDeque
group ((a, ints, enctype), views) = ((a, views), ints, enctype) mfd <- view id
a <- runRIO (WFormData viewsDeque mfd) wform
views <- dequeToList viewsDeque
pure (a, views)
-- | Converts a monadic form 'MForm' into another monadic form 'WForm'. -- | Converts a monadic form 'MForm' into another monadic form 'WForm'.
-- --
-- @since 1.4.14 -- @since 1.4.14
mFormToWForm :: (MonadHandler m, HandlerSite m ~ site) mFormToWForm
=> MForm m (a, FieldView site) -- ^ input form :: MForm site (a, FieldView site) -- ^ input form
-> WForm m a -- ^ output form -> WForm site a -- ^ output form
mFormToWForm = mapRWST $ \f -> do mFormToWForm mform = do
((a, view), ints, enctype) <- lift f WFormData viewsDeque mfd <- view id
writer ((a, ints, enctype), [view]) (a, view') <- runRIO mfd mform
pushBackDeque viewsDeque view'
pure a
-- | Converts a form field into monadic form. This field requires a value -- | Converts a form field into monadic form. This field requires a value
-- and will return 'FormFailure' if left empty. -- and will return 'FormFailure' if left empty.
mreq :: (RenderMessage site FormMessage, HandlerSite m ~ site, MonadHandler m) mreq :: RenderMessage site FormMessage
=> Field m a -- ^ form field => Field site a -- ^ form field
-> FieldSettings site -- ^ settings for this field -> FieldSettings site -- ^ settings for this field
-> Maybe a -- ^ optional default value -> Maybe a -- ^ optional default value
-> MForm m (FormResult a, FieldView site) -> MForm site (FormResult a, FieldView site)
mreq field fs mdef = mhelper field fs mdef (\m l -> FormFailure [renderMessage m l MsgValueRequired]) FormSuccess True mreq field fs mdef = mhelper field fs mdef (\m l -> FormFailure [renderMessage m l MsgValueRequired]) FormSuccess True
-- | Converts a form field into monadic form. This field is optional, i.e. -- | Converts a form field into monadic form. This field is optional, i.e.
-- if filled in, it returns 'Just a', if left empty, it returns 'Nothing'. -- if filled in, it returns 'Just a', if left empty, it returns 'Nothing'.
-- Arguments are the same as for 'mreq' (apart from type of default value). -- Arguments are the same as for 'mreq' (apart from type of default value).
mopt :: (site ~ HandlerSite m, MonadHandler m) mopt :: Field site a
=> Field m a
-> FieldSettings site -> FieldSettings site
-> Maybe (Maybe a) -> Maybe (Maybe a)
-> MForm m (FormResult (Maybe a), FieldView site) -> MForm site (FormResult (Maybe a), FieldView site)
mopt field fs mdef = mhelper field fs (join mdef) (const $ const $ FormSuccess Nothing) (FormSuccess . Just) False mopt field fs mdef = mhelper field fs (join mdef) (const $ const $ FormSuccess Nothing) (FormSuccess . Just) False
mhelper :: (site ~ HandlerSite m, MonadHandler m) mhelper :: Field site a
=> Field m a
-> FieldSettings site -> FieldSettings site
-> Maybe a -> Maybe a
-> (site -> [Text] -> FormResult b) -- ^ on missing -> (site -> [Text] -> FormResult b) -- ^ on missing
-> (a -> FormResult b) -- ^ on success -> (a -> FormResult b) -- ^ on success
-> Bool -- ^ is it required? -> Bool -- ^ is it required?
-> MForm m (FormResult b, FieldView site) -> MForm site (FormResult b, FieldView site)
mhelper Field {..} FieldSettings {..} mdef onMissing onFound isReq = do mhelper Field {..} FieldSettings {..} mdef onMissing onFound isReq = do
tell fieldEnctype tell fieldEnctype
mp <- askParams mp <- askParams
name <- maybe newFormIdent return fsName name <- maybe newFormIdent return fsName
theId <- lift $ maybe newIdent return fsId theId <- maybe newIdent return fsId
(_, site, langs) <- ask site <- getYesod
langs <- reqLangs <$> getRequest
let mr2 = renderMessage site langs let mr2 = renderMessage site langs
(res, val) <- (res, val) <-
case mp of case mp of
@ -208,7 +221,7 @@ mhelper Field {..} FieldSettings {..} mdef onMissing onFound isReq = do
mfs <- askFiles mfs <- askFiles
let mvals = fromMaybe [] $ Map.lookup name p let mvals = fromMaybe [] $ Map.lookup name p
files = fromMaybe [] $ mfs >>= Map.lookup name files = fromMaybe [] $ mfs >>= Map.lookup name
emx <- lift $ fieldParse mvals files emx <- liftHandler $ fieldParse mvals files
return $ case emx of return $ case emx of
Left (SomeMessage e) -> (FormFailure [renderMessage site langs e], maybe (Left "") Left (listToMaybe mvals)) Left (SomeMessage e) -> (FormFailure [renderMessage site langs e], maybe (Left "") Left (listToMaybe mvals))
Right mx -> Right mx ->
@ -228,28 +241,37 @@ mhelper Field {..} FieldSettings {..} mdef onMissing onFound isReq = do
}) })
-- | Applicative equivalent of 'mreq'. -- | Applicative equivalent of 'mreq'.
areq :: (RenderMessage site FormMessage, HandlerSite m ~ site, MonadHandler m) areq :: RenderMessage site FormMessage
=> Field m a => Field site a
-> FieldSettings site -> FieldSettings site
-> Maybe a -> Maybe a
-> AForm m a -> AForm site a
areq a b = formToAForm . liftM (second return) . mreq a b areq a b = formToAForm . liftM (second return) . mreq a b
-- | Applicative equivalent of 'mopt'. -- | Applicative equivalent of 'mopt'.
aopt :: MonadHandler m aopt :: Field site a
=> Field m a -> FieldSettings site
-> FieldSettings (HandlerSite m)
-> Maybe (Maybe a) -> Maybe (Maybe a)
-> AForm m (Maybe a) -> AForm site (Maybe a)
aopt a b = formToAForm . liftM (second return) . mopt a b aopt a b = formToAForm . liftM (second return) . mopt a b
runFormGeneric :: Monad m runFormGeneric
=> MForm m a :: HasHandlerData env
-> HandlerSite m => MForm (HandlerSite env) a
-> [Text] -> Maybe (Env, FileEnv)
-> Maybe (Env, FileEnv) -> RIO env (a, Enctype)
-> m (a, Enctype) runFormGeneric mform params = do
runFormGeneric form site langs env = evalRWST form (env, site, langs) (IntSingle 0) hd <- liftHandler $ view subHandlerDataL
enctypeRef <- newIORef mempty
intsRef <- newIORef $! IntSingle 0
let mfd = MFormData
{ mfdHandlerData = hd
, mfdEnctype = enctypeRef
, mfdParams = params
, mfdInts = intsRef
}
a <- runRIO mfd mform
(,) a <$> readIORef enctypeRef
-- | This function is used to both initially render a form and to later extract -- | This function is used to both initially render a form and to later extract
-- results from it. Note that, due to CSRF protection and a few other issues, -- results from it. Note that, due to CSRF protection and a few other issues,
@ -260,17 +282,19 @@ runFormGeneric form site langs env = evalRWST form (env, site, langs) (IntSingle
-- For example, a common case is displaying a form on a GET request and having -- For example, a common case is displaying a form on a GET request and having
-- the form submit to a POST page. In such a case, both the GET and POST -- the form submit to a POST page. In such a case, both the GET and POST
-- handlers should use 'runFormPost'. -- handlers should use 'runFormPost'.
runFormPost :: (RenderMessage (HandlerSite m) FormMessage, MonadResource m, MonadHandler m) runFormPost
=> (Html -> MForm m (FormResult a, xml)) :: (RenderMessage (HandlerSite env) FormMessage, HasHandlerData env)
-> m ((FormResult a, xml), Enctype) => (Html -> MForm (HandlerSite env) (FormResult a, xml))
-> RIO env ((FormResult a, xml), Enctype)
runFormPost form = do runFormPost form = do
env <- postEnv env <- postEnv
postHelper form env postHelper form env
postHelper :: (MonadHandler m, RenderMessage (HandlerSite m) FormMessage) postHelper
=> (Html -> MForm m (FormResult a, xml)) :: (HasHandlerData env, RenderMessage (HandlerSite env) FormMessage)
-> Maybe (Env, FileEnv) => (Html -> MForm (HandlerSite env) (FormResult a, xml))
-> m ((FormResult a, xml), Enctype) -> Maybe (Env, FileEnv)
-> RIO env ((FormResult a, xml), Enctype)
postHelper form env = do postHelper form env = do
req <- getRequest req <- getRequest
let tokenKey = defaultCsrfParamName let tokenKey = defaultCsrfParamName
@ -278,15 +302,14 @@ postHelper form env = do
case reqToken req of case reqToken req of
Nothing -> Data.Monoid.mempty Nothing -> Data.Monoid.mempty
Just n -> [shamlet|<input type=hidden name=#{tokenKey} value=#{n}>|] Just n -> [shamlet|<input type=hidden name=#{tokenKey} value=#{n}>|]
m <- getYesod ((res, xml), enctype) <- runFormGeneric (form token) env
langs <- languages site <- getYesod
((res, xml), enctype) <- runFormGeneric (form token) m langs env
let res' = let res' =
case (res, env) of case (res, env) of
(_, Nothing) -> FormMissing (_, Nothing) -> FormMissing
(FormSuccess{}, Just (params, _)) (FormSuccess{}, Just (params, _))
| not (Map.lookup tokenKey params === reqToken req) -> | not (Map.lookup tokenKey params === reqToken req) ->
FormFailure [renderMessage m langs MsgCsrfWarning] FormFailure [renderMessage site (reqLangs req) MsgCsrfWarning]
_ -> res _ -> res
-- It's important to use constant-time comparison (constEqBytes) in order to avoid timing attacks. -- It's important to use constant-time comparison (constEqBytes) in order to avoid timing attacks.
where (Just [t1]) === (Just t2) = TE.encodeUtf8 t1 `constEqBytes` TE.encodeUtf8 t2 where (Just [t1]) === (Just t2) = TE.encodeUtf8 t1 `constEqBytes` TE.encodeUtf8 t2
@ -299,12 +322,12 @@ postHelper form env = do
-- page will both receive and incoming form and produce a new, blank form. For -- page will both receive and incoming form and produce a new, blank form. For
-- general usage, you can stick with @runFormPost@. -- general usage, you can stick with @runFormPost@.
generateFormPost generateFormPost
:: (RenderMessage (HandlerSite m) FormMessage, MonadHandler m) :: (RenderMessage (HandlerSite env) FormMessage, HasHandlerData env)
=> (Html -> MForm m (FormResult a, xml)) => (Html -> MForm (HandlerSite env) (FormResult a, xml))
-> m (xml, Enctype) -> RIO env (xml, Enctype)
generateFormPost form = first snd `liftM` postHelper form Nothing generateFormPost form = first snd `liftM` postHelper form Nothing
postEnv :: MonadHandler m => m (Maybe (Env, FileEnv)) postEnv :: HasHandlerData env => RIO env (Maybe (Env, FileEnv))
postEnv = do postEnv = do
req <- getRequest req <- getRequest
if requestMethod (reqWaiRequest req) == "GET" if requestMethod (reqWaiRequest req) == "GET"
@ -314,18 +337,16 @@ postEnv = do
let p' = Map.unionsWith (++) $ map (\(x, y) -> Map.singleton x [y]) p let p' = Map.unionsWith (++) $ map (\(x, y) -> Map.singleton x [y]) p
return $ Just (p', Map.unionsWith (++) $ map (\(k, v) -> Map.singleton k [v]) f) return $ Just (p', Map.unionsWith (++) $ map (\(k, v) -> Map.singleton k [v]) f)
runFormPostNoToken :: MonadHandler m runFormPostNoToken :: HasHandlerData env
=> (Html -> MForm m a) => (Html -> MForm (HandlerSite env) a)
-> m (a, Enctype) -> RIO env (a, Enctype)
runFormPostNoToken form = do runFormPostNoToken form = do
langs <- languages params <- postEnv
m <- getYesod runFormGeneric (form mempty) params
env <- postEnv
runFormGeneric (form mempty) m langs env
runFormGet :: MonadHandler m runFormGet :: HasHandlerData env
=> (Html -> MForm m a) => (Html -> MForm (HandlerSite env) a)
-> m (a, Enctype) -> RIO env (a, Enctype)
runFormGet form = do runFormGet form = do
gets <- liftM reqGetParams getRequest gets <- liftM reqGetParams getRequest
let env = let env =
@ -339,29 +360,27 @@ runFormGet form = do
-- --
-- Since 1.3.11 -- Since 1.3.11
generateFormGet' generateFormGet'
:: MonadHandler m :: HasHandlerData env
=> (Html -> MForm m (FormResult a, xml)) => (Html -> MForm (HandlerSite env) (FormResult a, xml))
-> m (xml, Enctype) -> RIO env (xml, Enctype)
generateFormGet' form = first snd `liftM` getHelper form Nothing generateFormGet' form = first snd `liftM` getHelper form Nothing
{-# DEPRECATED generateFormGet "Will require RenderMessage in next version of Yesod" #-} {-# DEPRECATED generateFormGet "Will require RenderMessage in next version of Yesod" #-}
generateFormGet :: MonadHandler m generateFormGet :: HasHandlerData env
=> (Html -> MForm m a) => (Html -> MForm (HandlerSite env) a)
-> m (a, Enctype) -> RIO env (a, Enctype)
generateFormGet form = getHelper form Nothing generateFormGet form = getHelper form Nothing
getKey :: Text getKey :: Text
getKey = "_hasdata" getKey = "_hasdata"
getHelper :: MonadHandler m getHelper :: HasHandlerData env
=> (Html -> MForm m a) => (Html -> MForm (HandlerSite env) a)
-> Maybe (Env, FileEnv) -> Maybe (Env, FileEnv)
-> m (a, Enctype) -> RIO env (a, Enctype)
getHelper form env = do getHelper form params = do
let fragment = [shamlet|<input type=hidden name=#{getKey}>|] let fragment = [shamlet|<input type=hidden name=#{getKey}>|]
langs <- languages runFormGeneric (form fragment) params
m <- getYesod
runFormGeneric (form fragment) m langs env
-- | Creates a hidden field on the form that identifies it. This -- | Creates a hidden field on the form that identifies it. This
@ -386,10 +405,9 @@ getHelper form env = do
-- even if their number or order change between the HTML -- even if their number or order change between the HTML
-- generation and the form submission. -- generation and the form submission.
identifyForm identifyForm
:: Monad m :: Text -- ^ Form identification string.
=> Text -- ^ Form identification string. -> (Html -> MForm site (FormResult a, WidgetFor site ()))
-> (Html -> MForm m (FormResult a, WidgetFor (HandlerSite m) ())) -> (Html -> MForm site (FormResult a, WidgetFor site ()))
-> (Html -> MForm m (FormResult a, WidgetFor (HandlerSite m) ()))
identifyForm identVal form = \fragment -> do identifyForm identVal form = \fragment -> do
-- Create hidden <input>. -- Create hidden <input>.
let fragment' = let fragment' =
@ -406,7 +424,7 @@ identifyForm identVal form = \fragment -> do
-- data is missing, then do not provide any params to the -- data is missing, then do not provide any params to the
-- form, which will turn its result into FormMissing. Also, -- form, which will turn its result into FormMissing. Also,
-- doing this avoids having lots of fields with red errors. -- doing this avoids having lots of fields with red errors.
let eraseParams | missing = local (\(_, h, l) -> (Nothing, h, l)) let eraseParams | missing = local (const Nothing)
| otherwise = id | otherwise = id
( res', w) <- eraseParams (form fragment') ( res', w) <- eraseParams (form fragment')
@ -418,12 +436,12 @@ identifyFormKey :: Text
identifyFormKey = "_formid" identifyFormKey = "_formid"
type FormRender m a = type FormRender site a =
AForm m a AForm site a
-> Html -> Html
-> MForm m (FormResult a, WidgetFor (HandlerSite m) ()) -> MForm site (FormResult a, WidgetFor site ())
renderTable, renderDivs, renderDivsNoLabels :: Monad m => FormRender m a renderTable, renderDivs, renderDivsNoLabels :: FormRender env a
-- | Render a form into a series of tr tags. Note that, in order to allow -- | Render a form into a series of tr tags. Note that, in order to allow
-- you to add extra rows to the table, this function does /not/ wrap up -- you to add extra rows to the table, this function does /not/ wrap up
-- the resulting HTML in a table tag; you must do that yourself. -- the resulting HTML in a table tag; you must do that yourself.
@ -457,7 +475,7 @@ renderDivs = renderDivsMaybeLabels True
-- | render a field inside a div, not displaying any label -- | render a field inside a div, not displaying any label
renderDivsNoLabels = renderDivsMaybeLabels False renderDivsNoLabels = renderDivsMaybeLabels False
renderDivsMaybeLabels :: Monad m => Bool -> FormRender m a renderDivsMaybeLabels :: Bool -> FormRender env a
renderDivsMaybeLabels withLabels aform fragment = do renderDivsMaybeLabels withLabels aform fragment = do
(res, views') <- aFormToForm aform (res, views') <- aFormToForm aform
let views = views' [] let views = views' []
@ -495,7 +513,7 @@ $forall view <- views
-- > <input .btn .primary type=submit value=_{MsgSubmit}> -- > <input .btn .primary type=submit value=_{MsgSubmit}>
-- --
-- Since 1.3.14 -- Since 1.3.14
renderBootstrap2 :: Monad m => FormRender m a renderBootstrap2 :: FormRender env a
renderBootstrap2 aform fragment = do renderBootstrap2 aform fragment = do
(res, views') <- aFormToForm aform (res, views') <- aFormToForm aform
let views = views' [] let views = views' []
@ -516,26 +534,21 @@ renderBootstrap2 aform fragment = do
|] |]
return (res, widget) return (res, widget)
-- | Deprecated synonym for 'renderBootstrap2'. check :: RenderMessage site msg
renderBootstrap :: Monad m => FormRender m a
renderBootstrap = renderBootstrap2
{-# DEPRECATED renderBootstrap "Please use the Yesod.Form.Bootstrap3 module." #-}
check :: (Monad m, RenderMessage (HandlerSite m) msg)
=> (a -> Either msg a) => (a -> Either msg a)
-> Field m a -> Field site a
-> Field m a -> Field site a
check f = checkM $ return . f check f = checkM $ return . f
-- | Return the given error message if the predicate is false. -- | Return the given error message if the predicate is false.
checkBool :: (Monad m, RenderMessage (HandlerSite m) msg) checkBool :: RenderMessage site msg
=> (a -> Bool) -> msg -> Field m a -> Field m a => (a -> Bool) -> msg -> Field site a -> Field site a
checkBool b s = check $ \x -> if b x then Right x else Left s checkBool b s = check $ \x -> if b x then Right x else Left s
checkM :: (Monad m, RenderMessage (HandlerSite m) msg) checkM :: RenderMessage site msg
=> (a -> m (Either msg a)) => (a -> HandlerFor site (Either msg a))
-> Field m a -> Field site a
-> Field m a -> Field site a
checkM f = checkMMap f id checkM f = checkMMap f id
-- | Same as 'checkM', but modifies the datatype. -- | Same as 'checkM', but modifies the datatype.
@ -544,11 +557,11 @@ checkM f = checkMMap f id
-- the new datatype to the old one (the second argument to this function). -- the new datatype to the old one (the second argument to this function).
-- --
-- Since 1.1.2 -- Since 1.1.2
checkMMap :: (Monad m, RenderMessage (HandlerSite m) msg) checkMMap :: RenderMessage site msg
=> (a -> m (Either msg b)) => (a -> HandlerFor site (Either msg b))
-> (b -> a) -> (b -> a)
-> Field m a -> Field site a
-> Field m b -> Field site b
checkMMap f inv field = field checkMMap f inv field = field
{ fieldParse = \ts fs -> do { fieldParse = \ts fs -> do
e1 <- fieldParse field ts fs e1 <- fieldParse field ts fs
@ -560,7 +573,7 @@ checkMMap f inv field = field
} }
-- | Allows you to overwrite the error message on parse error. -- | Allows you to overwrite the error message on parse error.
customErrorMessage :: Monad m => SomeMessage (HandlerSite m) -> Field m a -> Field m a customErrorMessage :: SomeMessage site -> Field site a -> Field site a
customErrorMessage msg field = field customErrorMessage msg field = field
{ fieldParse = \ts fs -> { fieldParse = \ts fs ->
liftM (either (const $ Left msg) Right) liftM (either (const $ Left msg) Right)
@ -611,11 +624,10 @@ parseHelperGen f (x:_) _ = return $ either (Left . SomeMessage) (Right . Just) $
-- > lazyTextField = convertField TL.fromStrict TL.toStrict textField -- > lazyTextField = convertField TL.fromStrict TL.toStrict textField
-- --
-- Since 1.3.16 -- Since 1.3.16
convertField :: (Functor m) convertField :: (a -> b) -> (b -> a)
=> (a -> b) -> (b -> a) -> Field env a -> Field env b
-> Field m a -> Field m b convertField to' from (Field fParse fView fEnctype) = let
convertField to from (Field fParse fView fEnctype) = let fParse' ts = fmap (fmap (fmap to')) . fParse ts
fParse' ts = fmap (fmap (fmap to)) . fParse ts
fView' ti tn at ei = fView ti tn at (fmap from ei) fView' ti tn at ei = fView ti tn at (fmap from ei)
in Field fParse' fView' fEnctype in Field fParse' fView' fEnctype

View File

@ -1,3 +1,4 @@
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
-- | Provides for getting input from either GET or POST params without -- | Provides for getting input from either GET or POST params without
@ -26,14 +27,13 @@ type DText = [Text] -> [Text]
-- | Type for a form which parses a value of type @a@ with the base monad @m@ -- | Type for a form which parses a value of type @a@ with the base monad @m@
-- (usually your @Handler@). Can compose this using its @Applicative@ instance. -- (usually your @Handler@). Can compose this using its @Applicative@ instance.
newtype FormInput m a = FormInput { unFormInput :: HandlerSite m -> [Text] -> Env -> FileEnv -> m (Either DText a) } newtype FormInput site a = FormInput { unFormInput :: Env -> FileEnv -> HandlerFor site (Either DText a) }
instance Monad m => Functor (FormInput m) where deriving Functor
fmap a (FormInput f) = FormInput $ \c d e e' -> liftM (either Left (Right . a)) $ f c d e e' instance Control.Applicative.Applicative (FormInput site) where
instance Monad m => Control.Applicative.Applicative (FormInput m) where pure x = FormInput $ \_env _filenv -> pure $ Right x
pure = FormInput . const . const . const . const . return . Right (FormInput f) <*> (FormInput x) = FormInput $ \env fileEnv -> do
(FormInput f) <*> (FormInput x) = FormInput $ \c d e e' -> do res1 <- f env fileEnv
res1 <- f c d e e' res2 <- x env fileEnv
res2 <- x c d e e'
return $ case (res1, res2) of return $ case (res1, res2) of
(Left a, Left b) -> Left $ a . b (Left a, Left b) -> Left $ a . b
(Left a, _) -> Left a (Left a, _) -> Left a
@ -42,14 +42,16 @@ instance Monad m => Control.Applicative.Applicative (FormInput m) where
-- | Promote a @Field@ into a @FormInput@, requiring that the value be present -- | Promote a @Field@ into a @FormInput@, requiring that the value be present
-- and valid. -- and valid.
ireq :: (Monad m, RenderMessage (HandlerSite m) FormMessage) ireq :: RenderMessage site FormMessage
=> Field m a => Field site a
-> Text -- ^ name of the field -> Text -- ^ name of the field
-> FormInput m a -> FormInput site a
ireq field name = FormInput $ \m l env fenv -> do ireq field name = FormInput $ \env fenv -> do
let filteredEnv = fromMaybe [] $ Map.lookup name env let filteredEnv = fromMaybe [] $ Map.lookup name env
filteredFEnv = fromMaybe [] $ Map.lookup name fenv filteredFEnv = fromMaybe [] $ Map.lookup name fenv
emx <- fieldParse field filteredEnv filteredFEnv emx <- fieldParse field filteredEnv filteredFEnv
m <- getYesod
l <- reqLangs <$> getRequest
return $ case emx of return $ case emx of
Left (SomeMessage e) -> Left $ (:) $ renderMessage m l e Left (SomeMessage e) -> Left $ (:) $ renderMessage m l e
Right Nothing -> Left $ (:) $ renderMessage m l $ MsgInputNotFound name Right Nothing -> Left $ (:) $ renderMessage m l $ MsgInputNotFound name
@ -57,33 +59,34 @@ ireq field name = FormInput $ \m l env fenv -> do
-- | Promote a @Field@ into a @FormInput@, with its presence being optional. If -- | Promote a @Field@ into a @FormInput@, with its presence being optional. If
-- the value is present but does not parse correctly, the form will still fail. -- the value is present but does not parse correctly, the form will still fail.
iopt :: Monad m => Field m a -> Text -> FormInput m (Maybe a) iopt :: Field site a -> Text -> FormInput site (Maybe a)
iopt field name = FormInput $ \m l env fenv -> do iopt field name = FormInput $ \env fenv -> do
let filteredEnv = fromMaybe [] $ Map.lookup name env let filteredEnv = fromMaybe [] $ Map.lookup name env
filteredFEnv = fromMaybe [] $ Map.lookup name fenv filteredFEnv = fromMaybe [] $ Map.lookup name fenv
emx <- fieldParse field filteredEnv filteredFEnv emx <- fieldParse field filteredEnv filteredFEnv
return $ case emx of case emx of
Left (SomeMessage e) -> Left $ (:) $ renderMessage m l e Left (SomeMessage e) -> do
Right x -> Right x site <- getYesod
l <- reqLangs <$> getRequest
pure $ Left $ (:) $ renderMessage site l e
Right x -> pure $ Right x
-- | Run a @FormInput@ on the GET parameters (i.e., query string). If parsing -- | Run a @FormInput@ on the GET parameters (i.e., query string). If parsing
-- fails, calls 'invalidArgs'. -- fails, calls 'invalidArgs'.
runInputGet :: MonadHandler m => FormInput m a -> m a runInputGet :: HasHandlerData env => FormInput (HandlerSite env) a -> RIO env a
runInputGet = either invalidArgs return <=< runInputGetHelper runInputGet = either invalidArgs return <=< runInputGetHelper
-- | Run a @FormInput@ on the GET parameters (i.e., query string). Does /not/ -- | Run a @FormInput@ on the GET parameters (i.e., query string). Does /not/
-- throw exceptions on failure. -- throw exceptions on failure.
-- --
-- Since 1.4.1 -- Since 1.4.1
runInputGetResult :: MonadHandler m => FormInput m a -> m (FormResult a) runInputGetResult :: HasHandlerData env => FormInput (HandlerSite env) a -> RIO env (FormResult a)
runInputGetResult = fmap (either FormFailure FormSuccess) . runInputGetHelper runInputGetResult = fmap (either FormFailure FormSuccess) . runInputGetHelper
runInputGetHelper :: MonadHandler m => FormInput m a -> m (Either [Text] a) runInputGetHelper :: HasHandlerData env => FormInput (HandlerSite env) a -> RIO env (Either [Text] a)
runInputGetHelper (FormInput f) = do runInputGetHelper (FormInput f) = do
env <- liftM (toMap . reqGetParams) getRequest env <- liftM (toMap . reqGetParams) getRequest
m <- getYesod emx <- liftHandler $ f env Map.empty
l <- languages
emx <- f m l env Map.empty
return $ either (Left . ($ [])) Right emx return $ either (Left . ($ [])) Right emx
toMap :: [(Text, a)] -> Map.Map Text [a] toMap :: [(Text, a)] -> Map.Map Text [a]
@ -91,17 +94,15 @@ toMap = Map.unionsWith (++) . map (\(x, y) -> Map.singleton x [y])
-- | Run a @FormInput@ on the POST parameters (i.e., request body). If parsing -- | Run a @FormInput@ on the POST parameters (i.e., request body). If parsing
-- fails, calls 'invalidArgs'. -- fails, calls 'invalidArgs'.
runInputPost :: MonadHandler m => FormInput m a -> m a runInputPost :: HasHandlerData env => FormInput (HandlerSite env) a -> RIO env a
runInputPost = either invalidArgs return <=< runInputPostHelper runInputPost = either invalidArgs return <=< runInputPostHelper
-- | Run a @FormInput@ on the POST parameters (i.e., request body). Does /not/ -- | Run a @FormInput@ on the POST parameters (i.e., request body). Does /not/
-- throw exceptions on failure. -- throw exceptions on failure.
runInputPostResult :: MonadHandler m => FormInput m a -> m (FormResult a) runInputPostResult :: HasHandlerData env => FormInput (HandlerSite env) a -> RIO env (FormResult a)
runInputPostResult = fmap (either FormFailure FormSuccess) . runInputPostHelper runInputPostResult = fmap (either FormFailure FormSuccess) . runInputPostHelper
runInputPostHelper :: MonadHandler m => FormInput m a -> m (Either [Text] a) runInputPostHelper :: HasHandlerData env => FormInput (HandlerSite env) a -> RIO env (Either [Text] a)
runInputPostHelper (FormInput f) = do runInputPostHelper (FormInput f) = liftHandler $ do
(env, fenv) <- liftM (toMap *** toMap) runRequestBody (env, fenv) <- liftM (toMap *** toMap) runRequestBody
m <- getYesod fmap (either (Left . ($ [])) Right) $ f env fenv
l <- languages
fmap (either (Left . ($ [])) Right) $ f m l env fenv

View File

@ -53,16 +53,16 @@ class YesodJquery a where
urlJqueryUiDateTimePicker :: a -> Either (Route a) Text urlJqueryUiDateTimePicker :: a -> Either (Route a) Text
urlJqueryUiDateTimePicker _ = Right "http://github.com/gregwebs/jquery.ui.datetimepicker/raw/master/jquery.ui.datetimepicker.js" urlJqueryUiDateTimePicker _ = Right "http://github.com/gregwebs/jquery.ui.datetimepicker/raw/master/jquery.ui.datetimepicker.js"
jqueryDayField :: (RenderMessage site FormMessage, YesodJquery site) => JqueryDaySettings -> Field (HandlerFor site) Day jqueryDayField :: (RenderMessage site FormMessage, YesodJquery site) => JqueryDaySettings -> Field site Day
jqueryDayField = flip jqueryDayField' "date" jqueryDayField = flip jqueryDayField' "date"
-- | Use jQuery's datepicker as the underlying implementation. -- | Use jQuery's datepicker as the underlying implementation.
-- --
-- Since 1.4.3 -- Since 1.4.3
jqueryDatePickerDayField :: (RenderMessage site FormMessage, YesodJquery site) => JqueryDaySettings -> Field (HandlerFor site) Day jqueryDatePickerDayField :: (RenderMessage site FormMessage, YesodJquery site) => JqueryDaySettings -> Field site Day
jqueryDatePickerDayField = flip jqueryDayField' "text" jqueryDatePickerDayField = flip jqueryDayField' "text"
jqueryDayField' :: (RenderMessage site FormMessage, YesodJquery site) => JqueryDaySettings -> Text -> Field (HandlerFor site) Day jqueryDayField' :: (RenderMessage site FormMessage, YesodJquery site) => JqueryDaySettings -> Text -> Field site Day
jqueryDayField' jds inputType = Field jqueryDayField' jds inputType = Field
{ fieldParse = parseHelper $ maybe { fieldParse = parseHelper $ maybe
(Left MsgInvalidDay) (Left MsgInvalidDay)
@ -107,13 +107,13 @@ $(function(){
] ]
jqueryAutocompleteField :: (RenderMessage site FormMessage, YesodJquery site) jqueryAutocompleteField :: (RenderMessage site FormMessage, YesodJquery site)
=> Route site -> Field (HandlerFor site) Text => Route site -> Field site Text
jqueryAutocompleteField = jqueryAutocompleteField' 2 jqueryAutocompleteField = jqueryAutocompleteField' 2
jqueryAutocompleteField' :: (RenderMessage site FormMessage, YesodJquery site) jqueryAutocompleteField' :: (RenderMessage site FormMessage, YesodJquery site)
=> Int -- ^ autocomplete minimum length => Int -- ^ autocomplete minimum length
-> Route site -> Route site
-> Field (HandlerFor site) Text -> Field site Text
jqueryAutocompleteField' minLen src = Field jqueryAutocompleteField' minLen src = Field
{ fieldParse = parseHelper $ Right { fieldParse = parseHelper $ Right
, fieldView = \theId name attrs val isReq -> do , fieldView = \theId name attrs val isReq -> do
@ -130,14 +130,14 @@ $(function(){$("##{rawJS theId}").autocomplete({source:"@{src}",minLength:#{toJS
, fieldEnctype = UrlEncoded , fieldEnctype = UrlEncoded
} }
addScript' :: (HandlerSite m ~ site, MonadWidget m) => (site -> Either (Route site) Text) -> m () addScript' :: (site -> Either (Route site) Text) -> WidgetFor site ()
addScript' f = do addScript' f = do
y <- getYesod y <- getYesod
addScriptEither $ f y addScriptEither $ f y
addStylesheet' :: (MonadWidget m, HandlerSite m ~ site) addStylesheet' :: (HasWidgetData env, HandlerSite env ~ site)
=> (site -> Either (Route site) Text) => (site -> Either (Route site) Text)
-> m () -> RIO env ()
addStylesheet' f = do addStylesheet' f = do
y <- getYesod y <- getYesod
addStylesheetEither $ f y addStylesheetEither $ f y

View File

@ -1,4 +1,5 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
@ -11,11 +12,11 @@ module Yesod.Form.MassInput
, massTable , massTable
) where ) where
import RIO
import Yesod.Form.Types import Yesod.Form.Types
import Yesod.Form.Functions import Yesod.Form.Functions
import Yesod.Form.Fields (checkBoxField) import Yesod.Form.Fields (checkBoxField)
import Yesod.Core import Yesod.Core
import Control.Monad.Trans.RWS (get, put, ask)
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
import Data.Text.Read (decimal) import Data.Text.Read (decimal)
import Control.Monad (liftM) import Control.Monad (liftM)
@ -24,43 +25,45 @@ import Data.Traversable (sequenceA)
import qualified Data.Map as Map import qualified Data.Map as Map
import Data.Maybe (listToMaybe) import Data.Maybe (listToMaybe)
down :: Monad m => Int -> MForm m () down :: Int -> MForm site ()
down 0 = return () down 0 = return ()
down i | i < 0 = error "called down with a negative number" down i | i < 0 = error "called down with a negative number"
down i = do down i = do
is <- get ref <- view $ to mfdInts
put $ IntCons 0 is is <- readIORef ref
writeIORef ref $ IntCons 0 is
down $ i - 1 down $ i - 1
up :: Monad m => Int -> MForm m () up :: Int -> MForm site ()
up 0 = return () up 0 = return ()
up i | i < 0 = error "called down with a negative number" up i | i < 0 = error "called down with a negative number"
up i = do up i = do
is <- get ref <- view $ to mfdInts
is <- readIORef ref
case is of case is of
IntSingle _ -> error "up on IntSingle" IntSingle _ -> error "up on IntSingle"
IntCons _ is' -> put is' >> newFormIdent >> return () IntCons _ is' -> writeIORef ref is' >> newFormIdent >> return ()
up $ i - 1 up $ i - 1
-- | Generate a form that accepts 0 or more values from the user, allowing the -- | Generate a form that accepts 0 or more values from the user, allowing the
-- user to specify that a new row is necessary. -- user to specify that a new row is necessary.
inputList :: (xml ~ WidgetFor site (), RenderMessage site FormMessage) inputList :: RenderMessage site FormMessage
=> Html => Html
-- ^ label for the form -- ^ label for the form
-> ([[FieldView site]] -> xml) -> ([[FieldView site]] -> WidgetFor site ())
-- ^ how to display the rows, usually either 'massDivs' or 'massTable' -- ^ how to display the rows, usually either 'massDivs' or 'massTable'
-> (Maybe a -> AForm (HandlerFor site) a) -> (Maybe a -> AForm site a)
-- ^ display a single row of the form, where @Maybe a@ gives the -- ^ display a single row of the form, where @Maybe a@ gives the
-- previously submitted value -- previously submitted value
-> Maybe [a] -> Maybe [a]
-- ^ default initial values for the form -- ^ default initial values for the form
-> AForm (HandlerFor site) [a] -> AForm site [a]
inputList label fixXml single mdef = formToAForm $ do inputList label fixXml single mdef = formToAForm $ do
theId <- lift newIdent theId <- newIdent
down 1 down 1
countName <- newFormIdent countName <- newFormIdent
addName <- newFormIdent addName <- newFormIdent
(menv, _, _) <- ask menv <- view $ to mfdParams
let readInt t = let readInt t =
case decimal t of case decimal t of
Right (i, "") -> Just i Right (i, "") -> Just i
@ -94,13 +97,13 @@ $newline never
, fvRequired = False , fvRequired = False
}]) }])
withDelete :: (xml ~ WidgetFor site (), RenderMessage site FormMessage) withDelete :: RenderMessage site FormMessage
=> AForm (HandlerFor site) a => AForm site a
-> MForm (HandlerFor site) (Either xml (FormResult a, [FieldView site])) -> MForm site (Either (WidgetFor site ())(FormResult a, [FieldView site]))
withDelete af = do withDelete af = do
down 1 down 1
deleteName <- newFormIdent deleteName <- newFormIdent
(menv, _, _) <- ask menv <- view $ to mfdParams
res <- case menv >>= Map.lookup deleteName . fst of res <- case menv >>= Map.lookup deleteName . fst of
Just ("yes":_) -> return $ Left [whamlet| Just ("yes":_) -> return $ Left [whamlet|
$newline never $newline never

View File

@ -29,7 +29,7 @@ class Yesod a => YesodNic a where
urlNicEdit :: a -> Either (Route a) Text urlNicEdit :: a -> Either (Route a) Text
urlNicEdit _ = Right "http://js.nicedit.com/nicEdit-latest.js" urlNicEdit _ = Right "http://js.nicedit.com/nicEdit-latest.js"
nicHtmlField :: YesodNic site => Field (HandlerFor site) Html nicHtmlField :: YesodNic site => Field site Html
nicHtmlField = Field nicHtmlField = Field
{ fieldParse = \e _ -> return . Right . fmap (preEscapedToMarkup . sanitizeBalance) . listToMaybe $ e { fieldParse = \e _ -> return . Right . fmap (preEscapedToMarkup . sanitizeBalance) . listToMaybe $ e
, fieldView = \theId name attrs val _isReq -> do , fieldView = \theId name attrs val _isReq -> do
@ -52,9 +52,9 @@ bkLib.onDomLoaded(function(){new nicEditor({fullPanel:true}).panelInstance("#{ra
where where
showVal = either id (pack . renderHtml) showVal = either id (pack . renderHtml)
addScript' :: (MonadWidget m, HandlerSite m ~ site) addScript' :: (HasWidgetData env, HandlerSite env ~ site)
=> (site -> Either (Route site) Text) => (site -> Either (Route site) Text)
-> m () -> RIO env ()
addScript' f = do addScript' f = do
y <- getYesod y <- getYesod
addScriptEither $ f y addScriptEither $ f y

View File

@ -1,3 +1,5 @@
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE ExistentialQuantification #-}
@ -15,6 +17,8 @@ module Yesod.Form.Types
, WForm , WForm
, MForm , MForm
, AForm (..) , AForm (..)
, WFormData (..)
, MFormData (..)
-- * Build forms -- * Build forms
, Field (..) , Field (..)
, FieldSettings (..) , FieldSettings (..)
@ -22,8 +26,8 @@ module Yesod.Form.Types
, FieldViewFunc , FieldViewFunc
) where ) where
import Control.Monad.Trans.RWS (RWST) import RIO
import Control.Monad.Trans.Writer (WriterT) import RIO.Orphans
import Data.Text (Text) import Data.Text (Text)
import Data.Monoid (Monoid (..)) import Data.Monoid (Monoid (..))
import Text.Blaze (Markup, ToMarkup (toMarkup), ToValue (toValue)) import Text.Blaze (Markup, ToMarkup (toMarkup), ToValue (toValue))
@ -31,10 +35,9 @@ import Text.Blaze (Markup, ToMarkup (toMarkup), ToValue (toValue))
#define ToHtml ToMarkup #define ToHtml ToMarkup
#define toHtml toMarkup #define toHtml toMarkup
import Control.Applicative ((<$>), Alternative (..), Applicative (..)) import Control.Applicative ((<$>), Alternative (..), Applicative (..))
import Control.Monad (liftM)
import Control.Monad.Trans.Class
import Data.String (IsString (..)) import Data.String (IsString (..))
import Yesod.Core import Yesod.Core
import Yesod.Core.Types
import qualified Data.Map as Map import qualified Data.Map as Map
import Data.Semigroup (Semigroup, (<>)) import Data.Semigroup (Semigroup, (<>))
import Data.Traversable import Data.Traversable
@ -140,46 +143,53 @@ type FileEnv = Map.Map Text [FileInfo]
-- > return $ MyForm <$> field1F <*> field2F <*> field3F -- > return $ MyForm <$> field1F <*> field2F <*> field3F
-- --
-- @since 1.4.14 -- @since 1.4.14
type WForm m a = MForm (WriterT [FieldView (HandlerSite m)] m) a type WForm site = RIO (WFormData site)
data WFormData site = WFormData
{ wfdViews :: !(BDeque (PrimState IO) (FieldView site))
, wfdMfd :: !(MFormData site)
}
instance HasHandlerData (WFormData site) where
type HandlerSite (WFormData site) = site
type SubHandlerSite (WFormData site) = site
subHandlerDataL = (lens wfdMfd (\x y -> x { wfdMfd = y })).subHandlerDataL
instance HasResourceMap (WFormData site) where
resourceMapL = subHandlerDataL.resourceMapL
instance HasLogFunc (WFormData site) where
logFuncL = subHandlerDataL.logFuncL
type MForm m a = RWST type MForm site = RIO (MFormData site)
(Maybe (Env, FileEnv), HandlerSite m, [Lang]) data MFormData site = MFormData
Enctype { mfdHandlerData :: !(SubHandlerData site site)
Ints , mfdEnctype :: !(IORef Enctype)
m , mfdParams :: !(Maybe (Env, FileEnv))
a , mfdInts :: !(IORef Ints)
}
instance HasHandlerData (MFormData site) where
type HandlerSite (MFormData site) = site
type SubHandlerSite (MFormData site) = site
subHandlerDataL = lens mfdHandlerData (\x y -> x { mfdHandlerData = y})
instance HasResourceMap (MFormData site) where
resourceMapL = subHandlerDataL.resourceMapL
instance HasLogFunc (MFormData site) where
logFuncL = subHandlerDataL.logFuncL
newtype AForm m a = AForm newtype AForm site a = AForm (WForm site (FormResult a))
{ unAForm :: (HandlerSite m, [Text]) deriving Functor
-> Maybe (Env, FileEnv) instance Applicative (AForm site) where
-> Ints pure = AForm . pure . pure
-> m (FormResult a, [FieldView (HandlerSite m)] -> [FieldView (HandlerSite m)], Ints, Enctype) (AForm f) <*> (AForm g) = AForm $ do
} f' <- f
instance Monad m => Functor (AForm m) where g' <- g
fmap f (AForm a) = pure $ f' <*> g'
AForm $ \x y z -> liftM go $ a x y z instance Monoid a => Monoid (AForm site a) where
where
go (w, x, y, z) = (fmap f w, x, y, z)
instance Monad m => Applicative (AForm m) where
pure x = AForm $ const $ const $ \ints -> return (FormSuccess x, id, ints, mempty)
(AForm f) <*> (AForm g) = AForm $ \mr env ints -> do
(a, b, ints', c) <- f mr env ints
(x, y, ints'', z) <- g mr env ints'
return (a <*> x, b . y, ints'', c `mappend` z)
instance (Monad m, Monoid a) => Monoid (AForm m a) where
mempty = pure mempty mempty = pure mempty
mappend a b = mappend <$> a <*> b mappend a b = mappend <$> a <*> b
instance (Monad m, Semigroup a) => Semigroup (AForm m a) where instance Semigroup a => Semigroup (AForm site a) where
a <> b = (<>) <$> a <*> b a <> b = (<>) <$> a <*> b
instance MonadTrans AForm where data FieldSettings site = FieldSettings
lift f = AForm $ \_ _ ints -> do { fsLabel :: SomeMessage site
x <- f , fsTooltip :: Maybe (SomeMessage site)
return (FormSuccess x, id, ints, mempty)
data FieldSettings master = FieldSettings
{ fsLabel :: SomeMessage master
, fsTooltip :: Maybe (SomeMessage master)
, fsId :: Maybe Text , fsId :: Maybe Text
, fsName :: Maybe Text , fsName :: Maybe Text
, fsAttrs :: [(Text, Text)] , fsAttrs :: [(Text, Text)]
@ -197,17 +207,17 @@ data FieldView site = FieldView
, fvRequired :: Bool , fvRequired :: Bool
} }
type FieldViewFunc m a type FieldViewFunc site a
= Text -- ^ ID = Text -- ^ ID
-> Text -- ^ Name -> Text -- ^ Name
-> [(Text, Text)] -- ^ Attributes -> [(Text, Text)] -- ^ Attributes
-> Either Text a -- ^ Either (invalid text) or (legitimate result) -> Either Text a -- ^ Either (invalid text) or (legitimate result)
-> Bool -- ^ Required? -> Bool -- ^ Required?
-> WidgetFor (HandlerSite m) () -> WidgetFor site ()
data Field m a = Field data Field site a = Field
{ fieldParse :: [Text] -> [FileInfo] -> m (Either (SomeMessage (HandlerSite m)) (Maybe a)) { fieldParse :: [Text] -> [FileInfo] -> HandlerFor site (Either (SomeMessage site) (Maybe a))
, fieldView :: FieldViewFunc m a , fieldView :: FieldViewFunc site a
, fieldEnctype :: Enctype , fieldEnctype :: Enctype
} }

View File

@ -30,7 +30,7 @@ library
, containers >= 0.2 , containers >= 0.2
, data-default , data-default
, email-validate >= 1.0 , email-validate >= 1.0
, persistent , persistent >= 2.5
, resourcet , resourcet
, semigroups , semigroups
, shakespeare >= 2.0 , shakespeare >= 2.0
@ -41,6 +41,8 @@ library
, xss-sanitize >= 0.3.0.1 , xss-sanitize >= 0.3.0.1
, yesod-core >= 1.6 && < 1.7 , yesod-core >= 1.6 && < 1.7
, yesod-persistent >= 1.6 && < 1.7 , yesod-persistent >= 1.6 && < 1.7
, rio
, rio-orphans
if flag(network-uri) if flag(network-uri)
build-depends: network-uri >= 2.6 build-depends: network-uri >= 2.6

View File

@ -42,14 +42,14 @@ instance HasContentType RepAtom where
instance ToTypedContent RepAtom where instance ToTypedContent RepAtom where
toTypedContent = TypedContent typeAtom . toContent toTypedContent = TypedContent typeAtom . toContent
atomFeed :: MonadHandler m => Feed (Route (HandlerSite m)) -> m RepAtom atomFeed :: HasHandlerData env => Feed (Route (HandlerSite env)) -> RIO env RepAtom
atomFeed feed = do atomFeed feed = do
render <- getUrlRender render <- getUrlRender
return $ RepAtom $ toContent $ renderLBS def $ template feed render return $ RepAtom $ toContent $ renderLBS def $ template feed render
-- | Same as @'atomFeed'@ but for @'Feed Text'@. Useful for cases where you are -- | Same as @'atomFeed'@ but for @'Feed Text'@. Useful for cases where you are
-- generating a feed of external links. -- generating a feed of external links.
atomFeedText :: MonadHandler m => Feed Text -> m RepAtom atomFeedText :: HasHandlerData env => Feed Text -> RIO env RepAtom
atomFeedText feed = return $ RepAtom $ toContent $ renderLBS def $ template feed id atomFeedText feed = return $ RepAtom $ toContent $ renderLBS def $ template feed id
template :: Feed url -> (url -> Text) -> Document template :: Feed url -> (url -> Text) -> Document
@ -90,10 +90,10 @@ entryTemplate FeedEntry {..} render = Element "entry" Map.empty $ map NodeElemen
,("href", render enclosedUrl)]) []] ,("href", render enclosedUrl)]) []]
-- | Generates a link tag in the head of a widget. -- | Generates a link tag in the head of a widget.
atomLink :: MonadWidget m atomLink :: HasWidgetData env
=> Route (HandlerSite m) => Route (HandlerSite env)
-> Text -- ^ title -> Text -- ^ title
-> m () -> RIO env ()
atomLink r title = toWidgetHead [hamlet| atomLink r title = toWidgetHead [hamlet|
<link href=@{r} type=#{S8.unpack typeAtom} rel="alternate" title=#{title}> <link href=@{r} type=#{S8.unpack typeAtom} rel="alternate" title=#{title}>
|] |]

View File

@ -28,14 +28,14 @@ import Yesod.Core
import Data.Text import Data.Text
newsFeed :: MonadHandler m => Feed (Route (HandlerSite m)) -> m TypedContent newsFeed :: HasHandlerData env => Feed (Route (HandlerSite env)) -> RIO env TypedContent
newsFeed f = selectRep $ do newsFeed f = selectRep $ do
provideRep $ atomFeed f provideRep $ atomFeed f
provideRep $ rssFeed f provideRep $ rssFeed f
-- | Same as @'newsFeed'@ but for @'Feed Text'@. Useful for cases where you are -- | Same as @'newsFeed'@ but for @'Feed Text'@. Useful for cases where you are
-- generating a feed of external links. -- generating a feed of external links.
newsFeedText :: MonadHandler m => Feed Text -> m TypedContent newsFeedText :: HasHandlerData env => Feed Text -> RIO env TypedContent
newsFeedText f = selectRep $ do newsFeedText f = selectRep $ do
provideRep $ atomFeedText f provideRep $ atomFeedText f
provideRep $ rssFeedText f provideRep $ rssFeedText f

View File

@ -39,14 +39,14 @@ instance ToTypedContent RepRss where
toTypedContent = TypedContent typeRss . toContent toTypedContent = TypedContent typeRss . toContent
-- | Generate the feed -- | Generate the feed
rssFeed :: MonadHandler m => Feed (Route (HandlerSite m)) -> m RepRss rssFeed :: HasHandlerData env => Feed (Route (HandlerSite env)) -> RIO env RepRss
rssFeed feed = do rssFeed feed = do
render <- getUrlRender render <- getUrlRender
return $ RepRss $ toContent $ renderLBS def $ template feed render return $ RepRss $ toContent $ renderLBS def $ template feed render
-- | Same as @'rssFeed'@ but for @'Feed Text'@. Useful for cases where you are -- | Same as @'rssFeed'@ but for @'Feed Text'@. Useful for cases where you are
-- generating a feed of external links. -- generating a feed of external links.
rssFeedText :: MonadHandler m => Feed Text -> m RepRss rssFeedText :: HasHandlerData env => Feed Text -> RIO env RepRss
rssFeedText feed = return $ RepRss $ toContent $ renderLBS def $ template feed id rssFeedText feed = return $ RepRss $ toContent $ renderLBS def $ template feed id
template :: Feed url -> (url -> Text) -> Document template :: Feed url -> (url -> Text) -> Document
@ -93,10 +93,10 @@ entryTemplate FeedEntry {..} render = Element "item" Map.empty $ map NodeElement
,("url", render enclosedUrl)]) []] ,("url", render enclosedUrl)]) []]
-- | Generates a link tag in the head of a widget. -- | Generates a link tag in the head of a widget.
rssLink :: MonadWidget m rssLink :: HasWidgetData env
=> Route (HandlerSite m) => Route (HandlerSite env)
-> Text -- ^ title -> Text -- ^ title
-> m () -> RIO env ()
rssLink r title = toWidgetHead [hamlet| rssLink r title = toWidgetHead [hamlet|
<link href=@{r} type=#{S8.unpack typeRss} rel="alternate" title=#{title}> <link href=@{r} type=#{S8.unpack typeRss} rel="alternate" title=#{title}>
|] |]

View File

@ -61,9 +61,9 @@ data SitemapUrl url = SitemapUrl
} }
-- | A basic robots file which just lists the "Sitemap: " line. -- | A basic robots file which just lists the "Sitemap: " line.
robots :: MonadHandler m robots :: HasHandlerData env
=> Route (HandlerSite m) -- ^ sitemap url => Route (HandlerSite env) -- ^ sitemap url
-> m Text -> RIO env Text
robots smurl = do robots smurl = do
ur <- getUrlRender ur <- getUrlRender
return $ T.unlines return $ T.unlines

View File

@ -1080,7 +1080,7 @@ setUrl url' = do
site <- fmap rbdSite getSIO site <- fmap rbdSite getSIO
eurl <- Yesod.Core.Unsafe.runFakeHandler eurl <- Yesod.Core.Unsafe.runFakeHandler
M.empty M.empty
(const $ error "Yesod.Test: No logger available") mempty
site site
(toTextUrl url') (toTextUrl url')
url <- either (error . show) return eurl url <- either (error . show) return eurl

View File

@ -1,9 +1,9 @@
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Yesod.WebSockets module Yesod.WebSockets
( -- * Core API ( -- * Core API
WebSocketsT webSockets
, webSockets
, webSocketsWith , webSocketsWith
, webSocketsOptions , webSocketsOptions
, webSocketsOptionsWith , webSocketsOptionsWith
@ -39,12 +39,16 @@ import Conduit
import qualified Network.Wai.Handler.WebSockets as WaiWS import qualified Network.Wai.Handler.WebSockets as WaiWS
import qualified Network.WebSockets as WS import qualified Network.WebSockets as WS
import qualified Yesod.Core as Y import qualified Yesod.Core as Y
import UnliftIO (SomeException, tryAny, MonadIO, liftIO, MonadUnliftIO, withRunInIO, race, race_, concurrently, concurrently_) import RIO
-- | A transformer for a WebSockets handler. -- FIXME document
-- class Y.HasHandlerData env => HasWebsockets env where
-- Since 0.1.0 websocketsL :: Lens' env WS.Connection
type WebSocketsT = ReaderT WS.Connection
data WithWebsockets env = WithWebsockets
{ wwConnection :: !WS.Connection
, wwEnv :: !env
}
-- | Attempt to run a WebSockets handler. This function first checks if the -- | Attempt to run a WebSockets handler. This function first checks if the
-- client initiated a WebSockets connection and, if so, runs the provided -- client initiated a WebSockets connection and, if so, runs the provided
@ -54,9 +58,9 @@ type WebSocketsT = ReaderT WS.Connection
-- --
-- Since 0.1.0 -- Since 0.1.0
webSockets webSockets
:: (MonadUnliftIO m, Y.MonadHandler m) :: Y.HasHandlerData env
=> WebSocketsT m () => RIO (WithWebsockets env) ()
-> m () -> RIO env ()
webSockets = webSocketsOptions WS.defaultConnectionOptions webSockets = webSocketsOptions WS.defaultConnectionOptions
-- | Varient of 'webSockets' which allows you to specify -- | Varient of 'webSockets' which allows you to specify
@ -64,26 +68,26 @@ webSockets = webSocketsOptions WS.defaultConnectionOptions
-- --
-- Since 0.2.5 -- Since 0.2.5
webSocketsOptions webSocketsOptions
:: (MonadUnliftIO m, Y.MonadHandler m) :: Y.HasHandlerData env
=> WS.ConnectionOptions => WS.ConnectionOptions
-> WebSocketsT m () -> RIO (WithWebsockets env) ()
-> m () -> RIO env ()
webSocketsOptions opts = webSocketsOptionsWith opts $ const $ return $ Just $ WS.AcceptRequest Nothing [] webSocketsOptions opts = webSocketsOptionsWith opts $ const $ return $ Just $ WS.AcceptRequest Nothing []
-- | Varient of 'webSockets' which allows you to specify the 'WS.AcceptRequest' -- | Varient of 'webSockets' which allows you to specify the 'WS.AcceptRequest'
-- setttings when upgrading to a websocket connection. -- setttings when upgrading to a websocket connection.
-- --
-- Since 0.2.4 -- Since 0.2.4
webSocketsWith :: (MonadUnliftIO m, Y.MonadHandler m) webSocketsWith :: Y.HasHandlerData env
=> (WS.RequestHead -> m (Maybe WS.AcceptRequest)) => (WS.RequestHead -> RIO env (Maybe WS.AcceptRequest))
-- ^ A Nothing indicates that the websocket upgrade request should not happen -- ^ A Nothing indicates that the websocket upgrade request should not happen
-- and instead the rest of the handler will be called instead. This allows -- and instead the rest of the handler will be called instead. This allows
-- you to use 'WS.getRequestSubprotocols' and only accept the request if -- you to use 'WS.getRequestSubprotocols' and only accept the request if
-- a compatible subprotocol is given. Also, the action runs before upgrading -- a compatible subprotocol is given. Also, the action runs before upgrading
-- the request to websockets, so you can also use short-circuiting handler -- the request to websockets, so you can also use short-circuiting handler
-- actions such as 'Y.invalidArgs'. -- actions such as 'Y.invalidArgs'.
-> WebSocketsT m () -> RIO (WithWebsockets env) ()
-> m () -> RIO env ()
webSocketsWith = webSocketsOptionsWith WS.defaultConnectionOptions webSocketsWith = webSocketsOptionsWith WS.defaultConnectionOptions
-- | Varient of 'webSockets' which allows you to specify both -- | Varient of 'webSockets' which allows you to specify both
@ -91,18 +95,18 @@ webSocketsWith = webSocketsOptionsWith WS.defaultConnectionOptions
-- setttings when upgrading to a websocket connection. -- setttings when upgrading to a websocket connection.
-- --
-- Since 0.2.5 -- Since 0.2.5
webSocketsOptionsWith :: (MonadUnliftIO m, Y.MonadHandler m) webSocketsOptionsWith :: Y.HasHandlerData env
=> WS.ConnectionOptions => WS.ConnectionOptions
-- ^ Custom websockets options -- ^ Custom websockets options
-> (WS.RequestHead -> m (Maybe WS.AcceptRequest)) -> (WS.RequestHead -> RIO env (Maybe WS.AcceptRequest))
-- ^ A Nothing indicates that the websocket upgrade request should not happen -- ^ A Nothing indicates that the websocket upgrade request should not happen
-- and instead the rest of the handler will be called instead. This allows -- and instead the rest of the handler will be called instead. This allows
-- you to use 'WS.getRequestSubprotocols' and only accept the request if -- you to use 'WS.getRequestSubprotocols' and only accept the request if
-- a compatible subprotocol is given. Also, the action runs before upgrading -- a compatible subprotocol is given. Also, the action runs before upgrading
-- the request to websockets, so you can also use short-circuiting handler -- the request to websockets, so you can also use short-circuiting handler
-- actions such as 'Y.invalidArgs'. -- actions such as 'Y.invalidArgs'.
-> WebSocketsT m () -> RIO (WithWebsockets env) ()
-> m () -> RIO env ()
webSocketsOptionsWith wsConnOpts buildAr inner = do webSocketsOptionsWith wsConnOpts buildAr inner = do
req <- Y.waiRequest req <- Y.waiRequest
when (WaiWS.isWebSocketsReq req) $ do when (WaiWS.isWebSocketsReq req) $ do
@ -110,43 +114,45 @@ webSocketsOptionsWith wsConnOpts buildAr inner = do
mar <- buildAr rhead mar <- buildAr rhead
case mar of case mar of
Nothing -> return () Nothing -> return ()
Just ar -> Just ar -> do
env <- ask
Y.sendRawResponseNoConduit Y.sendRawResponseNoConduit
$ \src sink -> withRunInIO $ \runInIO -> WaiWS.runWebSockets $ \src sink -> liftIO $ WaiWS.runWebSockets
wsConnOpts wsConnOpts
rhead rhead
(\pconn -> do (\pconn -> do
conn <- WS.acceptRequestWith pconn ar conn <- WS.acceptRequestWith pconn ar
WS.forkPingThread conn 30 WS.forkPingThread conn 30
runInIO $ runReaderT inner conn) let ww = WithWebsockets conn env
runRIO ww inner)
src src
sink sink
-- | Wrapper for capturing exceptions -- | Wrapper for capturing exceptions
wrapWSE :: (MonadIO m, MonadReader WS.Connection m) wrapWSE :: HasWebsockets env
=> (WS.Connection -> a -> IO ()) => (WS.Connection -> a -> IO ())
-> a -> a
-> m (Either SomeException ()) -> RIO env (Either SomeException ())
wrapWSE ws x = do wrapWSE ws x = do
conn <- ask conn <- view websocketsL
liftIO $ tryAny $ ws conn x liftIO $ tryAny $ ws conn x
wrapWS :: (MonadIO m, MonadReader WS.Connection m) wrapWS :: HasWebsockets env
=> (WS.Connection -> a -> IO ()) => (WS.Connection -> a -> IO ())
-> a -> a
-> m () -> RIO env ()
wrapWS ws x = do wrapWS ws x = do
conn <- ask conn <- view websocketsL
liftIO $ ws conn x liftIO $ ws conn x
-- | Receive a piece of data from the client. -- | Receive a piece of data from the client.
-- --
-- Since 0.1.0 -- Since 0.1.0
receiveData receiveData
:: (MonadIO m, MonadReader WS.Connection m, WS.WebSocketsData a) :: (WS.WebSocketsData a, HasWebsockets env)
=> m a => RIO env a
receiveData = do receiveData = do
conn <- ask conn <- view websocketsL
liftIO $ WS.receiveData conn liftIO $ WS.receiveData conn
-- | Receive a piece of data from the client. -- | Receive a piece of data from the client.
@ -173,9 +179,9 @@ receiveDataMessageE = do
-- --
-- Since 0.1.0 -- Since 0.1.0
sendTextData sendTextData
:: (MonadIO m, WS.WebSocketsData a, MonadReader WS.Connection m) :: (WS.WebSocketsData a, HasWebsockets env)
=> a => a
-> m () -> RIO env ()
sendTextData = wrapWS WS.sendTextData sendTextData = wrapWS WS.sendTextData
-- | Send a textual message to the client. -- | Send a textual message to the client.
@ -184,45 +190,45 @@ sendTextData = wrapWS WS.sendTextData
-- `either handle_exception return =<< sendTextDataE ("Welcome" :: Text)` -- `either handle_exception return =<< sendTextDataE ("Welcome" :: Text)`
-- Since 0.2.2 -- Since 0.2.2
sendTextDataE sendTextDataE
:: (MonadIO m, WS.WebSocketsData a, MonadReader WS.Connection m) :: (WS.WebSocketsData a, HasWebsockets env)
=> a => a
-> m (Either SomeException ()) -> RIO env (Either SomeException ())
sendTextDataE = wrapWSE WS.sendTextData sendTextDataE = wrapWSE WS.sendTextData
-- | Send a binary message to the client. -- | Send a binary message to the client.
-- --
-- Since 0.1.0 -- Since 0.1.0
sendBinaryData sendBinaryData
:: (MonadIO m, WS.WebSocketsData a, MonadReader WS.Connection m) :: (WS.WebSocketsData a, HasWebsockets env)
=> a => a
-> m () -> RIO env ()
sendBinaryData = wrapWS WS.sendBinaryData sendBinaryData = wrapWS WS.sendBinaryData
-- | Send a binary message to the client. -- | Send a binary message to the client.
-- Capture SomeException as the result of operation -- Capture SomeException as the result of operation
-- Since 0.2.2 -- Since 0.2.2
sendBinaryDataE sendBinaryDataE
:: (MonadIO m, WS.WebSocketsData a, MonadReader WS.Connection m) :: (WS.WebSocketsData a, HasWebsockets env)
=> a => a
-> m (Either SomeException ()) -> RIO env (Either SomeException ())
sendBinaryDataE = wrapWSE WS.sendBinaryData sendBinaryDataE = wrapWSE WS.sendBinaryData
-- | Send a ping message to the client. -- | Send a ping message to the client.
-- --
-- Since 0.2.2 -- Since 0.2.2
sendPing sendPing
:: (MonadIO m, WS.WebSocketsData a, MonadReader WS.Connection m) :: (WS.WebSocketsData a, HasWebsockets env)
=> a => a
-> WebSocketsT m () -> RIO env ()
sendPing = wrapWS WS.sendPing sendPing = wrapWS WS.sendPing
-- | Send a ping message to the client. -- | Send a ping message to the client.
-- Capture SomeException as the result of operation -- Capture SomeException as the result of operation
-- Since 0.2.2 -- Since 0.2.2
sendPingE sendPingE
:: (MonadIO m, WS.WebSocketsData a, MonadReader WS.Connection m) :: (WS.WebSocketsData a, HasWebsockets env)
=> a => a
-> m (Either SomeException ()) -> RIO env (Either SomeException ())
sendPingE = wrapWSE WS.sendPing sendPingE = wrapWSE WS.sendPing
-- | Send a DataMessage to the client. -- | Send a DataMessage to the client.
@ -240,40 +246,40 @@ sendDataMessageE x = do
-- --
-- Since 0.2.2 -- Since 0.2.2
sendClose sendClose
:: (MonadIO m, WS.WebSocketsData a, MonadReader WS.Connection m) :: (WS.WebSocketsData a, HasWebsockets env)
=> a => a
-> WebSocketsT m () -> RIO env ()
sendClose = wrapWS WS.sendClose sendClose = wrapWS WS.sendClose
-- | Send a close request to the client. -- | Send a close request to the client.
-- Capture SomeException as the result of operation -- Capture SomeException as the result of operation
-- Since 0.2.2 -- Since 0.2.2
sendCloseE sendCloseE
:: (MonadIO m, WS.WebSocketsData a, MonadReader WS.Connection m) :: (WS.WebSocketsData a, HasWebsockets env)
=> a => a
-> m (Either SomeException ()) -> RIO env (Either SomeException ())
sendCloseE = wrapWSE WS.sendClose sendCloseE = wrapWSE WS.sendClose
-- | A @Source@ of WebSockets data from the user. -- | A @Source@ of WebSockets data from the user.
-- --
-- Since 0.1.0 -- Since 0.1.0
sourceWS sourceWS
:: (MonadIO m, WS.WebSocketsData a, MonadReader WS.Connection m) :: (WS.WebSocketsData a, HasWebsockets env)
=> ConduitT i a m () => ConduitT i a (RIO env) ()
sourceWS = forever $ lift receiveData >>= yield sourceWS = forever $ lift receiveData >>= yield
-- | A @Sink@ for sending textual data to the user. -- | A @Sink@ for sending textual data to the user.
-- --
-- Since 0.1.0 -- Since 0.1.0
sinkWSText sinkWSText
:: (MonadIO m, WS.WebSocketsData a, MonadReader WS.Connection m) :: (WS.WebSocketsData a, HasWebsockets env)
=> ConduitT a o m () => ConduitT a o (RIO env) ()
sinkWSText = mapM_C sendTextData sinkWSText = mapM_C sendTextData
-- | A @Sink@ for sending binary data to the user. -- | A @Sink@ for sending binary data to the user.
-- --
-- Since 0.1.0 -- Since 0.1.0
sinkWSBinary sinkWSBinary
:: (MonadIO m, WS.WebSocketsData a, MonadReader WS.Connection m) :: (WS.WebSocketsData a, HasWebsockets env)
=> ConduitT a o m () => ConduitT a o (RIO env) ()
sinkWSBinary = mapM_C sendBinaryData sinkWSBinary = mapM_C sendBinaryData

View File

@ -22,6 +22,7 @@ library
, wai-websockets >= 2.1 , wai-websockets >= 2.1
, websockets >= 0.10 , websockets >= 0.10
, yesod-core >= 1.6 , yesod-core >= 1.6
, rio
source-repository head source-repository head
type: git type: git

View File

@ -180,7 +180,7 @@ loadConfig :: ConfigSettings environment extra
-> IO (AppConfig environment extra) -> IO (AppConfig environment extra)
loadConfig (ConfigSettings env parseExtra getFile getObject) = do loadConfig (ConfigSettings env parseExtra getFile getObject) = do
fp <- getFile env fp <- getFile env
mtopObj <- decodeFile fp mtopObj <- decodeFileThrow fp
topObj <- maybe (fail "Invalid YAML file") return mtopObj topObj <- maybe (fail "Invalid YAML file") return mtopObj
obj <- getObject env topObj obj <- getObject env topObj
m <- m <-
@ -233,7 +233,7 @@ withYamlEnvironment :: Show e
-> (Value -> Parser a) -- ^ what to do with the mapping -> (Value -> Parser a) -- ^ what to do with the mapping
-> IO a -> IO a
withYamlEnvironment fp env f = do withYamlEnvironment fp env f = do
mval <- decodeFile fp mval <- decodeFileThrow fp
case mval of case mval of
Nothing -> fail $ "Invalid YAML file: " ++ show fp Nothing -> fail $ "Invalid YAML file: " ++ show fp
Just (Object obj) Just (Object obj)

View File

@ -6,7 +6,6 @@ module Yesod.Default.Config2
configSettingsYml configSettingsYml
, getDevSettings , getDevSettings
, develMainHelper , develMainHelper
, makeYesodLogger
-- * Re-exports from Data.Yaml.Config -- * Re-exports from Data.Yaml.Config
, applyCurrentEnv , applyCurrentEnv
, getCurrentEnv , getCurrentEnv
@ -28,7 +27,6 @@ module Yesod.Default.Config2
import Data.Yaml.Config import Data.Yaml.Config
import Data.Semigroup
import Data.Aeson import Data.Aeson
import qualified Data.HashMap.Strict as H import qualified Data.HashMap.Strict as H
import System.Environment (getEnvironment) import System.Environment (getEnvironment)
@ -39,9 +37,6 @@ import Data.Maybe (fromMaybe)
import Control.Concurrent (forkIO, threadDelay) import Control.Concurrent (forkIO, threadDelay)
import System.Exit (exitSuccess) import System.Exit (exitSuccess)
import System.Directory (doesFileExist) import System.Directory (doesFileExist)
import Network.Wai.Logger (clockDateCacher)
import Yesod.Core.Types (Logger (Logger))
import System.Log.FastLogger (LoggerSet)
#ifndef mingw32_HOST_OS #ifndef mingw32_HOST_OS
import System.Posix.Signals (installHandler, sigINT, Handler(Catch)) import System.Posix.Signals (installHandler, sigINT, Handler(Catch))
@ -117,10 +112,3 @@ develMainHelper getSettingsApp = do
terminateDevel :: IO () terminateDevel :: IO ()
terminateDevel = exitSuccess terminateDevel = exitSuccess
-- | Create a 'Logger' value (from yesod-core) out of a 'LoggerSet' (from
-- fast-logger).
makeYesodLogger :: LoggerSet -> IO Logger
makeYesodLogger loggerSet' = do
(getter, _) <- clockDateCacher
return $! Yesod.Core.Types.Logger loggerSet' getter

View File

@ -6,8 +6,8 @@ module Yesod.Default.Handlers
import Yesod.Core import Yesod.Core
getFaviconR :: MonadHandler m => m () getFaviconR :: HasHandlerData env => RIO env ()
getFaviconR = sendFile "image/x-icon" "config/favicon.ico" getFaviconR = sendFile "image/x-icon" "config/favicon.ico"
getRobotsR :: MonadHandler m => m () getRobotsR :: HasHandlerData env => RIO env ()
getRobotsR = sendFile "text/plain" "config/robots.txt" getRobotsR = sendFile "text/plain" "config/robots.txt"

View File

@ -14,6 +14,7 @@ homepage: http://www.yesodweb.com/
extra-source-files: README.md ChangeLog.md extra-source-files: README.md ChangeLog.md
library library
hs-source-dirs: src
if os(windows) if os(windows)
cpp-options: -DWINDOWS cpp-options: -DWINDOWS