From 73914586b1384b041e47da7349b1fd29a4012e9c Mon Sep 17 00:00:00 2001 From: Alp Mestanogullari Date: Mon, 9 Mar 2015 13:39:40 +0100 Subject: [PATCH 1/4] add the Canonicalize type family which turns an API type into its canonical form --- src/Servant/API.hs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/Servant/API.hs b/src/Servant/API.hs index 396f53f7..b1448bc7 100644 --- a/src/Servant/API.hs +++ b/src/Servant/API.hs @@ -1,3 +1,6 @@ +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE UndecidableInstances #-} module Servant.API ( -- * Combinators @@ -43,6 +46,9 @@ module Servant.API ( module Servant.Common.Text, -- | Classes and instances for types that can be converted to and from @Text@ + -- * Canonicalizing (flattening) API types + Canonicalize, + -- * Utilities module Servant.Utils.Links, -- | Type-safe internal URIs @@ -69,3 +75,25 @@ import Servant.API.ReqBody (ReqBody) import Servant.API.Sub ((:>)) import Servant.Utils.Links (HasLink (..), IsElem, IsElem', URI (..), safeLink) + +-- | Turn an API type into its canonical form. +-- +-- The canonical form is defined and will basically turn: +-- +-- > "hello" :> (Get Hello :<|> ReqBody Hello :> Put Hello) +-- +-- into +-- +-- > ("hello" :> Get Hello) :<|> ("hello" :> ReqBody Hello :> Put Hello) +-- +-- i.e distributing all ':>'-separated bits into the subsequent ':<|>'s. +type family Canonicalize api :: * where + -- requires UndecidableInstances + Canonicalize (a :> (b :<|> c)) = ((a :> Canonicalize b) :<|> (a :> Canonicalize c)) + Canonicalize (a :> b) = Redex b (Canonicalize b) a + Canonicalize (a :<|> b) = Canonicalize a :<|> Canonicalize b + Canonicalize a = a + +type family Redex a b c :: * where + Redex a a first = Canonicalize first :> a + Redex a b first = Canonicalize (first :> b) From 6af9b9e70f67f4477919eeb8dbde1eb185814bac Mon Sep 17 00:00:00 2001 From: Alp Mestanogullari Date: Mon, 9 Mar 2015 19:23:26 +0100 Subject: [PATCH 2/4] add 'canonicalize' to canonicalize the api type under a Proxy --- src/Servant/API.hs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Servant/API.hs b/src/Servant/API.hs index b1448bc7..5bbb0995 100644 --- a/src/Servant/API.hs +++ b/src/Servant/API.hs @@ -48,12 +48,14 @@ module Servant.API ( -- * Canonicalizing (flattening) API types Canonicalize, + canonicalize, -- * Utilities module Servant.Utils.Links, -- | Type-safe internal URIs ) where +import Data.Proxy (Proxy(..)) import Servant.Common.Text (FromText(..), ToText(..)) import Servant.API.Alternative ((:<|>) (..)) import Servant.API.Capture (Capture) @@ -97,3 +99,6 @@ type family Canonicalize api :: * where type family Redex a b c :: * where Redex a a first = Canonicalize first :> a Redex a b first = Canonicalize (first :> b) + +canonicalize :: Canonicalize layout ~ t => Proxy layout -> Proxy t +canonicalize Proxy = Proxy From 6909b54367ced89eaf6440186f72ec5a227ac0dc Mon Sep 17 00:00:00 2001 From: Alp Mestanogullari Date: Tue, 10 Mar 2015 12:08:17 +0100 Subject: [PATCH 3/4] add a clause to Canonicalize for (a :<|> b) :> c with the obvious definition --- src/Servant/API.hs | 3 ++- src/Servant/API/ContentTypes.hs | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Servant/API.hs b/src/Servant/API.hs index 5bbb0995..42ace00e 100644 --- a/src/Servant/API.hs +++ b/src/Servant/API.hs @@ -91,7 +91,8 @@ import Servant.Utils.Links (HasLink (..), IsElem, IsElem', -- i.e distributing all ':>'-separated bits into the subsequent ':<|>'s. type family Canonicalize api :: * where -- requires UndecidableInstances - Canonicalize (a :> (b :<|> c)) = ((a :> Canonicalize b) :<|> (a :> Canonicalize c)) + Canonicalize (a :> (b :<|> c)) = a :> Canonicalize b :<|> a :> Canonicalize c + Canonicalize ((a :<|> b) :> c) = a :> Canonicalize c :<|> b :> Canonicalize c Canonicalize (a :> b) = Redex b (Canonicalize b) a Canonicalize (a :<|> b) = Canonicalize a :<|> Canonicalize b Canonicalize a = a diff --git a/src/Servant/API/ContentTypes.hs b/src/Servant/API/ContentTypes.hs index d8e075af..3ae20b71 100644 --- a/src/Servant/API/ContentTypes.hs +++ b/src/Servant/API/ContentTypes.hs @@ -55,6 +55,8 @@ module Servant.API.ContentTypes , AcceptHeader(..) , AllCTRender(..) , AllCTUnrender(..) + , AllMimeRender(..) + , AllMimeUnrender(..) , FromFormUrlEncoded(..) , ToFormUrlEncoded(..) , IsNonEmpty From 7a83952badf91998afc10f3b4c2e9b47f663bf11 Mon Sep 17 00:00:00 2001 From: Alp Mestanogullari Date: Wed, 11 Mar 2015 11:46:35 +0100 Subject: [PATCH 4/4] some more documentation, code cleanup + changelog entry --- CHANGELOG.md | 1 + src/Servant/API.hs | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 741ff87e..a3194b03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ 0.3 --- +* Add a `Canonicalize` type family that distributes all `:>`s inside `:<|>`s to get to the canonical type of an API -- which is then used in all other packages to avoid weird handler types in *servant-server*. * Multiple content-type/accept support for all the relevant combinators * Provide *JSON*, *PlainText*, *OctetStream* and *FormUrlEncoded* content types out of the box * Type-safe link generation to API endpoints diff --git a/src/Servant/API.hs b/src/Servant/API.hs index 42ace00e..9a91d0f4 100644 --- a/src/Servant/API.hs +++ b/src/Servant/API.hs @@ -80,7 +80,12 @@ import Servant.Utils.Links (HasLink (..), IsElem, IsElem', -- | Turn an API type into its canonical form. -- --- The canonical form is defined and will basically turn: +-- The canonical form of an API type is basically the all-flattened form +-- of the original type. More formally, it takes a type as input and hands you +-- back an /equivalent/ type formed of toplevel `:<|>`-separated chains of `:>`s, +-- i.e with all `:>`s distributed inside the `:<|>`s. +-- +-- It basically turns: -- -- > "hello" :> (Get Hello :<|> ReqBody Hello :> Put Hello) -- @@ -101,5 +106,5 @@ type family Redex a b c :: * where Redex a a first = Canonicalize first :> a Redex a b first = Canonicalize (first :> b) -canonicalize :: Canonicalize layout ~ t => Proxy layout -> Proxy t +canonicalize :: Proxy layout -> Proxy (Canonicalize layout) canonicalize Proxy = Proxy