From 7b60fb68d5353b634ed4e93bde1f9642d54093d8 Mon Sep 17 00:00:00 2001 From: Nickolay Kudasov Date: Wed, 7 Oct 2015 18:42:56 +0300 Subject: [PATCH 1/7] Remove Servant.Common.Text --- servant/servant.cabal | 1 - servant/src/Servant/Common/Text.hs | 149 ----------------------------- 2 files changed, 150 deletions(-) delete mode 100644 servant/src/Servant/Common/Text.hs diff --git a/servant/servant.cabal b/servant/servant.cabal index 301a46d8..1787956c 100644 --- a/servant/servant.cabal +++ b/servant/servant.cabal @@ -44,7 +44,6 @@ library Servant.API.ResponseHeaders Servant.API.Sub Servant.API.Vault - Servant.Common.Text Servant.Utils.Links build-depends: base >=4.7 && <5 diff --git a/servant/src/Servant/Common/Text.hs b/servant/src/Servant/Common/Text.hs deleted file mode 100644 index 6e4a4690..00000000 --- a/servant/src/Servant/Common/Text.hs +++ /dev/null @@ -1,149 +0,0 @@ -{-# LANGUAGE CPP #-} -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE TypeSynonymInstances #-} -module Servant.Common.Text - ( FromText(..) - , ToText(..) - ) where - -#if !MIN_VERSION_base(4,8,0) -import Control.Applicative ((<$>)) -#endif -import Data.Int (Int16, Int32, Int64, Int8) -import Data.String.Conversions (cs) -import Data.Text (Text) -import Data.Text.Read (Reader, decimal, rational, signed) -import Data.Word (Word16, Word32, Word64, Word8 -#if !MIN_VERSION_base(4,8,0) - , Word -#endif - ) - --- | For getting values from url captures and query string parameters --- Instances should obey: --- > fromText (toText a) == Just a -class FromText a where - fromText :: Text -> Maybe a - --- | For putting values in paths and query string parameters --- Instances should obey: --- > fromText (toText a) == Just a -class ToText a where - toText :: a -> Text - -instance FromText Text where - fromText = Just - -instance ToText Text where - toText = id - -instance FromText String where - fromText = Just . cs - -instance ToText String where - toText = cs - --- | --- >>> fromText ("true"::Text) :: Maybe Bool --- Just True --- >>> fromText ("false"::Text) :: Maybe Bool --- Just False --- >>> fromText ("anything else"::Text) :: Maybe Bool --- Nothing -instance FromText Bool where - fromText "true" = Just True - fromText "false" = Just False - fromText _ = Nothing - --- | --- >>> toText True --- "true" --- >>> toText False --- "false" -instance ToText Bool where - toText True = "true" - toText False = "false" - -instance FromText Int where - fromText = runReader (signed decimal) - -instance ToText Int where - toText = cs . show - -instance FromText Int8 where - fromText = runReader (signed decimal) - -instance ToText Int8 where - toText = cs . show - -instance FromText Int16 where - fromText = runReader (signed decimal) - -instance ToText Int16 where - toText = cs . show - -instance FromText Int32 where - fromText = runReader (signed decimal) - -instance ToText Int32 where - toText = cs . show - -instance FromText Int64 where - fromText = runReader (signed decimal) - -instance ToText Int64 where - toText = cs . show - -instance FromText Word where - fromText = runReader decimal - -instance ToText Word where - toText = cs . show - -instance FromText Word8 where - fromText = runReader decimal - -instance ToText Word8 where - toText = cs . show - -instance FromText Word16 where - fromText = runReader decimal - -instance ToText Word16 where - toText = cs . show - -instance FromText Word32 where - fromText = runReader decimal - -instance ToText Word32 where - toText = cs . show - -instance FromText Word64 where - fromText = runReader decimal - -instance ToText Word64 where - toText = cs . show - -instance FromText Integer where - fromText = runReader (signed decimal) - -instance ToText Integer where - toText = cs . show - -instance FromText Double where - fromText x = fromRational <$> runReader rational x - -instance ToText Double where - toText = cs . show - -instance FromText Float where - -- Double is more practically accurate due to weird rounding when using - -- rational. We convert to double and then convert to Float. - fromText x = fromRational <$> runReader rational x - -instance ToText Float where - toText = cs . show - -runReader :: Reader a -> Text -> Maybe a -runReader reader t = either (const Nothing) (Just . fst) $ reader t From 40d2c688975cd638aa05fa6abc4736adbe04b8ef Mon Sep 17 00:00:00 2001 From: Nickolay Kudasov Date: Thu, 8 Oct 2015 00:38:47 +0300 Subject: [PATCH 2/7] Use http-api-data package instead of ToText/FromText --- servant-client/servant-client.cabal | 1 + servant-client/src/Servant/Client.hs | 36 ++++----- servant-client/src/Servant/Common/Req.hs | 7 +- servant-mock/src/Servant/Mock.hs | 12 +-- servant-server/servant-server.cabal | 1 + servant-server/src/Servant.hs | 3 - servant-server/src/Servant/Server/Internal.hs | 48 ++++++------ servant/servant.cabal | 2 +- servant/src/Servant/API.hs | 8 +- servant/src/Servant/API/Header.hs | 1 - servant/src/Servant/API/ReqBody.hs | 1 - servant/src/Servant/API/Sub.hs | 1 - servant/src/Servant/Utils/Links.hs | 22 +++--- servant/test/Servant/Common/TextSpec.hs | 74 ------------------- stack-ghc-7.8.4.yaml | 1 + stack.yaml | 1 + 16 files changed, 73 insertions(+), 146 deletions(-) delete mode 100644 servant/test/Servant/Common/TextSpec.hs diff --git a/servant-client/servant-client.cabal b/servant-client/servant-client.cabal index 0d70cea6..d801b37d 100644 --- a/servant-client/servant-client.cabal +++ b/servant-client/servant-client.cabal @@ -34,6 +34,7 @@ library , attoparsec , bytestring , exceptions + , http-api-data , http-client , http-client-tls , http-media diff --git a/servant-client/src/Servant/Client.hs b/servant-client/src/Servant/Client.hs index 657fe5af..94e9bd5e 100644 --- a/servant-client/src/Servant/Client.hs +++ b/servant-client/src/Servant/Client.hs @@ -92,7 +92,7 @@ instance (HasClient a, HasClient b) => HasClient (a :<|> b) where -- of this value at the right place in the request path. -- -- You can control how values for this type are turned into --- text by specifying a 'ToText' instance for your type. +-- text by specifying a 'ToHttpApiData' instance for your type. -- -- Example: -- @@ -105,7 +105,7 @@ instance (HasClient a, HasClient b) => HasClient (a :<|> b) where -- > getBook = client myApi host manager -- > where host = BaseUrl Http "localhost" 8080 -- > -- then you can just use "getBook" to query that endpoint -instance (KnownSymbol capture, ToText a, HasClient sublayout) +instance (KnownSymbol capture, ToHttpApiData a, HasClient sublayout) => HasClient (Capture capture a :> sublayout) where type Client (Capture capture a :> sublayout) = @@ -117,7 +117,7 @@ instance (KnownSymbol capture, ToText a, HasClient sublayout) baseurl manager - where p = unpack (toText val) + where p = unpack (toUrlPiece val) -- | If you have a 'Delete' endpoint in your API, the client -- side querying function that is created when calling 'client' @@ -205,12 +205,12 @@ instance -- That function will take care of encoding this argument as Text -- in the request headers. -- --- All you need is for your type to have a 'ToText' instance. +-- All you need is for your type to have a 'ToHttpApiData' instance. -- -- Example: -- -- > newtype Referer = Referer { referrer :: Text } --- > deriving (Eq, Show, Generic, FromText, ToText) +-- > deriving (Eq, Show, Generic, FromText, ToHttpApiData) -- > -- > -- GET /view-my-referer -- > type MyApi = "view-my-referer" :> Header "Referer" Referer :> Get '[JSON] Referer @@ -223,7 +223,7 @@ instance -- > where host = BaseUrl Http "localhost" 8080 -- > -- then you can just use "viewRefer" to query that endpoint -- > -- specifying Nothing or e.g Just "http://haskell.org/" as arguments -instance (KnownSymbol sym, ToText a, HasClient sublayout) +instance (KnownSymbol sym, ToHttpApiData a, HasClient sublayout) => HasClient (Header sym a :> sublayout) where type Client (Header sym a :> sublayout) = @@ -368,7 +368,7 @@ instance -- of inserting a textual representation of this value in the query string. -- -- You can control how values for your type are turned into --- text by specifying a 'ToText' instance for your type. +-- text by specifying a 'ToHttpApiData' instance for your type. -- -- Example: -- @@ -383,7 +383,7 @@ instance -- > -- then you can just use "getBooksBy" to query that endpoint. -- > -- 'getBooksBy Nothing' for all books -- > -- 'getBooksBy (Just "Isaac Asimov")' to get all books by Isaac Asimov -instance (KnownSymbol sym, ToText a, HasClient sublayout) +instance (KnownSymbol sym, ToHttpApiData a, HasClient sublayout) => HasClient (QueryParam sym a :> sublayout) where type Client (QueryParam sym a :> sublayout) = @@ -401,7 +401,7 @@ instance (KnownSymbol sym, ToText a, HasClient sublayout) where pname = cs pname' pname' = symbolVal (Proxy :: Proxy sym) - mparamText = fmap toText mparam + mparamText = fmap toQueryParam mparam -- | If you use a 'QueryParams' in one of your endpoints in your API, -- the corresponding querying function will automatically take @@ -415,7 +415,7 @@ instance (KnownSymbol sym, ToText a, HasClient sublayout) -- under the same query string parameter name. -- -- You can control how values for your type are turned into --- text by specifying a 'ToText' instance for your type. +-- text by specifying a 'ToHttpApiData' instance for your type. -- -- Example: -- @@ -431,7 +431,7 @@ instance (KnownSymbol sym, ToText a, HasClient sublayout) -- > -- 'getBooksBy []' for all books -- > -- 'getBooksBy ["Isaac Asimov", "Robert A. Heinlein"]' -- > -- to get all books by Asimov and Heinlein -instance (KnownSymbol sym, ToText a, HasClient sublayout) +instance (KnownSymbol sym, ToHttpApiData a, HasClient sublayout) => HasClient (QueryParams sym a :> sublayout) where type Client (QueryParams sym a :> sublayout) = @@ -447,7 +447,7 @@ instance (KnownSymbol sym, ToText a, HasClient sublayout) where pname = cs pname' pname' = symbolVal (Proxy :: Proxy sym) - paramlist' = map (Just . toText) paramlist + paramlist' = map (Just . toQueryParam) paramlist -- | If you use a 'QueryFlag' in one of your endpoints in your API, -- the corresponding querying function will automatically take @@ -498,7 +498,7 @@ instance (KnownSymbol sym, HasClient sublayout) -- of inserting a textual representation of this value in the query string. -- -- You can control how values for your type are turned into --- text by specifying a 'ToText' instance for your type. +-- text by specifying a 'ToHttpApiData' instance for your type. -- -- Example: -- @@ -513,7 +513,7 @@ instance (KnownSymbol sym, HasClient sublayout) -- > -- then you can just use "getBooksBy" to query that endpoint. -- > -- 'getBooksBy Nothing' for all books -- > -- 'getBooksBy (Just "Isaac Asimov")' to get all books by Isaac Asimov -instance (KnownSymbol sym, ToText a, HasClient sublayout) +instance (KnownSymbol sym, ToHttpApiData a, HasClient sublayout) => HasClient (MatrixParam sym a :> sublayout) where type Client (MatrixParam sym a :> sublayout) = @@ -529,7 +529,7 @@ instance (KnownSymbol sym, ToText a, HasClient sublayout) baseurl manager where pname = symbolVal (Proxy :: Proxy sym) - mparamText = fmap (cs . toText) mparam + mparamText = fmap (cs . toQueryParam) mparam -- | If you use a 'MatrixParams' in one of your endpoints in your API, -- the corresponding querying function will automatically take an @@ -543,7 +543,7 @@ instance (KnownSymbol sym, ToText a, HasClient sublayout) -- same matrix string parameter name. -- -- You can control how values for your type are turned into text by --- specifying a 'ToText' instance for your type. +-- specifying a 'ToHttpApiData' instance for your type. -- -- Example: -- @@ -559,7 +559,7 @@ instance (KnownSymbol sym, ToText a, HasClient sublayout) -- > -- 'getBooksBy []' for all books -- > -- 'getBooksBy ["Isaac Asimov", "Robert A. Heinlein"]' -- > -- to get all books by Asimov and Heinlein -instance (KnownSymbol sym, ToText a, HasClient sublayout) +instance (KnownSymbol sym, ToHttpApiData a, HasClient sublayout) => HasClient (MatrixParams sym a :> sublayout) where type Client (MatrixParams sym a :> sublayout) = @@ -575,7 +575,7 @@ instance (KnownSymbol sym, ToText a, HasClient sublayout) where pname = cs pname' pname' = symbolVal (Proxy :: Proxy sym) - paramlist' = map (Just . toText) paramlist + paramlist' = map (Just . toQueryParam) paramlist -- | If you use a 'MatrixFlag' in one of your endpoints in your API, -- the corresponding querying function will automatically take an diff --git a/servant-client/src/Servant/Common/Req.hs b/servant-client/src/Servant/Common/Req.hs index e050c964..592043be 100644 --- a/servant-client/src/Servant/Common/Req.hs +++ b/servant-client/src/Servant/Common/Req.hs @@ -26,10 +26,11 @@ import qualified Network.HTTP.Types.Header as HTTP import Network.URI hiding (path) import Servant.API.ContentTypes import Servant.Common.BaseUrl -import Servant.Common.Text import qualified Network.HTTP.Client as Client +import Web.HttpApiData + data ServantError = FailureResponse { responseStatus :: Status @@ -86,9 +87,9 @@ appendToQueryString pname pvalue req = req { qs = qs req ++ [(pname, pvalue)] } -addHeader :: ToText a => String -> a -> Req -> Req +addHeader :: ToHttpApiData a => String -> a -> Req -> Req addHeader name val req = req { headers = headers req - ++ [(name, toText val)] + ++ [(name, decodeUtf8 (toHeader val))] } setRQBody :: ByteString -> MediaType -> Req -> Req diff --git a/servant-mock/src/Servant/Mock.hs b/servant-mock/src/Servant/Mock.hs index 1bd93a04..bbd24b0e 100644 --- a/servant-mock/src/Servant/Mock.hs +++ b/servant-mock/src/Servant/Mock.hs @@ -107,7 +107,7 @@ instance (HasMock a, HasMock b) => HasMock (a :<|> b) where instance (KnownSymbol path, HasMock rest) => HasMock (path :> rest) where mock _ = mock (Proxy :: Proxy rest) -instance (KnownSymbol s, FromText a, HasMock rest) => HasMock (Capture s a :> rest) where +instance (KnownSymbol s, FromHttpApiData a, HasMock rest) => HasMock (Capture s a :> rest) where mock _ = \_ -> mock (Proxy :: Proxy rest) instance (AllCTUnrender ctypes a, HasMock rest) => HasMock (ReqBody ctypes a :> rest) where @@ -125,29 +125,29 @@ instance HasMock rest => HasMock (Vault :> rest) where instance HasMock rest => HasMock (HttpVersion :> rest) where mock _ = \_ -> mock (Proxy :: Proxy rest) -instance (KnownSymbol s, FromText a, HasMock rest) +instance (KnownSymbol s, FromHttpApiData a, HasMock rest) => HasMock (QueryParam s a :> rest) where mock _ = \_ -> mock (Proxy :: Proxy rest) -instance (KnownSymbol s, FromText a, HasMock rest) +instance (KnownSymbol s, FromHttpApiData a, HasMock rest) => HasMock (QueryParams s a :> rest) where mock _ = \_ -> mock (Proxy :: Proxy rest) instance (KnownSymbol s, HasMock rest) => HasMock (QueryFlag s :> rest) where mock _ = \_ -> mock (Proxy :: Proxy rest) -instance (KnownSymbol s, FromText a, HasMock rest) +instance (KnownSymbol s, FromHttpApiData a, HasMock rest) => HasMock (MatrixParam s a :> rest) where mock _ = \_ -> mock (Proxy :: Proxy rest) -instance (KnownSymbol s, FromText a, HasMock rest) +instance (KnownSymbol s, FromHttpApiData a, HasMock rest) => HasMock (MatrixParams s a :> rest) where mock _ = \_ -> mock (Proxy :: Proxy rest) instance (KnownSymbol s, HasMock rest) => HasMock (MatrixFlag s :> rest) where mock _ = \_ -> mock (Proxy :: Proxy rest) -instance (KnownSymbol h, FromText a, HasMock rest) => HasMock (Header h a :> rest) where +instance (KnownSymbol h, FromHttpApiData a, HasMock rest) => HasMock (Header h a :> rest) where mock _ = \_ -> mock (Proxy :: Proxy rest) instance (Arbitrary a, AllCTRender ctypes a) => HasMock (Delete ctypes a) where diff --git a/servant-server/servant-server.cabal b/servant-server/servant-server.cabal index b44e754b..5f9d8838 100644 --- a/servant-server/servant-server.cabal +++ b/servant-server/servant-server.cabal @@ -48,6 +48,7 @@ library , attoparsec >= 0.12 && < 0.14 , bytestring >= 0.10 && < 0.11 , containers >= 0.5 && < 0.6 + , http-api-data , http-types >= 0.8 && < 0.9 , network-uri >= 2.6 && < 2.7 , mtl >= 2 && < 3 diff --git a/servant-server/src/Servant.hs b/servant-server/src/Servant.hs index ae4679fe..96fd219f 100644 --- a/servant-server/src/Servant.hs +++ b/servant-server/src/Servant.hs @@ -5,8 +5,6 @@ module Servant ( module Servant.API, -- | For implementing servers for servant APIs. module Servant.Server, - -- | Using your types in request paths and query string parameters - module Servant.Common.Text, -- | Utilities on top of the servant core module Servant.Utils.Links, module Servant.Utils.StaticFiles, @@ -16,7 +14,6 @@ module Servant ( import Data.Proxy import Servant.API -import Servant.Common.Text import Servant.Server import Servant.Utils.Links import Servant.Utils.StaticFiles diff --git a/servant-server/src/Servant/Server/Internal.hs b/servant-server/src/Servant/Server/Internal.hs index a94ce045..d7175d55 100644 --- a/servant-server/src/Servant/Server/Internal.hs +++ b/servant-server/src/Servant/Server/Internal.hs @@ -53,13 +53,15 @@ import Servant.API.ContentTypes (AcceptHeader (..), AllCTUnrender (..)) import Servant.API.ResponseHeaders (Headers, getResponse, GetHeaders, getHeaders) -import Servant.Common.Text (FromText, fromText) import Servant.Server.Internal.PathInfo import Servant.Server.Internal.Router import Servant.Server.Internal.RoutingApplication import Servant.Server.Internal.ServantErr +import Web.HttpApiData (FromHttpApiData) +import Web.HttpApiData.Internal (parseUrlPieceMaybe, parseHeaderMaybe, parseQueryParamMaybe) + class HasServer layout where type ServerT layout (m :: * -> *) :: * @@ -89,8 +91,8 @@ instance (HasServer a, HasServer b) => HasServer (a :<|> b) where where pa = Proxy :: Proxy a pb = Proxy :: Proxy b -captured :: FromText a => proxy (Capture sym a) -> Text -> Maybe a -captured _ = fromText +captured :: FromHttpApiData a => proxy (Capture sym a) -> Text -> Maybe a +captured _ = parseUrlPieceMaybe -- | If you use 'Capture' in one of the endpoints for your API, -- this automatically requires your server-side handler to be a function @@ -99,7 +101,7 @@ captured _ = fromText -- it into a value of the type you specify. -- -- You can control how it'll be converted from 'Text' to your type --- by simply providing an instance of 'FromText' for your type. +-- by simply providing an instance of 'FromHttpApiData' for your type. -- -- Example: -- @@ -109,7 +111,7 @@ captured _ = fromText -- > server = getBook -- > where getBook :: Text -> ExceptT ServantErr IO Book -- > getBook isbn = ... -instance (KnownSymbol capture, FromText a, HasServer sublayout) +instance (KnownSymbol capture, FromHttpApiData a, HasServer sublayout) => HasServer (Capture capture a :> sublayout) where type ServerT (Capture capture a :> sublayout) m = @@ -282,12 +284,12 @@ instance -- This lets servant worry about extracting it from the request and turning -- it into a value of the type you specify. -- --- All it asks is for a 'FromText' instance. +-- All it asks is for a 'FromHttpApiData' instance. -- -- Example: -- -- > newtype Referer = Referer Text --- > deriving (Eq, Show, FromText, ToText) +-- > deriving (Eq, Show, FromHttpApiData, ToText) -- > -- > -- GET /view-my-referer -- > type MyApi = "view-my-referer" :> Header "Referer" Referer :> Get '[JSON] Referer @@ -296,14 +298,14 @@ instance -- > server = viewReferer -- > where viewReferer :: Referer -> ExceptT ServantErr IO referer -- > viewReferer referer = return referer -instance (KnownSymbol sym, FromText a, HasServer sublayout) +instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout) => HasServer (Header sym a :> sublayout) where type ServerT (Header sym a :> sublayout) m = Maybe a -> ServerT sublayout m route Proxy subserver = WithRequest $ \ request -> - let mheader = fromText . decodeUtf8 =<< lookup str (requestHeaders request) + let mheader = parseHeaderMaybe =<< lookup str (requestHeaders request) in route (Proxy :: Proxy sublayout) (feedTo subserver mheader) where str = fromString $ symbolVal (Proxy :: Proxy sym) @@ -451,7 +453,7 @@ instance -- hand you 'Nothing'. -- -- You can control how it'll be converted from 'Text' to your type --- by simply providing an instance of 'FromText' for your type. +-- by simply providing an instance of 'FromHttpApiData' for your type. -- -- Example: -- @@ -462,7 +464,7 @@ instance -- > where getBooksBy :: Maybe Text -> ExceptT ServantErr IO [Book] -- > getBooksBy Nothing = ...return all books... -- > getBooksBy (Just author) = ...return books by the given author... -instance (KnownSymbol sym, FromText a, HasServer sublayout) +instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout) => HasServer (QueryParam sym a :> sublayout) where type ServerT (QueryParam sym a :> sublayout) m = @@ -474,7 +476,7 @@ instance (KnownSymbol sym, FromText a, HasServer sublayout) case lookup paramname querytext of Nothing -> Nothing -- param absent from the query string Just Nothing -> Nothing -- param present with no value -> Nothing - Just (Just v) -> fromText v -- if present, we try to convert to + Just (Just v) -> parseQueryParamMaybe v -- if present, we try to convert to -- the right type in route (Proxy :: Proxy sublayout) (feedTo subserver param) where paramname = cs $ symbolVal (Proxy :: Proxy sym) @@ -488,7 +490,7 @@ instance (KnownSymbol sym, FromText a, HasServer sublayout) -- the type you specify. -- -- You can control how the individual values are converted from 'Text' to your type --- by simply providing an instance of 'FromText' for your type. +-- by simply providing an instance of 'FromHttpApiData' for your type. -- -- Example: -- @@ -498,7 +500,7 @@ instance (KnownSymbol sym, FromText a, HasServer sublayout) -- > server = getBooksBy -- > where getBooksBy :: [Text] -> ExceptT ServantErr IO [Book] -- > getBooksBy authors = ...return all books by these authors... -instance (KnownSymbol sym, FromText a, HasServer sublayout) +instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout) => HasServer (QueryParams sym a :> sublayout) where type ServerT (QueryParams sym a :> sublayout) m = @@ -507,7 +509,7 @@ instance (KnownSymbol sym, FromText a, HasServer sublayout) route Proxy subserver = WithRequest $ \ request -> let querytext = parseQueryText $ rawQueryString request -- if sym is "foo", we look for query string parameters - -- named "foo" or "foo[]" and call fromText on the + -- named "foo" or "foo[]" and call parseQueryParam on the -- corresponding values parameters = filter looksLikeParam querytext values = mapMaybe (convert . snd) parameters @@ -515,7 +517,7 @@ instance (KnownSymbol sym, FromText a, HasServer sublayout) where paramname = cs $ symbolVal (Proxy :: Proxy sym) looksLikeParam (name, _) = name == paramname || name == (paramname <> "[]") convert Nothing = Nothing - convert (Just v) = fromText v + convert (Just v) = parseQueryParamMaybe v -- | If you use @'QueryFlag' "published"@ in one of the endpoints for your API, -- this automatically requires your server-side handler to be a function @@ -559,7 +561,7 @@ parseMatrixText = parseQueryText -- hand you 'Nothing'. -- -- You can control how it'll be converted from 'Text' to your type --- by simply providing an instance of 'FromText' for your type. +-- by simply providing an instance of 'FromHttpApiData' for your type. -- -- Example: -- @@ -570,7 +572,7 @@ parseMatrixText = parseQueryText -- > where getBooksBy :: Maybe Text -> ExceptT ServantErr IO [Book] -- > getBooksBy Nothing = ...return all books... -- > getBooksBy (Just author) = ...return books by the given author... -instance (KnownSymbol sym, FromText a, HasServer sublayout) +instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout) => HasServer (MatrixParam sym a :> sublayout) where type ServerT (MatrixParam sym a :> sublayout) m = @@ -583,7 +585,7 @@ instance (KnownSymbol sym, FromText a, HasServer sublayout) param = case lookup paramname querytext of Nothing -> Nothing -- param absent from the query string Just Nothing -> Nothing -- param present with no value -> Nothing - Just (Just v) -> fromText v -- if present, we try to convert to + Just (Just v) -> parseQueryParamMaybe v -- if present, we try to convert to -- the right type route (Proxy :: Proxy sublayout) (feedTo subserver param) _ -> route (Proxy :: Proxy sublayout) (feedTo subserver Nothing) @@ -599,7 +601,7 @@ instance (KnownSymbol sym, FromText a, HasServer sublayout) -- the type you specify. -- -- You can control how the individual values are converted from 'Text' to your type --- by simply providing an instance of 'FromText' for your type. +-- by simply providing an instance of 'FromHttpApiData' for your type. -- -- Example: -- @@ -609,7 +611,7 @@ instance (KnownSymbol sym, FromText a, HasServer sublayout) -- > server = getBooksBy -- > where getBooksBy :: [Text] -> ExceptT ServantErr IO [Book] -- > getBooksBy authors = ...return all books by these authors... -instance (KnownSymbol sym, FromText a, HasServer sublayout) +instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout) => HasServer (MatrixParams sym a :> sublayout) where type ServerT (MatrixParams sym a :> sublayout) m = @@ -620,7 +622,7 @@ instance (KnownSymbol sym, FromText a, HasServer sublayout) (first : _) -> do let matrixtext = parseMatrixText . encodeUtf8 $ T.tail first -- if sym is "foo", we look for matrix parameters - -- named "foo" or "foo[]" and call fromText on the + -- named "foo" or "foo[]" and call parseQueryParam on the -- corresponding values parameters = filter looksLikeParam matrixtext values = mapMaybe (convert . snd) parameters @@ -629,7 +631,7 @@ instance (KnownSymbol sym, FromText a, HasServer sublayout) where paramname = cs $ symbolVal (Proxy :: Proxy sym) looksLikeParam (name, _) = name == paramname || name == (paramname <> "[]") convert Nothing = Nothing - convert (Just v) = fromText v + convert (Just v) = parseQueryParamMaybe v -- | If you use @'MatrixFlag' "published"@ in one of the endpoints for your API, -- this automatically requires your server-side handler to be a function diff --git a/servant/servant.cabal b/servant/servant.cabal index 1787956c..1e9e517f 100644 --- a/servant/servant.cabal +++ b/servant/servant.cabal @@ -52,6 +52,7 @@ library , bytestring == 0.10.* , bytestring-conversion == 0.3.* , case-insensitive >= 1.2 + , http-api-data , http-media >= 0.4 && < 0.7 , http-types == 0.8.* , text >= 1 && < 2 @@ -92,7 +93,6 @@ test-suite spec other-modules: Servant.API.ContentTypesSpec Servant.API.ResponseHeadersSpec - Servant.Common.TextSpec Servant.Utils.LinksSpec build-depends: base == 4.* diff --git a/servant/src/Servant/API.hs b/servant/src/Servant/API.hs index 0fa12093..f17f2b67 100644 --- a/servant/src/Servant/API.hs +++ b/servant/src/Servant/API.hs @@ -50,9 +50,9 @@ module Servant.API ( module Servant.API.Raw, -- | Plugging in a wai 'Network.Wai.Application', serving directories - -- * FromText and ToText - module Servant.Common.Text, - -- | Classes and instances for types that can be converted to and from @Text@ + -- * FromHttpApiData and ToHttpApiData + module Web.HttpApiData, + -- | Classes and instances for types that can be converted to and from HTTP API data. -- * Utilities module Servant.Utils.Links, @@ -88,7 +88,7 @@ import Servant.API.ResponseHeaders (AddHeader (addHeader), getHeadersHList, getResponse) import Servant.API.Sub ((:>)) import Servant.API.Vault (Vault) -import Servant.Common.Text (FromText (..), ToText (..)) +import Web.HttpApiData (FromHttpApiData (..), ToHttpApiData (..)) import Servant.Utils.Links (HasLink (..), IsElem, IsElem', URI (..), safeLink) diff --git a/servant/src/Servant/API/Header.hs b/servant/src/Servant/API/Header.hs index 7d58d762..ac7471c1 100644 --- a/servant/src/Servant/API/Header.hs +++ b/servant/src/Servant/API/Header.hs @@ -23,7 +23,6 @@ data Header (sym :: Symbol) a = Header a -- $setup -- >>> import Servant.API --- >>> import Servant.Common.Text -- >>> import Data.Aeson -- >>> import Data.Text -- >>> data Book diff --git a/servant/src/Servant/API/ReqBody.hs b/servant/src/Servant/API/ReqBody.hs index 672af912..abfec02b 100644 --- a/servant/src/Servant/API/ReqBody.hs +++ b/servant/src/Servant/API/ReqBody.hs @@ -16,7 +16,6 @@ data ReqBody (contentTypes :: [*]) a -- $setup -- >>> import Servant.API --- >>> import Servant.Common.Text -- >>> import Data.Aeson -- >>> import Data.Text -- >>> data Book diff --git a/servant/src/Servant/API/Sub.hs b/servant/src/Servant/API/Sub.hs index 43e1c698..616ecb2a 100644 --- a/servant/src/Servant/API/Sub.hs +++ b/servant/src/Servant/API/Sub.hs @@ -20,7 +20,6 @@ infixr 9 :> -- $setup -- >>> import Servant.API --- >>> import Servant.Common.Text -- >>> import Data.Aeson -- >>> import Data.Text -- >>> data World diff --git a/servant/src/Servant/Utils/Links.hs b/servant/src/Servant/Utils/Links.hs index 7224ff79..0b660797 100644 --- a/servant/src/Servant/Utils/Links.hs +++ b/servant/src/Servant/Utils/Links.hs @@ -113,7 +113,7 @@ import Network.URI ( URI(..), escapeURIString, isUnreserved ) import GHC.TypeLits ( KnownSymbol, symbolVal ) import GHC.Exts(Constraint) -import Servant.Common.Text +import Web.HttpApiData import Servant.API.Capture ( Capture ) import Servant.API.ReqBody ( ReqBody ) import Servant.API.QueryParam ( QueryParam, QueryParams, QueryFlag ) @@ -271,22 +271,22 @@ instance (KnownSymbol sym, HasLink sub) => HasLink (sym :> sub) where -- QueryParam instances -instance (KnownSymbol sym, ToText v, HasLink sub) +instance (KnownSymbol sym, ToHttpApiData v, HasLink sub) => HasLink (QueryParam sym v :> sub) where type MkLink (QueryParam sym v :> sub) = Maybe v -> MkLink sub toLink _ l mv = toLink (Proxy :: Proxy sub) $ - maybe id (addQueryParam . SingleParam k . toText) mv l + maybe id (addQueryParam . SingleParam k . toQueryParam) mv l where k :: String k = symbolVal (Proxy :: Proxy sym) -instance (KnownSymbol sym, ToText v, HasLink sub) +instance (KnownSymbol sym, ToHttpApiData v, HasLink sub) => HasLink (QueryParams sym v :> sub) where type MkLink (QueryParams sym v :> sub) = [v] -> MkLink sub toLink _ l = toLink (Proxy :: Proxy sub) . - foldl' (\l' v -> addQueryParam (ArrayElemParam k (toText v)) l') l + foldl' (\l' v -> addQueryParam (ArrayElemParam k (toQueryParam v)) l') l where k = symbolVal (Proxy :: Proxy sym) @@ -301,21 +301,21 @@ instance (KnownSymbol sym, HasLink sub) k = symbolVal (Proxy :: Proxy sym) -- MatrixParam instances -instance (KnownSymbol sym, ToText v, HasLink sub) +instance (KnownSymbol sym, ToHttpApiData v, HasLink sub) => HasLink (MatrixParam sym v :> sub) where type MkLink (MatrixParam sym v :> sub) = Maybe v -> MkLink sub toLink _ l mv = toLink (Proxy :: Proxy sub) $ - maybe id (addMatrixParam . SingleParam k . toText) mv l + maybe id (addMatrixParam . SingleParam k . toQueryParam) mv l where k = symbolVal (Proxy :: Proxy sym) -instance (KnownSymbol sym, ToText v, HasLink sub) +instance (KnownSymbol sym, ToHttpApiData v, HasLink sub) => HasLink (MatrixParams sym v :> sub) where type MkLink (MatrixParams sym v :> sub) = [v] -> MkLink sub toLink _ l = toLink (Proxy :: Proxy sub) . - foldl' (\l' v -> addMatrixParam (ArrayElemParam k (toText v)) l') l + foldl' (\l' v -> addMatrixParam (ArrayElemParam k (toQueryParam v)) l') l where k = symbolVal (Proxy :: Proxy sym) @@ -334,12 +334,12 @@ instance HasLink sub => HasLink (ReqBody ct a :> sub) where type MkLink (ReqBody ct a :> sub) = MkLink sub toLink _ = toLink (Proxy :: Proxy sub) -instance (ToText v, HasLink sub) +instance (ToHttpApiData v, HasLink sub) => HasLink (Capture sym v :> sub) where type MkLink (Capture sym v :> sub) = v -> MkLink sub toLink _ l v = toLink (Proxy :: Proxy sub) $ - addSegment (escape . unpack $ toText v) l + addSegment (escape . unpack $ toUrlPiece v) l instance HasLink sub => HasLink (Header sym a :> sub) where type MkLink (Header sym a :> sub) = MkLink sub diff --git a/servant/test/Servant/Common/TextSpec.hs b/servant/test/Servant/Common/TextSpec.hs deleted file mode 100644 index 144cd405..00000000 --- a/servant/test/Servant/Common/TextSpec.hs +++ /dev/null @@ -1,74 +0,0 @@ -{-# LANGUAGE CPP #-} -module Servant.Common.TextSpec where - -import Data.Int (Int16, Int32, Int64, Int8) -import Data.Text (Text) -import Data.Word (Word16, Word32, Word64, Word8 -#if !MIN_VERSION_base(4,8,0) - , Word -#endif - ) -import Servant.Common.Text -import Test.Hspec -import Test.QuickCheck -import Test.QuickCheck.Instances () - -spec :: Spec -spec = describe "Servant.Common.Text" $ do - - context "FromText and ToText laws" $ do - - it "holds for Text" $ - property $ \x -> textLaw (x :: Text) - - it "holds for String" $ - property $ \x -> textLaw (x :: String) - - it "holds for Bool" $ - property $ \x -> textLaw (x :: Bool) - - it "holds for Int" $ - property $ \x -> textLaw (x :: Int) - - it "holds for Int8" $ - property $ \x -> textLaw (x :: Int8) - - it "holds for Int16" $ - property $ \x -> textLaw (x :: Int16) - - it "holds for Int32" $ - property $ \x -> textLaw (x :: Int32) - - it "holds for Int64" $ - property $ \x -> textLaw (x :: Int64) - - it "holds for Word" $ - property $ \x -> textLaw (x :: Word) - - it "holds for Word8" $ - property $ \x -> textLaw (x :: Word8) - - it "holds for Word16" $ - property $ \x -> textLaw (x :: Word16) - - it "holds for Word32" $ - property $ \x -> textLaw (x :: Word32) - - it "holds for Word64" $ - property $ \x -> textLaw (x :: Word64) - - it "holds for Integer" $ - property $ \x -> textLaw (x :: Integer) - - -- The following two properties are only reasonably expected to hold up - -- to a certain precision. - -- - -- http://en.wikipedia.org/wiki/Floating_point#Internal_representation - it "holds for Double" $ - property $ \x -> textLaw (x :: Double) - - it "holds for Float" $ - property $ \x -> textLaw (x :: Float) - -textLaw :: (FromText a, ToText a, Eq a) => a -> Bool -textLaw a = fromText (toText a) == Just a diff --git a/stack-ghc-7.8.4.yaml b/stack-ghc-7.8.4.yaml index 22b3a94d..a0ad05f8 100644 --- a/stack-ghc-7.8.4.yaml +++ b/stack-ghc-7.8.4.yaml @@ -21,4 +21,5 @@ extra-deps: - socket-io-1.3.3 - stm-delay-0.1.1.1 - control-monad-omega-0.3.1 +- http-api-data-0.1.0 resolver: lts-2.22 diff --git a/stack.yaml b/stack.yaml index 006f455c..c0e3b5a7 100644 --- a/stack.yaml +++ b/stack.yaml @@ -16,4 +16,5 @@ packages: extra-deps: - engine-io-wai-1.0.2 - control-monad-omega-0.3.1 +- http-api-data-0.1.0 resolver: nightly-2015-09-10 From 63a36e3b4ea4d2766fda918315a68a6160bf5aae Mon Sep 17 00:00:00 2001 From: Nickolay Kudasov Date: Thu, 8 Oct 2015 00:57:18 +0300 Subject: [PATCH 3/7] Fix warning --- servant-server/src/Servant/Server/Internal.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/servant-server/src/Servant/Server/Internal.hs b/servant-server/src/Servant/Server/Internal.hs index d7175d55..2e6ade1a 100644 --- a/servant-server/src/Servant/Server/Internal.hs +++ b/servant-server/src/Servant/Server/Internal.hs @@ -32,7 +32,7 @@ import Data.String (fromString) import Data.String.Conversions (cs, (<>), ConvertibleStrings) import Data.Text (Text) import qualified Data.Text as T -import Data.Text.Encoding (decodeUtf8, encodeUtf8) +import Data.Text.Encoding (encodeUtf8) import Data.Typeable import GHC.TypeLits (KnownSymbol, symbolVal) import Network.HTTP.Types hiding (Header, ResponseHeaders) From 1e26ba25d133431147bcaae008f18094c9dce0ed Mon Sep 17 00:00:00 2001 From: Nickolay Kudasov Date: Thu, 8 Oct 2015 00:57:39 +0300 Subject: [PATCH 4/7] Update http-api-data dependency --- stack-ghc-7.8.4.yaml | 2 +- stack.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/stack-ghc-7.8.4.yaml b/stack-ghc-7.8.4.yaml index a0ad05f8..9632e808 100644 --- a/stack-ghc-7.8.4.yaml +++ b/stack-ghc-7.8.4.yaml @@ -21,5 +21,5 @@ extra-deps: - socket-io-1.3.3 - stm-delay-0.1.1.1 - control-monad-omega-0.3.1 -- http-api-data-0.1.0 +- http-api-data-0.1.1.1 resolver: lts-2.22 diff --git a/stack.yaml b/stack.yaml index c0e3b5a7..bffe865f 100644 --- a/stack.yaml +++ b/stack.yaml @@ -16,5 +16,5 @@ packages: extra-deps: - engine-io-wai-1.0.2 - control-monad-omega-0.3.1 -- http-api-data-0.1.0 +- http-api-data-0.1.1.1 resolver: nightly-2015-09-10 From dfb5cc771364ffa1807e7b9a8a65df24c01dd193 Mon Sep 17 00:00:00 2001 From: Nickolay Kudasov Date: Thu, 8 Oct 2015 01:15:30 +0300 Subject: [PATCH 5/7] Update changelogs --- servant-client/CHANGELOG.md | 1 + servant-server/CHANGELOG.md | 1 + servant/CHANGELOG.md | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/servant-client/CHANGELOG.md b/servant-client/CHANGELOG.md index 29398d95..1d6ea04e 100644 --- a/servant-client/CHANGELOG.md +++ b/servant-client/CHANGELOG.md @@ -4,6 +4,7 @@ HEAD * Support for the `HttpVersion`, `IsSecure`, `RemoteHost` and `Vault` combinators * Added support for `path` on `BaseUrl`. * `client` now takes an explicit `Manager` argument. +* Use `http-api-data` instead of `Servant.Common.Text` 0.4.1 ----- diff --git a/servant-server/CHANGELOG.md b/servant-server/CHANGELOG.md index bbc54235..ce7acc78 100644 --- a/servant-server/CHANGELOG.md +++ b/servant-server/CHANGELOG.md @@ -3,6 +3,7 @@ HEAD * Support for the `HttpVersion`, `IsSecure`, `RemoteHost` and `Vault` combinators * Drop `EitherT` in favor of `ExceptT` +* Use `http-api-data` instead of `Servant.Common.Text` 0.4.1 ----- diff --git a/servant/CHANGELOG.md b/servant/CHANGELOG.md index 043320ae..82f3d816 100644 --- a/servant/CHANGELOG.md +++ b/servant/CHANGELOG.md @@ -3,7 +3,8 @@ HEAD * Add `HttpVersion`, `IsSecure`, `RemoteHost` and `Vault` combinators * Fix safeLink, so Header is not in fact required. -* Added more instances for (:<|>) +* Add more instances for (:<|>) +* Use `http-api-data` instead of `Servant.Common.Text` 0.4.2 ----- From 2a7d110c175bda47a1aa79c29caa37b301d1d1f0 Mon Sep 17 00:00:00 2001 From: Nickolay Kudasov Date: Thu, 8 Oct 2015 16:21:02 +0300 Subject: [PATCH 6/7] Add version bounds for http-api-data --- servant-client/servant-client.cabal | 2 +- servant-server/servant-server.cabal | 2 +- servant/servant.cabal | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/servant-client/servant-client.cabal b/servant-client/servant-client.cabal index d801b37d..e7534522 100644 --- a/servant-client/servant-client.cabal +++ b/servant-client/servant-client.cabal @@ -34,7 +34,7 @@ library , attoparsec , bytestring , exceptions - , http-api-data + , http-api-data >= 0.1 && < 0.2 , http-client , http-client-tls , http-media diff --git a/servant-server/servant-server.cabal b/servant-server/servant-server.cabal index 5f9d8838..a5245552 100644 --- a/servant-server/servant-server.cabal +++ b/servant-server/servant-server.cabal @@ -48,7 +48,7 @@ library , attoparsec >= 0.12 && < 0.14 , bytestring >= 0.10 && < 0.11 , containers >= 0.5 && < 0.6 - , http-api-data + , http-api-data >= 0.1 && < 0.2 , http-types >= 0.8 && < 0.9 , network-uri >= 2.6 && < 2.7 , mtl >= 2 && < 3 diff --git a/servant/servant.cabal b/servant/servant.cabal index 1e9e517f..2b769c63 100644 --- a/servant/servant.cabal +++ b/servant/servant.cabal @@ -52,7 +52,7 @@ library , bytestring == 0.10.* , bytestring-conversion == 0.3.* , case-insensitive >= 1.2 - , http-api-data + , http-api-data >= 0.1 && < 0.2 , http-media >= 0.4 && < 0.7 , http-types == 0.8.* , text >= 1 && < 2 From 707e46a8aac96ae2542c8fe29285c624ff1edb97 Mon Sep 17 00:00:00 2001 From: Nickolay Kudasov Date: Thu, 8 Oct 2015 16:21:22 +0300 Subject: [PATCH 7/7] Update resolver to the latest nightly --- stack.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/stack.yaml b/stack.yaml index bffe865f..f370da09 100644 --- a/stack.yaml +++ b/stack.yaml @@ -16,5 +16,4 @@ packages: extra-deps: - engine-io-wai-1.0.2 - control-monad-omega-0.3.1 -- http-api-data-0.1.1.1 -resolver: nightly-2015-09-10 +resolver: nightly-2015-10-08