scotty-form 0.4.1.0 → 0.5.0.0
raw patch · 4 files changed
+138/−71 lines, 4 filesdep +unliftiodep ~basedep ~dittodep ~ditto-lucidPVP ok
version bump matches the API change (PVP)
Dependencies added: unliftio
Dependency ranges changed: base, ditto, ditto-lucid, lucid, scotty, text
API changes (from Hackage documentation)
- Web.Scotty.Trans.Form: instance (Web.Scotty.Internal.Types.ScottyError e, GHC.Base.Monad m) => Ditto.Core.Environment (Web.Scotty.Internal.Types.ActionT e m) [Web.Scotty.Internal.Types.Param]
+ Web.Scotty.Trans.Form: instance Control.Monad.IO.Unlift.MonadUnliftIO m => Ditto.Core.Environment (Web.Scotty.Internal.Types.ActionT m) [Web.Scotty.Internal.Types.Param]
- Web.Scotty.Form: ditto :: Monoid view => ([(Text, Text)] -> view -> view) -> Text -> Form ActionM [Param] err view a -> ActionM (Result err a, view)
+ Web.Scotty.Form: ditto :: ([(Text, Text)] -> view -> view) -> Text -> Form ActionM [Param] err view a -> ActionM (Result err a, view)
- Web.Scotty.Form: simpleDittoGET :: Applicative f => Text -> Form ActionM [Param] err (HtmlT f ()) b -> ActionM (Result err b, HtmlT f ())
+ Web.Scotty.Form: simpleDittoGET :: forall (f :: Type -> Type) err b. Applicative f => Text -> Form ActionM [Param] err (HtmlT f ()) b -> ActionM (Result err b, HtmlT f ())
- Web.Scotty.Form: simpleDittoPOST :: Applicative f => Text -> Form ActionM [Param] err (HtmlT f ()) b -> ActionM (Result err b, HtmlT f ())
+ Web.Scotty.Form: simpleDittoPOST :: forall (f :: Type -> Type) err b. Applicative f => Text -> Form ActionM [Param] err (HtmlT f ()) b -> ActionM (Result err b, HtmlT f ())
- Web.Scotty.Form: type ScottyForm a = Form ActionM [Param] ScottyFormError (Html ()) a
+ Web.Scotty.Form: type ScottyForm a = Form ActionM [Param] ScottyFormError Html () a
- Web.Scotty.Trans.Form: ditto :: (Monoid view, Monad m, ScottyError e) => ([(Text, Text)] -> view -> view) -> Text -> Form (ActionT e m) [Param] err view a -> ActionT e m (Result err a, view)
+ Web.Scotty.Trans.Form: ditto :: forall (m :: Type -> Type) view err a. Monad m => ([(Text, Text)] -> view -> view) -> Text -> Form (ActionT m) [Param] err view a -> ActionT m (Result err a, view)
- Web.Scotty.Trans.Form: dittoSingle :: (Monad m, ScottyError e) => ([(Text, Text)] -> view -> view) -> Text -> Form (ActionT e m) [Param] err view a -> ActionT e m (Result err a, view)
+ Web.Scotty.Trans.Form: dittoSingle :: forall (m :: Type -> Type) view err a. Monad m => ([(Text, Text)] -> view -> view) -> Text -> Form (ActionT m) [Param] err view a -> ActionT m (Result err a, view)
- Web.Scotty.Trans.Form: simpleDittoGET :: (Applicative f, Monad m, ScottyError e) => Text -> Form (ActionT e m) [Param] err (HtmlT f ()) b -> ActionT e m (Result err b, HtmlT f ())
+ Web.Scotty.Trans.Form: simpleDittoGET :: forall (f :: Type -> Type) (m :: Type -> Type) err b. (Applicative f, Monad m) => Text -> Form (ActionT m) [Param] err (HtmlT f ()) b -> ActionT m (Result err b, HtmlT f ())
- Web.Scotty.Trans.Form: simpleDittoPOST :: (Applicative f, Monad m, ScottyError e) => Text -> Form (ActionT e m) [Param] err (HtmlT f ()) b -> ActionT e m (Result err b, HtmlT f ())
+ Web.Scotty.Trans.Form: simpleDittoPOST :: forall (f :: Type -> Type) (m :: Type -> Type) err b. (Applicative f, Monad m) => Text -> Form (ActionT m) [Param] err (HtmlT f ()) b -> ActionT m (Result err b, HtmlT f ())
- Web.Scotty.Trans.Form: type ScottyForm e m a = Form (ActionT e m) [Param] ScottyFormError (HtmlT (ActionT e m) ()) a
+ Web.Scotty.Trans.Form: type ScottyForm (m :: Type -> Type) a = Form ActionT m [Param] ScottyFormError HtmlT ActionT m () a
Files
- CHANGELOG.md +21/−0
- scotty-form.cabal +74/−15
- src/Web/Scotty/Form.hs +7/−14
- src/Web/Scotty/Trans/Form.hs +36/−42
CHANGELOG.md view
@@ -0,0 +1,21 @@+# Changelog++## 0.5.0.0 — 2026-08-27++### Breaking+- Require `scotty >= 0.20` (drops `ScottyError` and `ActionT e m`; use `ActionT m`)+- `ScottyForm e m a` renamed to `ScottyForm m a` in `Web.Scotty.Trans.Form`+- `Param` values are strict `Text` (scotty >= 0.20); `liftParser` now takes strict `Text` parsers (same as `liftParser'`)+- `Environment` instance for `ActionT` requires `MonadUnliftIO` (needed for `formParams`)++### Changed+- Compatible with `ditto` 0.4 and 0.5 (ditto 0.5 is backwards compatible)+- Replace deprecated `params` with `pathParams`, `queryParams`, and `formParams`+- Prefer GHC 9.10+ (`GHC2024`) with fallback to `GHC2021` / `Haskell2010`+- Widen dependency bounds (`base >= 4.14`, `text >= 1.2`)+- Move language extensions into cabal `default-extensions`+- Modern cabal format (`cabal-version: 3.8`)++### Added+- GitHub Actions CI+- `cabal.project.local.example` for local `ditto` / `ditto-lucid` development
scotty-form.cabal view
@@ -1,35 +1,94 @@-cabal-version: >=1.10+cabal-version: 3.8 name: scotty-form-version: 0.4.1.0+version: 0.5.0.0 synopsis: Html form validation using `ditto` description: Formlet library for `scotty` using `lucid` and `ditto`--- bug-reports: license: MIT license-file: LICENSE author: goolord maintainer: zacharyachurchill@gmail.com--- copyright:+homepage: https://github.com/goolord/scotty-form+bug-reports: https://github.com/goolord/scotty-form/issues category: Web build-type: Simple-extra-source-files: CHANGELOG.md+extra-doc-files:+ CHANGELOG.md+tested-with:+ GHC == 9.6.7+ GHC == 9.8.4+ GHC == 9.10.3+ GHC == 9.12.4+ GHC == 9.14.1 source-repository head type: git location: https://github.com/goolord/scotty-form.git +common warnings-base+ ghc-options: -Wall++common warnings-modern+ if impl(ghc >= 8.10)+ ghc-options:+ -Wcompat+ -Widentities+ -Wincomplete-record-updates+ -Wincomplete-uni-patterns+ -Wmissing-deriving-strategies+ -Wunused-packages++common warnings-strict+ if impl(ghc >= 9.4)+ ghc-options: -Wredundant-constraints++common extensions-shared+ default-extensions:+ BangPatterns+ FlexibleInstances+ MultiParamTypeClasses+ OverloadedStrings+ RecordWildCards+ TypeApplications+ TypeFamilies++common defaults-ghc2024+ import:+ warnings-base, warnings-modern, warnings-strict, extensions-shared+ default-language: GHC2024++common defaults-ghc2021+ import:+ warnings-base, warnings-modern, warnings-strict, extensions-shared+ default-language: GHC2021+ default-extensions:+ DataKinds+ DerivingStrategies+ LambdaCase++common defaults-legacy+ import: warnings-base, extensions-shared+ default-language: Haskell2010++common defaults+ if impl(ghc >= 9.10)+ import: defaults-ghc2024+ elif impl(ghc >= 9.2)+ import: defaults-ghc2021+ else+ import: defaults-legacy+ library+ import: defaults exposed-modules: Web.Scotty.Form Web.Scotty.Trans.Form- ghc-options: -Wall- -- other-modules:- -- other-extensions: build-depends:- base >=4.12 && <5- , text >=1.2.4 && <3.0- , lucid >=2.9.12 && <3.0- , scotty >=0.12 && <1- , ditto >=0.4 && <=0.5- , ditto-lucid >= 0.4 && <= 0.5+ base >= 4.14 && < 5+ , text >= 1.2 && < 3+ , lucid >= 2.9 && < 3+ , scotty >= 0.20 && < 1+ , ditto >= 0.4 && < 0.6+ , ditto-lucid >= 0.4 && < 0.6+ , unliftio >= 0.2 && < 0.3 hs-source-dirs: src- default-language: Haskell2010+ ghc-options: -fno-warn-orphans -Wno-error=orphans
src/Web/Scotty/Form.hs view
@@ -15,17 +15,16 @@ import Ditto.Types import Web.Scotty import qualified Web.Scotty.Trans.Form as Trans-import qualified Data.Text.Lazy as TL import Lucid -encQP :: [(a, TL.Text)] -> Text+encQP :: [(a, Text)] -> Text encQP = Trans.encQP -- | a @ditto@ formlet for @scotty@ type ScottyForm a = Form ActionM [Param] Trans.ScottyFormError (Html ()) a -ditto :: (Monoid view)- => ([(Text, Text)] -> view -> view) -- ^ wrap raw form html inside a <form> tag+ditto+ :: ([(Text, Text)] -> view -> view) -- ^ wrap raw form html inside a <form> tag -> Text -- ^ form name prefix -> Form ActionM [Param] err view a -- ^ the formlet -> ActionM (Result err a, view)@@ -40,14 +39,14 @@ dittoSingle = Trans.dittoSingle -- | create @\<form action=action method=\"GET\" enctype=\"application/xxx-form-urlencoded\"\>@-simpleDittoGET :: (Applicative f)+simpleDittoGET :: Applicative f => Text -- ^ action -> Form ActionM [Param] err (HtmlT f ()) b -- ^ formlet -> ActionM (Result err b, HtmlT f ()) simpleDittoGET = Trans.simpleDittoGET -- | create @\<form action=action method=\"POST\" enctype=\"application/xxx-form-urlencoded\"\>@-simpleDittoPOST :: (Applicative f)+simpleDittoPOST :: Applicative f => Text -- ^ action -> Form ActionM [Param] err (HtmlT f ()) b -- ^ formlet -> ActionM (Result err b, HtmlT f ())@@ -57,12 +56,6 @@ liftParser' :: (Text -> Either Text a) -> ([Param] -> Either Trans.ScottyFormError a) liftParser' = Trans.liftParser' --- | lift a function which parses lazy @Text@ into a function which parses a @[Param]@--- e.g.------ @--- parserRead :: Read a => [Param] -> Either ScottyFormError a--- parserRead = liftParser readEither--- @-liftParser :: (TL.Text -> Either TL.Text a) -> ([Param] -> Either Trans.ScottyFormError a)+-- | alias for 'liftParser''. Parses strict `Text` from each `Param`.+liftParser :: (Text -> Either Text a) -> ([Param] -> Either Trans.ScottyFormError a) liftParser = Trans.liftParser
src/Web/Scotty/Trans/Form.hs view
@@ -1,38 +1,32 @@-{-# LANGUAGE- OverloadedStrings- , MultiParamTypeClasses- , FlexibleInstances- , TypeFamilies- , BangPatterns-#-}--{-# OPTIONS_GHC -fno-warn-orphans #-}+{-# OPTIONS_GHC -fno-warn-orphans -Wno-error=orphans #-} module Web.Scotty.Trans.Form where +import Data.Bifunctor (first)+import UnliftIO (MonadUnliftIO) import Data.Text (Text)+import Ditto.Backend import Ditto.Core hiding (view) import Ditto.Lucid import Ditto.Types import Lucid (HtmlT, ToHtml (toHtml))+import Lucid.Base (ToHtml (toHtmlRaw)) import Web.Scotty.Trans-import Ditto.Backend import qualified Data.Text as T-import qualified Data.Text.Lazy as TL-import Data.Bifunctor (first)-import Lucid.Base (ToHtml (toHtmlRaw)) -instance (ScottyError e, Monad m) => Environment (ActionT e m) [Param] where+instance MonadUnliftIO m => Environment (ActionT m) [Param] where environment formId = do- qp <- params- let !formId' = TL.fromStrict $ encodeFormId formId- case filter (\(x,_) -> x == formId') qp of+ pp <- pathParams+ qp <- queryParams+ fp <- formParams+ let !formId' = encodeFormId formId+ case filter (\(x, _) -> x == formId') (pp ++ qp ++ fp) of [] -> pure Missing xs -> pure (Found xs) instance FormInput [Param] where type FileType [Param] = ()- getInputStrings xs = fmap (TL.unpack . snd) xs+ getInputStrings xs = fmap (T.unpack . snd) xs getInputFile _ = Left $ commonFormError $ (NoFileFound [("","No support for file uploads")] :: CommonFormError [Param]) instance FormError [Param] ScottyFormError where@@ -55,18 +49,18 @@ toHtmlRaw SFEUnexpectedEmpty = "Unexpected empty query param list" toHtmlRaw SFEUnexpectedMultiple = "Unexpected multiple query param list" -encQP :: [(a, TL.Text)] -> Text+encQP :: [(a, Text)] -> Text encQP [] = ""-encQP xs = T.intercalate ", " (fmap (TL.toStrict . snd) xs)+encQP xs = T.intercalate ", " (fmap snd xs) -- | a @ditto@ formlet for @scotty@-type ScottyForm e m a = Form (ActionT e m) [Param] ScottyFormError (HtmlT (ActionT e m) ()) a+type ScottyForm m a = Form (ActionT m) [Param] ScottyFormError (HtmlT (ActionT m) ()) a -ditto :: (Monoid view, Monad m, ScottyError e)+ditto :: Monad m => ([(Text, Text)] -> view -> view) -- ^ wrap raw form html inside a <form> tag -> Text -- ^ form name prefix- -> Form (ActionT e m) [Param] err view a -- ^ the formlet- -> ActionT e m (Result err a, view)+ -> Form (ActionT m) [Param] err view a -- ^ the formlet+ -> ActionT m (Result err a, view) ditto toForm prefix formlet = do dittoSingle toForm' prefix formlet where@@ -74,11 +68,11 @@ -- | a helpful wrapper around 'runForm' dittoSingle- :: (Monad m, ScottyError e)+ :: Monad m => ([(Text, Text)] -> view -> view) -- ^ wrap raw form html inside a <form> tag -> Text -- ^ form name prefix- -> Form (ActionT e m) [Param] err view a -- ^ the formlet- -> ActionT e m (Result err a, view)+ -> Form (ActionT m) [Param] err view a -- ^ the formlet+ -> ActionT m (Result err a, view) dittoSingle toForm prefix formlet = do (View viewf, res) <- runForm prefix formlet case res of@@ -86,33 +80,33 @@ Ok (Proved _ unProved') -> pure (Ok unProved', toForm [] $ viewf []) -- | create @\<form action=action method=\"GET\" enctype=\"application/xxx-form-urlencoded\"\>@-simpleDittoGET :: (Applicative f, Monad m, ScottyError e)+simpleDittoGET+ :: (Applicative f, Monad m) => Text -- ^ action- -> Form (ActionT e m) [Param] err (HtmlT f ()) b -- ^ formlet- -> ActionT e m (Result err b, HtmlT f ())+ -> Form (ActionT m) [Param] err (HtmlT f ()) b -- ^ formlet+ -> ActionT m (Result err b, HtmlT f ()) simpleDittoGET action form = ditto (formGenGET action) "ditto" form -- | create @\<form action=action method=\"POST\" enctype=\"application/xxx-form-urlencoded\"\>@-simpleDittoPOST :: (Applicative f, Monad m, ScottyError e)+simpleDittoPOST+ :: (Applicative f, Monad m) => Text -- ^ action- -> Form (ActionT e m) [Param] err (HtmlT f ()) b -- ^ formlet- -> ActionT e m (Result err b, HtmlT f ())+ -> Form (ActionT m) [Param] err (HtmlT f ()) b -- ^ formlet+ -> ActionT m (Result err b, HtmlT f ()) simpleDittoPOST action form = ditto (formGenPOST action) "ditto" form -- | lift a function which parses strict @Text@ into a function which parses a @[Param]@ liftParser' :: (Text -> Either Text a) -> ([Param] -> Either ScottyFormError a)-liftParser' f [(_,x)] = first SFEParseError $ f (TL.toStrict x)+liftParser' f [(_, x)] = first SFEParseError $ f x liftParser' _ [] = Left SFEUnexpectedEmpty liftParser' _ _ = Left SFEUnexpectedMultiple --- | lift a function which parses lazy @Text@ into a function which parses a @[Param]@--- e.g.+-- | lift a function which parses strict @Text@ into a function which parses a @[Param]@ -- -- @--- parserRead :: Read a => [Param] -> Either ScottyFormError a--- parserRead = liftParser readEither+-- parserRead :: [Param] -> Either ScottyFormError Int+-- parserRead = liftParser' $ \t ->+-- maybe (Left "not an integer") Right (readMaybe @Int (T.unpack t)) -- @-liftParser :: (TL.Text -> Either TL.Text a) -> ([Param] -> Either ScottyFormError a)-liftParser f [(_,x)] = first (SFEParseError . TL.toStrict) $ f x-liftParser _ [] = Left SFEUnexpectedEmpty-liftParser _ _ = Left SFEUnexpectedMultiple+liftParser :: (Text -> Either Text a) -> ([Param] -> Either ScottyFormError a)+liftParser = liftParser'