Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Forms #32

Merged
merged 43 commits into from
Aug 31, 2016
Merged
Changes from 1 commit
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
1b468b0
Move FormUrlEncoded from servant.
jkarni Jan 21, 2016
0e7f261
Fix test for `decodeForm . encodeForm`
cdepillabout Aug 24, 2016
ac72f9e
Fix PR so it works on ghc-7.8.
cdepillabout Aug 24, 2016
acc2cd4
Remove dependency on string-conversions package.
cdepillabout Aug 25, 2016
97f200f
Replace uses for toUrlPiece and parseUrlPiece with toQueryParam and p…
cdepillabout Aug 25, 2016
dd1baa5
Change uses of String to Text.
cdepillabout Aug 26, 2016
8ab1a1f
Add comments and doctests to the FormUrlEncoded module.
cdepillabout Aug 26, 2016
6487f48
Add two convenience functions for encoding / decoding a ByteString di…
cdepillabout Aug 26, 2016
fb08da7
Rephrase documentation and amend some examples for FormUrlEncoded
fizruk Aug 29, 2016
6ab8a56
Use a custom Show instanse for Form (like Map has)
fizruk Aug 29, 2016
236c59a
Rename encode/decodeWith*From to encode/decodeAsForm
fizruk Aug 29, 2016
7c3d63b
Ignore stack haddock's output to src/
fizruk Aug 29, 2016
4233ec9
Move Form-related stuff to Web.FormUrlEncoded
fizruk Aug 29, 2016
9b7a61d
Simplify To/FromForm instances for lists
fizruk Aug 29, 2016
6e97168
Remove utf8-string dependency
fizruk Aug 29, 2016
f2a7554
Refactor decodeForm
fizruk Aug 29, 2016
28713e6
Remove mtl dependency, simplify GToForm and GFromForm
fizruk Aug 29, 2016
765249b
Add test case for empty form data
fizruk Aug 29, 2016
d04cfba
Refactor qualified names
fizruk Aug 29, 2016
5322053
Allow duplicate keys in Form
fizruk Aug 29, 2016
3d6065a
Add FormKey classes
fizruk Aug 30, 2016
a9976c2
Generalize ToForm/FromForm instances
fizruk Aug 30, 2016
6cd7a03
Add ToForm/FormForm instances for IntMap and HashMap
fizruk Aug 30, 2016
70060ee
Add helpers for FromForm instances
fizruk Aug 30, 2016
0456604
Change Form's internal representation to HashMap
fizruk Aug 30, 2016
550df9a
Export Generic-based toForm and fromForm
fizruk Aug 30, 2016
173ef33
Fix Travis builds for GHC 7
fizruk Aug 30, 2016
02fadd3
Rename encode/decode to urlEncode/urlDecode
fizruk Aug 30, 2016
ea020ae
Use uri-bytestring for a more efficient url-encoding and decoding
fizruk Aug 30, 2016
5a448b3
Remove Generic support for sum types
fizruk Aug 30, 2016
e9c728b
Fix GHC 7.8 builds
fizruk Aug 30, 2016
d944721
Add instances for Natural (close #33)
fizruk Aug 30, 2016
fa05cca
Rename lookupKey -> lookupAll, improve docs a bit
fizruk Aug 30, 2016
3158b55
Add notes that Generic-based implementation only works for records
fizruk Aug 30, 2016
bea3afe
Add nice type error for when ToForm/FromForm is derived for sum type
fizruk Aug 30, 2016
7a7b274
Add basic FormOptions
fizruk Aug 30, 2016
57c0a23
Fix unticked promoted constructors warning
fizruk Aug 30, 2016
4bc8d3e
Treat Maybes and lists specially when deriving Generic instances
fizruk Aug 31, 2016
9cb90d5
Fix GHC 7 builds
fizruk Aug 31, 2016
2df7799
Re-export FormOptions
fizruk Aug 31, 2016
e613d05
Add an example for non-default FormOptions
fizruk Aug 31, 2016
de9fe0f
Enable ConstraintKinds and fix typo for GHC 7.8
fizruk Aug 31, 2016
f59b5c9
Add more docs explaining special roles of Maybe and lists
fizruk Aug 31, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Treat Maybes and lists specially when deriving Generic instances
fizruk committed Aug 31, 2016

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
commit 4bc8d3e767b15a8deac5890067ce7fb97b0e8608
1 change: 1 addition & 0 deletions .ghci
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
:set -itest -isrc -optP-include -optP .stack-work/dist/x86_64-osx/Cabal-1.24.0.0/build/autogen/cabal_macros.h -optP-I -optPinclude
2 changes: 2 additions & 0 deletions http-api-data.cabal
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@ stability: unstable
cabal-version: >= 1.10
build-type: Custom
extra-source-files:
include/overlapping-compat.h
test/*.hs
CHANGELOG.md
README.md
@@ -22,6 +23,7 @@ flag use-text-show

library
hs-source-dirs: src/
include-dirs: include/
build-depends: base >= 4.6 && < 4.10
, bytestring
, containers
8 changes: 8 additions & 0 deletions include/overlapping-compat.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#if __GLASGOW_HASKELL__ >= 710
#define OVERLAPPABLE_ {-# OVERLAPPABLE #-}
#define OVERLAPPING_ {-# OVERLAPPING #-}
#else
{-# LANGUAGE OverlappingInstances #-}
#define OVERLAPPABLE_
#define OVERLAPPING_
#endif
2 changes: 2 additions & 0 deletions src/Web/FormUrlEncoded.hs
Original file line number Diff line number Diff line change
@@ -25,9 +25,11 @@ module Web.FormUrlEncoded (
fromEntriesByKey,

lookupAll,
lookupMaybe,
lookupUnique,

parseAll,
parseMaybe,
parseUnique,
) where

88 changes: 77 additions & 11 deletions src/Web/Internal/FormUrlEncoded.hs
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
#include "overlapping-compat.h"
module Web.Internal.FormUrlEncoded where

#if __GLASGOW_HASKELL__ < 710
@@ -288,7 +289,25 @@ instance (GToForm t f) => GToForm t (M1 D x f) where
instance (GToForm t f) => GToForm t (M1 C x f) where
gToForm p opts (M1 a) = gToForm p opts a

instance (Selector s, ToHttpApiData c) => GToForm t (M1 S s (K1 i c)) where
instance OVERLAPPABLE_ (Selector s, ToHttpApiData c) => GToForm t (M1 S s (K1 i c)) where
gToForm _ opts (M1 (K1 c)) = fromList [(key, toQueryParam c)]
where
key = Text.pack $ fieldLabelModifier opts $ selName (Proxy3 :: Proxy3 s g p)

instance (Selector s, ToHttpApiData c) => GToForm t (M1 S s (K1 i (Maybe c))) where
gToForm _ opts (M1 (K1 c)) =
case c of
Nothing -> mempty
Just x -> fromList [(key, toQueryParam x)]
where
key = Text.pack $ fieldLabelModifier opts $ selName (Proxy3 :: Proxy3 s g p)

instance (Selector s, ToHttpApiData c) => GToForm t (M1 S s (K1 i [c])) where
gToForm _ opts (M1 (K1 cs)) = fromList (map (\c -> (key, toQueryParam c)) cs)
where
key = Text.pack $ fieldLabelModifier opts $ selName (Proxy3 :: Proxy3 s g p)

instance OVERLAPPING_ (Selector s) => GToForm t (M1 S s (K1 i String)) where
gToForm _ opts (M1 (K1 c)) = fromList [(key, toQueryParam c)]
where
key = Text.pack $ fieldLabelModifier opts $ selName (Proxy3 :: Proxy3 s g p)
@@ -379,17 +398,32 @@ class GFromForm t (f :: * -> *) where
instance (GFromForm t f, GFromForm t g) => GFromForm t (f :*: g) where
gFromForm p opts f = (:*:) <$> gFromForm p opts f <*> gFromForm p opts f

instance (Selector s, FromHttpApiData f) => GFromForm t (M1 S s (K1 i f)) where
gFromForm _ opts form = M1 . K1 <$> parseUnique key form
where
key = Text.pack $ fieldLabelModifier opts $ selName (Proxy3 :: Proxy3 s g p)

instance GFromForm t f => GFromForm t (M1 D x f) where
gFromForm p opts f = M1 <$> gFromForm p opts f

instance GFromForm t f => GFromForm t (M1 C x f) where
gFromForm p opts f = M1 <$> gFromForm p opts f

instance OVERLAPPABLE_ (Selector s, FromHttpApiData c) => GFromForm t (M1 S s (K1 i c)) where
gFromForm _ opts form = M1 . K1 <$> parseUnique key form
where
key = Text.pack $ fieldLabelModifier opts $ selName (Proxy3 :: Proxy3 s g p)

instance (Selector s, FromHttpApiData c) => GFromForm t (M1 S s (K1 i (Maybe c))) where
gFromForm _ opts form = M1 . K1 <$> parseMaybe key form
where
key = Text.pack $ fieldLabelModifier opts $ selName (Proxy3 :: Proxy3 s g p)

instance (Selector s, FromHttpApiData c) => GFromForm t (M1 S s (K1 i [c])) where
gFromForm _ opts form = M1 . K1 <$> parseAll key form
where
key = Text.pack $ fieldLabelModifier opts $ selName (Proxy3 :: Proxy3 s g p)

instance OVERLAPPING_ (Selector s) => GFromForm t (M1 S s (K1 i String)) where
gFromForm _ opts form = M1 . K1 <$> parseUnique key form
where
key = Text.pack $ fieldLabelModifier opts $ selName (Proxy3 :: Proxy3 s g p)

instance NotSupported FromForm t "is a sum type" => GFromForm t (f :+: g) where gFromForm = error "impossible"

-- | Encode a 'Form' to an @application/x-www-form-urlencoded@ 'BSL.ByteString'.
@@ -505,6 +539,22 @@ urlEncodeAsForm = urlEncodeForm . toForm
lookupAll :: Text -> Form -> [Text]
lookupAll key = F.concat . HashMap.lookup key . unForm

-- | Lookup an optional value for a key.
-- Fail if there is more than one value.
--
-- >>> lookupMaybe "name" []
-- Right Nothing
-- >>> lookupMaybe "name" [("name", "Oleg")]
-- Right (Just "Oleg")
-- >>> lookupMaybe "name" [("name", "Oleg"), ("name", "David")]
-- Left "Duplicate key \"name\""
lookupMaybe :: Text -> Form -> Either Text (Maybe Text)
lookupMaybe key form =
case lookupAll key form of
[] -> pure Nothing
[v] -> pure (Just v)
_ -> Left $ "Duplicate key " <> Text.pack (show key)

-- | Lookup a unique value for a key.
-- Fail if there is zero or more than one value.
--
@@ -515,11 +565,11 @@ lookupAll key = F.concat . HashMap.lookup key . unForm
-- >>> lookupUnique "name" [("name", "Oleg"), ("name", "David")]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the examples!

-- Left "Duplicate key \"name\""
lookupUnique :: Text -> Form -> Either Text Text
lookupUnique key form =
case lookupAll key form of
[v] -> pure v
[] -> Left $ "Could not find key " <> Text.pack (show key)
_ -> Left $ "Duplicate key " <> Text.pack (show key)
lookupUnique key form = do
mv <- lookupMaybe key form
case mv of
Just v -> pure v
Nothing -> Left $ "Could not find key " <> Text.pack (show key)

-- | Lookup all values for a given key in a 'Form' and parse them with 'parseQueryParams'.
--
@@ -534,6 +584,22 @@ lookupUnique key form =
parseAll :: FromHttpApiData v => Text -> Form -> Either Text [v]
parseAll key = parseQueryParams . lookupAll key

-- | Lookup an optional value for a given key and parse it with 'parseQueryParam'.
-- Fail if there is more than one value for the key.
--
-- >>> parseMaybe "age" [] :: Either Text (Maybe Word8)
-- Right Nothing
-- >>> parseMaybe "age" [("age", "12"), ("age", "25")] :: Either Text (Maybe Word8)
-- Left "Duplicate key \"age\""
-- >>> parseMaybe "age" [("age", "seven")] :: Either Text (Maybe Word8)
-- Left "could not parse: `seven' (input does not start with a digit)"
-- >>> parseMaybe "age" [("age", "777")] :: Either Text (Maybe Word8)
-- Left "out of bounds: `777' (should be between 0 and 255)"
-- >>> parseMaybe "age" [("age", "7")] :: Either Text (Maybe Word8)
-- Right (Just 7)
parseMaybe :: FromHttpApiData v => Text -> Form -> Either Text (Maybe v)
parseMaybe key = parseQueryParams <=< lookupMaybe key

-- | Lookup a unique value for a given key and parse it with 'parseQueryParam'.
-- Fail if there is zero or more than one value for the key.
--
1 change: 1 addition & 0 deletions test/DocTest.hs
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@ import Test.DocTest
main :: IO ()
main = getSources >>= \sources -> doctest $
"-isrc"
: "-Iinclude"
: ("-i" ++ autogen_dir)
: "-optP-include"
: ("-optP" ++ autogen_dir ++ "/cabal_macros.h")