http-api-data-0.3: Converting to/from HTTP API data like URL pieces, headers and query parameters.

Safe HaskellNone
LanguageHaskell2010

Web.Internal.FormUrlEncoded

Synopsis

Documentation

>>> :set -XDeriveGeneric
>>> :set -XOverloadedLists
>>> :set -XOverloadedStrings
>>> :set -XFlexibleContexts
>>> :set -XScopedTypeVariables
>>> :set -XTypeFamilies
>>> import Data.Char (toLower)
>>> data Person = Person { name :: String, age :: Int } deriving (Show, Generic)
>>> instance ToForm Person
>>> instance FromForm Person
>>> data Post = Post { title :: String, subtitle :: Maybe String, comments :: [String]} deriving (Generic, Show)
>>> instance ToForm Post
>>> instance FromForm Post
>>> data Project = Project { projectName :: String, projectSize :: Int } deriving (Generic, Show)
>>> let myOptions = FormOptions { fieldLabelModifier = map toLower . drop (length ("project" :: String)) }
>>> instance ToForm Project where toForm = genericToForm myOptions
>>> instance FromForm Project where fromForm = genericFromForm myOptions

class ToFormKey k where Source #

Typeclass for types that can be used as keys in a Form-like container (like Map).

Minimal complete definition

toFormKey

Methods

toFormKey :: k -> Text Source #

Render a key for a Form.

Instances

ToFormKey Bool Source # 

Methods

toFormKey :: Bool -> Text Source #

ToFormKey Char Source # 

Methods

toFormKey :: Char -> Text Source #

ToFormKey Double Source # 
ToFormKey Float Source # 

Methods

toFormKey :: Float -> Text Source #

ToFormKey Int Source # 

Methods

toFormKey :: Int -> Text Source #

ToFormKey Int8 Source # 

Methods

toFormKey :: Int8 -> Text Source #

ToFormKey Int16 Source # 

Methods

toFormKey :: Int16 -> Text Source #

ToFormKey Int32 Source # 

Methods

toFormKey :: Int32 -> Text Source #

ToFormKey Int64 Source # 

Methods

toFormKey :: Int64 -> Text Source #

ToFormKey Integer Source # 
ToFormKey Ordering Source # 
ToFormKey Word Source # 

Methods

toFormKey :: Word -> Text Source #

ToFormKey Word8 Source # 

Methods

toFormKey :: Word8 -> Text Source #

ToFormKey Word16 Source # 
ToFormKey Word32 Source # 
ToFormKey Word64 Source # 
ToFormKey () Source # 

Methods

toFormKey :: () -> Text Source #

ToFormKey Text Source # 

Methods

toFormKey :: Text -> Text Source #

ToFormKey Text Source # 

Methods

toFormKey :: Text -> Text Source #

ToFormKey String Source # 
ToFormKey Natural Source # 
ToFormKey Void Source # 

Methods

toFormKey :: Void -> Text Source #

ToFormKey All Source # 

Methods

toFormKey :: All -> Text Source #

ToFormKey Any Source # 

Methods

toFormKey :: Any -> Text Source #

ToFormKey LocalTime Source # 
ToFormKey ZonedTime Source # 
ToFormKey UTCTime Source # 
ToFormKey NominalDiffTime Source # 
ToFormKey Day Source # 

Methods

toFormKey :: Day -> Text Source #

ToFormKey a => ToFormKey (Dual a) Source # 

Methods

toFormKey :: Dual a -> Text Source #

ToFormKey a => ToFormKey (Sum a) Source # 

Methods

toFormKey :: Sum a -> Text Source #

ToFormKey a => ToFormKey (Product a) Source # 

Methods

toFormKey :: Product a -> Text Source #

class FromFormKey k where Source #

Typeclass for types that can be parsed from keys of a Form. This is the reverse of ToFormKey.

Minimal complete definition

parseFormKey

Methods

parseFormKey :: Text -> Either Text k Source #

Parse a key of a Form.

Instances

FromFormKey Bool Source # 
FromFormKey Char Source # 
FromFormKey Double Source # 
FromFormKey Float Source # 
FromFormKey Int Source # 
FromFormKey Int8 Source # 
FromFormKey Int16 Source # 
FromFormKey Int32 Source # 
FromFormKey Int64 Source # 
FromFormKey Integer Source # 
FromFormKey Ordering Source # 
FromFormKey Word Source # 
FromFormKey Word8 Source # 
FromFormKey Word16 Source # 
FromFormKey Word32 Source # 
FromFormKey Word64 Source # 
FromFormKey () Source # 
FromFormKey Text Source # 
FromFormKey Text Source # 
FromFormKey String Source # 
FromFormKey Natural Source # 
FromFormKey Void Source # 
FromFormKey All Source # 
FromFormKey Any Source # 
FromFormKey LocalTime Source # 
FromFormKey ZonedTime Source # 
FromFormKey UTCTime Source # 
FromFormKey NominalDiffTime Source # 
FromFormKey Day Source # 
FromFormKey a => FromFormKey (Dual a) Source # 
FromFormKey a => FromFormKey (Sum a) Source # 
FromFormKey a => FromFormKey (Product a) Source # 

newtype Form Source #

The contents of a form, not yet URL-encoded.

Form can be URL-encoded with urlEncodeForm and URL-decoded with urlDecodeForm.

Constructors

Form 

Fields

Instances

IsList Form Source # 

Associated Types

type Item Form :: * #

Methods

fromList :: [Item Form] -> Form #

fromListN :: Int -> [Item Form] -> Form #

toList :: Form -> [Item Form] #

Eq Form Source # 

Methods

(==) :: Form -> Form -> Bool #

(/=) :: Form -> Form -> Bool #

Read Form Source # 
Show Form Source # 

Methods

showsPrec :: Int -> Form -> ShowS #

show :: Form -> String #

showList :: [Form] -> ShowS #

Generic Form Source # 

Associated Types

type Rep Form :: * -> * #

Methods

from :: Form -> Rep Form x #

to :: Rep Form x -> Form #

Monoid Form Source # 

Methods

mempty :: Form #

mappend :: Form -> Form -> Form #

mconcat :: [Form] -> Form #

FromForm Form Source # 
ToForm Form Source # 

Methods

toForm :: Form -> Form Source #

type Rep Form Source # 
type Rep Form = D1 (MetaData "Form" "Web.Internal.FormUrlEncoded" "http-api-data-0.3-9EXnbKgXII42BpE3MY7DOo" True) (C1 (MetaCons "Form" PrefixI True) (S1 (MetaSel (Just Symbol "unForm") NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 (HashMap Text [Text]))))
type Item Form Source # 
type Item Form = (Text, Text)

class ToForm a where Source #

Convert a value into Form.

An example type and instance:

{-# LANGUAGE OverloadedLists #-}

data Person = Person
  { name :: String
  , age  :: Int }

instance ToForm Person where
  toForm person =
    [ ("name", toQueryParam (name person))
    , ("age", toQueryParam (age person)) ]

Instead of manually writing ToForm instances you can use a default generic implementation of toForm.

To do that, simply add deriving Generic clause to your datatype and declare a ToForm instance for your datatype without giving definition for toForm.

For instance, the previous example can be simplified into this:

data Person = Person
  { name :: String
  , age  :: Int
  } deriving (Generic)

instance ToForm Person

The default implementation of toForm is genericToForm.

Methods

toForm :: a -> Form Source #

Convert a value into Form.

toForm :: (Generic a, GToForm a (Rep a)) => a -> Form Source #

Convert a value into Form.

Instances

ToForm Form Source # 

Methods

toForm :: Form -> Form Source #

(ToFormKey k, ToHttpApiData v) => ToForm [(k, v)] Source # 

Methods

toForm :: [(k, v)] -> Form Source #

ToHttpApiData v => ToForm (IntMap [v]) Source # 

Methods

toForm :: IntMap [v] -> Form Source #

(ToFormKey k, ToHttpApiData v) => ToForm (Map k [v]) Source # 

Methods

toForm :: Map k [v] -> Form Source #

(ToFormKey k, ToHttpApiData v) => ToForm (HashMap k [v]) Source # 

Methods

toForm :: HashMap k [v] -> Form Source #

fromEntriesByKey :: (ToFormKey k, ToHttpApiData v) => [(k, [v])] -> Form Source #

Convert a list of entries groupped by key into a Form.

>>> fromEntriesByKey [("name",["Nick"]),("color",["red","blue"])]
fromList [("color","red"),("color","blue"),("name","Nick")]

data Proxy3 a b c Source #

Constructors

Proxy3 

type family NotSupported (cls :: k1) (a :: k2) (reason :: Symbol) :: Constraint where ... Source #

Equations

NotSupported cls a reason = TypeError (((((((Text "Cannot derive a Generic-based " :<>: ShowType cls) :<>: Text " instance for ") :<>: ShowType a) :<>: Text ".") :$$: (((ShowType a :<>: Text " ") :<>: Text reason) :<>: Text ",")) :$$: ((Text "but Generic-based " :<>: ShowType cls) :<>: Text " instances can be derived only for records")) :$$: Text "(i.e. product types with named fields).") 

genericToForm :: forall a. (Generic a, GToForm a (Rep a)) => FormOptions -> a -> Form Source #

A Generic-based implementation of toForm. This is used as a default implementation in ToForm.

Note that this only works for records (i.e. product data types with named fields):

data Person = Person
  { name :: String
  , age  :: Int
  } deriving (Generic)

In this implementation each field's value gets encoded using toQueryParam. Two field types are exceptions:

  • for values of type Maybe a an entry is added to the Form only when it is Just x and the encoded value is toQueryParam x; Nothing values are omitted from the Form;
  • for values of type [a] (except [Char]) an entry is added for every item in the list; if the list is empty no entries are added to the Form;

Here's an example:

data Post = Post
  { title    :: String
  , subtitle :: Maybe String
  , comments :: [String]
  } deriving (Generic, Show)

instance ToForm Post
>>> urlEncodeAsForm Post { title = "Test", subtitle = Nothing, comments = ["Nice post!", "+1"] }
"comments=Nice%20post%21&comments=%2B1&title=Test"

class GToForm t f where Source #

Minimal complete definition

gToForm

Methods

gToForm :: Proxy t -> FormOptions -> f x -> Form Source #

Instances

NotSupported k (* -> Constraint) ToForm t "is a sum type" => GToForm k t ((:+:) f g) Source # 

Methods

gToForm :: Proxy t (f :+: g) -> FormOptions -> f x -> Form Source #

(GToForm k t f, GToForm k t g) => GToForm k t ((:*:) f g) Source # 

Methods

gToForm :: Proxy t (f :*: g) -> FormOptions -> f x -> Form Source #

Selector Meta s => GToForm k t (M1 S s (K1 i String)) Source # 

Methods

gToForm :: Proxy t (M1 S s (K1 i String)) -> FormOptions -> f x -> Form Source #

(Selector Meta s, ToHttpApiData c) => GToForm k t (M1 S s (K1 i [c])) Source # 

Methods

gToForm :: Proxy t (M1 S s (K1 i [c])) -> FormOptions -> f x -> Form Source #

(Selector Meta s, ToHttpApiData c) => GToForm k t (M1 S s (K1 i (Maybe c))) Source # 

Methods

gToForm :: Proxy t (M1 S s (K1 i (Maybe c))) -> FormOptions -> f x -> Form Source #

(Selector Meta s, ToHttpApiData c) => GToForm k t (M1 S s (K1 i c)) Source # 

Methods

gToForm :: Proxy t (M1 S s (K1 i c)) -> FormOptions -> f x -> Form Source #

GToForm k t f => GToForm k t (M1 C x f) Source # 

Methods

gToForm :: Proxy t (M1 C x f) -> FormOptions -> f x -> Form Source #

GToForm k t f => GToForm k t (M1 D x f) Source # 

Methods

gToForm :: Proxy t (M1 D x f) -> FormOptions -> f x -> Form Source #

class FromForm a where Source #

Parse Form into a value.

An example type and instance:

data Person = Person
  { name :: String
  , age  :: Int }

instance FromForm Person where
  fromForm (Form m) = Person
    <$> parseUnique "name" m
    <*> parseUnique "age"  m

Instead of manually writing FromForm instances you can use a default generic implementation of fromForm.

To do that, simply add deriving Generic clause to your datatype and declare a FromForm instance for your datatype without giving definition for fromForm.

For instance, the previous example can be simplified into this:

data Person = Person
  { name :: String
  , age  :: Int
  } deriving (Generic)

instance FromForm Person

The default implementation of fromForm is genericFromForm. It only works for records and it will use parseQueryParam for each field's value.

Methods

fromForm :: Form -> Either Text a Source #

Parse Form into a value.

fromForm :: (Generic a, GFromForm a (Rep a)) => Form -> Either Text a Source #

Parse Form into a value.

Instances

toEntriesByKey :: (FromFormKey k, FromHttpApiData v) => Form -> Either Text [(k, [v])] Source #

Parse a Form into a list of entries groupped by key.

>>> toEntriesByKey [("name", "Nick"), ("color", "red"), ("color", "white")] :: Either Text [(Text, [Text])]
Right [("color",["red","white"]),("name",["Nick"])]

genericFromForm :: forall a. (Generic a, GFromForm a (Rep a)) => FormOptions -> Form -> Either Text a Source #

A Generic-based implementation of fromForm. This is used as a default implementation in FromForm.

Note that this only works for records (i.e. product data types with named fields):

data Person = Person
  { name :: String
  , age  :: Int
  } deriving (Generic)

In this implementation each field's value gets decoded using parseQueryParam. Two field types are exceptions:

  • for values of type Maybe a an entry is parsed if present in the Form and the is decoded with parseQueryParam; if no entry is present result is Nothing;
  • for values of type [a] (except [Char]) all entries are parsed to produce a list of parsed values;

Here's an example:

data Post = Post
  { title    :: String
  , subtitle :: Maybe String
  , comments :: [String]
  } deriving (Generic, Show)

instance FromForm Post
>>> urlDecodeAsForm "comments=Nice%20post%21&comments=%2B1&title=Test" :: Either Text Post
Right (Post {title = "Test", subtitle = Nothing, comments = ["Nice post!","+1"]})

class GFromForm t f where Source #

Minimal complete definition

gFromForm

Methods

gFromForm :: Proxy t -> FormOptions -> Form -> Either Text (f x) Source #

Instances

NotSupported k (* -> Constraint) FromForm t "is a sum type" => GFromForm k t ((:+:) f g) Source # 

Methods

gFromForm :: Proxy t (f :+: g) -> FormOptions -> Form -> Either Text (f x) Source #

(GFromForm k t f, GFromForm k t g) => GFromForm k t ((:*:) f g) Source # 

Methods

gFromForm :: Proxy t (f :*: g) -> FormOptions -> Form -> Either Text (f x) Source #

Selector Meta s => GFromForm k t (M1 S s (K1 i String)) Source # 

Methods

gFromForm :: Proxy t (M1 S s (K1 i String)) -> FormOptions -> Form -> Either Text (f x) Source #

(Selector Meta s, FromHttpApiData c) => GFromForm k t (M1 S s (K1 i [c])) Source # 

Methods

gFromForm :: Proxy t (M1 S s (K1 i [c])) -> FormOptions -> Form -> Either Text (f x) Source #

(Selector Meta s, FromHttpApiData c) => GFromForm k t (M1 S s (K1 i (Maybe c))) Source # 

Methods

gFromForm :: Proxy t (M1 S s (K1 i (Maybe c))) -> FormOptions -> Form -> Either Text (f x) Source #

(Selector Meta s, FromHttpApiData c) => GFromForm k t (M1 S s (K1 i c)) Source # 

Methods

gFromForm :: Proxy t (M1 S s (K1 i c)) -> FormOptions -> Form -> Either Text (f x) Source #

GFromForm k t f => GFromForm k t (M1 C x f) Source # 

Methods

gFromForm :: Proxy t (M1 C x f) -> FormOptions -> Form -> Either Text (f x) Source #

GFromForm k t f => GFromForm k t (M1 D x f) Source # 

Methods

gFromForm :: Proxy t (M1 D x f) -> FormOptions -> Form -> Either Text (f x) Source #

urlEncodeForm :: Form -> ByteString Source #

Encode a Form to an application/x-www-form-urlencoded ByteString.

Key-value pairs get encoded to key=value and separated by &:

>>> urlEncodeForm [("name", "Julian"), ("lastname", "Arni")]
"lastname=Arni&name=Julian"

Keys with empty values get encoded to just key (without the = sign):

>>> urlEncodeForm [("is_test", "")]
"is_test"

Empty keys are allowed too:

>>> urlEncodeForm [("", "foobar")]
"=foobar"

However, if not key and value are empty, the key-value pair is ignored. (This prevents urlDecodeForm . urlEncodeForm from being a true isomorphism).

>>> urlEncodeForm [("", "")]
""

Everything is escaped with escapeURIString isUnreserved:

>>> urlEncodeForm [("fullname", "Andres Löh")]
"fullname=Andres%20L%C3%B6h"

urlDecodeForm :: ByteString -> Either Text Form Source #

Decode an application/x-www-form-urlencoded ByteString to a Form.

Key-value pairs get decoded normally:

>>> urlDecodeForm "name=Greg&lastname=Weber"
Right (fromList [("lastname","Weber"),("name","Greg")])

Keys with no values get decoded to pairs with empty values.

>>> urlDecodeForm "is_test"
Right (fromList [("is_test","")])

Empty keys are allowed:

>>> urlDecodeForm "=foobar"
Right (fromList [("","foobar")])

The empty string gets decoded into an empty Form:

>>> urlDecodeForm ""
Right (fromList [])

Everything is un-escaped with unEscapeString:

>>> urlDecodeForm "fullname=Andres%20L%C3%B6h"
Right (fromList [("fullname","Andres L\246h")])

Improperly formed strings result in an error:

>>> urlDecodeForm "this=has=too=many=equals"
Left "not a valid pair: this=has=too=many=equals"

urlDecodeAsForm :: FromForm a => ByteString -> Either Text a Source #

This is a convenience function for decoding a application/x-www-form-urlencoded ByteString directly to a datatype that has an instance of FromForm.

This is effectively fromForm <=< urlDecodeForm.

>>> urlDecodeAsForm "name=Dennis&age=22" :: Either Text Person
Right (Person {name = "Dennis", age = 22})

urlEncodeAsForm :: ToForm a => a -> ByteString Source #

This is a convenience function for encoding a datatype that has instance of ToForm directly to a application/x-www-form-urlencoded ByteString.

This is effectively urlEncodeForm . toForm.

>>> urlEncodeAsForm Person {name = "Dennis", age = 22}
"age=22&name=Dennis"

lookupAll :: Text -> Form -> [Text] Source #

Find all values corresponding to a given key in a Form.

>>> lookupAll "name" []
[]
>>> lookupAll "name" [("name", "Oleg")]
["Oleg"]
>>> lookupAll "name" [("name", "Oleg"), ("name", "David")]
["Oleg","David"]

lookupMaybe :: Text -> Form -> Either Text (Maybe Text) Source #

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\""

lookupUnique :: Text -> Form -> Either Text Text Source #

Lookup a unique value for a key. Fail if there is zero or more than one value.

>>> lookupUnique "name" []
Left "Could not find key \"name\""
>>> lookupUnique "name" [("name", "Oleg")]
Right "Oleg"
>>> lookupUnique "name" [("name", "Oleg"), ("name", "David")]
Left "Duplicate key \"name\""

parseAll :: FromHttpApiData v => Text -> Form -> Either Text [v] Source #

Lookup all values for a given key in a Form and parse them with parseQueryParams.

>>> parseAll "age" [] :: Either Text [Word8]
Right []
>>> parseAll "age" [("age", "8"), ("age", "seven")] :: Either Text [Word8]
Left "could not parse: `seven' (input does not start with a digit)"
>>> parseAll "age" [("age", "8"), ("age", "777")] :: Either Text [Word8]
Left "out of bounds: `777' (should be between 0 and 255)"
>>> parseAll "age" [("age", "12"), ("age", "25")] :: Either Text [Word8]
Right [12,25]

parseMaybe :: FromHttpApiData v => Text -> Form -> Either Text (Maybe v) Source #

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)

parseUnique :: FromHttpApiData v => Text -> Form -> Either Text v Source #

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.

>>> parseUnique "age" [] :: Either Text Word8
Left "Could not find key \"age\""
>>> parseUnique "age" [("age", "12"), ("age", "25")] :: Either Text Word8
Left "Duplicate key \"age\""
>>> parseUnique "age" [("age", "seven")] :: Either Text Word8
Left "could not parse: `seven' (input does not start with a digit)"
>>> parseUnique "age" [("age", "777")] :: Either Text Word8
Left "out of bounds: `777' (should be between 0 and 255)"
>>> parseUnique "age" [("age", "7")] :: Either Text Word8
Right 7

data FormOptions Source #

Generic-based deriving options for ToForm and FromForm.

A common use case for non-default FormOptions is to strip a prefix off of field labels:

data Project = Project
  { projectName :: String
  , projectSize :: Int
  } deriving (Generic, Show)

myOptions :: FormOptions
myOptions = FormOptions
 { fieldLabelModifier = map toLower . drop (length "project") }

instance ToForm Project where
  toForm = genericToForm myOptions

instance FromForm Project where
  fromForm = genericFromForm myOptions
>>> urlEncodeAsForm Project { projectName = "http-api-data", projectSize = 172 }
"size=172&name=http-api-data"
>>> urlDecodeAsForm "name=http-api-data&size=172" :: Either Text Project
Right (Project {projectName = "http-api-data", projectSize = 172})

Constructors

FormOptions 

Fields