@@ -41,7 +41,7 @@ import qualified Data.ByteString as B
4141import qualified Data.ByteString.Builder as BB
4242import qualified Data.ByteString.Char8 as BC8
4343import qualified Data.ByteString.Lazy as BL
44−import Data.Constraint (Dict(..))
44+import Data.Constraint (Constraint, Dict(..))
4545import Data.Either
4646 (partitionEithers)
4747import Data.Maybe
@@ -56,7 +56,7 @@ import qualified Data.Text as T
5656import Data.Typeable
5757import GHC.Generics
5858import GHC.TypeLits
59− (KnownNat, KnownSymbol, symbolVal)
59+ (KnownNat, KnownSymbol, TypeError, symbolVal)
6060import qualified Network.HTTP.Media as NHM
6161import Network.HTTP.Types hiding
6262 (Header, ResponseHeaders)
@@ -90,6 +90,7 @@ import Servant.API.ResponseHeaders
9090import Servant.API.Status
9191 (statusFromNat)
9292import qualified Servant.Types.SourceT as S
93+import Servant.API.TypeErrors
9394import Web.HttpApiData
9495 (FromHttpApiData, parseHeader, parseQueryParam, parseUrlPiece,
9596 parseUrlPieces)
@@ -172,7 +173,7 @@ instance (HasServer a context, HasServer b context) => HasServer (a :<|> b) cont
172173-- > server = getBook
173174-- > where getBook :: Text -> Handler Book
174175-- > getBook isbn = ...
175−instance (KnownSymbol capture, FromHttpApiData a
176+instance (KnownSymbol capture, FromHttpApiData a, Typeable a
176177 , HasServer api context, SBoolI (FoldLenient mods)
177178 , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
178179 )
@@ -184,7 +185,7 @@ instance (KnownSymbol capture, FromHttpApiData a
184185 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
185186
186187 route Proxy context d =
187− CaptureRouter $
188+ CaptureRouter [hint] $
188189 route (Proxy :: Proxy api)
189190 context
190191 (addCapture d $ \ txt -> withRequest $ \ request ->
@@ -196,6 +197,7 @@ instance (KnownSymbol capture, FromHttpApiData a
196197 where
197198 rep = typeRep (Proxy :: Proxy Capture')
198199 formatError = urlParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
200+ hint = CaptureHint (T.pack $ symbolVal $ Proxy @capture) (typeRep (Proxy :: Proxy a))
199201
200202-- | If you use 'CaptureAll' in one of the endpoints for your API,
201203-- this automatically requires your server-side handler to be a
@@ -214,7 +216,7 @@ instance (KnownSymbol capture, FromHttpApiData a
214216-- > server = getSourceFile
215217-- > where getSourceFile :: [Text] -> Handler Book
216218-- > getSourceFile pathSegments = ...
217−instance (KnownSymbol capture, FromHttpApiData a
219+instance (KnownSymbol capture, FromHttpApiData a, Typeable a
218220 , HasServer api context
219221 , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
220222 )
@@ -226,7 +228,7 @@ instance (KnownSymbol capture, FromHttpApiData a
226228 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
227229
228230 route Proxy context d =
229− CaptureAllRouter $
231+ CaptureAllRouter [hint] $
230232 route (Proxy :: Proxy api)
231233 context
232234 (addCapture d $ \ txts -> withRequest $ \ request ->
@@ -237,6 +239,7 @@ instance (KnownSymbol capture, FromHttpApiData a
237239 where
238240 rep = typeRep (Proxy :: Proxy CaptureAll)
239241 formatError = urlParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
242+ hint = CaptureHint (T.pack $ symbolVal $ Proxy @capture) (typeRep (Proxy :: Proxy [a]))
240243
241244allowedMethodHead :: Method -> Request -> Bool
242245allowedMethodHead method request = method == methodGet && requestMethod request == methodHead
@@ -814,38 +817,15 @@ instance (HasContextEntry context (NamedContext name subContext), HasServer subA
814817 hoistServerWithContext _ _ nt s = hoistServerWithContext (Proxy :: Proxy subApi) (Proxy :: Proxy subContext) nt s
815818
816819-------------------------------------------------------------------------------
817−-- TypeError helpers
820+-- Custom type errors
818821-------------------------------------------------------------------------------
819822
820−-- | This instance catches mistakes when there are non-saturated
821−-- type applications on LHS of ':>'.
822−--
823−-- >>> serve (Proxy :: Proxy (Capture "foo" :> Get '[JSON] Int)) (error "...")
824−-- ...
825−-- ...Expected something of kind Symbol or *, got: k -> l on the LHS of ':>'.
826−-- ...Maybe you haven't applied enough arguments to
827−-- ...Capture' '[] "foo"
828−-- ...
829−--
830−-- >>> undefined :: Server (Capture "foo" :> Get '[JSON] Int)
831−-- ...
832−-- ...Expected something of kind Symbol or *, got: k -> l on the LHS of ':>'.
833−-- ...Maybe you haven't applied enough arguments to
834−-- ...Capture' '[] "foo"
835−-- ...
836−--
837−instance TypeError (HasServerArrowKindError arr) => HasServer ((arr :: k -> l) :> api) context
823+-- Erroring instance for 'HasServer' when a combinator is not fully applied
824+instance TypeError (PartialApplication HasServer arr) => HasServer ((arr :: a -> b) :> sub) context
838825 where
839− type ServerT (arr :> api) m = TypeError (HasServerArrowKindError arr)
840− -- it doesn't really matter what sub route we peak
841− route _ _ _ = error "servant-server panic: impossible happened in HasServer (arr :> api)"
842− hoistServerWithContext _ _ _ = id
843−
844−-- Cannot have TypeError here, otherwise use of this symbol will error :)
845−type HasServerArrowKindError arr =
846− 'Text "Expected something of kind Symbol or *, got: k -> l on the LHS of ':>'."
847− ':$$: 'Text "Maybe you haven't applied enough arguments to"
848− ':$$: 'ShowType arr
826+ type ServerT (arr :> sub) _ = TypeError (PartialApplication (HasServer :: * -> [*] -> Constraint) arr)
827+ route = error "unreachable"
828+ hoistServerWithContext _ _ _ _ = error "unreachable"
849829
850830-- | This instance prevents from accidentally using '->' instead of ':>'
851831--
@@ -880,6 +860,15 @@ type HasServerArrowTypeError a b =
880860 ':$$: 'Text "and"
881861 ':$$: 'ShowType b
882862
863+-- Erroring instances for 'HasServer' for unknown API combinators
864+
865+-- XXX: This omits the @context@ parameter, e.g.:
866+--
867+-- "There is no instance for HasServer (Bool :> …)". Do we care ?
868+instance {-# OVERLAPPABLE #-} TypeError (NoInstanceForSub HasServer ty) => HasServer (ty :> sub) context
869+
870+instance {-# OVERLAPPABLE #-} TypeError (NoInstanceFor (HasServer api context)) => HasServer api context
871+
883872-- | Ignore @'Fragment'@ in server handlers.
884873-- See <https://ietf.org/rfc/rfc2616.html#section-15.1.3> for more details.
885874--