Merge pull request #500 from branch14/improve-some-docs

Improve some docs
This commit is contained in:
Tommi Reiman 2021-07-27 21:22:05 +03:00 committed by GitHub
commit 7a1cc78a80
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 45 additions and 45 deletions

View file

@ -1,10 +1,10 @@
# RESTful form methods
When designing RESTful applications you will be doing a lot of "PATCH" and "DELETE" request, but most browsers don't support methods other than "GET" and "POST" when it comes to submitting forms.
When designing RESTful applications you will be doing a lot of "PATCH" and "DELETE" request, but most browsers don't support methods other than "GET" and "POST" when it comes to submitting forms.
There is a pattern to solve this (pioneered by Rails) using a hidden "_method" field in the form and swapping out the "POST" method for whatever is in that field.
We can do this with middleware in reitit like this:
We can do this with middleware in reitit like this:
```clj
(defn- hidden-method
[request]
@ -18,20 +18,20 @@ We can do this with middleware in reitit like this:
:wrap (fn [handler]
(fn [request]
(if-let [fm (and (= :post (:request-method request)) ;; if this is a :post request
(hidden-method request))] ;; and there is a "_method" field
(hidden-method request))] ;; and there is a "_method" field
(handler (assoc request :request-method fm)) ;; replace :request-method
(handler request))))})
```
And apply the middleware like this:
And apply the middleware like this:
```clj
(reitit.ring/ring-handler
(reitit.ring/router ...)
(reitit.ring/create-default-handler)
{:middleware
{:middleware
[reitit.ring.middleware.parameters/parameters-middleware ;; needed to have :form-params in the request map
reitit.ring.middleware.multipart/multipart-middleware ;; needed to have :multipart-params in the request map
wrap-hidden-method]}) ;; our hidden method wrapper
```
(NOTE: This middleware must be placed here and not inside the route data given to `reitit.ring/handler`.
(NOTE: This middleware must be placed here and not inside the route data given to `reitit.ring/handler`.
This is so that our middleware is applied before reitit matches the request with a specific handler using the wrong method.)

View file

@ -39,7 +39,7 @@ Responses are defined in route data under `:responses` key. It's value should be
Below is an example with [Plumatic Schema](https://github.com/plumatic/schema). It defines schemas for `:query`, `:body` and `:path` parameters and for http 200 response `:body`.
Handler can access the coerced parameters can be read under `:parameters` key in the request.
Handlers can access the coerced parameters via the `:parameters` key in the request.
```clj
(require '[reitit.coercion.schema])
@ -71,7 +71,7 @@ Defining a coercion for a route data doesn't do anything, as it's just data. We
### Full example
Here's an full example for applying coercion with Reitit, Ring and Schema:
Here is a full example for applying coercion with Reitit, Ring and Schema:
```clj
(require '[reitit.ring.coercion :as rrc])
@ -150,7 +150,7 @@ Invalid response:
## Pretty printing spec errors
Spec problems are exposed as-is into request & response coercion errors, enabling pretty-printers like [expound](https://github.com/bhb/expound) to be used:
Spec problems are exposed as is in request & response coercion errors. Pretty-printers like [expound](https://github.com/bhb/expound) can be enabled like this:
```clj
(require '[reitit.ring :as ring])
@ -216,7 +216,7 @@ Spec problems are exposed as-is into request & response coercion errors, enablin
### Optimizations
The coercion middleware are [compiled against a route](compiling_middleware.md). In the middleware compilation step the actual coercer implementations are constructed for the defined models. Also, the middleware doesn't mount itself if a route doesn't have `:coercion` and `:parameters` or `:responses` defined.
The coercion middlewares are [compiled against a route](compiling_middleware.md). In the middleware compilation step the actual coercer implementations are constructed for the defined models. Also, the middleware doesn't mount itself if a route doesn't have `:coercion` and `:parameters` or `:responses` defined.
We can query the compiled middleware chain for the routes:

View file

@ -1,12 +1,12 @@
# Compiling Middleware
The [dynamic extensions](dynamic_extensions.md) is a easy way to extend the system. To enable fast lookups into route data, we can compile them into any shape (records, functions etc.) we want, enabling fast access at request-time.
The [dynamic extensions](dynamic_extensions.md) are an easy way to extend the system. To enable fast lookup of route data, we can compile them into any shape (records, functions etc.), enabling fast access at request-time.
But, we can do much better. As we know the exact route that middleware/interceptor is linked to, we can pass the (compiled) route information into the middleware at creation-time. It can do local reasoning: extract and transform relevant data just for it and pass the optimized data into the actual request-handler via a closure - yielding much faster runtime processing. Middleware can also decide not to mount itself by returning `nil`. Why mount a `wrap-enforce-roles` middleware for a route if there are no roles required for it?
But, we can do much better. As we know the exact route that a middleware/interceptor is linked to, we can pass the (compiled) route information into the middleware at creation-time. It can do local reasoning: Extract and transform relevant data just for it and pass the optimized data into the actual request-handler via a closure - yielding much faster runtime processing. A middleware can also decide not to mount itself by returning `nil`. (E.g. Why mount a `wrap-enforce-roles` middleware for a route if there are no roles required for it?)
To enable this we use [middleware records](data_driven_middleware.md) `:compile` key instead of the normal `:wrap`. `:compile` expects a function of `route-data router-opts => ?IntoMiddleware`.
To demonstrate the two approaches, below are response coercion middleware written as normal ring middleware function and as middleware record with `:compile`.
To demonstrate the two approaches, below is the response coercion middleware written as normal ring middleware function and as middleware record with `:compile`.
## Normal Middleware

View file

@ -1,8 +1,8 @@
# Content Negotiation
Wrapper for [Muuntaja](https://github.com/metosin/muuntaja) middleware for content-negotiation, request decoding and response encoding. Takes explicit configuration via `:muuntaja` key in route data. Emit's [swagger](swagger.md) `:produces` and `:consumes` definitions automatically based on the Muuntaja configuration.
Wrapper for [Muuntaja](https://github.com/metosin/muuntaja) middleware for content negotiation, request decoding and response encoding. Takes explicit configuration via `:muuntaja` key in route data. Emits [swagger](swagger.md) `:produces` and `:consumes` definitions automatically based on the Muuntaja configuration.
Negotiates a request body based on `Content-Type` header and response body based on `Accept`, `Accept-Charset` headers. Publishes the negotiation results as `:muuntaja/request` and `:muuntaja/response` keys into the request.
Negotiates a request body based on `Content-Type` header and response body based on `Accept` and `Accept-Charset` headers. Publishes the negotiation results as `:muuntaja/request` and `:muuntaja/response` keys into the request.
Decodes the request body into `:body-params` using the `:muuntaja/request` key in request if the `:body-params` doesn't already exist.
@ -87,7 +87,7 @@ Server: Jetty(9.2.21.v20170120)
## Changing default parameters
The current JSON formatter used by `reitit` already have the option to parse keys as `keyword` which is a sane default in Clojure. However, if you would like to parse all the `double` as `bigdecimal` you'd need to change an option of the [JSON formatter](https://github.com/metosin/jsonista)
The current JSON formatter used by `reitit` already has the option to parse keys as `keyword` which is a sane default in Clojure. However, if you would like to parse all the `double` as `bigdecimal` you'd need to change an option of the [JSON formatter](https://github.com/metosin/jsonista)
```clj
@ -102,7 +102,7 @@ The current JSON formatter used by `reitit` already have the option to parse key
Now you should change the `m/instance` installed in the router with the `new-muuntaja-instance`.
You can find more options for [JSON](https://cljdoc.org/d/metosin/jsonista/0.2.5/api/jsonista.core#object-mapper) and [EDN].
Here you can find more options for [JSON](https://cljdoc.org/d/metosin/jsonista/0.2.5/api/jsonista.core#object-mapper) and EDN.
## Adding custom encoder
@ -125,9 +125,9 @@ The example below is from `muuntaja` explaining how to add a custom encoder to p
```
## Adding all together
## Putting it all together
If you inspect `m/default-options` it's only a map, therefore you can compose your new muuntaja instance with as many options as you need it.
If you inspect `m/default-options` you'll find it's only a map. This means you can compose your new muuntaja instance with as many options as you need.
```clj
(def new-muuntaja

View file

@ -1,19 +1,19 @@
# Data-driven Middleware
Ring [defines middleware](https://github.com/ring-clojure/ring/wiki/Concepts#middleware) as a function of type `handler & args => request => response`. It's relatively easy to understand and enables good performance. Downside is that the middleware-chain is just a opaque function, making things like debugging and composition hard. It's too easy to apply the middleware in wrong order.
Ring [defines middleware](https://github.com/ring-clojure/ring/wiki/Concepts#middleware) as a function of type `handler & args => request => response`. It is relatively easy to understand and allows for good performance. A downside is that the middleware chain is just a opaque function, making things like debugging and composition hard. It is too easy to apply the middlewares in wrong order.
Reitit defines middleware as data:
1. Middleware can be defined as first-class data entries
2. Middleware can be mounted as a [duct-style](https://github.com/duct-framework/duct/wiki/Configuration) vector (of middleware)
4. Middleware can be optimized & [compiled](compiling_middleware.md) against an endpoint
3. Middleware chain can be transformed by the router
1. A middleware can be defined as first-class data entries
2. A middleware can be mounted as a [duct-style](https://github.com/duct-framework/duct/wiki/Configuration) vector (of middlewares)
4. A middleware can be optimized & [compiled](compiling_middleware.md) against an endpoint
3. A middleware chain can be transformed by the router
## Middleware as data
All values in the `:middleware` vector in the route data are expanded into `reitit.middleware/Middleware` Records with using the `reitit.middleware/IntoMiddleware` Protocol. By default, functions, maps and `Middleware` records are allowed.
All values in the `:middleware` vector of route data are expanded into `reitit.middleware/Middleware` Records by using the `reitit.middleware/IntoMiddleware` Protocol. By default, functions, maps and `Middleware` records are allowed.
Records can have arbitrary keys, but the following keys have a special purpose:
Records can have arbitrary keys, but the following keys have special purpose:
| key | description |
| ---------------|-------------|
@ -22,13 +22,13 @@ Records can have arbitrary keys, but the following keys have a special purpose:
| `:wrap` | The actual middleware function of `handler & args => request => response`
| `:compile` | Middleware compilation function, see [compiling middleware](compiling_middleware.md).
Middleware Records are accessible in their raw form in the compiled route results, thus available for inventories, creating api-docs etc.
Middleware Records are accessible in their raw form in the compiled route results, and thus are available for inventories, creating api-docs, etc.
For the actual request processing, the Records are unwrapped into normal functions and composed into a middleware function chain, yielding zero runtime penalty.
### Creating Middleware
The following produce identical middleware runtime function.
The following examples produce identical middleware runtime functions.
### Function
@ -77,7 +77,7 @@ The following produce identical middleware runtime function.
:handler handler}}]])))
```
All the middleware are applied correctly:
All the middlewares are applied correctly:
```clj
(app {:request-method :get, :uri "/api/ping"})
@ -86,7 +86,7 @@ All the middleware are applied correctly:
## Compiling middleware
Middleware can be optimized against an endpoint using [middleware compilation](compiling_middleware.md).
Middlewares can be optimized against an endpoint using [middleware compilation](compiling_middleware.md).
## Ideas for the future

View file

@ -1,6 +1,6 @@
# Default handler
By default, if no routes match, `nil` is returned, which is not valid response in Ring:
By default, if no routes match, `nil` is returned, which is not a valid response in Ring:
```clj
(require '[reitit.ring :as ring])

View file

@ -4,7 +4,7 @@
[metosin/reitit-middleware "0.5.13"]
```
Any Ring middleware can be used with `reitit-ring`, but using data-driven middleware is preferred as they are easier to manage and in many cases, yield better performance. `reitit-middleware` contains a set of common ring middleware, lifted into data-driven middleware.
Any Ring middleware can be used with `reitit-ring`, but using data-driven middleware is preferred as they are easier to manage and in many cases yield better performance. `reitit-middleware` contains a set of common ring middleware, lifted into data-driven middleware.
* [Parameter Handling](#parameters-handling)
* [Exception Handling](#exception-handling)
@ -32,7 +32,7 @@ See [Content Negotiation](content_negotiation.md).
Wrapper for [Ring Multipart Middleware](https://github.com/ring-clojure/ring/blob/master/ring-core/src/ring/middleware/multipart_params.clj). Emits swagger `:consumes` definitions automatically.
Expected route data:
| key | description |
| -------------|-------------|
| `[:parameters :multipart]` | mounts only if defined for a route.

View file

@ -1,8 +1,8 @@
# Dynamic Extensions
`ring-handler` injects the `Match` into a request and it can be extracted at runtime with `reitit.ring/get-match`. This can be used to build ad-hoc extensions to the system.
`ring-handler` injects the `Match` into a request and it can be extracted at runtime with `reitit.ring/get-match`. This can be used to build ad hoc extensions to the system.
Example middleware to guard routes based on user roles:
This example shows a middleware to guard routes based on user roles:
```clj
(require '[reitit.ring :as ring])

View file

@ -4,7 +4,7 @@
[metosin/reitit-middleware "0.5.13"]
```
Exceptions thrown in router creation can be [handled with custom exception handler](../basics/error_messages.md). By default, exceptions thrown at runtime from a handler or a middleware are not caught by the `reitit.ring/ring-handler`. A good practise is a have an top-level exception handler to log and format the errors for clients.
Exceptions thrown in router creation can be [handled with custom exception handler](../basics/error_messages.md). By default, exceptions thrown at runtime from a handler or a middleware are not caught by the `reitit.ring/ring-handler`. A good practice is to have a top-level exception handler to log and format errors for clients.
```clj
(require '[reitit.ring.middleware.exception :as exception])
@ -36,7 +36,7 @@ A preconfigured middleware using `exception/default-handlers`. Catches:
### `exception/create-exception-middleware`
Creates the exception-middleware with custom options. Takes a map of `identifier => exception request => response` that is used to select the exception handler for the thrown/raised exception identifier. Exception identifier is either a `Keyword` or a Exception Class.
Creates the exception-middleware with custom options. Takes a map of `identifier => exception request => response` that is used to select the exception handler for the thrown/raised exception identifier. Exception identifier is either a `Keyword` or an Exception Class.
The following handlers are available by default:
@ -55,7 +55,7 @@ The handler is selected from the options map by exception identifier in the foll
2) Class of exception
3) `:type` ancestors of exception ex-data
4) Super Classes of exception
5) The ::default handler
5) The `::default` handler
```clj
;; type hierarchy
@ -94,7 +94,7 @@ The handler is selected from the options map by exception identifier in the foll
(def app
(ring/ring-handler
(ring/router
["/fail" (fn [_] (throw (ex-info "fail" {:type ::failue})))]
["/fail" (fn [_] (throw (ex-info "fail" {:type ::failure})))]
{:data {:middleware [exception-middleware]}})))
(app {:request-method :get, :uri "/fail"})
@ -102,6 +102,6 @@ The handler is selected from the options map by exception identifier in the foll
; => {:status 500,
; :body {:message "default"
; :exception clojure.lang.ExceptionInfo
; :data {:type :user/failue}
; :data {:type :user/failure}
; :uri "/fail"}}
```

View file

@ -11,8 +11,8 @@ Read more about the [Ring Concepts](https://github.com/ring-clojure/ring/wiki/Co
## `reitit.ring/ring-router`
`ring-router` is a higher order router, which adds support for `:request-method` based routing, [handlers](https://github.com/ring-clojure/ring/wiki/Concepts#handlers) and [middleware](https://github.com/ring-clojure/ring/wiki/Concepts#middleware).
It accepts the following options:
It accepts the following options:
| key | description |
| ----------------------------------------|-------------|
@ -53,7 +53,7 @@ Given a `ring-router`, optional default-handler & options, `ring-handler` functi
| key | description |
| ------------------|-------------|
| `:middleware` | Optional sequence of middleware that wrap the ring-handler"
| `:middleware` | Optional sequence of middlewares that wrap the ring-handler
| `:inject-match?` | Boolean to inject `match` into request under `:reitit.core/match` key (default true)
| `:inject-router?` | Boolean to inject `router` into request under `:reitit.core/router` key (default true)
@ -91,7 +91,7 @@ The router can be accessed via `get-router`:
# Request-method based routing
Handlers can be placed either to the top-level (all methods) or under a specific method (`:get`, `:head`, `:patch`, `:delete`, `:options`, `:post`, `:put` or `:trace`). Top-level handler is used if request-method based handler is not found.
Handlers can be placed either to the top-level (all methods) or under a specific method (`:get`, `:head`, `:patch`, `:delete`, `:options`, `:post`, `:put` or `:trace`). Top-level handler is used if request-method based handler is not found.
By default, the `:options` route is generated for all paths - to enable thing like [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing).
@ -196,7 +196,7 @@ Top-level middleware, applied before any routing is done:
(ring/router
["/api" {:middleware [[mw :api]]}
["/get" {:get handler}]])
nil
nil
{:middleware [[mw :top]]}))
(app {:request-method :get, :uri "/api/get"})