@@ -19,8 +19,6 @@
19-- >>> import Servant.Utils.Links
19-- >>> import Servant.Utils.Links
20-- >>> import Data.Proxy
20-- >>> import Data.Proxy
24-- >>> type Hello = "hello" :> Get '[JSON] Int
22-- >>> type Hello = "hello" :> Get '[JSON] Int
25-- >>> type Bye = "bye" :> QueryParam "name" String :> Delete '[JSON] NoContent
23-- >>> type Bye = "bye" :> QueryParam "name" String :> Delete '[JSON] NoContent
26-- >>> type API = Hello :<|> Bye
24-- >>> type API = Hello :<|> Bye
@@ -63,10 +61,24 @@
63-- >>> :set -XConstraintKinds
61-- >>> :set -XConstraintKinds
65-- >>> let apiLink :: (IsElem endpoint API, HasLink endpoint)
63-- >>> let apiLink :: (IsElem endpoint API, HasLink endpoint)
66−-- >>> => Proxy endpoint -> MkLink endpoint
64+-- >>> => Proxy endpoint -> MkLink endpoint Link
67-- >>> apiLink = safeLink api
65-- >>> apiLink = safeLink api
68+-- `safeLink'` allows to make specialise the output:
70+-- >>> safeLink' toUrlPiece api without
74+-- >>> let apiTextLink :: (IsElem endpoint API, HasLink endpoint)
75+-- >>> => Proxy endpoint -> MkLink endpoint Text
76+-- >>> apiTextLink = safeLink' toUrlPiece api
79+-- >>> apiTextLink without
70-- Attempting to construct a link to an endpoint that does not exist in api
82-- Attempting to construct a link to an endpoint that does not exist in api
71-- will result in a type error like this:
83-- will result in a type error like this:
@@ -86,7 +98,9 @@ module Servant.Utils.Links (
87 -- | Note that 'URI' is from the "Network.URI" module in the @network-uri@ package.
99 -- | Note that 'URI' is from the "Network.URI" module in the @network-uri@ package.
89 , allLinks
102 , allLinks
90 , URI(..)
104 , URI(..)
91 -- * Adding custom types
105 -- * Adding custom types
92 , HasLink(..)
106 , HasLink(..)
@@ -109,8 +123,6 @@ import Data.Singletons.Bool
109 (SBool (..), SBoolI (..))
123 (SBool (..), SBoolI (..))
110import qualified Data.Text as Text
124import qualified Data.Text as Text
111import qualified Data.Text.Encoding as TE
125import qualified Data.Text.Encoding as TE
112−import Data.Type.Bool
114import Data.Type.Bool
126import Data.Type.Bool
116import GHC.TypeLits
128import GHC.TypeLits
@@ -278,8 +290,18 @@ safeLink
278 :: forall endpoint api. (IsElem endpoint api, HasLink endpoint)
290 :: forall endpoint api. (IsElem endpoint api, HasLink endpoint)
279 => Proxy api -- ^ The whole API that this endpoint is a part of
291 => Proxy api -- ^ The whole API that this endpoint is a part of
280 -> Proxy endpoint -- ^ The API endpoint you would like to point to
292 -> Proxy endpoint -- ^ The API endpoint you would like to point to
281− -> MkLink endpoint
293+ -> MkLink endpoint Link
294+safeLink = safeLink' id
296+-- | More general 'safeLink'.
299+ :: forall endpoint api a. (IsElem endpoint api, HasLink endpoint)
301+ -> Proxy api -- ^ The whole API that this endpoint is a part of
302+ -> Proxy endpoint -- ^ The API endpoint you would like to point to
303+ -> MkLink endpoint a
282−safeLink _ endpoint = toLink endpoint (Link mempty mempty)
304+safeLink' toA _ endpoint = toLink toA endpoint (Link mempty mempty)
284-- | Create all links in an API.
306-- | Create all links in an API.
@@ -295,37 +317,47 @@ safeLink _ endpoint = toLink endpoint (Link mempty mempty)
296-- Note: nested APIs don't work well with this approach
318-- Note: nested APIs don't work well with this approach
298−-- >>> :kind! MkLink (Capture "nest" Char :> (Capture "x" Int :> Get '[JSON] Int :<|> Capture "y" Double :> Get '[JSON] Double))
320+-- >>> :kind! MkLink (Capture "nest" Char :> (Capture "x" Int :> Get '[JSON] Int :<|> Capture "y" Double :> Get '[JSON] Double)) Link
299−-- MkLink (Capture "nest" Char :> (Capture "x" Int :> Get '[JSON] Int :<|> Capture "y" Double :> Get '[JSON] Double)) :: *
321+-- MkLink (Capture "nest" Char :> (Capture "x" Int :> Get '[JSON] Int :<|> Capture "y" Double :> Get '[JSON] Double)) Link :: *
300-- = Char -> (Int -> Link) :<|> (Double -> Link)
322-- = Char -> (Int -> Link) :<|> (Double -> Link)
303 :: forall api. HasLink api
324 :: forall api. HasLink api
304 => Proxy api
325 => Proxy api
305− -> MkLink api
326+ -> MkLink api Link
327+allLinks = allLinks' id
329+-- | More general 'allLinks'. See `safeLink'`.
331+ :: forall api a. HasLink api
306−allLinks api = toLink api (Link mempty mempty)
335+allLinks' toA api = toLink toA api (Link mempty mempty)
308-- | Construct a toLink for an endpoint.
337-- | Construct a toLink for an endpoint.
309class HasLink endpoint where
338class HasLink endpoint where
310− type MkLink endpoint
339+ type MkLink endpoint (a :: *)
311− toLink :: Proxy endpoint -- ^ The API endpoint you would like to point to
342+ -> Proxy endpoint -- ^ The API endpoint you would like to point to
313− -> MkLink endpoint
344+ -> MkLink endpoint a
315-- Naked symbol instance
346-- Naked symbol instance
316instance (KnownSymbol sym, HasLink sub) => HasLink (sym :> sub) where
347instance (KnownSymbol sym, HasLink sub) => HasLink (sym :> sub) where
317− type MkLink (sym :> sub) = MkLink sub
348+ type MkLink (sym :> sub) a = MkLink sub a
318− toLink _ =
349+ toLink toA _ =
319− toLink (Proxy :: Proxy sub) . addSegment (escaped seg)
350+ toLink toA (Proxy :: Proxy sub) . addSegment (escaped seg)
321 seg = symbolVal (Proxy :: Proxy sym)
352 seg = symbolVal (Proxy :: Proxy sym)
323-- QueryParam instances
354-- QueryParam instances
324instance (KnownSymbol sym, ToHttpApiData v, HasLink sub, SBoolI (FoldRequired mods))
355instance (KnownSymbol sym, ToHttpApiData v, HasLink sub, SBoolI (FoldRequired mods))
325− => HasLink (QueryParam' mods sym v :> sub) where
356+ => HasLink (QueryParam' mods sym v :> sub)
326− type MkLink (QueryParam' mods sym v :> sub) = If (FoldRequired mods) v (Maybe v) -> MkLink sub
358+ type MkLink (QueryParam' mods sym v :> sub) a = If (FoldRequired mods) v (Maybe v) -> MkLink sub a
327− toLink _ l mv =
359+ toLink toA _ l mv =
328− toLink (Proxy :: Proxy sub) $
360+ toLink toA (Proxy :: Proxy sub) $
329 case sbool :: SBool (FoldRequired mods) of
361 case sbool :: SBool (FoldRequired mods) of
330 STrue -> (addQueryParam . SingleParam k . toQueryParam) mv l
362 STrue -> (addQueryParam . SingleParam k . toQueryParam) mv l
331 SFalse -> maybe id (addQueryParam . SingleParam k . toQueryParam) mv l
363 SFalse -> maybe id (addQueryParam . SingleParam k . toQueryParam) mv l
@@ -334,105 +366,121 @@ instance (KnownSymbol sym, ToHttpApiData v, HasLink sub, SBoolI (FoldRequired mo
334 k = symbolVal (Proxy :: Proxy sym)
366 k = symbolVal (Proxy :: Proxy sym)
336instance (KnownSymbol sym, ToHttpApiData v, HasLink sub)
368instance (KnownSymbol sym, ToHttpApiData v, HasLink sub)
337− => HasLink (QueryParams sym v :> sub) where
369+ => HasLink (QueryParams sym v :> sub)
338− type MkLink (QueryParams sym v :> sub) = [v] -> MkLink sub
371+ type MkLink (QueryParams sym v :> sub) a = [v] -> MkLink sub a
339− toLink _ l =
372+ toLink toA _ l =
340− toLink (Proxy :: Proxy sub) .
373+ toLink toA (Proxy :: Proxy sub) .
341 foldl' (\l' v -> addQueryParam (ArrayElemParam k (toQueryParam v)) l') l
374 foldl' (\l' v -> addQueryParam (ArrayElemParam k (toQueryParam v)) l') l
343 k = symbolVal (Proxy :: Proxy sym)
376 k = symbolVal (Proxy :: Proxy sym)
345instance (KnownSymbol sym, HasLink sub)
378instance (KnownSymbol sym, HasLink sub)
346− => HasLink (QueryFlag sym :> sub) where
379+ => HasLink (QueryFlag sym :> sub)
347− type MkLink (QueryFlag sym :> sub) = Bool -> MkLink sub
381+ type MkLink (QueryFlag sym :> sub) a = Bool -> MkLink sub a
348− toLink _ l False =
382+ toLink toA _ l False =
349− toLink (Proxy :: Proxy sub) l
383+ toLink toA (Proxy :: Proxy sub) l
350− toLink _ l True =
384+ toLink toA _ l True =
351− toLink (Proxy :: Proxy sub) $ addQueryParam (FlagParam k) l
385+ toLink toA (Proxy :: Proxy sub) $ addQueryParam (FlagParam k) l
353 k = symbolVal (Proxy :: Proxy sym)
387 k = symbolVal (Proxy :: Proxy sym)
355-- :<|> instance - Generate all links at once
389-- :<|> instance - Generate all links at once
356instance (HasLink a, HasLink b) => HasLink (a :<|> b) where
390instance (HasLink a, HasLink b) => HasLink (a :<|> b) where
357− type MkLink (a :<|> b) = MkLink a :<|> MkLink b
391+ type MkLink (a :<|> b) r = MkLink a r :<|> MkLink b r
358− toLink _ l = toLink (Proxy :: Proxy a) l :<|> toLink (Proxy :: Proxy b) l
392+ toLink toA _ l = toLink toA (Proxy :: Proxy a) l :<|> toLink toA (Proxy :: Proxy b) l
360-- Misc instances
394-- Misc instances
361instance HasLink sub => HasLink (ReqBody' mods ct a :> sub) where
395instance HasLink sub => HasLink (ReqBody' mods ct a :> sub) where
362− type MkLink (ReqBody' mods ct a :> sub) = MkLink sub
396+ type MkLink (ReqBody' mods ct a :> sub) r = MkLink sub r
363− toLink _ = toLink (Proxy :: Proxy sub)
397+ toLink toA _ = toLink toA (Proxy :: Proxy sub)
365instance (ToHttpApiData v, HasLink sub)
399instance (ToHttpApiData v, HasLink sub)
366− => HasLink (Capture' mods sym v :> sub) where
400+ => HasLink (Capture' mods sym v :> sub)
367− type MkLink (Capture' mods sym v :> sub) = v -> MkLink sub
402+ type MkLink (Capture' mods sym v :> sub) a = v -> MkLink sub a
368− toLink _ l v =
403+ toLink toA _ l v =
369− toLink (Proxy :: Proxy sub) $
404+ toLink toA (Proxy :: Proxy sub) $
370 addSegment (escaped . Text.unpack $ toUrlPiece v) l
405 addSegment (escaped . Text.unpack $ toUrlPiece v) l
372instance (ToHttpApiData v, HasLink sub)
407instance (ToHttpApiData v, HasLink sub)
373− => HasLink (CaptureAll sym v :> sub) where
408+ => HasLink (CaptureAll sym v :> sub)
374− type MkLink (CaptureAll sym v :> sub) = [v] -> MkLink sub
410+ type MkLink (CaptureAll sym v :> sub) a = [v] -> MkLink sub a
375− toLink _ l vs =
411+ toLink toA _ l vs = toLink toA (Proxy :: Proxy sub) $
376− toLink (Proxy :: Proxy sub) $
377− foldl' (flip $ addSegment . escaped . Text.unpack . toUrlPiece) l vs
412+ foldl' (flip $ addSegment . escaped . Text.unpack . toUrlPiece) l vs
379−instance HasLink sub => HasLink (Header' mods sym a :> sub) where
414+instance HasLink sub => HasLink (Header' mods sym (a :: *) :> sub) where
380− type MkLink (Header' mods sym a :> sub) = MkLink sub
415+ type MkLink (Header' mods sym a :> sub) r = MkLink sub r
381− toLink _ = toLink (Proxy :: Proxy sub)
416+ toLink = simpleToLink (Proxy :: Proxy sub)
383instance HasLink sub => HasLink (Vault :> sub) where
418instance HasLink sub => HasLink (Vault :> sub) where
384− type MkLink (Vault :> sub) = MkLink sub
419+ type MkLink (Vault :> sub) a = MkLink sub a
385− toLink _ = toLink (Proxy :: Proxy sub)
420+ toLink = simpleToLink (Proxy :: Proxy sub)
387instance HasLink sub => HasLink (Description s :> sub) where
422instance HasLink sub => HasLink (Description s :> sub) where
388− type MkLink (Description s :> sub) = MkLink sub
423+ type MkLink (Description s :> sub) a = MkLink sub a
389− toLink _ = toLink (Proxy :: Proxy sub)
424+ toLink = simpleToLink (Proxy :: Proxy sub)
391instance HasLink sub => HasLink (Summary s :> sub) where
426instance HasLink sub => HasLink (Summary s :> sub) where
392− type MkLink (Summary s :> sub) = MkLink sub
427+ type MkLink (Summary s :> sub) a = MkLink sub a
393− toLink _ = toLink (Proxy :: Proxy sub)
428+ toLink = simpleToLink (Proxy :: Proxy sub)
395instance HasLink sub => HasLink (HttpVersion :> sub) where
430instance HasLink sub => HasLink (HttpVersion :> sub) where
396− type MkLink (HttpVersion:> sub) = MkLink sub
431+ type MkLink (HttpVersion:> sub) a = MkLink sub a
397− toLink _ = toLink (Proxy :: Proxy sub)
432+ toLink = simpleToLink (Proxy :: Proxy sub)
399instance HasLink sub => HasLink (IsSecure :> sub) where
434instance HasLink sub => HasLink (IsSecure :> sub) where
400− type MkLink (IsSecure :> sub) = MkLink sub
435+ type MkLink (IsSecure :> sub) a = MkLink sub a
401− toLink _ = toLink (Proxy :: Proxy sub)
436+ toLink = simpleToLink (Proxy :: Proxy sub)
403instance HasLink sub => HasLink (WithNamedContext name context sub) where
438instance HasLink sub => HasLink (WithNamedContext name context sub) where
404− type MkLink (WithNamedContext name context sub) = MkLink sub
439+ type MkLink (WithNamedContext name context sub) a = MkLink sub a
405− toLink _ = toLink (Proxy :: Proxy sub)
440+ toLink toA _ = toLink toA (Proxy :: Proxy sub)
407instance HasLink sub => HasLink (RemoteHost :> sub) where
442instance HasLink sub => HasLink (RemoteHost :> sub) where
408− type MkLink (RemoteHost :> sub) = MkLink sub
443+ type MkLink (RemoteHost :> sub) a = MkLink sub a
409− toLink _ = toLink (Proxy :: Proxy sub)
444+ toLink = simpleToLink (Proxy :: Proxy sub)
411instance HasLink sub => HasLink (BasicAuth realm a :> sub) where
446instance HasLink sub => HasLink (BasicAuth realm a :> sub) where
412− type MkLink (BasicAuth realm a :> sub) = MkLink sub
447+ type MkLink (BasicAuth realm a :> sub) r = MkLink sub r
413− toLink _ = toLink (Proxy :: Proxy sub)
448+ toLink = simpleToLink (Proxy :: Proxy sub)
415instance HasLink EmptyAPI where
450instance HasLink EmptyAPI where
416− type MkLink EmptyAPI = EmptyAPI
451+ type MkLink EmptyAPI a = EmptyAPI
417− toLink _ _ = EmptyAPI
452+ toLink _ _ _ = EmptyAPI
419-- Verb (terminal) instances
454-- Verb (terminal) instances
420instance HasLink (Verb m s ct a) where
455instance HasLink (Verb m s ct a) where
421− type MkLink (Verb m s ct a) = Link
456+ type MkLink (Verb m s ct a) r = r
422− toLink _ = id
457+ toLink toA _ = toA
424instance HasLink Raw where
459instance HasLink Raw where
425− type MkLink Raw = Link
460+ type MkLink Raw a = a
426− toLink _ = id
461+ toLink toA _ = toA
428instance HasLink (Stream m fr ct a) where
463instance HasLink (Stream m fr ct a) where
429− type MkLink (Stream m fr ct a) = Link
464+ type MkLink (Stream m fr ct a) r = r
430− toLink _ = id
465+ toLink toA _ = toA
432-- AuthProtext instances
467-- AuthProtext instances
433instance HasLink sub => HasLink (AuthProtect tag :> sub) where
468instance HasLink sub => HasLink (AuthProtect tag :> sub) where
434− type MkLink (AuthProtect tag :> sub) = MkLink sub
469+ type MkLink (AuthProtect tag :> sub) a = MkLink sub a
470+ toLink = simpleToLink (Proxy :: Proxy sub)
472+-- | Helper for implemneting 'toLink' for combinators not affecting link
475+ :: forall sub a combinator.
476+ (HasLink sub, MkLink sub a ~ MkLink (combinator :> sub) a)
479+ -> Proxy (combinator :> sub)
481+ -> MkLink (combinator :> sub) a
435− toLink _ = toLink (Proxy :: Proxy sub)
482+simpleToLink _ toA _ = toLink toA (Proxy :: Proxy sub)
438-- >>> import Servant.API
486-- >>> import Servant.API