on master help sign in
Merge pull request #968 from phadej/issue-952-safelink-prime

Add safeLink'

commite1b848f67cafe4c1141f7f9ff49894517386d13e authorOleg Grenrus <oleg.grenrus@iki.fi> dateFri 1 Jun 2018, 16:47 (8 years ago) parenta66aa8a0b084af
20 files changed+151−105
.travis.yml+8−12 cabal.project+3−1 doc/cookbook/basic-auth/basic-auth.cabal+1−1 doc/cookbook/db-postgres-pool/db-postgres-pool.cabal+1−1 doc/cookbook/db-sqlite-simple/db-sqlite-simple.cabal+1−1 doc/cookbook/file-upload/file-upload.cabal+1−1 doc/cookbook/https/https.cabal+1−1 doc/cookbook/jwt-and-basic-auth/jwt-and-basic-auth.cabal+1−1 doc/cookbook/pagination/pagination.cabal+1−1 doc/cookbook/structuring-apis/structuring-apis.cabal+1−1 doc/cookbook/using-custom-monad/using-custom-monad.cabal+1−1 doc/tutorial/tutorial.cabal+1−1 servant-client-core/servant-client-core.cabal+1−1 servant-client/servant-client.cabal+1−1 servant-docs/servant-docs.cabal+1−1 servant-foreign/servant-foreign.cabal+1−1 servant-server/servant-server.cabal+1−1 servant/servant.cabal+1−1 servant/src/Servant/Utils/Links.hs+123−75 servant/test/Servant/Utils/LinksSpec.hs+1−1
.travis.yml+8−12
@@ -33,9 +33,9 @@ before_cache:
3333
3434matrix:
3535 include:
36− - compiler: "ghc-8.4.2"
36+ - compiler: "ghc-8.4.3"
3737 # env: TEST=--disable-tests BENCH=--disable-benchmarks
38− addons: {apt: {packages: [ghc-ppa-tools,ghc-8.4.2], sources: [hvr-ghc]}}
38+ addons: {apt: {packages: [ghc-ppa-tools,ghc-8.4.3], sources: [hvr-ghc]}}
3939 - compiler: "ghc-8.2.2"
4040 # env: TEST=--disable-tests BENCH=--disable-benchmarks
4141 addons: {apt: {packages: [ghc-ppa-tools,ghc-8.2.2], sources: [hvr-ghc]}}
@@ -83,9 +83,9 @@ install:
8383 - rm -fv cabal.project cabal.project.local
8484 - "if [ $HCNUMVER -ge 70800 ]; then sed -i.bak 's/-- ghc-options:.*/ghc-options: -j2/' ${HOME}/.cabal/config; fi"
8585 - grep -Ev -- '^\s*--' ${HOME}/.cabal/config | grep -Ev '^\s*$'
86− - "printf 'packages: \"servant\" \"servant-client\" \"servant-client-core\" \"servant-docs\" \"servant-foreign\" \"servant-server\" \"doc/tutorial\" \"doc/cookbook/basic-auth\" \"doc/cookbook/db-postgres-pool\" \"doc/cookbook/db-sqlite-simple\" \"doc/cookbook/file-upload\" \"doc/cookbook/https\" \"doc/cookbook/pagination\" \"doc/cookbook/structuring-apis\" \"doc/cookbook/using-custom-monad\"\\n' > cabal.project"
86+ - "printf 'packages: \"servant\" \"servant-client\" \"servant-client-core\" \"servant-docs\" \"servant-foreign\" \"servant-server\" \"doc/tutorial\" \"doc/cookbook/basic-auth\" \"doc/cookbook/db-postgres-pool\" \"doc/cookbook/db-sqlite-simple\" \"doc/cookbook/https\" \"doc/cookbook/pagination\" \"doc/cookbook/structuring-apis\" \"doc/cookbook/using-custom-monad\"\\n' > cabal.project"
8787 - "echo 'constraints: foundation >=0.0.14,memory <0.14.12 || >0.14.12' >> cabal.project"
88− - "echo 'allow-newer: servant-js:servant,servant-js:servant-foreign,servant-auth-server:http-types,servant-multipart:lens,servant-multipart:resourcet,servant-multipart:servant,servant-multipart:servant-server,servant-auth-server:servant-server, http-media:base' >> cabal.project"
88+ - "echo 'allow-newer: servant-auth-server:http-types,servant-auth-server:servant-server, http-media:base' >> cabal.project"
8989 - cat cabal.project
9090 - if [ -f "servant/configure.ac" ]; then
9191 (cd "servant" && autoreconf -i);
@@ -117,9 +117,6 @@ install:
117117 - if [ -f "doc/cookbook/db-sqlite-simple/configure.ac" ]; then
118118 (cd "doc/cookbook/db-sqlite-simple" && autoreconf -i);
119119 fi
120− - if [ -f "doc/cookbook/file-upload/configure.ac" ]; then
121− (cd "doc/cookbook/file-upload" && autoreconf -i);
122− fi
123120 - if [ -f "doc/cookbook/https/configure.ac" ]; then
124121 (cd "doc/cookbook/https" && autoreconf -i);
125122 fi
@@ -133,7 +130,7 @@ install:
133130 (cd "doc/cookbook/using-custom-monad" && autoreconf -i);
134131 fi
135132 - rm -f cabal.project.freeze
136− - rm -rf .ghc.environment.* "servant"/dist "servant-client"/dist "servant-client-core"/dist "servant-docs"/dist "servant-foreign"/dist "servant-server"/dist "doc/tutorial"/dist "doc/cookbook/basic-auth"/dist "doc/cookbook/db-postgres-pool"/dist "doc/cookbook/db-sqlite-simple"/dist "doc/cookbook/file-upload"/dist "doc/cookbook/https"/dist "doc/cookbook/pagination"/dist "doc/cookbook/structuring-apis"/dist "doc/cookbook/using-custom-monad"/dist
133+ - rm -rf .ghc.environment.* "servant"/dist "servant-client"/dist "servant-client-core"/dist "servant-docs"/dist "servant-foreign"/dist "servant-server"/dist "doc/tutorial"/dist "doc/cookbook/basic-auth"/dist "doc/cookbook/db-postgres-pool"/dist "doc/cookbook/db-sqlite-simple"/dist "doc/cookbook/https"/dist "doc/cookbook/pagination"/dist "doc/cookbook/structuring-apis"/dist "doc/cookbook/using-custom-monad"/dist
137134 - DISTDIR=$(mktemp -d /tmp/dist-test.XXXX)
138135
139136# Here starts the actual work to be performed for the package under test;
@@ -151,19 +148,18 @@ script:
151148 - (cd "doc/cookbook/basic-auth" && cabal sdist)
152149 - (cd "doc/cookbook/db-postgres-pool" && cabal sdist)
153150 - (cd "doc/cookbook/db-sqlite-simple" && cabal sdist)
154− - (cd "doc/cookbook/file-upload" && cabal sdist)
155151 - (cd "doc/cookbook/https" && cabal sdist)
156152 - (cd "doc/cookbook/pagination" && cabal sdist)
157153 - (cd "doc/cookbook/structuring-apis" && cabal sdist)
158154 - (cd "doc/cookbook/using-custom-monad" && cabal sdist)
159155 - echo -en 'travis_fold:end:sdist\\r'
160156 - echo Unpacking... && echo -en 'travis_fold:start:unpack\\r'
161− - mv "servant"/dist/servant-*.tar.gz "servant-client"/dist/servant-client-*.tar.gz "servant-client-core"/dist/servant-client-core-*.tar.gz "servant-docs"/dist/servant-docs-*.tar.gz "servant-foreign"/dist/servant-foreign-*.tar.gz "servant-server"/dist/servant-server-*.tar.gz "doc/tutorial"/dist/tutorial-*.tar.gz "doc/cookbook/basic-auth"/dist/cookbook-basic-auth-*.tar.gz "doc/cookbook/db-postgres-pool"/dist/cookbook-db-postgres-pool-*.tar.gz "doc/cookbook/db-sqlite-simple"/dist/cookbook-db-sqlite-simple-*.tar.gz "doc/cookbook/file-upload"/dist/cookbook-file-upload-*.tar.gz "doc/cookbook/https"/dist/cookbook-https-*.tar.gz "doc/cookbook/pagination"/dist/cookbook-pagination-*.tar.gz "doc/cookbook/structuring-apis"/dist/cookbook-structuring-apis-*.tar.gz "doc/cookbook/using-custom-monad"/dist/cookbook-using-custom-monad-*.tar.gz ${DISTDIR}/
157+ - mv "servant"/dist/servant-*.tar.gz "servant-client"/dist/servant-client-*.tar.gz "servant-client-core"/dist/servant-client-core-*.tar.gz "servant-docs"/dist/servant-docs-*.tar.gz "servant-foreign"/dist/servant-foreign-*.tar.gz "servant-server"/dist/servant-server-*.tar.gz "doc/tutorial"/dist/tutorial-*.tar.gz "doc/cookbook/basic-auth"/dist/cookbook-basic-auth-*.tar.gz "doc/cookbook/db-postgres-pool"/dist/cookbook-db-postgres-pool-*.tar.gz "doc/cookbook/db-sqlite-simple"/dist/cookbook-db-sqlite-simple-*.tar.gz "doc/cookbook/https"/dist/cookbook-https-*.tar.gz "doc/cookbook/pagination"/dist/cookbook-pagination-*.tar.gz "doc/cookbook/structuring-apis"/dist/cookbook-structuring-apis-*.tar.gz "doc/cookbook/using-custom-monad"/dist/cookbook-using-custom-monad-*.tar.gz ${DISTDIR}/
162158 - cd ${DISTDIR} || false
163159 - find . -maxdepth 1 -name '*.tar.gz' -exec tar -xvf '{}' \;
164− - "printf 'packages: servant-*/*.cabal servant-client-*/*.cabal servant-client-core-*/*.cabal servant-docs-*/*.cabal servant-foreign-*/*.cabal servant-server-*/*.cabal tutorial-*/*.cabal cookbook-basic-auth-*/*.cabal cookbook-db-postgres-pool-*/*.cabal cookbook-db-sqlite-simple-*/*.cabal cookbook-file-upload-*/*.cabal cookbook-https-*/*.cabal cookbook-pagination-*/*.cabal cookbook-structuring-apis-*/*.cabal cookbook-using-custom-monad-*/*.cabal\\n' > cabal.project"
160+ - "printf 'packages: servant-*/*.cabal servant-client-*/*.cabal servant-client-core-*/*.cabal servant-docs-*/*.cabal servant-foreign-*/*.cabal servant-server-*/*.cabal tutorial-*/*.cabal cookbook-basic-auth-*/*.cabal cookbook-db-postgres-pool-*/*.cabal cookbook-db-sqlite-simple-*/*.cabal cookbook-https-*/*.cabal cookbook-pagination-*/*.cabal cookbook-structuring-apis-*/*.cabal cookbook-using-custom-monad-*/*.cabal\\n' > cabal.project"
165161 - "echo 'constraints: foundation >=0.0.14,memory <0.14.12 || >0.14.12' >> cabal.project"
166− - "echo 'allow-newer: servant-js:servant,servant-js:servant-foreign,servant-auth-server:http-types,servant-multipart:lens,servant-multipart:resourcet,servant-multipart:servant,servant-multipart:servant-server,servant-auth-server:servant-server, http-media:base' >> cabal.project"
162+ - "echo 'allow-newer: servant-auth-server:http-types,servant-auth-server:servant-server, http-media:base' >> cabal.project"
167163 - cat cabal.project
168164 - echo -en 'travis_fold:end:unpack\\r'
169165
cabal.project+3−1
@@ -11,8 +11,10 @@ packages: servant/
1111 doc/cookbook/basic-auth
1212 doc/cookbook/db-postgres-pool
1313 doc/cookbook/db-sqlite-simple
14− doc/cookbook/file-upload
14+ -- MkLink changed
15+ -- doc/cookbook/file-upload
1516 doc/cookbook/https
17+ -- servant-auth-* doesn't support GHC-8.4
1618 -- doc/cookbook/jwt-and-basic-auth
1719 doc/cookbook/pagination
1820 doc/cookbook/structuring-apis
doc/cookbook/basic-auth/basic-auth.cabal+1−1
@@ -8,7 +8,7 @@ author: Servant Contributors
88maintainer: haskell-servant-maintainers@googlegroups.com
99build-type: Simple
1010cabal-version: >=1.10
11−tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.2, GHC==8.4.2
11+tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.2, GHC==8.4.3
1212
1313executable cookbook-basic-auth
1414 main-is: BasicAuth.lhs
doc/cookbook/db-postgres-pool/db-postgres-pool.cabal+1−1
@@ -8,7 +8,7 @@ author: Servant Contributors
88maintainer: haskell-servant-maintainers@googlegroups.com
99build-type: Simple
1010cabal-version: >=1.10
11−tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.2, GHC==8.4.2
11+tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.2, GHC==8.4.3
1212
1313executable cookbook-db-postgres-pool
1414 main-is: PostgresPool.lhs
doc/cookbook/db-sqlite-simple/db-sqlite-simple.cabal+1−1
@@ -8,7 +8,7 @@ author: Servant Contributors
88maintainer: haskell-servant-maintainers@googlegroups.com
99build-type: Simple
1010cabal-version: >=1.10
11−tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.2, GHC==8.4.2
11+tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.2, GHC==8.4.3
1212
1313executable cookbook-db-sqlite-simple
1414 main-is: DBConnection.lhs
doc/cookbook/file-upload/file-upload.cabal+1−1
@@ -8,7 +8,7 @@ author: Servant Contributors
88maintainer: haskell-servant-maintainers@googlegroups.com
99build-type: Simple
1010cabal-version: >=1.10
11−tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.2, GHC==8.4.2
11+tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.2, GHC==8.4.3
1212
1313executable cookbook-file-upload
1414 main-is: FileUpload.lhs
doc/cookbook/https/https.cabal+1−1
@@ -8,7 +8,7 @@ author: Servant Contributors
88maintainer: haskell-servant-maintainers@googlegroups.com
99build-type: Simple
1010cabal-version: >=1.10
11−tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.2, GHC==8.4.2
11+tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.2, GHC==8.4.3
1212
1313executable cookbook-https
1414 main-is: Https.lhs
doc/cookbook/jwt-and-basic-auth/jwt-and-basic-auth.cabal+1−1
@@ -11,7 +11,7 @@ maintainer: haskell-servant-maintainers@googlegroups.com
1111category: Servant
1212build-type: Simple
1313cabal-version: >=1.10
14−tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.2, GHC==8.4.2
14+tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.2, GHC==8.4.3
1515
1616executable cookbook-jwt-and-basic-auth
1717 if !impl(ghc >= 7.10)
doc/cookbook/pagination/pagination.cabal+1−1
@@ -11,7 +11,7 @@ cabal-version: >=1.10
1111extra-source-files:
1212 Pagination.lhs
1313 dummy/Pagination.lhs
14−tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.2, GHC==8.4.2
14+tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.2, GHC==8.4.3
1515
1616executable cookbook-pagination
1717 main-is: Pagination.lhs
doc/cookbook/structuring-apis/structuring-apis.cabal+1−1
@@ -8,7 +8,7 @@ author: Servant Contributors
88maintainer: haskell-servant-maintainers@googlegroups.com
99build-type: Simple
1010cabal-version: >=1.10
11−tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.2, GHC==8.4.2
11+tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.2, GHC==8.4.3
1212
1313executable cookbook-structuring-apis
1414 main-is: StructuringApis.lhs
doc/cookbook/using-custom-monad/using-custom-monad.cabal+1−1
@@ -8,7 +8,7 @@ author: Servant Contributors
88maintainer: haskell-servant-maintainers@googlegroups.com
99build-type: Simple
1010cabal-version: >=1.10
11−tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.2, GHC==8.4.2
11+tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.2, GHC==8.4.3
1212
1313executable cookbook-using-custom-monad
1414 main-is: UsingCustomMonad.lhs
doc/tutorial/tutorial.cabal+1−1
@@ -17,7 +17,7 @@ tested-with:
1717 GHC==7.10.3
1818 GHC==8.0.2
1919 GHC==8.2.2
20− GHC==8.4.2
20+ GHC==8.4.3
2121extra-source-files:
2222 static/index.html
2323 static/ui.js
servant-client-core/servant-client-core.cabal+1−1
@@ -23,7 +23,7 @@ tested-with:
2323 GHC==7.10.3
2424 GHC==8.0.2
2525 GHC==8.2.2
26− GHC==8.4.2
26+ GHC==8.4.3
2727
2828source-repository head
2929 type: git
servant-client/servant-client.cabal+1−1
@@ -21,7 +21,7 @@ tested-with:
2121 GHC==7.10.3
2222 GHC==8.0.2
2323 GHC==8.2.2
24− GHC==8.4.2
24+ GHC==8.4.3
2525homepage: http://haskell-servant.readthedocs.org/
2626Bug-reports: http://github.com/haskell-servant/servant/issues
2727extra-source-files:
servant-docs/servant-docs.cabal+1−1
@@ -21,7 +21,7 @@ tested-with:
2121 GHC==7.10.3
2222 GHC==8.0.2
2323 GHC==8.2.2
24− GHC==8.4.2
24+ GHC==8.4.3
2525homepage: http://haskell-servant.readthedocs.org/
2626Bug-reports: http://github.com/haskell-servant/servant/issues
2727extra-source-files:
servant-foreign/servant-foreign.cabal+1−1
@@ -27,7 +27,7 @@ tested-with:
2727 GHC==7.10.3
2828 GHC==8.0.2
2929 GHC==8.2.2
30− GHC==8.4.2
30+ GHC==8.4.3
3131
3232source-repository head
3333 type: git
servant-server/servant-server.cabal+1−1
@@ -26,7 +26,7 @@ tested-with:
2626 GHC==7.10.3
2727 GHC==8.0.2
2828 GHC==8.2.2
29− GHC==8.4.2
29+ GHC==8.4.3
3030extra-source-files:
3131 include/*.h
3232 CHANGELOG.md
servant/servant.cabal+1−1
@@ -22,7 +22,7 @@ tested-with:
2222 GHC==7.10.3
2323 GHC==8.0.2
2424 GHC==8.2.2
25− GHC==8.4.2
25+ GHC==8.4.3
2626extra-source-files:
2727 include/*.h
2828 CHANGELOG.md
servant/src/Servant/Utils/Links.hs+123−75
@@ -19,8 +19,6 @@
1919-- >>> import Servant.Utils.Links
2020-- >>> import Data.Proxy
2121-- >>>
22−-- >>>
23−-- >>>
2422-- >>> type Hello = "hello" :> Get '[JSON] Int
2523-- >>> type Bye = "bye" :> QueryParam "name" String :> Delete '[JSON] NoContent
2624-- >>> type API = Hello :<|> Bye
@@ -63,10 +61,24 @@
6361-- >>> :set -XConstraintKinds
6462-- >>> :{
6563-- >>> let apiLink :: (IsElem endpoint API, HasLink endpoint)
66−-- >>> => Proxy endpoint -> MkLink endpoint
64+-- >>> => Proxy endpoint -> MkLink endpoint Link
6765-- >>> apiLink = safeLink api
6866-- >>> :}
6967--
68+-- `safeLink'` allows to make specialise the output:
69+--
70+-- >>> safeLink' toUrlPiece api without
71+-- "bye"
72+--
73+-- >>> :{
74+-- >>> let apiTextLink :: (IsElem endpoint API, HasLink endpoint)
75+-- >>> => Proxy endpoint -> MkLink endpoint Text
76+-- >>> apiTextLink = safeLink' toUrlPiece api
77+-- >>> :}
78+--
79+-- >>> apiTextLink without
80+-- "bye"
81+--
7082-- Attempting to construct a link to an endpoint that does not exist in api
7183-- will result in a type error like this:
7284--
@@ -86,7 +98,9 @@ module Servant.Utils.Links (
8698 --
8799 -- | Note that 'URI' is from the "Network.URI" module in the @network-uri@ package.
88100 safeLink
101+ , safeLink'
89102 , allLinks
103+ , allLinks'
90104 , URI(..)
91105 -- * Adding custom types
92106 , HasLink(..)
@@ -109,8 +123,6 @@ import Data.Singletons.Bool
109123 (SBool (..), SBoolI (..))
110124import qualified Data.Text as Text
111125import qualified Data.Text.Encoding as TE
112−import Data.Type.Bool
113− (If)
114126import Data.Type.Bool
115127 (If)
116128import GHC.TypeLits
@@ -278,8 +290,18 @@ safeLink
278290 :: forall endpoint api. (IsElem endpoint api, HasLink endpoint)
279291 => Proxy api -- ^ The whole API that this endpoint is a part of
280292 -> Proxy endpoint -- ^ The API endpoint you would like to point to
281− -> MkLink endpoint
282−safeLink _ endpoint = toLink endpoint (Link mempty mempty)
293+ -> MkLink endpoint Link
294+safeLink = safeLink' id
295+
296+-- | More general 'safeLink'.
297+--
298+safeLink'
299+ :: forall endpoint api a. (IsElem endpoint api, HasLink endpoint)
300+ => (Link -> a)
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
304+safeLink' toA _ endpoint = toLink toA endpoint (Link mempty mempty)
283305
284306-- | Create all links in an API.
285307--
@@ -295,37 +317,47 @@ safeLink _ endpoint = toLink endpoint (Link mempty mempty)
295317--
296318-- Note: nested APIs don't work well with this approach
297319--
298−-- >>> :kind! MkLink (Capture "nest" Char :> (Capture "x" Int :> Get '[JSON] Int :<|> Capture "y" Double :> Get '[JSON] Double))
299−-- 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
321+-- MkLink (Capture "nest" Char :> (Capture "x" Int :> Get '[JSON] Int :<|> Capture "y" Double :> Get '[JSON] Double)) Link :: *
300322-- = Char -> (Int -> Link) :<|> (Double -> Link)
301−--
302323allLinks
303324 :: forall api. HasLink api
304325 => Proxy api
305− -> MkLink api
306−allLinks api = toLink api (Link mempty mempty)
326+ -> MkLink api Link
327+allLinks = allLinks' id
328+
329+-- | More general 'allLinks'. See `safeLink'`.
330+allLinks'
331+ :: forall api a. HasLink api
332+ => (Link -> a)
333+ -> Proxy api
334+ -> MkLink api a
335+allLinks' toA api = toLink toA api (Link mempty mempty)
307336
308337-- | Construct a toLink for an endpoint.
309338class HasLink endpoint where
310− type MkLink endpoint
311− toLink :: Proxy endpoint -- ^ The API endpoint you would like to point to
312− -> Link
313− -> MkLink endpoint
339+ type MkLink endpoint (a :: *)
340+ toLink
341+ :: (Link -> a)
342+ -> Proxy endpoint -- ^ The API endpoint you would like to point to
343+ -> Link
344+ -> MkLink endpoint a
314345
315346-- Naked symbol instance
316347instance (KnownSymbol sym, HasLink sub) => HasLink (sym :> sub) where
317− type MkLink (sym :> sub) = MkLink sub
318− toLink _ =
319− toLink (Proxy :: Proxy sub) . addSegment (escaped seg)
348+ type MkLink (sym :> sub) a = MkLink sub a
349+ toLink toA _ =
350+ toLink toA (Proxy :: Proxy sub) . addSegment (escaped seg)
320351 where
321352 seg = symbolVal (Proxy :: Proxy sym)
322353
323354-- QueryParam instances
324355instance (KnownSymbol sym, ToHttpApiData v, HasLink sub, SBoolI (FoldRequired mods))
325− => HasLink (QueryParam' mods sym v :> sub) where
326− type MkLink (QueryParam' mods sym v :> sub) = If (FoldRequired mods) v (Maybe v) -> MkLink sub
327− toLink _ l mv =
328− toLink (Proxy :: Proxy sub) $
356+ => HasLink (QueryParam' mods sym v :> sub)
357+ where
358+ type MkLink (QueryParam' mods sym v :> sub) a = If (FoldRequired mods) v (Maybe v) -> MkLink sub a
359+ toLink toA _ l mv =
360+ toLink toA (Proxy :: Proxy sub) $
329361 case sbool :: SBool (FoldRequired mods) of
330362 STrue -> (addQueryParam . SingleParam k . toQueryParam) mv l
331363 SFalse -> maybe id (addQueryParam . SingleParam k . toQueryParam) mv l
@@ -334,105 +366,121 @@ instance (KnownSymbol sym, ToHttpApiData v, HasLink sub, SBoolI (FoldRequired mo
334366 k = symbolVal (Proxy :: Proxy sym)
335367
336368instance (KnownSymbol sym, ToHttpApiData v, HasLink sub)
337− => HasLink (QueryParams sym v :> sub) where
338− type MkLink (QueryParams sym v :> sub) = [v] -> MkLink sub
339− toLink _ l =
340− toLink (Proxy :: Proxy sub) .
369+ => HasLink (QueryParams sym v :> sub)
370+ where
371+ type MkLink (QueryParams sym v :> sub) a = [v] -> MkLink sub a
372+ toLink toA _ l =
373+ toLink toA (Proxy :: Proxy sub) .
341374 foldl' (\l' v -> addQueryParam (ArrayElemParam k (toQueryParam v)) l') l
342375 where
343376 k = symbolVal (Proxy :: Proxy sym)
344377
345378instance (KnownSymbol sym, HasLink sub)
346− => HasLink (QueryFlag sym :> sub) where
347− type MkLink (QueryFlag sym :> sub) = Bool -> MkLink sub
348− toLink _ l False =
349− toLink (Proxy :: Proxy sub) l
350− toLink _ l True =
351− toLink (Proxy :: Proxy sub) $ addQueryParam (FlagParam k) l
379+ => HasLink (QueryFlag sym :> sub)
380+ where
381+ type MkLink (QueryFlag sym :> sub) a = Bool -> MkLink sub a
382+ toLink toA _ l False =
383+ toLink toA (Proxy :: Proxy sub) l
384+ toLink toA _ l True =
385+ toLink toA (Proxy :: Proxy sub) $ addQueryParam (FlagParam k) l
352386 where
353387 k = symbolVal (Proxy :: Proxy sym)
354388
355389-- :<|> instance - Generate all links at once
356390instance (HasLink a, HasLink b) => HasLink (a :<|> b) where
357− type MkLink (a :<|> b) = MkLink a :<|> MkLink b
358− toLink _ l = toLink (Proxy :: Proxy a) l :<|> toLink (Proxy :: Proxy b) l
391+ type MkLink (a :<|> b) r = MkLink a r :<|> MkLink b r
392+ toLink toA _ l = toLink toA (Proxy :: Proxy a) l :<|> toLink toA (Proxy :: Proxy b) l
359393
360394-- Misc instances
361395instance HasLink sub => HasLink (ReqBody' mods ct a :> sub) where
362− type MkLink (ReqBody' mods ct a :> sub) = MkLink sub
363− toLink _ = toLink (Proxy :: Proxy sub)
396+ type MkLink (ReqBody' mods ct a :> sub) r = MkLink sub r
397+ toLink toA _ = toLink toA (Proxy :: Proxy sub)
364398
365399instance (ToHttpApiData v, HasLink sub)
366− => HasLink (Capture' mods sym v :> sub) where
367− type MkLink (Capture' mods sym v :> sub) = v -> MkLink sub
368− toLink _ l v =
369− toLink (Proxy :: Proxy sub) $
400+ => HasLink (Capture' mods sym v :> sub)
401+ where
402+ type MkLink (Capture' mods sym v :> sub) a = v -> MkLink sub a
403+ toLink toA _ l v =
404+ toLink toA (Proxy :: Proxy sub) $
370405 addSegment (escaped . Text.unpack $ toUrlPiece v) l
371406
372407instance (ToHttpApiData v, HasLink sub)
373− => HasLink (CaptureAll sym v :> sub) where
374− type MkLink (CaptureAll sym v :> sub) = [v] -> MkLink sub
375− toLink _ l vs =
376− toLink (Proxy :: Proxy sub) $
377− foldl' (flip $ addSegment . escaped . Text.unpack . toUrlPiece) l vs
408+ => HasLink (CaptureAll sym v :> sub)
409+ where
410+ type MkLink (CaptureAll sym v :> sub) a = [v] -> MkLink sub a
411+ toLink toA _ l vs = toLink toA (Proxy :: Proxy sub) $
412+ foldl' (flip $ addSegment . escaped . Text.unpack . toUrlPiece) l vs
378413
379−instance HasLink sub => HasLink (Header' mods sym a :> sub) where
380− type MkLink (Header' mods sym a :> sub) = MkLink sub
381− toLink _ = toLink (Proxy :: Proxy sub)
414+instance HasLink sub => HasLink (Header' mods sym (a :: *) :> sub) where
415+ type MkLink (Header' mods sym a :> sub) r = MkLink sub r
416+ toLink = simpleToLink (Proxy :: Proxy sub)
382417
383418instance HasLink sub => HasLink (Vault :> sub) where
384− type MkLink (Vault :> sub) = MkLink sub
385− toLink _ = toLink (Proxy :: Proxy sub)
419+ type MkLink (Vault :> sub) a = MkLink sub a
420+ toLink = simpleToLink (Proxy :: Proxy sub)
386421
387422instance HasLink sub => HasLink (Description s :> sub) where
388− type MkLink (Description s :> sub) = MkLink sub
389− toLink _ = toLink (Proxy :: Proxy sub)
423+ type MkLink (Description s :> sub) a = MkLink sub a
424+ toLink = simpleToLink (Proxy :: Proxy sub)
390425
391426instance HasLink sub => HasLink (Summary s :> sub) where
392− type MkLink (Summary s :> sub) = MkLink sub
393− toLink _ = toLink (Proxy :: Proxy sub)
427+ type MkLink (Summary s :> sub) a = MkLink sub a
428+ toLink = simpleToLink (Proxy :: Proxy sub)
394429
395430instance HasLink sub => HasLink (HttpVersion :> sub) where
396− type MkLink (HttpVersion:> sub) = MkLink sub
397− toLink _ = toLink (Proxy :: Proxy sub)
431+ type MkLink (HttpVersion:> sub) a = MkLink sub a
432+ toLink = simpleToLink (Proxy :: Proxy sub)
398433
399434instance HasLink sub => HasLink (IsSecure :> sub) where
400− type MkLink (IsSecure :> sub) = MkLink sub
401− toLink _ = toLink (Proxy :: Proxy sub)
435+ type MkLink (IsSecure :> sub) a = MkLink sub a
436+ toLink = simpleToLink (Proxy :: Proxy sub)
402437
403438instance HasLink sub => HasLink (WithNamedContext name context sub) where
404− type MkLink (WithNamedContext name context sub) = MkLink sub
405− toLink _ = toLink (Proxy :: Proxy sub)
439+ type MkLink (WithNamedContext name context sub) a = MkLink sub a
440+ toLink toA _ = toLink toA (Proxy :: Proxy sub)
406441
407442instance HasLink sub => HasLink (RemoteHost :> sub) where
408− type MkLink (RemoteHost :> sub) = MkLink sub
409− toLink _ = toLink (Proxy :: Proxy sub)
443+ type MkLink (RemoteHost :> sub) a = MkLink sub a
444+ toLink = simpleToLink (Proxy :: Proxy sub)
410445
411446instance HasLink sub => HasLink (BasicAuth realm a :> sub) where
412− type MkLink (BasicAuth realm a :> sub) = MkLink sub
413− toLink _ = toLink (Proxy :: Proxy sub)
447+ type MkLink (BasicAuth realm a :> sub) r = MkLink sub r
448+ toLink = simpleToLink (Proxy :: Proxy sub)
414449
415450instance HasLink EmptyAPI where
416− type MkLink EmptyAPI = EmptyAPI
417− toLink _ _ = EmptyAPI
451+ type MkLink EmptyAPI a = EmptyAPI
452+ toLink _ _ _ = EmptyAPI
418453
419454-- Verb (terminal) instances
420455instance HasLink (Verb m s ct a) where
421− type MkLink (Verb m s ct a) = Link
422− toLink _ = id
456+ type MkLink (Verb m s ct a) r = r
457+ toLink toA _ = toA
423458
424459instance HasLink Raw where
425− type MkLink Raw = Link
426− toLink _ = id
460+ type MkLink Raw a = a
461+ toLink toA _ = toA
427462
428463instance HasLink (Stream m fr ct a) where
429− type MkLink (Stream m fr ct a) = Link
430− toLink _ = id
464+ type MkLink (Stream m fr ct a) r = r
465+ toLink toA _ = toA
431466
432467-- AuthProtext instances
433468instance HasLink sub => HasLink (AuthProtect tag :> sub) where
434− type MkLink (AuthProtect tag :> sub) = MkLink sub
435− toLink _ = toLink (Proxy :: Proxy sub)
469+ type MkLink (AuthProtect tag :> sub) a = MkLink sub a
470+ toLink = simpleToLink (Proxy :: Proxy sub)
471+
472+-- | Helper for implemneting 'toLink' for combinators not affecting link
473+-- structure.
474+simpleToLink
475+ :: forall sub a combinator.
476+ (HasLink sub, MkLink sub a ~ MkLink (combinator :> sub) a)
477+ => Proxy sub
478+ -> (Link -> a)
479+ -> Proxy (combinator :> sub)
480+ -> Link
481+ -> MkLink (combinator :> sub) a
482+simpleToLink _ toA _ = toLink toA (Proxy :: Proxy sub)
483+
436484
437485-- $setup
438486-- >>> import Servant.API
servant/test/Servant/Utils/LinksSpec.hs+1−1
@@ -41,7 +41,7 @@ type LinkableApi =
4141
4242
4343apiLink :: (IsElem endpoint TestApi, HasLink endpoint)
44− => Proxy endpoint -> MkLink endpoint
44+ => Proxy endpoint -> MkLink endpoint Link
4545apiLink = safeLink (Proxy :: Proxy TestApi)
4646
4747-- | Convert a link to a URI and ensure that this maps to the given string