diff --git a/docs/Routing.md b/docs/Routing.md index f168e0f..fc871be 100644 --- a/docs/Routing.md +++ b/docs/Routing.md @@ -8,6 +8,7 @@ * [Adding further types](#adding-further-types) * [Composing routes](#composing-routes) * [Reverse routing](#reverse-routing) + * [Catch-all route](#catch-all-route) * [Working with request headers](#working-with-request-headers) ## Routing introduction @@ -244,6 +245,51 @@ main = serve { port: 8080 } { route, router } redirect = headers [ Tuple "Location" $ print route $ New account ] ``` +### Catch-all route + +You can easily create a catch all route by using the predefined `catchAll`. + +```purescript +import HTTPurple + +data Route = CatchAll (Array String) -- list of paths +derive instance Generic Route _ + +route :: RouteDuplex' Route +route = mkRoute + { "CatchAll": catchAll + } + +main :: ServerM +main = serve {} { route, router } + where + router { route: CatchAll paths } = ok $ "hello 🗺!" +``` + +### Routing prefixes + +You can create a route prefix such as `/v1/` using the `prefix` method. Here is an example: + +```purescript +import HTTPurple + +data Route = Home -- list of paths +derive instance Generic Route _ + +route :: RouteDuplex' Route +route = root $ prefix "v1" $ sum + { "Home": noArgs + } + +main :: ServerM +main = serve {} { route, router } + where + router { route: Home } = ok $ "hello 🗺!" +``` + +Now all requests need to be prefixed with `/v1/`. + +**Note**: `prefix` takes exactly one path segment. If you need two prefixes, e.g. for `/api/v1/` you need to use two prefixes: `root $ prefix "api" $ prefix "v1" $ sum`. ## Matching HTTP Methods diff --git a/src/HTTPure.purs b/src/HTTPure.purs index d964068..0abcd81 100644 --- a/src/HTTPure.purs +++ b/src/HTTPure.purs @@ -30,7 +30,7 @@ import HTTPurple.Path (Path) import HTTPurple.Query (Query) import HTTPurple.Request (Request, fullPath) import HTTPurple.Response (Response, ResponseM, accepted, accepted', alreadyReported, alreadyReported', badGateway, badGateway', badRequest, badRequest', conflict, conflict', continue, continue', created, created', emptyResponse, emptyResponse', expectationFailed, expectationFailed', failedDependency, failedDependency', forbidden, forbidden', found, found', gatewayTimeout, gatewayTimeout', gone, gone', hTTPVersionNotSupported, hTTPVersionNotSupported', iMUsed, iMUsed', imATeapot, imATeapot', insufficientStorage, insufficientStorage', internalServerError, internalServerError', lengthRequired, lengthRequired', locked, locked', loopDetected, loopDetected', methodNotAllowed, methodNotAllowed', misdirectedRequest, misdirectedRequest', movedPermanently, movedPermanently', multiStatus, multiStatus', multipleChoices, multipleChoices', networkAuthenticationRequired, networkAuthenticationRequired', noContent, noContent', nonAuthoritativeInformation, nonAuthoritativeInformation', notAcceptable, notAcceptable', notExtended, notExtended', notFound, notFound', notImplemented, notImplemented', notModified, notModified', ok, ok', partialContent, partialContent', payloadTooLarge, payloadTooLarge', paymentRequired, paymentRequired', permanentRedirect, permanentRedirect', preconditionFailed, preconditionFailed', preconditionRequired, preconditionRequired', processing, processing', proxyAuthenticationRequired, proxyAuthenticationRequired', rangeNotSatisfiable, rangeNotSatisfiable', requestHeaderFieldsTooLarge, requestHeaderFieldsTooLarge', requestTimeout, requestTimeout', resetContent, resetContent', response, response', seeOther, seeOther', serviceUnavailable, serviceUnavailable', switchingProtocols, switchingProtocols', temporaryRedirect, temporaryRedirect', tooManyRequests, tooManyRequests', uRITooLong, uRITooLong', unauthorized, unauthorized', unavailableForLegalReasons, unavailableForLegalReasons', unprocessableEntity, unprocessableEntity', unsupportedMediaType, unsupportedMediaType', upgradeRequired, upgradeRequired', useProxy, useProxy', variantAlsoNegotiates, variantAlsoNegotiates') -import HTTPurple.Routes (type (<+>), combineRoutes, mkRoute, orElse, (<+>)) +import HTTPurple.Routes (type (<+>), combineRoutes, mkRoute, orElse, (<+>), catchAll) import HTTPurple.Server (ServerM, serve) import HTTPurple.Status (Status) import HTTPurple.Validation (fromValidated, fromValidatedE) diff --git a/src/HTTPurple/Routes.purs b/src/HTTPurple/Routes.purs index be4e0b6..18fe9f4 100644 --- a/src/HTTPurple/Routes.purs +++ b/src/HTTPurple/Routes.purs @@ -1,10 +1,12 @@ module HTTPurple.Routes ( (<+>) + , catchAll , combineRoutes , mkRoute , orElse , type (<+>) - ) where + ) + where import Prelude @@ -19,8 +21,10 @@ import Routing.Duplex as RD import Routing.Duplex.Generic as RG import Type.Proxy (Proxy(..)) +-- | Type-level operator two combine two route definitions. infixr 0 type Either as <+> +-- | Combine two routes combineRoutes :: forall left right. RD.RouteDuplex' left -> @@ -31,8 +35,10 @@ combineRoutes (RD.RouteDuplex lEnc lDec) (RD.RouteDuplex rEnc rDec) = (RD.RouteD enc = lEnc ||| rEnc dec = (lDec <#> Left) <|> (rDec <#> Right) +-- | Infix operator for `orElse` infixr 3 combineRoutes as <+> +-- | Combine two request handlers. orElse :: forall left right. (Request left -> ResponseM) -> @@ -42,6 +48,7 @@ orElse :: orElse leftRouter _ request@{ route: Left l } = leftRouter $ Record.set (Proxy :: _ "route") l request orElse _ rightRouter request@{ route: Right r } = rightRouter $ Record.set (Proxy :: _ "route") r request +-- | Make a route from a `RoudeDuplex` definition. mkRoute :: forall i iGen r. Generic i iGen => @@ -49,3 +56,7 @@ mkRoute :: Record r -> RD.RouteDuplex i i mkRoute = RD.root <<< RG.sum + +-- | A catch-all route that matches any request. +catchAll :: RD.RouteDuplex' (Array String) +catchAll = RD.many RD.segment