packages feed

servant-ede (empty) → 0.4

raw patch · 11 files changed

+787/−0 lines, 11 filesdep +aesondep +basedep +bytestringsetup-changed

Dependencies added: aeson, base, bytestring, ede, either, filepath, http-media, http-types, semigroups, servant, servant-ede, servant-server, text, transformers, unordered-containers, wai, warp

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2015, Alp Mestanogullari++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Alp Mestanogullari nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,3 @@+# servant-ede++Support ede templates in servant. See the documentation of the `Servant.HTML.EDE` module for examples and explanations.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ example/example.hs view
@@ -0,0 +1,33 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE OverloadedStrings #-}+import Control.Monad+import Data.Monoid+import GHC.Generics+import Network.Wai.Handler.Warp+import Servant+import Servant.HTML.EDE+import Text.EDE++data User = User { name :: String, age :: Int }+  deriving (Eq, Show, Generic)++instance ToObject User where++type API = "index" :> Tpl "index.tpl"+      :<|> "foo"   :> Tpl "foo.tpl"+      :<|> "user"  :> Get '[HTML "user.tpl"] User++api :: Proxy API+api = Proxy++server :: Server API+server = rawTemplate :<|> rawTemplate :<|> return (User "lambdabot" 35)++  where rawTemplate = return mempty++main :: IO ()+main = do+  loadTemplates api "example"+  run 8082 (serve api server)
+ servant-ede.cabal view
@@ -0,0 +1,62 @@+-- Initial servant-ede.cabal generated by cabal init.  For further +-- documentation, see http://haskell.org/cabal/users-guide/++name:                servant-ede+version:             0.4+synopsis:            Combinators for rendering EDE templates in servant web applications+description:+  Combinators for rendering EDE templates in servant web applications.+  .+  Documentation and examples available at "Servant.HTML.EDE".+homepage:            http://github.com/alpmestan/servant-ede+license:             BSD3+license-file:        LICENSE+author:              Alp Mestanogullari+maintainer:          alpmestan@gmail.com+copyright:           2015 Alp Mestanogullari+category:            Web+build-type:          Simple+extra-source-files:  README.md+cabal-version:       >=1.10++library+  exposed-modules:     Servant.HTML.EDE+  other-modules:+      Servant.HTML.EDE.Internal+    , Servant.HTML.EDE.Internal.Reify+    , Servant.HTML.EDE.Internal.Templates+    , Servant.HTML.EDE.Internal.ToObject+    , Servant.HTML.EDE.Internal.Validate++  build-depends:+      aeson >= 0.8+    , base >=4.7 && <5+    , bytestring >= 0.10+    , filepath >= 1.2+    , ede >= 0.2+    , either >= 4+    , http-media >= 0.6+    , http-types >= 0.8+    , semigroups >= 0.16+    , servant >= 0.4+    , servant-server >= 0.4+    , text >= 1.0+    , transformers >= 0.4+    , unordered-containers >= 0.2+    , wai >= 3.0++  ghc-options:         -O2 -Wall+  hs-source-dirs:      src+  default-language:    Haskell2010+++executable servant-ede-example+  main-is: example.hs+  hs-source-dirs: example+  default-language: Haskell2010+  build-depends:+      base >= 4.7 && <5+    , ede+    , servant-server+    , servant-ede+    , warp >= 3.0
+ src/Servant/HTML/EDE.hs view
@@ -0,0 +1,229 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+-- |+-- Module:      Servant.HTML.EDE+-- Copyright:   (c) 2015 Alp Mestanogullari+-- License:     BSD3+-- Maintainer:  Alp Mestanogullari <alpmestan@gmail.com>+-- Stability:   experimental+--+-- Combinators for rendering <http://hackage.haskell.org/package/ede ede>+-- templates in servant web applications.+module Servant.HTML.EDE (+    -- * Introduction+    -- $intro++    -- * Explicitly binding data to a template+    -- $explicit++    -- * Template rendering as a content-type+    -- $contenttype+    +    -- * Reference++    -- ** 'Tpl' combinator, for explicit data binding+    Tpl+  +  , -- ** 'HTML' content type, for serializing data types to HTML+    HTML+  , ToObject(..)++  , -- ** Loading templates+    loadTemplates+  , TemplateError+  , Errors++  , -- ** Helpers+    TemplateFiles+  , Reify(..)++  , -- * Global template store+    -- $templatestore+  ) where++import Control.Applicative+import Control.Concurrent.MVar+import Control.Monad.IO.Class+import Data.Aeson+import Data.Foldable+import Data.HashMap.Strict+import Data.Traversable+import Servant+import Servant.HTML.EDE.Internal++-- | Same as 'loadTemplates', except that it initializes a global+--   template store (i.e a 'Templates' value) and fills it with+--   the resulting compiled templates if all of them are compiled+--   successfully. If that's not the case, the global template store+--   (under an 'MVar') is left empty.+--+--   /IMPORTANT/: Must /always/ be called before starting your /servant/ application.+loadTemplates :: (Reify (TemplateFiles api), Applicative m, MonadIO m)+              => Proxy api+              -> FilePath -- ^ root directory for the templates+              -> m Errors+loadTemplates proxy dir = do+  res <- loadTemplates' proxy dir+  case res of+    Left errs  -> return errs+    Right tpls -> do+      liftIO $ putMVar __template_store tpls+      return []++loadTemplates' :: (Reify (TemplateFiles api), Applicative m, MonadIO m)+               => Proxy api+               -> FilePath   -- ^ root directory for the templates+               -> m (Either Errors Templates)+loadTemplates' proxy templatedir =+  fmap (eitherValidate . fmap fold) . runValidateT $+    traverse (processFile templatedir) files++  where files :: [FilePath]+        files = templateFiles proxy++-- $intro+--+-- The <http://hackage.haskell.org/package/ede ede> library provides+-- a reasonably good template engine. This package explores+-- a smooth integration of /ede/ into <http://haskell-servant.github.io/ servant>+-- through two combinators.++-- $explicit+--+-- The first combinator is 'Tpl', which is used for generating web pages+-- from /ede/ templates by explicitly returning an 'Object'+-- (which is a synonym for @'HashMap' 'Text' 'Value'@, i.e a /JSON object/)+-- from the handler which will then be rendered against a given template.+--+-- @+-- -- we want our template file, index.html,+-- -- to be rendered under /index+-- type API = "index" :> 'Tpl' "index.html"+--+-- api :: 'Proxy' API+-- api = Proxy+--+-- server :: 'Server' API+-- server = return indexData+--+--   where indexData :: 'Object'+--         indexData =+--           HM.fromList [ ("company_name", "Foo Inc.")+--                       , ("ceo", "Bar Baz")+--                       ]+--+-- main :: IO ()+-- main = do+--   -- this tells the library to look for index.html+--   -- under the ./templates directory+--   'loadTemplates' api "./templates"+--   run 8080 server+-- @+--+-- Note that if your template doesn't need any data, you can just+-- return the empty 'Object', with "Data.Monoid"\'s 'mempty'.+--+-- The call to 'loadTemplates' is mandatory. It loads and compiles all the+-- templates used in your API and puts them in a global \"compiled template store\".+-- I'm not really satisfied with this, see the /Global template store/ section+-- for more on this topic.++-- $contenttype+--+-- The other way to use this package is reminiscent of how you can render HTML+-- with <http://hackage.haskell.org/package/servant-blaze servant-blaze> or+-- its cousin <http://hackage.haskell.org/package/servant-lucid servant-lucid>.+-- Indeed, this package provides an 'HTML' content type just like the two+-- aforementionned libraries. However, unlike in these packages, this 'HTML'+-- type is parametrised by a type-level string meant to be the name of the+-- template file (or path to the template file starting from a \"root\" directory+-- of templates).+--+-- In the same way that /servant/'s standard 'JSON' combinator+-- carries the precise way in which we encode haskell values to JSON,+-- in addition to representing the @application/json@ content type,+-- 'HTML' carries the template used to render values of a given type.+--+-- If we wanted to have a @/user@ endpoint accessible in JSON or HTML,+-- returning a user, we could write:+--+-- @+-- type UserAPI = "user" :> 'Get' '['JSON', 'HTML' "user.tpl"] User+--+-- userAPI :: Proxy UserAPI+-- userAPI = Proxy+-- @+--+-- How, then, can /servant/ know how to marshall @User@ to the template+-- in order to render it? Simple, you just have to provide an instance of+-- the following 'ToObject' class:+--+-- @+-- class ToObject a where+--   toObject :: a -> 'Object'+-- @+--+-- If our @User@ data type is:+--+-- > data User = User { name :: String, age :: Int }+--+-- we can simply do, using functions from "Text.EDE":+--+-- @+-- instance ToObject User where+--   toObject u =+--     fromPairs [ "name" .= name u+--               , "age"  .= age u+--               ]+-- @+--+-- However, this is actually not necessary. This library provides can+-- derive the 'ToObject' instance for you as long as your data type+-- derives the "GHC.Generics.Generic" class, which can be done by specifying+-- @{-# LANGUAGE DeriveGeneric #-}@ at the top of your module, adding+-- @import GHC.Generics@ and by changing the @User@ data type declaration to:+--+-- > data User = User { name :: String, age :: Int } deriving Generic+--+-- You can then simply write:+--+-- > instance ToObject User+--+-- and the library will figure out how to encode @User@ to JSON for you.+-- /IMPORTANT/: This only works with data types with a single record and+-- field selectors.+--+-- Now we can put this all to work with a simple webservice:+--+-- @+-- server :: Server UserAPI+-- server = return (User "lambdabot" 31)+--+-- main :: IO ()+-- main = do+--   loadTemplates userAPI "./templates"+--   run 8082 (serve userAPI server)+-- @+--+-- Again, the call to 'loadTemplates' is mandatory because the 'HTML'+-- content type relies on having its hands on already-compiled templates.+--+-- You can now write a @user.tpl@ template under the @./templates@ directory+-- using any of <http://hackage.haskell.org/package/ede ede>'s constructs,+-- assuming that a @name@ string variable and an @age@ int variable are in scope.++-- $templatestore+--+-- Why have a global template store? Well, while for 'Tpl' we can run arbitrary code+-- in the handlers, take arguments and what not, that's not the case when writing the+-- 'MimeRender' instance for 'HTML'.+--+-- All we have is a value of some type and we have to pull a compiled template out of+-- thin air. That's why we use a global 'MVar'-protected template store indexed by filename.+-- It's filled once at the beginning when you call 'loadTemplates' and is then only accessed+-- in a /read-only/ fashion. I would be interested in hearing about any suggestion,+-- improvement or replacement for this. If you have an idea, feel free to shoot me an+-- email at the address specified in the cabal description.
+ src/Servant/HTML/EDE/Internal.hs view
@@ -0,0 +1,12 @@+module Servant.HTML.EDE.Internal+  ( module Servant.HTML.EDE.Internal.Reify+  , module Servant.HTML.EDE.Internal.Templates+  , module Servant.HTML.EDE.Internal.ToObject+  , module Servant.HTML.EDE.Internal.Validate+  ) where++import Servant.HTML.EDE.Internal.Reify+import Servant.HTML.EDE.Internal.Templates+import Servant.HTML.EDE.Internal.ToObject+import Servant.HTML.EDE.Internal.Validate+
+ src/Servant/HTML/EDE/Internal/Reify.hs view
@@ -0,0 +1,25 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE ScopedTypeVariables #-}+module Servant.HTML.EDE.Internal.Reify (Reify(..)) where++import Data.Proxy+import GHC.TypeLits++-- | Helper class to reify a type-level list of strings+--   into a value-level list of string. Used to turn+--   the type-level list of template file names into+--   a value-level list.+class Reify (symbols :: [Symbol]) where+  reify :: Proxy symbols -> [String]++instance Reify '[] where+  reify _ = []++instance (KnownSymbol s, Reify symbols)+      => Reify (s ': symbols) where+  reify _ = symbolVal ps : reify psymbols++    where ps = Proxy :: Proxy s+          psymbols = Proxy :: Proxy symbols
+ src/Servant/HTML/EDE/Internal/Templates.hs view
@@ -0,0 +1,223 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+module Servant.HTML.EDE.Internal.Templates+  ( Tpl+  , HTML+  , Templates(..)+  , templateMap+  , __template_store+  , TemplateFiles+  , TemplateError+  , Errors+  , processFile+  , templateFiles+  ) where++import Control.Concurrent.MVar+import Control.Monad.IO.Class+import Control.Monad.Trans.Either+import Data.Aeson (Object)+import Data.ByteString.Lazy.Char8 (pack)+import Data.HashMap.Strict+import Data.Proxy+import Data.Semigroup+import Data.Text.Lazy.Encoding (encodeUtf8)+import GHC.TypeLits+import Network.HTTP.Media hiding (Accept)+import Network.HTTP.Types+import Network.Wai+import Servant+import Servant.HTML.EDE.Internal.Reify+import Servant.HTML.EDE.Internal.ToObject+import Servant.HTML.EDE.Internal.Validate+import Servant.Server.Internal+import Servant.Server.Internal.ServantErr+import System.FilePath+import System.IO.Unsafe (unsafePerformIO)+import Text.EDE++import qualified Data.HashMap.Strict as HM++__template_store :: MVar Templates+__template_store = unsafePerformIO newEmptyMVar++-- | Combinator for serving EDE templates without arguments. Usage:+--+-- > type API = "index" :> Tpl "index.tpl"+-- >       :<|> "about" :> Tpl "about.tpl"+-- >+-- > api :: Proxy API+-- > api = Proxy+-- >+-- > server :: Templates -> Server API+-- > server tpls = return mempty :<|> return mempty+-- >+-- > main :: IO ()+-- > main = do+-- >   loadTemplates_ api "./templates"+-- >   run 8080 (serve api server)+data Tpl (tplfile :: Symbol)+++-- | The so-called "request handler" for an endpoint ending+--   with 'Tpl' just has to be the opaque 'Templates' value+--   returned by 'Servant.HTML.EDE.loadTemplates' applied to your API, which+--   is just a compiled template store indexed by file name.+instance KnownSymbol tplfile => HasServer (Tpl tplfile) where+  type ServerT (Tpl tplfile) m = m Object++  route Proxy mobj request respond+    | pathIsEmpty request && requestMethod request == methodGet = do+        tpls <- getTemplates+        val <- runEitherT mobj+        case val of+          Left e -> respond . succeedWith $ responseServantErr e+          Right v  -> +            case mbody tpls v of+              Success body -> respond . succeedWith $+                responseLBS ok200 [("Content-Type", "text/html")] (encodeUtf8 body)+              Failure doc  -> respond . succeedWith $+                responseLBS status500 [] ("template error: " <> pack (show doc))++    | pathIsEmpty request && requestMethod request /= methodGet =+        respond (failWith WrongMethod)++    | otherwise = respond (failWith NotFound)++    where filename = symbolVal (Proxy :: Proxy tplfile)+          mbody ts val = render (ts HM.! filename) val+          getTemplates = fmap templateMap (readMVar __template_store)++-- | 'HTML' content type, but more than just that.+--+--   'HTML' takes a type-level string which is+--   a filename for the template you want to use to+--   render values. Example:+--+-- @+-- type UserAPI = "user" :> Get '[JSON, HTML "user.tpl"] User+--+-- userAPI :: Proxy UserAPI+-- userAPI = Proxy+--+-- data User = User { name :: String, age :: Int } deriving Generic+--+-- instance ToJSON User+-- instance ToObject User+--+-- server :: Server API+-- server = return (User "lambdabot" 31)+--+-- main :: IO ()+-- main = do+--   loadTemplates userAPI "./templates"+--   run 8082 (serve userAPI server)+-- @+--+-- This will look for a template at @.\/templates\/user.tpl@, which could+-- for example be:+--+-- > <ul>+-- >   <li><strong>Name:</strong> {{ name }}</li>+-- >   <li><strong>Age:</strong> {{ age }}</li>+-- > </ul>+data HTML (tplfile :: Symbol)++-- | @text\/html;charset=utf-8@+instance Accept (HTML tplfile) where+  contentType _ = "text" // "html" /: ("charset", "utf-8")++instance (KnownSymbol tplfile, ToObject a)+      => MimeRender (HTML tplfile) a where+  mimeRender _ val = encodeUtf8 . result (error . show) id $+    render templ (toObject val)++    where templ = tmap ! filename+          filename = symbolVal (Proxy :: Proxy tplfile)+          tmap = templateMap $ unsafePerformIO (readMVar __template_store)++type family Append (xs :: [k]) (ys :: [k]) :: [k] where+  Append '[]       ys = ys+  Append (x ': xs) ys = x ': Append xs ys++type family Member (x :: k) (xs :: [k]) :: Bool where+  Member x (x ': xs) = 'True+  Member x (y ': xs) = Member x xs+  Member x       '[] = False++-- | Collect all the template filenames of an API as a type-level+--   list of strings, by simply looking at all occurences of the+--   'Tpl' and 'HTML' combinators and keeping the filenames associated to them.+type family TemplateFiles (api :: k) :: [Symbol]+type instance TemplateFiles (a :<|> b)    = Append (TemplateFiles a) (TemplateFiles b)+type instance TemplateFiles (a :> r)      = TemplateFiles r+type instance TemplateFiles (Tpl f)       = '[f]+type instance TemplateFiles (Delete cs a) = CTFiles cs+type instance TemplateFiles (Get cs a)    = CTFiles cs+type instance TemplateFiles (Patch cs a)  = CTFiles cs+type instance TemplateFiles (Post cs a)   = CTFiles cs+type instance TemplateFiles (Put cs a)    = CTFiles cs++type family CTFiles (cts :: [*]) :: [Symbol] where+  CTFiles '[]        = '[]+  CTFiles (c ': cts) = Append (CTFile c) (CTFiles cts)++type family CTFile c :: [Symbol] where+  CTFile (HTML fp) = '[fp]+  CTFile         a = '[]++templates :: Proxy api -> Proxy (TemplateFiles api)+templates Proxy = Proxy++templateFiles :: Reify (TemplateFiles api) => Proxy api -> [FilePath]+templateFiles = reify . templates++-- | An opaque "compiled-template store".+--+-- The only way to get a value of this type is to use+-- 'Servant.EDE.loadTemplates' on a proxy of your API.+--+-- This ensures that when we lookup a template (in order+-- to render it) in our 'Templates' store, we are+-- guaranteed to find it.+newtype Templates = Templates (HashMap String Template)+  deriving Eq++templateMap :: Templates -> HashMap String Template+templateMap (Templates m) = m++instance Semigroup Templates where+  Templates a <> Templates b = Templates (a <> b)++instance Monoid Templates where+  mempty = Templates mempty++  a `mappend` b = a <> b++tpl :: FilePath -> Template -> Templates+tpl fp t = Templates $ HM.singleton fp t++-- | A 'TemplateError' is a pair of a template filename+--   and the error string for that file.+type TemplateError = (FilePath, String)++-- | A list of 'TemplateError's.+type Errors = [TemplateError]++err :: Show a => FilePath -> a -> Errors+err fp d = [(fp, show d)]++processFile :: MonadIO m => FilePath -> FilePath -> ValidateT Errors m Templates+processFile d fp = validate . liftIO $ parseFile' (d </> fp)++  where parseFile' f = fmap validateResult (parseFile f)+        validateResult (Success t) = OK (tpl fp t)+        validateResult (Failure e) = NotOK (err fp e)
+ src/Servant/HTML/EDE/Internal/ToObject.hs view
@@ -0,0 +1,97 @@+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE DefaultSignatures #-}+{-# LANGUAGE FlexibleInstances #-}+-- {-# LANGUAGE OverloadedStrings #-}+module Servant.HTML.EDE.Internal.ToObject where++import Data.Aeson+import Data.HashMap.Strict+import Data.Monoid+import Data.Text+import GHC.Generics++-- | Turn haskell values into JSON objects.+--+-- This is the mechanism used by EDE to marshall data from Haskell+-- to the templates. The rendering is then just about feeding the+-- resulting 'Object' to a compiled 'Template'. Example:+--+-- > import Text.EDE+-- >+-- > data User = User { name :: String, age :: Int }+-- > instance ToObject User where+-- >   toObject user =+-- >     fromPairs [ "name" .= name user+-- >               , "age"  .= age user+-- >               ]+--+-- However, you're not forced to write the instance yourself for such a type.+-- Indeed, for any record type (i.e a datatype with a single constructor and+-- with field selectors) you can let @GHC.Generics@ derive the 'ToObject' instance+-- for you.+--+-- > data User = User { name :: String, age :: Int } deriving Generic+-- > instance ToObject User+--+-- This will generate an equivalent instance to the previous one.+class ToObject a where++  -- | Turn values of type @a@ into JSON 'Object's.+  --+  -- @ +  -- -- Reminder:+  -- type Object = 'HashMap' 'Text' 'Value'+  -- @+  toObject :: a -> Object+  +  default toObject :: (Generic a, GToObject (Rep a)) => a -> Object+  toObject = genericToObject++instance ToObject (HashMap Text Value) where+  toObject = id++class GToObject f where+  gtoObject :: f a -> Object++instance GToObject V1 where+  gtoObject _ = mempty++instance GToObject U1 where+  gtoObject U1 = mempty++instance (GToObject f, GToObject g)+      => GToObject (f :*: g) where+  gtoObject (f :*: g) = gtoObject f <> gtoObject g++instance GToObject a => GToObject (M1 D d a) where+  gtoObject (M1 x) = gtoObject x++instance GToObject a => GToObject (M1 C c a) where+  gtoObject (M1 x) = gtoObject x++instance (Selector s, ToJSON a) => GToObject (M1 S s (K1 r a)) where+  gtoObject s@(M1 (K1 x)) = fromList [(fieldname, value)]+    where fieldname = pack (selName s)+          value     = toJSON x++genericToObject :: (Generic a, GToObject (Rep a)) => a -> Object+genericToObject = gtoObject . from++++++++++++++++++
+ src/Servant/HTML/EDE/Internal/Validate.hs view
@@ -0,0 +1,71 @@+module Servant.HTML.EDE.Internal.Validate where++import Control.Applicative+import Data.Foldable+import Data.Traversable+import Data.Functor.Compose+import Data.Semigroup++data Validated e a = OK a | NotOK e+  deriving (Eq, Show)++instance Functor (Validated e) where+  fmap f (OK x)    = OK (f x)+  fmap _ (NotOK e) = NotOK e++instance Semigroup e => Applicative (Validated e) where+  pure x = OK x++  OK f    <*> OK x     = OK (f x)+  OK _    <*> NotOK e  = NotOK e+  NotOK e <*> OK _     = NotOK e+  NotOK e <*> NotOK e' = NotOK (e <> e')++instance Foldable (Validated e) where+  foldMap f (OK x)    = f x+  foldMap _ (NotOK _) = mempty++instance Traversable (Validated e) where+  traverse f (OK x)    = fmap OK (f x)+  traverse _ (NotOK x) = pure (NotOK x)++instance (Semigroup e, Semigroup a) => Semigroup (Validated e a) where+  NotOK e <> NotOK e' = NotOK (e <> e')+  NotOK e <> OK _     = NotOK e+  OK a    <> OK b     = OK (a <> b)+  OK _    <> NotOK e  = NotOK e++validateEither :: Either e a -> Validated e a+validateEither (Left e)  = NotOK e+validateEither (Right x) = OK x++eitherValidate :: Validated e a -> Either e a+eitherValidate (OK x)    = Right x+eitherValidate (NotOK e) = Left e++ok :: Applicative m => a -> ValidateT e m a+ok = VT . pure . OK++no :: Applicative m => e -> ValidateT e m a+no = VT . pure . NotOK++validated :: (e -> r) -> (a -> r) -> Validated e a -> r+validated f _ (NotOK e) = f e+validated _ f (OK x)    = f x++newtype ValidateT e m a = VT+  { runValidateT :: m (Validated e a) }++validate :: m (Validated e a) -> ValidateT e m a+validate = VT++instance Functor m => Functor (ValidateT e m) where+  fmap f (VT m) = VT $ fmap (fmap f) m++instance (Applicative m, Semigroup e) => Applicative (ValidateT e m) where+  pure = VT . pure . pure++  VT f <*> VT x = VT . getCompose $ Compose f <*> Compose x++instance (Applicative m, Semigroup e, Semigroup a) => Semigroup (ValidateT e m a) where+  VT a <> VT b = VT $ (<>) <$> a <*> b