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

Safe HaskellNone
LanguageHaskell2010

Web.FormUrlEncoded

Contents

Description

Convert Haskell values to and from application/xxx-form-urlencoded format.

Synopsis

Classes

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 #

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 f = Person
    <$> parseUnique "name" f
    <*> parseUnique "age"  f

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

FromForm Form Source # 
(FromFormKey k, FromHttpApiData v) => FromForm [(k, v)] Source #

_NOTE:_ this conversion is unstable and may result in different key order (but not values).

Methods

fromForm :: Form -> Either Text [(k, v)] Source #

FromHttpApiData v => FromForm (IntMap [v]) Source # 

Methods

fromForm :: Form -> Either Text (IntMap [v]) Source #

(Ord k, FromFormKey k, FromHttpApiData v) => FromForm (Map k [v]) Source # 

Methods

fromForm :: Form -> Either Text (Map k [v]) Source #

(Eq k, Hashable k, FromFormKey k, FromHttpApiData v) => FromForm (HashMap k [v]) Source # 

Methods

fromForm :: Form -> Either Text (HashMap k [v]) Source #

Keys for Form entries

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 Natural 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 Void Source # 

Methods

toFormKey :: Void -> Text Source #

ToFormKey All Source # 

Methods

toFormKey :: All -> Text Source #

ToFormKey Any Source # 

Methods

toFormKey :: Any -> Text Source #

ToFormKey ZonedTime Source # 
ToFormKey LocalTime 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 Natural 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 Void Source # 
FromFormKey All Source # 
FromFormKey Any Source # 
FromFormKey ZonedTime Source # 
FromFormKey LocalTime 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 # 

Form type

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 #

_NOTE:_ toList is unstable and may result in different key order (but not values). For a stable conversion use toListStable.

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 #

Semigroup Form Source # 

Methods

(<>) :: Form -> Form -> Form #

sconcat :: NonEmpty Form -> Form #

stimes :: Integral b => b -> Form -> 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.8-9ObGBMZb1uyCnhL9qiCBWq" 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)

Encoding and decoding Forms

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.

_NOTE:_ this encoding is unstable and may result in different key order (but not values). For a stable encoding see urlEncodeAsFormStable.

urlEncodeAsFormStable :: 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 urlEncodeFormStable . toForm.

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

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})

urlEncodeForm :: Form -> ByteString Source #

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

_NOTE:_ this encoding is unstable and may result in different key order (but not values). For a stable encoding see urlEncodeFormStable.

urlEncodeFormStable :: Form -> ByteString Source #

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

For an unstable (but faster) encoding see urlEncodeForm.

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

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

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

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

Empty keys are allowed too:

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

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

>>> urlEncodeFormStable [("", "")]
""

Everything is escaped with escapeURIString isUnreserved:

>>> urlEncodeFormStable [("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"

Generics

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
>>> urlEncodeAsFormStable Post { title = "Test", subtitle = Nothing, comments = ["Nice post!", "+1"] }
"comments=Nice%20post%21&comments=%2B1&title=Test"

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"]})

Encoding options

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
>>> urlEncodeAsFormStable Project { projectName = "http-api-data", projectSize = 172 }
"name=http-api-data&size=172"
>>> urlDecodeAsForm "name=http-api-data&size=172" :: Either Text Project
Right (Project {projectName = "http-api-data", projectSize = 172})

Constructors

FormOptions 

Fields

Helpers

toListStable :: Form -> [(Text, Text)] Source #

A stable version of toList.

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

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

_NOTE:_ this conversion is unstable and may result in different key order (but not values). For a stable encoding see toEntriesByKeyStable.

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

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

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

For an unstable (but faster) conversion see toEntriesByKey.

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")]

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

urlEncodeParams :: [(Text, Text)] -> ByteString Source #

Encode a list of key-value pairs to an application/x-www-form-urlencoded ByteString.

See also urlEncodeFormStable.

urlDecodeParams :: ByteString -> Either Text [(Text, Text)] Source #

Decode an application/x-www-form-urlencoded ByteString to a list of key-value pairs.

See also urlDecodeForm.