Compare commits

...

257 commits

Author SHA1 Message Date
Joel Kaasinen
7c1544c3ce
doc: document match-by-name :url-encode? option
Some checks failed
testsuite / Clojure 11 (Java 11) (push) Has been cancelled
testsuite / Clojure 11 (Java 17) (push) Has been cancelled
testsuite / Clojure 11 (Java 21) (push) Has been cancelled
testsuite / Clojure 11 (Java 25) (push) Has been cancelled
testsuite / Clojure 12 (Java 11) (push) Has been cancelled
testsuite / Clojure 12 (Java 17) (push) Has been cancelled
testsuite / Clojure 12 (Java 21) (push) Has been cancelled
testsuite / Clojure 12 (Java 25) (push) Has been cancelled
testsuite / ClojureScript (push) Has been cancelled
testsuite / Lint cljdoc.edn (push) Has been cancelled
testsuite / Check cljdoc analysis (push) Has been cancelled
for #778 #519
2026-02-06 13:25:33 +02:00
Joel Kaasinen
0724c0c5a0
doc: update CHANGELOG.md 2026-02-06 13:20:51 +02:00
Joel Kaasinen
7051c99e99
Merge pull request #778 from lucacervello/master
feat: add url-encode? options to path-params
2026-02-06 13:13:49 +02:00
Luca Cervello
4c8a69c616 refactor: remove repetitions 2026-02-06 12:01:44 +01:00
Joel Kaasinen
e3306e1876
Merge pull request #774 from metosin/feat/753-doc-parameter-coercion
Some checks failed
testsuite / Check cljdoc analysis (push) Has been cancelled
testsuite / Clojure 11 (Java 11) (push) Has been cancelled
testsuite / Clojure 11 (Java 17) (push) Has been cancelled
testsuite / Clojure 11 (Java 21) (push) Has been cancelled
testsuite / Clojure 11 (Java 25) (push) Has been cancelled
testsuite / Clojure 12 (Java 11) (push) Has been cancelled
testsuite / Clojure 12 (Java 17) (push) Has been cancelled
testsuite / Clojure 12 (Java 21) (push) Has been cancelled
testsuite / Clojure 12 (Java 25) (push) Has been cancelled
testsuite / ClojureScript (push) Has been cancelled
testsuite / Lint cljdoc.edn (push) Has been cancelled
doc: document nuances of reitit.coercion/default-parameter-coercion
2026-01-23 14:38:39 +02:00
Joel Kaasinen
e6e1bfd5c4
doc: clarify malli body coercion doc 2026-01-23 14:25:19 +02:00
Joel Kaasinen
8391fafbe2
Merge pull request #776 from metosin/feat/337-fix-external-redirect
fix: redirect-trailing-slash-handler won't make external redirects
2026-01-23 14:16:11 +02:00
Luca Cervello
97bfafa907 feat: add url-encode? options to path-params 2026-01-23 11:20:38 +01:00
Joel Kaasinen
71a777b4fa
fix: redirect-trailing-slash-handler won't make external redirects
A redirect header like

Location: //malicious.com/foo

Would result in a redirect to https://malicious.com/foo . We never
want Locations like that out of redirect-trailing-slash-handler.

Fixes #337
2026-01-23 09:54:26 +02:00
Joel Kaasinen
a6b68cc3d6
doc: document nuances of reitit.coercion/default-parameter-coercion
for #753
2026-01-23 09:00:47 +02:00
Joel Kaasinen
248200aad3
Merge pull request #770 from lucacervello/master
Some checks failed
testsuite / Clojure 11 (Java 11) (push) Has been cancelled
testsuite / Clojure 11 (Java 17) (push) Has been cancelled
testsuite / Clojure 11 (Java 21) (push) Has been cancelled
testsuite / Clojure 11 (Java 25) (push) Has been cancelled
testsuite / Clojure 12 (Java 11) (push) Has been cancelled
testsuite / Clojure 12 (Java 17) (push) Has been cancelled
testsuite / Clojure 12 (Java 21) (push) Has been cancelled
testsuite / Clojure 12 (Java 25) (push) Has been cancelled
testsuite / ClojureScript (push) Has been cancelled
testsuite / Lint cljdoc.edn (push) Has been cancelled
testsuite / Check cljdoc analysis (push) Has been cancelled
Allow colons in bracket parameter syntax
2026-01-09 13:00:33 +02:00
Joel Kaasinen
bf18586d75
examples/openapi: fix /complex example 2026-01-09 11:28:04 +02:00
Joel Kaasinen
373ea9bb62
Release 0.10.0
Some checks are pending
testsuite / Clojure 11 (Java 11) (push) Waiting to run
testsuite / Clojure 11 (Java 17) (push) Waiting to run
testsuite / Clojure 11 (Java 21) (push) Waiting to run
testsuite / Clojure 11 (Java 25) (push) Waiting to run
testsuite / Clojure 12 (Java 11) (push) Waiting to run
testsuite / Clojure 12 (Java 17) (push) Waiting to run
testsuite / Clojure 12 (Java 21) (push) Waiting to run
testsuite / Clojure 12 (Java 25) (push) Waiting to run
testsuite / ClojureScript (push) Waiting to run
testsuite / Lint cljdoc.edn (push) Waiting to run
testsuite / Check cljdoc analysis (push) Waiting to run
2026-01-09 10:07:15 +02:00
Joel Kaasinen
334a42e03d
Merge pull request #773 from metosin/bump-deps
chore: bump deps
2026-01-09 10:06:04 +02:00
Joel Kaasinen
5ac4e65284
doc: mention Java 25 2026-01-09 10:01:28 +02:00
Joel Kaasinen
dbac18546b
chore: don't run CI twice on pull requests
both the push and pull_request triggers matched
2026-01-09 09:58:35 +02:00
Joel Kaasinen
69b23c49b9
chore: upgrade clojure; add clj11, clj12 and java 25 to ci matrix 2026-01-09 09:55:05 +02:00
Joel Kaasinen
e1d5789f40
chore: bump deps 2026-01-09 09:40:53 +02:00
Joel Kaasinen
d27454efdc
doc: update CHANGELOG.md 2026-01-09 09:36:54 +02:00
Joel Kaasinen
1d4473e1f4
Merge pull request #772 from metosin/fix/768-ancestors
fix create-exception-middleware for hierarchical keywords
2026-01-09 09:35:14 +02:00
Joel Kaasinen
75faf709e2
fix: create-exception-middleware for deep hierarchies
The code was not finding the closest ancestor to the error type,
because `ancestors` is not ordered. Now the code does a DFS to find a
nearest ancestor. If the nearest ancestor is non-unique, an arbitrary
one is picked.
2026-01-09 09:26:21 +02:00
Joel Kaasinen
2c87d90bda
fix: create-exception-middleware for hierarchical keywords
Previously, the code was searching among the descendants, not the
ancestors, of the error type for an error handler. The test also got
this wrong, perhaps due to a mistake in the parameter order of derive.
2026-01-09 08:37:49 +02:00
Joel Kaasinen
8907dfc5f5
Merge pull request #771 from mthl/lint
Fix linting issues and run Clj-kondo in CI
2026-01-09 08:12:44 +02:00
Mathieu Lirzin
63429a2d1e
chore: Run Linter on CI 2026-01-01 18:40:21 +01:00
Mathieu Lirzin
8721c7ae37
refactor: Implement all Executor protocol method 2026-01-01 18:40:21 +01:00
Mathieu Lirzin
e3180e4d6a
refactor: Reify protocol instead of interface 2026-01-01 18:40:21 +01:00
Mathieu Lirzin
2597d14125
refactor: Ignore :missing-protocol-method linter 2026-01-01 18:40:21 +01:00
Mathieu Lirzin
54a040f136
test: Use atom in reloading-ring-handler-test
This removes usage of inline defs.
2026-01-01 18:40:21 +01:00
Mathieu Lirzin
e4c53a64e2
test: Add missing protocol method implementation 2026-01-01 18:40:21 +01:00
Mathieu Lirzin
218f05972e
test: Remove unused def 2026-01-01 18:40:21 +01:00
Mathieu Lirzin
e342ac5401
test: Comment unused values 2026-01-01 18:40:21 +01:00
Mathieu Lirzin
e6137cb47a
refactor: Remove redundant let 2026-01-01 18:40:21 +01:00
Mathieu Lirzin
3191d9ee59
refactor: Remove unneeded and 2026-01-01 18:40:21 +01:00
Mathieu Lirzin
579eb28a50
refactor: Remove redundant str calls 2026-01-01 18:40:21 +01:00
Mathieu Lirzin
20735730c9
chore: Lint defspec as deftest 2026-01-01 18:40:20 +01:00
Mathieu Lirzin
aa6c1ac460
test: Use .clj extension instead of .cljc
Those tests are only working on Clojure.
2026-01-01 18:40:20 +01:00
Mathieu Lirzin
c113bded4e
refactor: Remove unused required namespaces 2026-01-01 18:40:17 +01:00
Luca Cervello
ed6397cd05 feat: allow colons in bracket parameter syntax
closes #748
2025-12-30 10:05:20 +01:00
Joel Kaasinen
c3a152a44e
doc: update CHANGELOG.md
Some checks failed
testsuite / Clojure (Java 11) (push) Has been cancelled
testsuite / Clojure (Java 17) (push) Has been cancelled
testsuite / Clojure (Java 21) (push) Has been cancelled
testsuite / ClojureScript (push) Has been cancelled
testsuite / Lint cljdoc.edn (push) Has been cancelled
testsuite / Check cljdoc analysis (push) Has been cancelled
2025-11-17 11:01:37 +02:00
Joel Kaasinen
c0bc789863
Merge pull request #767 from metosin/humanize-opts
feat: support humanize options
2025-11-17 10:59:23 +02:00
Joel Kaasinen
78aba57d2d
doc: document configuring malli registry
Some checks failed
testsuite / Clojure (Java 11) (push) Has been cancelled
testsuite / Clojure (Java 17) (push) Has been cancelled
testsuite / Clojure (Java 21) (push) Has been cancelled
testsuite / ClojureScript (push) Has been cancelled
testsuite / Lint cljdoc.edn (push) Has been cancelled
testsuite / Check cljdoc analysis (push) Has been cancelled
2025-11-14 14:58:52 +02:00
Joel Kaasinen
451b286f1d
doc: add brief docs for configuring humanized error messages 2025-11-14 14:16:48 +02:00
Joel Kaasinen
eb06404f1e
feat: fold malli :humanize-opts into :options 2025-11-14 14:06:43 +02:00
Joel Kaasinen
af7313bd9b
test: add test for overriding malli registry 2025-11-14 14:05:36 +02:00
Joel Kaasinen
ea58100fec
test: add test for malli coercion :humanize-opts 2025-11-14 13:51:23 +02:00
ertugrulcetin
a4576cc622
feat: support humanize options 2025-11-14 13:15:50 +02:00
Joel Kaasinen
9d88d92241
Merge pull request #766 from metosin/spec-and-or
Some checks failed
testsuite / Clojure (Java 11) (push) Has been cancelled
testsuite / Clojure (Java 17) (push) Has been cancelled
testsuite / Clojure (Java 21) (push) Has been cancelled
testsuite / ClojureScript (push) Has been cancelled
testsuite / Lint cljdoc.edn (push) Has been cancelled
testsuite / Check cljdoc analysis (push) Has been cancelled
Bump spec-tools, test openapi + s/keys + or
2025-11-14 11:49:34 +02:00
Joel Kaasinen
d16aac673e
test: test openapi + s/keys + or 2025-11-14 11:30:51 +02:00
Joel Kaasinen
dede2db213
chore: bump spec-tools 2025-11-14 11:22:03 +02:00
Joel Kaasinen
1dc961f661
Merge pull request #764 from metosin/483-doc-allow-symlinks
Some checks failed
testsuite / Clojure (Java 11) (push) Has been cancelled
testsuite / Clojure (Java 17) (push) Has been cancelled
testsuite / Clojure (Java 21) (push) Has been cancelled
testsuite / ClojureScript (push) Has been cancelled
testsuite / Lint cljdoc.edn (push) Has been cancelled
testsuite / Check cljdoc analysis (push) Has been cancelled
doc: document allow-symlinks? option
2025-10-31 13:41:31 +02:00
Joel Kaasinen
2ce9850de6
doc: document allow-symlinks? option
... for create-resource-handler and create-file-handler

fixes #483
2025-10-31 11:43:25 +02:00
Joel Kaasinen
0bc30e9361
doc: update CHANGELOG.md 2025-10-31 10:45:36 +02:00
Joel Kaasinen
9b26d5c0fd
Merge pull request #763 from metosin/remove-dead-code
refactor: remove unused reitit.dependency ns
2025-10-31 10:44:38 +02:00
Joel Kaasinen
e671f78741
Merge pull request #762 from metosin/745-coerce-response-content-type
Some checks are pending
testsuite / Clojure (Java 11) (push) Waiting to run
testsuite / Clojure (Java 17) (push) Waiting to run
testsuite / Clojure (Java 21) (push) Waiting to run
testsuite / ClojureScript (push) Waiting to run
testsuite / Lint cljdoc.edn (push) Waiting to run
testsuite / Check cljdoc analysis (push) Waiting to run
improve & document response coercion content-type selection
2025-10-31 09:48:00 +02:00
Joel Kaasinen
55f8d98bde
test: improve per-content-type coercion tests
- The :headers "Content-Type" case in per-content-type-test was
  unrealistic. Ring would've thrown an exception at the non-string
  :body.
- Test response Content-Type in muuntaja-per-content-type-coercion-test
2025-10-31 09:38:56 +02:00
Joel Kaasinen
342bae3ffe
refactor: remove unused reitit.dependency ns
leftover from #33 #210
2025-10-30 15:32:27 +02:00
Joel Kaasinen
ae52000b29
doc: update CHANGELOG.md 2025-10-29 10:57:59 +02:00
Joel Kaasinen
39c5ae86a4
doc: return random content-type from openapi example /pizza 2025-10-29 10:54:16 +02:00
Joel Kaasinen
7fb9c27e46
feat: use request Content-Type or :muuntaja/content-type to coerce
Previously, `extract-response-format-default` was only looking at
(-> request :muuntaja/response :format). This led to wrong behaviour
when there were separate schemas for separate response content types
and an explicitly picked content-type for the response.
2025-10-29 10:54:10 +02:00
Joel Kaasinen
f4da07c222
Release 0.9.2
Some checks failed
testsuite / Clojure (Java 11) (push) Has been cancelled
testsuite / Clojure (Java 17) (push) Has been cancelled
testsuite / Clojure (Java 21) (push) Has been cancelled
testsuite / ClojureScript (push) Has been cancelled
testsuite / Lint cljdoc.edn (push) Has been cancelled
testsuite / Check cljdoc analysis (push) Has been cancelled
2025-10-28 14:57:54 +02:00
Joel Kaasinen
d25dca19f6 chore: disable release signing harder
Some checks failed
testsuite / Clojure (Java 11) (push) Has been cancelled
testsuite / Clojure (Java 17) (push) Has been cancelled
testsuite / Clojure (Java 21) (push) Has been cancelled
testsuite / ClojureScript (push) Has been cancelled
testsuite / Lint cljdoc.edn (push) Has been cancelled
testsuite / Check cljdoc analysis (push) Has been cancelled
2025-10-24 16:14:11 +03:00
Joel Kaasinen
1b02662c78 chore: disable release signing
now that we have automated releases, there's no point
2025-10-24 16:08:19 +03:00
Joel Kaasinen
36af88b65e doc: update release instructions 2025-10-24 16:08:08 +03:00
Joel Kaasinen
152c598858 Release 0.9.2-rc1 2025-10-24 15:52:47 +03:00
Joel Kaasinen
6d9632e85e chore: automated release pipeline 2025-10-24 15:45:42 +03:00
Joel Kaasinen
5ff8ba2e3e
Merge pull request #761 from metosin/bump-deps
bump deps
2025-10-24 15:45:27 +03:00
Joel Kaasinen
c684c83c99 chore: update openapi-schema-validator, fix test
validator now disallows having both "example" and "examples"
2025-10-24 15:38:29 +03:00
Joel Kaasinen
defebb0f1f chore: upgrade dependencies
... and pin pedestal versions since we don't work with 0.7/0.8
2025-10-24 15:31:39 +03:00
Joel Kaasinen
54c0935078 doc: updating deps 2025-10-24 15:26:20 +03:00
Joel Kaasinen
cde050f964 doc: remove old TODO 2025-10-24 14:24:38 +03:00
Joel Kaasinen
560c6d7969 doc: update CHANGELOG.md 2025-10-24 14:16:59 +03:00
Joel Kaasinen
abe95bfc17
Merge pull request #760 from metosin/fix/758-match-by-name-bang-nil
fix: match-by-name! should throw when match-by-name is PartialMatch
2025-10-24 14:15:37 +03:00
Joel Kaasinen
d2f44b8015 fix: match-by-name! should throw when match-by-name is PartialMatch
If a path param was nil, match-by-name (via impl/path-for) was
treating the parameter as missing, but match-by-name!
(via impl/throw-on-missing-path-params) was treating it as present.

That is:

(reitit/match-by-name router :page {:id "1"}) ;; => Match
(reitit/match-by-name router :page) ;; => PartialMatch
(reitit/match-by-name router :page {:id nil}) ;; => PartialMatch

(reitit/match-by-name! router :page {:id "1"}) ;; => Match
(reitit/match-by-name! router :page) ;; => ExceptionInfo: missing path-params for route /pages/:id -> #{:id}
(reitit/match-by-name! router :page {:id nil}) ;; => nil  !!!

fixes #758
2025-10-24 09:58:03 +03:00
Joel Kaasinen
ef9dd495be doc: update CHANGELOG.md
Some checks failed
testsuite / Clojure (Java 11) (push) Has been cancelled
testsuite / Clojure (Java 17) (push) Has been cancelled
testsuite / Clojure (Java 21) (push) Has been cancelled
testsuite / ClojureScript (push) Has been cancelled
testsuite / Lint cljdoc.edn (push) Has been cancelled
testsuite / Check cljdoc analysis (push) Has been cancelled
2025-10-13 15:41:31 +03:00
Joel Kaasinen
9509e40dae
Merge pull request #756 from metosin/feat/defaults-for-optional-keys
setting default values for optional keys in malli coercion
2025-10-13 15:38:45 +03:00
Joel Kaasinen
67918a3f9c feat: reuse :default-values config key instead of adding a new one 2025-10-13 15:18:29 +03:00
Joel Kaasinen
45951aa82e doc: update CHANGELOG.md
Some checks are pending
testsuite / Clojure (Java 11) (push) Waiting to run
testsuite / Clojure (Java 17) (push) Waiting to run
testsuite / Clojure (Java 21) (push) Waiting to run
testsuite / ClojureScript (push) Waiting to run
testsuite / Lint cljdoc.edn (push) Waiting to run
testsuite / Check cljdoc analysis (push) Waiting to run
2025-10-13 09:17:33 +03:00
Joel Kaasinen
1cdca2e3d5
Merge pull request #739 from Ramblurr/fix/top-level-mw-registry
Apply router options to top-level middleware chain
2025-10-13 09:14:36 +03:00
Joel Kaasinen
2f22838820 doc: using middleware from registry at ring-handler level 2025-10-13 09:09:11 +03:00
Joel Kaasinen
d809291553 test: ring-handler middleware from registry inside router 2025-10-13 09:01:21 +03:00
Joel Kaasinen
4e572e86d6 Merge remote-tracking branch 'origin/master' into fix/top-level-mw-registry 2025-10-13 08:50:38 +03:00
Joel Kaasinen
10700e0ca2
Merge pull request #757 from metosin/doc/multipart-middleware-order
Some checks failed
testsuite / Clojure (Java 11) (push) Has been cancelled
testsuite / Clojure (Java 17) (push) Has been cancelled
testsuite / Clojure (Java 21) (push) Has been cancelled
testsuite / ClojureScript (push) Has been cancelled
testsuite / Lint cljdoc.edn (push) Has been cancelled
testsuite / Check cljdoc analysis (push) Has been cancelled
doc: multipart-middleware should be after coerce-request-middleware
2025-10-10 13:04:57 +03:00
Joel Kaasinen
3e0f1d3188 doc: multipart-middleware should be after coerce-request-middleware 2025-10-10 12:50:24 +03:00
Joel Kaasinen
f26dc1ab19 feat: :default-values-for-optional-keys for malli coercion 2025-10-10 08:51:05 +03:00
Joel Kaasinen
01766ee211 doc: update CHANGELOG.md
Some checks are pending
testsuite / Clojure (Java 11) (push) Waiting to run
testsuite / Clojure (Java 17) (push) Waiting to run
testsuite / Clojure (Java 21) (push) Waiting to run
testsuite / ClojureScript (push) Waiting to run
testsuite / Lint cljdoc.edn (push) Waiting to run
testsuite / Check cljdoc analysis (push) Waiting to run
2025-10-09 15:30:55 +03:00
Joel Kaasinen
79627aea7b
Merge pull request #755 from metosin/feat/multimethod-as-handler
feat: allow multimethods as :handlers in validation
2025-10-09 15:26:37 +03:00
Joel Kaasinen
4d284385ec
Merge pull request #754 from metosin/openapi-error-reporting
feat: better error reporting while building openapi docs
2025-10-09 15:26:27 +03:00
Joel Kaasinen
31fa0376cf feat: better error reporting while building openapi docs
Include the path and method when openapi generation goes wrong.
2025-10-09 15:19:56 +03:00
Joel Kaasinen
05bc331397 feat: allow multimethods as :handlers in validation
fixes #749
2025-10-07 15:50:51 +03:00
Juho Teperi
7520d20f12 No need to use specific Java version for releases 2025-05-27 14:35:50 +03:00
Juho Teperi
210f20e714 Release 0.9.1 2025-05-27 14:04:31 +03:00
Juho Teperi
aad054ef39
Merge pull request #743 from metosin/fix-responses
fix: response coercion for unlisted http statuses, when no default
2025-05-27 14:03:20 +03:00
Joel Kaasinen
653db7efa3 doc: update CHANGELOG.md 2025-05-27 13:08:59 +03:00
Joel Kaasinen
5025ca3a75 fix: response coercion for unlisted http statuses, when no default
fixes #742
2025-05-27 12:09:38 +03:00
Joel Kaasinen
55c30af979
Merge pull request #741 from metosin/readme-example
doc: improve middleware docs & examples in README.md & elsewhere
2025-05-26 12:56:11 +03:00
Joel Kaasinen
9ee8e364f3 doc: add a reason to use top-level mw 2025-05-26 10:27:00 +03:00
Joel Kaasinen
ad9cd31168 doc: mention one more reason to use top-level route data for mw 2025-05-26 10:21:40 +03:00
Joel Kaasinen
99267d2943 doc: mention that coercion uses top-level-route-data-middleware 2025-05-26 10:14:14 +03:00
Joel Kaasinen
ecf22d5c4a doc: elaborate on different ways of setting middleware 2025-05-26 10:06:36 +03:00
Joel Kaasinen
1803643f1f doc: link to ring.md#middleware from data_driven_middleware.md
Data Driven Middleware is the first doc title with "Middleware" in it,
so people will land in it while trying to figure out the basics of
middleware.
2025-05-26 08:52:05 +03:00
Joel Kaasinen
f247751409 doc: remove misleading link to open issue
the factoring hasn't happened, so no reason to talk about issue #134
in the docs until it does
2025-05-26 08:45:48 +03:00
Joel Kaasinen
48fdb692ef doc: improve ring example in README.md
- format-response-middleware wasn't doing anything, use
  format-middleware instead as that's what the users usually want
- add exception handling
- document middleware stack a bit
2025-05-26 08:44:53 +03:00
Juho Teperi
b9cef492f8 Release 0.9.0 2025-05-23 10:47:21 +03:00
Joel Kaasinen
524d0d4d57
Merge pull request #740 from metosin/openapi-multi-example
doc: add :multi, :enum etc to examples/openapi
2025-05-13 09:18:20 +03:00
Joel Kaasinen
20c28ecc7c doc: add :multi, :enum etc to examples/openapi 2025-05-13 08:23:02 +03:00
Joel Kaasinen
3793a7b23b
Merge pull request #732 from metosin/unslash
malli now uses . instead of ~1 in json-schema/swagger $refs
2025-05-12 14:59:03 +03:00
Joel Kaasinen
ef64fc50b8 doc: add note about malli update to CHANGELOG.md 2025-05-12 14:39:38 +03:00
Joel Kaasinen
15987d9952 test: bump openapi-schema-validator 2025-05-12 14:28:19 +03:00
Joel Kaasinen
2bf8aa98a7 test: malli now uses . instead of ~1 in json-schema/swagger $refs
update our assertions accordingly
2025-05-12 14:28:19 +03:00
Joel Kaasinen
832d9ebe95 deps: bump malli 2025-05-12 14:28:13 +03:00
Joel Kaasinen
4b7a596ece doc: update CHANGELOG.md 2025-05-05 11:28:36 +03:00
Casey Link
0454e8914f Apply router options to top-level middleware chain
Middleware supplied to the `ring-handler` function could not be resolved
from the middleware registry, because the router options (which contain
the registry) were not being propagated.

Fixes #738
2025-04-29 14:52:09 +02:00
Joel Kaasinen
7a77c9f86b
Merge pull request #735 from metosin/response-default
Resurrect :responses :default
2025-04-29 09:30:02 +03:00
Joel Kaasinen
a8b4bc0d2d feat: rework & document response coercer defaulting rules 2025-04-28 10:01:09 +03:00
Joel Kaasinen
9db58f21dc doc: update CHANGELOG.md 2025-04-25 15:15:11 +03:00
Joel Kaasinen
9797725ae8
Merge pull request #714 from mokshasoft/openapi3-parameter-deprecation
Place the openapi deprecation tag directly under parameters
2025-04-25 15:13:26 +03:00
Joel Kaasinen
3c6139950c doc: deprecated openapi parameters in docs & example 2025-04-25 15:07:52 +03:00
Joel Kaasinen
9534f6df8b feat: avoid duplicated :description for openapi parameters 2025-04-25 15:04:51 +03:00
Joel Kaasinen
a390180975 test: add description and deprecated to openapi-test 2025-04-25 15:03:40 +03:00
Joel Kaasinen
f038fe1941 test: corner cases in how :responses :default gets applied 2025-04-24 09:31:27 +03:00
Joel Kaasinen
8058cecae0 doc: document :responses :default 2025-04-11 11:45:12 +03:00
Joel Kaasinen
dd835e73a8 feat: allow :default response status code again
it is an old feature, but didn't have a test, so it was broken by #715

also add a test so we don't break it again
2025-04-11 10:30:52 +03:00
Joel Kaasinen
78cf477b88
Merge pull request #733 from evaogbe/static-mime-type
Add mime-types option to static handler
2025-04-08 08:24:22 +03:00
Joel Kaasinen
2d1d0cc5d2 doc: update CHANGELOG.md 2025-04-08 08:19:04 +03:00
Joel Kaasinen
75a5e816a9 reindent 2025-04-08 08:15:59 +03:00
Eva Ogbe
e16c95c5b3 Add mime-types option to static handler 2025-04-07 20:35:58 -04:00
Juho Teperi
91fa60324f Use same javac options for reiti-core project.clj 2025-03-28 16:13:47 +02:00
Juho Teperi
6dd9cb1b7c Prepare release 0.8.0 2025-03-28 16:06:12 +02:00
Juho Teperi
fa28489b63
Merge pull request #728 from metosin/update-deps
Update dependencies
2025-03-28 15:57:56 +02:00
Juho Teperi
9849ed5ebb Mention Java 11 requirement 2025-03-28 15:54:24 +02:00
Juho Teperi
a103411bf7 Update ring 2025-03-28 15:40:58 +02:00
Juho Teperi
a57662693c Drop Java 8 tests and support 2025-03-28 15:40:26 +02:00
Juho Teperi
44d54cc9f2
Merge pull request #730 from agorgl/index-redirect-default
Change default index-redirect? value to false
2025-03-28 15:27:14 +02:00
Loukas Agorgianitis
3342e77538
Change default index-redirect? value to false
Signed-off-by: Loukas Agorgianitis <loukas@agorgianitis.com>
2025-03-28 15:14:15 +02:00
Juho Teperi
8fb44467a0
Merge pull request #727 from agorgl/directory-index
Add option to disable index files served on paths that are not directories
2025-03-28 15:11:09 +02:00
Loukas Agorgianitis
c2feb5b983
Add option to disable index files served on paths that are not directories
Signed-off-by: Loukas Agorgianitis <loukas@agorgianitis.com>
2025-03-28 13:49:36 +02:00
Juho Teperi
9fb0b7233a Update dependencies 2025-03-27 13:58:30 +02:00
Joel Kaasinen
89c05932dc doc: update CHANGELOG.md 2025-03-26 11:10:21 +02:00
Joel Kaasinen
2f1defe3cd
Merge pull request #725 from agorgl/index-redirect
Add option to allow serving index files without redirect
2025-03-26 11:08:54 +02:00
Loukas Agorgianitis
f50feff63c
Add option to allow serving index files without redirect
Signed-off-by: Loukas Agorgianitis <loukas@agorgianitis.com>
2025-03-25 23:07:36 +02:00
Joel Kaasinen
34e6db0d3f
Merge pull request #726 from metosin/bump-gha
chore: bump actions/cache for cljs workflow
2025-03-24 08:46:15 +02:00
Joel Kaasinen
04cb70f1f7 chore: bump actions/cache for cljs workflow 2025-03-24 08:40:12 +02:00
Joel Kaasinen
58195eed68 doc: update CHANGELOG.md 2025-02-25 12:52:33 +02:00
Joel Kaasinen
d5d46d5b0b
Merge pull request #715 from filipesilva/coerce-response-int
fix: throw if response status is not int
2025-02-25 12:49:23 +02:00
Filipe Silva
f0fc440425 fix: throw if response status is not int
Fix #667
2025-02-25 10:12:47 +00:00
Juho Teperi
30fd739fa9 Update CHANGELOG 2025-02-04 11:23:47 +02:00
Joel Kaasinen
de0e1f4462
Merge pull request #718 from metosin/doc-param-merge
doc: document nested parameter definitions
2025-02-03 14:55:38 +02:00
Juho Teperi
34b6bb9349 Link to PR from changelog 2025-01-31 14:42:33 +02:00
Joel Kaasinen
3fcd6cfd73 doc: document nested parameter definitions
originally implemented in #626 for #422
2025-01-31 14:31:54 +02:00
Juho Teperi
e5483cb1fc Release 0.8.0-alpha1 2025-01-31 14:06:05 +02:00
Juho Teperi
481c653139
Merge pull request #716 from metosin/query-string-encoding
Use coercion to encode query-string values in match->path
2025-01-31 14:04:31 +02:00
Juho Teperi
5ca22193d0 Use defined :string :default transformer for query-string-coercer 2025-01-31 09:39:19 +02:00
Juho Teperi
4eb29d3ed9 Extend frontend docs 2025-01-28 15:46:37 +02:00
Juho Teperi
dfc5a4ef67 Remove todo comments 2025-01-28 15:12:46 +02:00
Juho Teperi
7e9116f77e Simplify Malli coercion for query-params to only encode 2025-01-28 15:09:31 +02:00
Juho Teperi
7ae118fbb5 Move query string coercion to coercion ns from core 2025-01-28 14:34:21 +02:00
Juho Teperi
ce6d9e26cd Update docstrings and changelog 2025-01-22 14:35:56 +02:00
Juho Teperi
7ae2bfafc2 Cleanup 2025-01-22 14:20:02 +02:00
Juho Teperi
1b37c87aa2 Test set-query without a match 2025-01-22 14:18:54 +02:00
Juho Teperi
f60a7ad902 Fixes 2025-01-22 14:05:45 +02:00
Juho Teperi
1ba77a7267 Apply query parameters encoding on rfe/set-query 2025-01-22 14:05:45 +02:00
Juho Teperi
21e5840f13 Ensure extra query-string params aren't removed by coercion 2025-01-22 14:05:45 +02:00
Juho Teperi
5f10465533 Another test case 2025-01-22 14:05:26 +02:00
Juho Teperi
dba8d159cc . 2025-01-22 14:05:26 +02:00
Juho Teperi
5829e1c656 Add reitit.frontend test case 2025-01-22 14:05:26 +02:00
Juho Teperi
1819fa5d75 Note 2025-01-22 14:05:26 +02:00
Juho Teperi
25dd0abcaf Use coercion to encode query-string values in match->path 2025-01-22 14:05:26 +02:00
Juho Teperi
a19b6034dd
Merge pull request #717 from metosin/shadow-cljs
Update CI tests and add shadow-cljs to run cljs tests
2025-01-22 14:05:10 +02:00
Juho Teperi
0256642f64 Add bb tasks for Cljs tests 2025-01-22 13:57:10 +02:00
Juho Teperi
b1d066246a Cleanup 2025-01-22 13:36:59 +02:00
Juho Teperi
b19dda8d5d Cleanup 2025-01-22 13:31:09 +02:00
Juho Teperi
0a7b50a730 Test fix, silence extra cljs warnings 2025-01-22 12:09:14 +02:00
Juho Teperi
bf82533028 Handle shadow-cljs infern warnings 2025-01-22 12:01:10 +02:00
Juho Teperi
0370750a3f Run some ring tests only on jvm 2025-01-22 11:56:35 +02:00
Juho Teperi
ea88b06206 Update CI tests and add shadow-cljs to run cljs tests 2025-01-22 11:54:38 +02:00
Jonas Claeson
f1e3ed88ea The deprecation tag should be directly under parameters 2025-01-17 14:45:17 +01:00
Tommi Reiman
ada41ec7dd
Merge pull request #712 from p-himik/p-himik/708-incorrect-ring-responses
Make exception middleware return proper Ring responses
2024-12-08 14:59:08 +02:00
Tommi Reiman
1abff4937c
Update exception_test.clj
ring.util.http-response should be enough
2024-12-08 14:54:14 +02:00
Eugene Pakhomov
cc1cd114e4 Make exception middleware return proper Ring responses 2024-12-07 23:47:05 +02:00
Joel Kaasinen
e86662561f
Merge pull request #711 from metosin/doc-named-schemas
document status of named definition support for openapi&swagger
2024-12-02 07:54:23 +02:00
Joel Kaasinen
059f93aee8 doc: fix typo, mention swagger ui handling of names schemas 2024-12-02 07:47:36 +02:00
Joel Kaasinen
fc132f3a92 doc: swagger reusable schema definitions supported only for malli 2024-11-28 09:56:02 +02:00
Joel Kaasinen
431242c926 doc: openapi named schemas only produced for malli 2024-11-28 09:51:31 +02:00
Joel Kaasinen
0f9414847a
Merge pull request #706 from metosin/openapi-query-warning-master
feat: add warning for unsupported openapi parameter schemas
2024-11-07 10:01:00 +02:00
Joel Kaasinen
86e04414ba
Merge pull request #703 from cloudpermit/handle_form_coercion_properly_in_openapi
Add OpenAPI :requestBody for :form request schema
2024-11-01 08:26:48 +02:00
Joel Kaasinen
c89b6bbe31 feat: add warning for unsupported openapi parameter schemas
for #705
2024-10-30 09:58:32 +02:00
Joel Kaasinen
f6b8054669
Merge pull request #704 from metosin/commercial-support
mention metosin commercial support in README.md
2024-10-10 16:27:27 +03:00
Joel Kaasinen
4f4d05fe65 doc: mention metosin commercial support in README.md 2024-10-10 10:31:26 +03:00
Markus Penttilä
702e7b8972 Add OpenAPI :requestBody for :form request schema
OpenAPI Specification 3 requires defining form parameters, i.e. classic
application/x-www-form-urlencoded type body as a :requestBody. They are
not supported as regular parameters like in OAS 2.
2024-10-09 22:28:57 -04:00
Tommi Reiman
d11deb3473
Merge pull request #701 from bsless/fix-interface-maps
Add dispatch for every implementation of IPersistentMap
2024-09-30 20:03:41 +01:00
Joel Kaasinen
e2c63d6579
Merge pull request #697 from dekelpilli/master
fix: fix bug where http ring handler would cause :path to be applied twice
2024-09-25 10:45:02 +03:00
Joel Kaasinen
86871a6a55
Merge pull request #702 from metosin/openapi-example-docs
clarify openapi docs plus minor fix
2024-09-16 15:51:39 +03:00
Joel Kaasinen
923bafdc9b doc: update changelog 2024-09-16 12:49:30 +03:00
Joel Kaasinen
afc8945d78 doc: improve openapi docs 2024-09-16 12:47:33 +03:00
Joel Kaasinen
610586f0d3 fix: OpenAPI :description belongs at Response level, not Media Type
also, support singular :example in addition to :examples
2024-09-16 12:46:57 +03:00
Joel Kaasinen
5a811421db
Merge pull request #699 from metosin/improve-syntax-docs
improve route syntax docs a bit
2024-09-16 08:20:02 +03:00
Ben Sless
c96b22bc5f Add dispatch for every implementation of IPersistentMap
Closes #700
2024-09-13 20:25:51 +03:00
Joel Kaasinen
21a967145f doc: link to name_based_routing.md from route_data.md 2024-09-11 11:25:46 +03:00
Joel Kaasinen
494aa29cdb doc: improve route_syntax.md
- don't use no-data no-child routes as examples, they confuse newbies
- spell out what route data can be
- link to other docs
2024-09-11 11:25:46 +03:00
Tommi Reiman
02d4f797ca
Update README.md
not monstrously big logo
2024-09-02 13:50:39 +03:00
Tommi Reiman
0158dbb54b smaller 2024-09-02 13:44:31 +03:00
Tommi Reiman
4aff73305f 0.7.2 2024-09-02 13:36:25 +03:00
Tommi Reiman
f5bb9ce70e
Update README.md 2024-09-02 13:36:03 +03:00
Martín Varela
337e6c39c7
Update README.md
Fix logo width
2024-09-02 13:30:41 +03:00
Martín Varela
3136948453
Update README.md
fix width
2024-09-02 13:29:58 +03:00
Martín Varela
c155ee7160 Transparent logo replacement 2024-09-02 13:28:55 +03:00
Tommi Reiman
ff99ab3ff9 rollback pedestal, works only on java11 2024-09-02 13:20:44 +03:00
Martín Varela
a40d9893fb
Update README.md
Point logo to the file on `master`
2024-09-02 13:12:20 +03:00
Martín Varela
ed78552a72
Merge pull request #698 from metosin/add-logo
Add logo to Readme
2024-09-02 13:11:09 +03:00
Martín Varela
dd1d507d90 Make logo even smaller and align right 2024-09-02 13:04:59 +03:00
Martín Varela
2ac6ee84df Make logo smaller 2024-09-02 13:02:58 +03:00
Martín Varela
400a2be0fe
Update README.md
fix logo link
2024-09-02 13:01:57 +03:00
Martín Varela
65ff2ee22d Added logo image (temp location) 2024-09-02 12:51:07 +03:00
Dekel Pilli
f1ec7bbe8e
fix: fix bug where http ring handler would cause :path to be applied twice 2024-09-02 15:40:00 +10:00
Tommi Reiman
f466271e15 pedestal-test fail with java8 2024-08-30 18:15:45 +03:00
Tommi Reiman
d926ef7591 0.7.2 2024-08-30 18:05:18 +03:00
Tommi Reiman
c9848bd6e4 CHANGELOG + deps 2024-08-30 18:04:22 +03:00
Tommi Reiman
8b0c8a3c18
Merge pull request #696 from metosin/post-693
Followup 693
2024-08-27 14:16:04 +03:00
Tommi Reiman
bffe360c6d
Merge pull request #506 from bsless/faster-keywordize
Faster keywordize
2024-08-27 14:11:48 +03:00
Tommi Reiman
734fca7d4a
Merge pull request #694 from whamtet/master
bugfix
2024-08-27 14:08:56 +03:00
Tommi Reiman
5a2ae56991 simplify 2024-08-27 14:06:15 +03:00
Tommi Reiman
d8a8bce272 move out of public api 2024-08-27 14:06:09 +03:00
Tommi Reiman
517ae8ffc3
Merge pull request #693 from bsless/faster-routes
Speed up routes and inline it in code ring handler
2024-08-27 13:58:57 +03:00
Ben Sless
78cc54d3a8 Add generative test for new keywordize 2024-08-26 17:07:51 +03:00
Matthew Molloy
c94ecf5ca7 bugfix 2024-08-26 09:00:30 +09:00
Ben Sless
4eab67a8db reduce-kv over treemap 2024-08-25 19:25:54 +03:00
Ben Sless
7dfc0e5fca Fix dynamism 2024-08-25 19:19:26 +03:00
Ben Sless
61783e4c81 Statically def transducer
Eliminates allocation and friendlier to JIT
2024-08-25 18:56:30 +03:00
Ben Sless
59642e51f1 Decrease code size and eliminate an allocation 2024-08-25 18:54:55 +03:00
Ben Sless
dcb7258caf Tailor keywordize implementation to concrete types
Even faster
2024-08-25 18:50:43 +03:00
Ben Sless
7ab6021630 Add faster keywordize-keys implementation for clj 2024-08-25 18:25:54 +03:00
Ben Sless
c48b6a3704 Speed up routes code path
Fixes #692
2024-08-25 16:20:49 +03:00
Ben Sless
a0467d52cd Inline call to routes 2024-08-25 16:20:15 +03:00
Tommi Reiman
5589328a3c 0.7.1 2024-06-30 18:58:46 +03:00
Tommi Reiman
af2fc137d5 update deps 2024-06-30 18:58:13 +03:00
Tommi Reiman
be35375387 prepare for release 2024-06-30 18:37:23 +03:00
Tommi Reiman
d88b693e26
Merge pull request #688 from metosin/issue-679
Issue 679
2024-06-30 18:36:46 +03:00
Tommi Reiman
e8c3035254 . 2024-06-30 18:31:14 +03:00
Tommi Reiman
aec024a943 fix 2024-06-30 18:29:52 +03:00
Tommi Reiman
49e8d887da fixes #679 2024-06-30 17:55:47 +03:00
Tommi Reiman
ee67a746d4 reduce-kv 2024-06-30 17:55:23 +03:00
Tommi Reiman
72dadb3c76
Merge pull request #687 from metosin/fix-686
Fix for #686
2024-06-29 16:32:47 +03:00
Tommi Reiman
e0cc8b4cd8 Changelog 2024-06-29 16:27:47 +03:00
Tommi Reiman
c792a6b794 updated deps 2024-06-29 16:27:10 +03:00
Tommi Reiman
129de37d3e . 2024-06-29 16:23:35 +03:00
Tommi Reiman
77f0798c06 . 2024-06-29 16:22:31 +03:00
Tommi Reiman
eb4c231c23 format 2024-06-29 16:21:35 +03:00
Tommi Reiman
2da94f733d don't merge records 2024-06-29 16:21:28 +03:00
Tommi Reiman
d136975154 format 2024-06-29 16:20:44 +03:00
Tommi Reiman
411bf39c7d
Merge pull request #685 from PEZ/rf-match-to-path-optional-arities
Add arities 1 and 2 to rf/match->path
2024-06-21 12:05:23 +03:00
Peter Strömberg
2f3fc21c84 Add arities 1 and 2 to rf/match->path
To adhere to the docstring's info about parameter 2 and 3 being optional
2024-06-21 09:59:33 +02:00
Joel Kaasinen
4e85cb14c5
Merge pull request #681 from alexhall/openapi-readme
Add reitit-openapi to readme, clarify group-ids
2024-05-17 09:05:28 +03:00
Alex Hall
2da5d8bb7d Add reitit-openapi to readme, clarify group-ids 2024-05-10 20:30:20 -04:00
Tommi Reiman
4d4307ef57 . 2024-04-30 12:00:06 +03:00
Tommi Reiman
fbfd7ad5f3 README 2024-04-30 11:58:41 +03:00
Tommi Reiman
509b4dd232 CHANGELOG for 0.7.0 2024-04-30 11:51:29 +03:00
Tommi Reiman
877c45af90 bump up version 2024-04-30 11:51:18 +03:00
125 changed files with 3892 additions and 3797 deletions

View file

@ -1,5 +1,6 @@
{;;:skip-comments true
:lint-as {potemkin/def-derived-map clojure.core/defrecord}
:lint-as {potemkin/def-derived-map clojure.core/defrecord
clojure.test.check.clojure-test/defspec clojure.test/deftest}
:linters {:missing-else-branch {:level :off}
:unused-binding {:level :off}
:unused-referred-var {:exclude {clojure.test [deftest testing is are]

26
.github/workflows/release.yml vendored Normal file
View file

@ -0,0 +1,26 @@
name: Release
on:
release:
types:
- published # reacts to releases and prereleases, but not their drafts
jobs:
build-and-release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: "Setup Java"
uses: actions/setup-java@v5
with:
distribution: zulu
java-version: 11
- name: "Setup Clojure"
uses: DeLaGuardo/setup-clojure@master
with:
lein: 2.9.5
- name: Deploy to Clojars
run: ./scripts/lein-modules do clean, deploy clojars
env:
CLOJARS_USERNAME: metosinci
CLOJARS_PASSWORD: "${{ secrets.CLOJARS_DEPLOY_TOKEN }}"

View file

@ -2,73 +2,91 @@ name: testsuite
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
build-clj:
strategy:
matrix:
# Supported Java versions: LTS releases and latest
jdk: [8, 11, 17, 21]
jdk: [11, 17, 21, 25]
clojure: [11, 12]
name: Clojure (Java ${{ matrix.jdk }})
name: Clojure ${{ matrix.clojure }} (Java ${{ matrix.jdk }})
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Cache dependencies
uses: actions/cache@v2
uses: actions/cache@v4
with:
path: ~/.m2/repository
key: ${{ runner.os }}-clj-${{ hashFiles('**/project.clj') }}
restore-keys: |
${{ runner.os }}-clj-
- name: Setup Java ${{ matrix.jdk }}
uses: actions/setup-java@v1.4.3
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: ${{ matrix.jdk }}
- name: Setup Clojure
uses: DeLaGuardo/setup-clojure@3.1
uses: DeLaGuardo/setup-clojure@13.1
with:
lein: 2.9.5
clj-kondo: 2025.12.23
# Install openapi-schema-validator for openapi-tests
# Uses node version from the ubuntu-latest
- name: Install dependencies
run: |
npm ci
run: npm ci
- name: Run Linter
run: ./lint.sh
- name: Run tests
run: ./scripts/test.sh clj
run: ./scripts/test.sh clj${{ matrix.clojure }}
build-cljs:
name: ClojureScript
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Cache dependencies
uses: actions/cache@v2
uses: actions/cache@v4
with:
path: |
~/.m2/repository
**/node_modules
key: ${{ runner.os }}-cljs-${{ hashFiles('**/project.clj', '**/package-lock.json') }}
key: ${{ runner.os }}-cljs-${{ hashFiles('**/project.clj') }}
restore-keys: |
${{ runner.os }}-cljs-
- name: Setup Java 11
uses: actions/setup-java@v1.4.3
- name: Setup Java
uses: actions/setup-java@v4
with:
java-version: 11
distribution: temurin
java-version: 21
- name: Setup Clojure
uses: DeLaGuardo/setup-clojure@3.1
uses: DeLaGuardo/setup-clojure@13.1
with:
lein: 2.9.5
- name: Setup Node.js
uses: actions/setup-node@v2.1.2
uses: actions/setup-node@v4
with:
node-version: 18
node-version: 22
cache: npm
- name: Install dependencies
run: |
npm ci
- name: Install modules
run: ./scripts/lein-modules install
run: npm ci
- name: Run tests
run: ./scripts/test.sh cljs
@ -76,7 +94,8 @@ jobs:
name: Lint cljdoc.edn
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Verify cljdoc.edn
run: curl -fsSL https://raw.githubusercontent.com/cljdoc/cljdoc/master/script/verify-cljdoc-edn | bash -s doc/cljdoc.edn
@ -84,13 +103,16 @@ jobs:
name: Check cljdoc analysis
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v4
- name: Setup Clojure
uses: DeLaGuardo/setup-clojure@11.0
uses: DeLaGuardo/setup-clojure@13.1
with:
lein: 2.9.5
cli: 1.11.0.1100
- name: Install cljdoc analyzer
run: clojure -Ttools install io.github.cljdoc/cljdoc-analyzer '{:git/tag "RELEASE"}' :as cljdoc-analyzer
- name: CljDoc Check
run: ./scripts/cljdoc-check.sh

2
.gitignore vendored
View file

@ -14,3 +14,5 @@ pom.xml.asc
figwheel_server.log
/.idea
.clj-kondo
.shadow-cljs
.cache

View file

@ -12,8 +12,177 @@ We use [Break Versioning][breakver]. The version numbers follow a `<major>.<mino
[breakver]: https://github.com/ptaoussanis/encore/blob/master/BREAK-VERSIONING.md
## UNRELEASED
* **FIX** redirect-trailing-slash-handler won't make external redirects. [#776](https://github.com/metosin/reitit/pull/776)
* Allow colons in bracket parameter syntax. [#770](https://github.com/metosin/reitit/pull/770)
* Add `url-encode?` option to `match-by-name`. [#778](https://github.com/metosin/reitit/pull/778)
## 0.10.0 (2026-01-09)
* Improve & document how response schemas get picked in per-content-type coercion. See [docs](./doc/ring/coercion.md#per-content-type-coercion). [#745](https://github.com/metosin/reitit/issues/745).
* **BREAKING** Remove unused `reitit.dependency` ns. [#763](https://github.com/metosin/reitit/pull/763)
* Support passing options to malli humanize. See [docs](./doc/coercion/malli_coercion.md). [#467](https://github.com/metosin/reitit/issues/467)
* **FIX** Handling of ex-type keyword hierarchies in create-exception-middleware. [#768](https://github.com/metosin/reitit/issues/768)
* Updated dependencies:
```
[metosin/malli "0.20.0"] is available but we use "0.19.2"
[com.fasterxml.jackson.core/jackson-core "2.20.1"] is available but we use "2.20.0"
[com.fasterxml.jackson.core/jackson-databind "2.20.1"] is available but we use "2.20.0"
[org.clojure/core.rrb-vector "0.2.1"] is available but we use "0.2.0"
```
## 0.9.2 (2025-10-28)
* Allow multimethods as handlers when validating [#755](https://github.com/metosin/reitit/pull/755)
* Improve error reporting when generating OpenAPI fails [#754](https://github.com/metosin/reitit/pull/754)
* Allow middleware registry to be used when defining middleware in `ring-handler`. See [docs](./doc/ring/middleware_registry.md). [#739](https://github.com/metosin/reitit/pull/739)
* Allow passing options (eg. `:malli.transform/add-optional-keys`) to malli's `default-value-transformer`. See [docs](./doc/coercion/malli_coercion.md). [#756](https://github.com/metosin/reitit/pull/756)
* **FIX**: `match-by-name!` returning `nil` instead of throwing an exception for some partial matches [#758](https://github.com/metosin/reitit/issues/758)
* Updated dependencies:
```
[com.fasterxml.jackson.core/jackson-core "2.20.0"] is available but we use "2.18.2"
[com.fasterxml.jackson.core/jackson-databind "2.20.0"] is available but we use "2.18.2"
[compojure "1.7.2"] is available but we use "1.7.1"
[fipp "0.6.29"] is available but we use "0.6.27"
[metosin/malli "0.19.2"] is available but we use "0.18.0"
[ring "1.15.3"] is available but we use "1.14.1"
[ring/ring-core "1.15.3"] is available but we use "1.14.1"
[ring/ring-defaults "0.7.0"] is available but we use "0.6.0"
```
## 0.9.1 (2025-05-27)
* **FIX**: response coercion threw an exception for unlisted HTTP status codes if there was no `:default`. Broken in 0.9.0. [#742](https://github.com/metosin/reitit/issues/742)
## 0.9.0 (2025-05-23)
* Improvements to mime type handling in `create-file-handler` and `create-resource-handler` [#733](https://github.com/metosin/reitit/pull/733)
* New `:mime-types` option to configure a map from file extension to mime type
* Don't set Content-Type header at all if mime type is not known
* Fix location of OpenAPI deprecated metadata [#714](https://github.com/metosin/reitit/pull/714)
* **BREAKING** Fix & clarify `:responses :default` and `:content :default` handling. See [docs](./doc/ring/coercion.md). [#735](https://github.com/metosin/reitit/pull/735)
* Summary: If `:responses <status>` is present, `:responses :default` is not used, even if `:responses <status>` defines no schema.
* Should not break normal use, but might cause surprises related to defaults applying/not applying
* **NOTE** This release depends on malli 0.18.0, which changes the format of OpenAPI & Swagger named schemas from `foo.bar/quux` to `foo.bar.quux`
## 0.8.0 (2025-03-28)
**[compare](https://github.com/metosin/reitit/compare/0.7.2..0.8.0)**
* **BREAKING**: throw error if `:responses` keys are not integers [#667](https://github.com/metosin/reitit/issues/667)
* **BREAKING**: Java 8 is no longer supported (Ring-core requires Apache Commons FileUpload which now requires Java 11)
* File and resource handlers (`create-file-handler` and `create-resource-handler`)
* **BREAKING**: New default is to redirect from `dir` path to `dir/` and serve the index file (if found) on the path ending with `/`
* For example the Swagger UI handler now serves the index from `/api-docs/` instead of redirecting to `/api-docs/index.html` (both work)
* Mostly this is a visual change, though if you have unit tests checking for response status or redirect, those could break
* New option `:index-redirect?` (default false) allows enable redirecting to the index file, e.g. `dir` -> `dir/index.html` (same as the old default)
* New option `:canonicalize-uris?` (default true) enables redirect from `dir` to `dir/` if the index file exists for the path
* Without this option `dir` would return 404 and `dir/` and `dir/index.html` would return the file
* Changes in 0.8.0-alpha1
* Updated dependencies:
```
[fipp "0.6.27"] is available but we use "0.6.26"
[metosin/jsonista "0.3.13"] is available but we use "0.3.10"
[metosin/malli "0.17.0"] is available but we use "0.16.4"
[metosin/muuntaja "0.6.11"] is available but we use "0.6.10"
[metosin/ring-swagger-ui "5.20.0"] is available but we use "5.9.0"
[ring/ring-core "1.14.1"] is available but we use "1.12.2"
[ring/ring-defaults "0.6.0"] is available but we use "0.5.0"
```
## 0.8.0-alpha1 (2025-01-31)
**[compare](https://github.com/metosin/reitit/compare/0.7.2..0.8.0-alpha1)**
* Improve OpenAPI docs, plus don't emit `:description` in the wrong place [#702](https://github.com/metosin/reitit/pull/702)
* Support reitit.walk for all IPersitentMap implementations, fixes coercion with
aleph 0.7.2 [#700](https://github.com/metosin/reitit/issues/700), [#701](https://github.com/metosin/reitit/pull/701)
* *POTENTIALLY BREAKING* The frontend functions (href, push/replace-state, navigate, set-query) now
encode query-string values using configured coercion when possible (only Malli supports encoding).
[#716](https://github.com/metosin/reitit/pull/716)
- You can use this to encode query parameter values before they are URL-encoded. This works for DateTimes, collections etc.
- In most cases this shouldn't break existing uses, but it is possible even without
a custom encoding function, the default Malli string-transformer could encode some values differently
then previously.
## 0.7.2 (2024-09-02)
**[compare](https://github.com/metosin/reitit/compare/0.7.1..0.7.2)**
* Speed up routes and inline it in code ring handler [#693](https://github.com/metosin/reitit/pull/693) [#693](https://github.com/metosin/reitit/pull/696)
* Fix: Can't get descendants of classes [#555](https://github.com/metosin/reitit/issues/555)
* Faster keywordize [#506](https://github.com/metosin/reitit/pull/506)
* Updated dependencies:
```clojure
[metosin/jsonista "0.3.10"] is available but we use "0.3.9"
[metosin/malli "0.16.4"] is available but we use "0.16.2"
[com.fasterxml.jackson.core/jackson-core "2.17.2"] is available but we use "2.17.1"
[com.fasterxml.jackson.core/jackson-databind "2.17.2"] is available but we use "2.17.1"
```
## 0.7.1 (2024-06-30)
**[compare](https://github.com/metosin/reitit/compare/0.7.0..0.7.1)**
* FIX: Route data maps ignore meta-merge options in 0.7.0, breaking compatibility [#679](https://github.com/metosin/reitit/issues/679)
* FIX: Clojure record in route data is converted to a plain map [#686](https://github.com/metosin/reitit/issues/686)
* Add arities 1 and 2 to rf/match->path [#685](https://github.com/metosin/reitit/pull/685)
* Updated dependencies:
```clojure
[ring/ring-core "1.12.2"] is available but we use "1.12.1"
[metosin/malli "0.16.2"] is available but we use "0.16.1"
[metosin/jsonista "0.3.9"] is available but we use "0.3.8"
[metosin/spec-tools "0.10.7"] is available but we use "0.10.6"
[com.fasterxml.jackson.core/jackson-core "2.17.1"] is available but we use "2.17.0"
[com.fasterxml.jackson.core/jackson-databind "2.17.1"] is available but we use "2.17.0"
```
## 0.7.0 (2024-04-30)
**[compare](https://github.com/metosin/reitit/compare/0.6.0..0.7.0)**
The OpenAPI3 release, Year in the making - the changes span over multiple repositories.
* Openapi3 support, see the [docs](https://github.com/metosin/reitit/blob/master/doc/ring/openapi.md)
* Fetch OpenAPI content types from Muuntaja [#636](https://github.com/metosin/reitit/issues/636)
* OpenAPI 3 parameter descriptions get populated from malli/spec/schema descriptions. [#612](https://github.com/metosin/reitit/issues/612)
* Generate correct OpenAPI $ref schemas for malli var and ref schemas [#673](https://github.com/metosin/reitit/pull/673)
* new syntax for `:request` and `:response` per-content-type coercions. See [coercion.md](doc/ring/coercion.md). [#627](https://github.com/metosin/reitit/issues/627)
* [#84](https://github.com/metosin/reitit/issues/84)
* Handlers can be vars [#585](https://github.com/metosin/reitit/pull/585)
* Fix swagger generation when unsupported coercions are present [#671](https://github.com/metosin/reitit/pull/671)
* **BREAKING**: require Clojure 1.11, drop support for Clojure 1.10
* **BREAKING**: `compile-request-coercers` returns a map with `:data` and `:coerce` instead of plain `:coerce` function
* **BREAKING**: Parameter and Response schemas are merged into the route data vector - so they can be properly merged into the compiled result, fixes [#422](https://github.com/metosin/reitit/issues/422) - merging multiple schemas together works with `Malli` and `Schema`, partially with `data-spec` but not with `spec`.
* Fixed some module dependencies so Cljdoc can properly analyze all the modules
* Fix reading fragment string on `Html5History` initialization
* Add fragment string parameter to reitit-frontend functions ([#604](https://github.com/metosin/reitit/pull/604))
* Frontend: provide easy way to update current query params. [#600](https://github.com/metosin/reitit/issues/600)
* Updated dependencies:
```clojure
[metosin/malli "0.16.1"] is available but we use "0.10.1"
[metosin/muuntaja "0.6.10"] is available but we use "0.6.8"
[metosin/spec-tools "0.10.6"] is available but we use "0.10.5"
[metosin/schema-tools "0.13.1"] is available but we use "0.13.0"
[metosin/jsonista "0.3.8"] is available but we use "0.3.7"
[com.fasterxml.jackson.core/jackson-core "2.17.0"] is available but we use "2.14.2"
[com.fasterxml.jackson.core/jackson-databind "2.17.0"] is available but we use "2.14.2"
[ring/ring-core "1.12.1"] is available but we use "1.9.6"
[metosin/ring-swagger-ui "5.9.0"] is available but we use "4.15.5"
```
## 0.7.0-alpha8 (2024-04-30)
**[compare](https://github.com/metosin/reitit/compare/0.7.0-alpha7...0.7.0-alpha8)**
* Handlers can be vars [#585](https://github.com/metosin/reitit/pull/585)
* Fetch OpenAPI content types from Muuntaja [#636](https://github.com/metosin/reitit/issues/636)
* **BREAKING** OpenAPI support is now clj only
@ -34,6 +203,8 @@ We use [Break Versioning][breakver]. The version numbers follow a `<major>.<mino
## 0.7.0-alpha7 (2023-10-03)
**[compare](https://github.com/metosin/reitit/compare/0.7.0-alpha6...0.7.0-alpha7)**
* Revert the group id change from alpha6
* New release to bring alpha6 changes to the old group id
* Updated dependencies:
@ -42,20 +213,20 @@ We use [Break Versioning][breakver]. The version numbers follow a `<major>.<mino
[metosin/ring-swagger-ui "4.19.1"] is available but we use "4.18.1"
```
**[compare](https://github.com/metosin/reitit/compare/0.7.0-alpha6...0.7.0-alpha7)**
## 0.7.0-alpha6 (2023-09-11)
**[compare](https://github.com/metosin/reitit/compare/0.7.0-alpha5...0.7.0-alpha6)**
* **BREAKING**: require Clojure 1.11, drop support for Clojure 1.10
* **BREAKING**: new syntax for `:request` and `:response` per-content-type coercions. See [coercion.md](doc/ring/coercion.md). [#627](https://github.com/metosin/reitit/issues/627)
* **BREAKING**: replace the openapi `:content-types` keyword with separate `:openapi/request-content-types` and `:openapi/response-content-types`. See [openapi.md](doc/ring/openapi.md)
* **NOTE!**: all reitit libraries are now under the `fi.metosin` group on clojars instead of `metosin`. Use `fi.metosin/reitit` in your dependencies instead of `metosin/reitit` to get new versions.
- **Reverted in alpha7 due to problems with renaming artifacts**
**[compare](https://github.com/metosin/reitit/compare/0.7.0-alpha5...0.7.0-alpha6)**
## 0.7.0-alpha5 (2023-06-14)
**[compare](https://github.com/metosin/reitit/compare/0.7.0-alpha4...0.7.0-alpha5)**
* **BREAKING**: `compile-request-coercers` returns a map with `:data` and `:coerce` instead of plain `:coerce` function
* **BREAKING**: Parameter and Response schemas are merged into the route data vector - so they can be properly merged into the compiled result, fixes [#422](https://github.com/metosin/reitit/issues/422) - merging multiple schemas together works with `Malli` and `Schema`, partially with `data-spec` but not with `spec`.
* Fixed some module dependencies so Cljdoc can properly analyze all the modules
@ -67,29 +238,29 @@ We use [Break Versioning][breakver]. The version numbers follow a `<major>.<mino
[com.fasterxml.jackson.core/jackson-databind "2.15.1"] is available but we use "2.14.2"
```
**[compare](https://github.com/metosin/reitit/compare/0.7.0-alpha4...0.7.0-alpha5)**
## 0.7.0-alpha4 (2023-05-17)
* OpenAPI 3 parameter descriptions get populated from malli/spec/schema descriptions. [#612](https://github.com/metosin/reitit/issues/612)
**[compare](https://github.com/metosin/reitit/compare/0.7.0-alpha3...0.7.0-alpha4)**
## 0.7.0-alpha3 (2023-05-05)
* OpenAPI 3 parameter descriptions get populated from malli/spec/schema descriptions. [#612](https://github.com/metosin/reitit/issues/612)
* Compile `reitit.Trie` with Java 1.8 target for compatibility
## 0.7.0-alpha3 (2023-05-05)
**[compare](https://github.com/metosin/reitit/compare/0.7.0-alpha2...0.7.0-alpha3)**
* Compile `reitit.Trie` with Java 1.8 target for compatibility
## 0.7.0-alpha2 (2023-05-04)
**[compare](https://github.com/metosin/reitit/compare/0.7.0-alpha1...0.7.0-alpha2)**
* Fix reading fragment string on `Html5History` initialization
* Add fragment string parameter to reitit-frontend functions ([#604](https://github.com/metosin/reitit/pull/604))
**[compare](https://github.com/metosin/reitit/compare/0.7.0-alpha1...0.7.0-alpha2)**
## 0.7.0-alpha1 (2023-05-03)
**[compare](https://github.com/metosin/reitit/compare/0.6.0...0.7.0-alpha1)**
* Initial Openapi3 support. See [docs](./doc/ring/openapi.md). Works for simple cases but might still have some rough edges. [#84](https://github.com/metosin/reitit/issues/84)
* Frontend: provide easy way to update current query params. [#600](https://github.com/metosin/reitit/issues/600)
* Updated dependencies:
@ -100,10 +271,10 @@ We use [Break Versioning][breakver]. The version numbers follow a `<major>.<mino
[ring/ring-core "1.10.0"] is available but we use "1.9.6"
```
**[compare](https://github.com/metosin/reitit/compare/0.6.0...0.7.0-alpha1)**
## 0.6.0 (2023-02-21)
**[compare](https://github.com/metosin/reitit/compare/0.5.18...0.6.0)**
* Add reitit-frontend support for fragment string [#581](https://github.com/metosin/reitit/pull/581)
* reloading-ring-handler [#584](https://github.com/metosin/reitit/pull/584)
* Remove redundant s/and [#552](https://github.com/metosin/reitit/pull/552)
@ -129,23 +300,21 @@ We use [Break Versioning][breakver]. The version numbers follow a `<major>.<mino
[com.fasterxml.jackson.core/jackson-databind "2.14.2"] is available but we use "2.14.1"
```
**[compare](https://github.com/metosin/reitit/compare/0.5.18...0.6.0)**
## 0.5.18 (2022-04-05)
**[compare](https://github.com/metosin/reitit/compare/0.5.17...0.5.18)**
* FIX [#334](https://github.com/metosin/reitit/pull/334) - Frontend: there is no way to catch the exception if coercion fails (via [#549](https://github.com/metosin/reitit/pull/549))
* Save three seq constructions [#537](https://github.com/metosin/reitit/pull/537)
* update jackson-databind for CVE-2020-36518 [#544](https://github.com/metosin/reitit/pull/544)
* Balance parenthesis in docs [#547](https://github.com/metosin/reitit/pull/547)
**[compare](https://github.com/metosin/reitit/compare/0.5.17...0.5.18)**
## 0.5.17 (2022-03-10)
* FIX match-by-path is broken if there are no non-conflicting wildcard routes [#538](https://github.com/metosin/reitit/issues/538)
**[compare](https://github.com/metosin/reitit/compare/0.5.16...0.5.17)**
* FIX match-by-path is broken if there are no non-conflicting wildcard routes [#538](https://github.com/metosin/reitit/issues/538)
## 0.5.16 (2022-02-15)
**[compare](https://github.com/metosin/reitit/compare/0.5.15...0.5.16)**

View file

@ -1,10 +0,0 @@
help:
@just --list
# Initializes lint
init-lint:
clj-kondo --lint $(lein classpath)
# Lints the project
lint:
./lint.sh

View file

@ -1,5 +1,11 @@
# reitit [![Build Status](https://github.com/metosin/reitit/workflows/testsuite/badge.svg)](https://github.com/metosin/reitit/actions?query=workflow%3Atestsuite) [![cljdoc badge](https://cljdoc.org/badge/metosin/reitit)](https://cljdoc.org/jump/release/metosin/reitit) [![Slack](https://img.shields.io/badge/clojurians-reitit-blue.svg?logo=slack)](https://clojurians.slack.com/messages/reitit/)
# reitit
[![Build Status](https://github.com/metosin/reitit/actions/workflows/testsuite.yml/badge.svg)](https://github.com/metosin/reitit/actions)
[![cljdoc badge](https://cljdoc.org/badge/metosin/reitit)](https://cljdoc.org/d/metosin/reitit/)
[![Clojars Project](https://img.shields.io/clojars/v/metosin/reitit.svg)](https://clojars.org/metosin/reitit)
[![Slack](https://img.shields.io/badge/clojurians-reitit-blue.svg?logo=slack)](https://clojurians.slack.com/messages/reitit/)
<img src="https://github.com/metosin/reitit/blob/master/doc/images/reitit.png?raw=true" align="right" width="200" />
A fast data-driven router for Clojure(Script).
* Simple data-driven [route syntax](https://cljdoc.org/d/metosin/reitit/CURRENT/doc/basics/route-syntax/)
@ -22,26 +28,34 @@ Presentations:
**Status:** [stable](https://github.com/metosin/open-source#project-lifecycle-model)
> Hi! We are [Metosin](https://metosin.fi), a consulting company. These libraries have evolved out of the work we do for our clients.
> We maintain & develop this project, for you, for free. Issues and pull requests welcome!
> However, if you want more help using the libraries, or want us to build something as cool for you, consider our [commercial support](https://www.metosin.fi/en/open-source-support).
## [Full Documentation](https://cljdoc.org/d/metosin/reitit/CURRENT)
There is [#reitit](https://clojurians.slack.com/messages/reitit/) in [Clojurians Slack](http://clojurians.net/) for discussion & help.
## Main Modules
* `reitit` - all bundled
* `reitit-core` - the routing core
* `reitit-ring` - a [ring router](https://cljdoc.org/d/metosin/reitit/CURRENT/doc/ring/ring-router/)
* `reitit-middleware` - [common middleware](https://cljdoc.org/d/metosin/reitit/CURRENT/doc/ring/default-middleware/)
* `reitit-spec` [clojure.spec](https://clojure.org/about/spec) coercion
* `reitit-malli` [malli](https://github.com/metosin/malli) coercion
* `reitit-schema` [Schema](https://github.com/plumatic/schema) coercion
* `reitit-swagger` [Swagger2](https://swagger.io/) apidocs
* `reitit-swagger-ui` Integrated [Swagger UI](https://github.com/swagger-api/swagger-ui)
* `reitit-frontend` Tools for [frontend routing]((https://cljdoc.org/d/metosin/reitit/CURRENT/doc/frontend/basics/))
* `reitit-http` http-routing with Interceptors
* `reitit-interceptors` - [common interceptors](https://cljdoc.org/d/metosin/reitit/CURRENT/doc/http/default-interceptors/)
* `reitit-sieppari` support for [Sieppari](https://github.com/metosin/sieppari)
* `reitit-dev` - development utilities
* `metosin/reitit` - all bundled
* `metosin/reitit-core` - the routing core
* `metosin/reitit-ring` - a [ring router](https://cljdoc.org/d/metosin/reitit/CURRENT/doc/ring/ring-router/)
* `metosin/reitit-middleware` - [common middleware](https://cljdoc.org/d/metosin/reitit/CURRENT/doc/ring/default-middleware/)
* `metosin/reitit-spec` [clojure.spec](https://clojure.org/about/spec) coercion
* `metosin/reitit-malli` [malli](https://github.com/metosin/malli) coercion
* `metosin/reitit-schema` [Schema](https://github.com/plumatic/schema) coercion
* `fi.metosin/reitit-openapi` [OpenAPI](https://www.openapis.org/) apidocs *
* `metosin/reitit-swagger` [Swagger2](https://swagger.io/) apidocs
* `metosin/reitit-swagger-ui` Integrated [Swagger UI](https://github.com/swagger-api/swagger-ui)
* `metosin/reitit-frontend` Tools for [frontend routing]((https://cljdoc.org/d/metosin/reitit/CURRENT/doc/frontend/basics/))
* `metosin/reitit-http` http-routing with Interceptors
* `metosin/reitit-interceptors` - [common interceptors](https://cljdoc.org/d/metosin/reitit/CURRENT/doc/http/default-interceptors/)
* `metosin/reitit-sieppari` support for [Sieppari](https://github.com/metosin/sieppari)
* `metosin/reitit-dev` - development utilities
... * This is not a typo; the new `reitit-openapi` was released under the new, verified `fi.metosin` group. Existing
modules will continue to be released under `metosin` for compatibility purposes.
## Extra modules
@ -52,14 +66,14 @@ There is [#reitit](https://clojurians.slack.com/messages/reitit/) in [Clojurians
All main modules bundled:
```clj
[metosin/reitit "0.7.0-alpha8"]
[metosin/reitit "0.10.0"]
```
Optionally, the parts can be required separately.
Malli requires Clojure 1.11.
Reitit requires Clojure 1.11 and Java 11.
Malli is tested with the LTS releases Java 8, 11, 17 and 21.
Reitit is tested with the LTS releases Java 11, 17, 21 and 25
## Quick start
@ -95,6 +109,7 @@ A Ring routing app with input & output coercion using [data-specs](https://githu
(require '[reitit.ring :as ring])
(require '[reitit.coercion.spec])
(require '[reitit.ring.coercion :as rrc])
(require '[reitit.ring.middleware.exception :as exception])
(require '[reitit.ring.middleware.muuntaja :as muuntaja])
(require '[reitit.ring.middleware.parameters :as parameters])
@ -110,39 +125,45 @@ A Ring routing app with input & output coercion using [data-specs](https://githu
;; router data affecting all routes
{:data {:coercion reitit.coercion.spec/coercion
:muuntaja m/instance
:middleware [parameters/parameters-middleware
:middleware [parameters/parameters-middleware ; decoding query & form params
muuntaja/format-middleware ; content negotiation
exception/exception-middleware ; converting exceptions to HTTP responses
rrc/coerce-request-middleware
muuntaja/format-response-middleware
rrc/coerce-response-middleware]}})))
```
Valid request:
```clj
(app {:request-method :get
:uri "/api/math"
:query-params {:x "1", :y "2"}})
(-> (app {:request-method :get
:uri "/api/math"
:query-params {:x "1", :y "2"}})
(update :body slurp))
; {:status 200
; :body {:total 3}}
; :body "{\"total\":3}"
; :headers {"Content-Type" "application/json; charset=utf-8"}}
```
Invalid request:
```clj
(app {:request-method :get
:uri "/api/math"
:query-params {:x "1", :y "a"}})
;{:status 400,
; :body {:type :reitit.coercion/request-coercion,
; :coercion :spec,
; :spec "(spec-tools.core/spec {:spec (clojure.spec.alpha/keys :req-un [:$spec20745/x :$spec20745/y]), :type :map, :keys #{:y :x}, :keys/req #{:y :x}})",
; :problems [{:path [:y],
; :pred "clojure.core/int?",
; :val "a",
; :via [:$spec20745/y],
; :in [:y]}],
; :value {:x "1", :y "a"},
; :in [:request :query-params]}}
(-> (app {:request-method :get
:uri "/api/math"
:query-params {:x "1", :y "a"}})
(update :body jsonista.core/read-value))
; {:status 400
; :headers {"Content-Type" "application/json; charset=utf-8"}
; :body {"spec" "(spec-tools.core/spec {:spec (clojure.spec.alpha/keys :req-un [:spec$8974/x :spec$8974/y]), :type :map, :leaf? false})"
; "value" {"x" "1"
; "y" "a"}
; "problems" [{"via" ["spec$8974/y"]
; "path" ["y"]
; "pred" "clojure.core/int?"
; "in" ["y"]
; "val" "a"}]
; "type" "reitit.coercion/request-coercion"
; "coercion" "spec"
; "in" ["request" "query-params"]}}
```
## More examples

26
bb.edn Normal file
View file

@ -0,0 +1,26 @@
{:tasks
{init-lint {:task (shell "sh -c" "clj-kondo --copy-configs --lint $(lein classpath)")}
lint {:doc "Run clj-kondo"
:task (shell "./lint.sh")}
watch-node-test {:doc "Watch files for changes and run Cljs tests on Node.js"
:task (shell "npx shadow-cljs watch node-test")}
node-test {:doc "Compile and run Cljs tests"
:task (shell "npx shadow-cljs compile node-test")}
watch-browser-test-local {:doc "Start watching Cljs tests for changes and start HTTP server for running tests in a local browser"
:task (shell "npx shadow-cljs watch browser-test")}
;; Karma watch needs to file to exist before start
-karma-placeholder (shell "sh -c" "mkdir -p target/karma && touch target/karma/ci.js")
-watch-karma-cljs {:depends [-karma-placeholder]
:task (shell "npx shadow-cljs watch karma")}
-watch-karma-test (shell "npx karma start")
-watch-karma {:depends [-watch-karma-cljs -watch-karma-test]}
watch-karma {:doc "Watch Cljs tests for changes, compile for Karma and run Karma tests on changes"
:task (run '-watch-karma {:parallel true})}
test-karma {:doc "Compile Cljs tests and run using Karma once"
:task (do
(shell "npx shadow-cljs compile karma")
(shell "npx karma start --single-run"))}}}

View file

@ -0,0 +1,3 @@
{
"name": "Example"
}

View file

@ -41,7 +41,7 @@ There is [#reitit](https://clojurians.slack.com/messages/reitit/) in [Clojurians
All bundled:
```clj
[metosin/reitit "0.7.0-alpha8"]
[metosin/reitit "0.10.0"]
```
Optionally, the parts can be required separately.

View file

@ -22,7 +22,7 @@ The default exception formatting uses `reitit.exception/exception`. It produces
## Pretty Errors
```clj
[metosin/reitit-dev "0.7.0-alpha8"]
[metosin/reitit-dev "0.10.0"]
```
For human-readable and developer-friendly exception messages, there is `reitit.dev.pretty/exception` (in the `reitit-dev` module). It is inspired by the lovely errors messages of [ELM](https://elm-lang.org/blog/compiler-errors-for-humans) and [ETA](https://twitter.com/jyothsnasrin/status/1037703436043603968) and uses [fipp](https://github.com/brandonbloom/fipp), [expound](https://github.com/bhb/expound) and [spell-spec](https://github.com/bhauman/spell-spec) for most of heavy lifting.

View file

@ -75,6 +75,17 @@ Path-parameters are automatically coerced into strings, with the help of (curren
; :path-params {:id "1"}}
```
In case you want to do something like generate a template path for documentation, you can disable url-encoding:
```clj
(r/match-by-name router ::user {:id "<id goes here>"} {:url-encode? false})
; #reitit.core.Match{:template "/api/user/:id"
; :data {:name :user/user}
; :path "/api/user/<id goes here>"
; :result nil
; :path-params {:id "<id goes here>"}}
```
There is also an exception throwing version:
```clj
@ -97,5 +108,5 @@ It can take an optional map of query-parameters too:
(-> router
(r/match-by-name ::user {:id 1})
(r/match->path {:iso "möly"}))
; "/api/user/1?iso=m%C3%B6ly"
; "/api/user/1?iso=m%C3%B6ly"
```

View file

@ -9,9 +9,10 @@ Route data is the key feature of reitit. Routes can have any map-like data attac
:handler identity}}]]
```
Besides map-like data, raw routes can have any non-sequential route argument after the path. This argument is expanded by `Router` (via `:expand` option) into route data at router creation time.
Besides map-like data, raw routes can have any non-sequential route argument after the path. This argument is expanded by `Router` (via `:expand` option) into route data at router creation time.
By default, Keywords are expanded into `:name` and functions into `:handler` keys.
By default, Keywords are expanded into `:name` (see [Name-based Routing](./name_based_routing.md))
and functions into `:handler` keys.
```clj
(require '[reitit.core :as r])
@ -84,6 +85,8 @@ Resolved route tree:
; :roles #{:db-admin}}]]
```
See also [nested parameter definitions for coercions](../ring/coercion.md#nested-parameter-definitions)
## Route Data Fragments
Just like [fragments in React.js](https://reactjs.org/docs/fragments.html), we can create routing tree fragments by using empty path `""`. This allows us to add route data without accumulating to path.
@ -117,7 +120,7 @@ Accumulated route data:
["/api-docs" ::api-docs]]
["/api/ping" ::ping]
["/api/pong" ::pong]]))
(r/routes router)
; [["/swagger.json" {:no-doc true, :name ::swagger}]
; ["/api-docs" {:no-doc true, :name ::api-docs}]

View file

@ -1,6 +1,9 @@
# Route Syntax
Routes are defined as vectors of String path and optional (non-sequential) route argument child routes.
Routes are defined as vectors of:
- path (a string)
- optional route data: usually a map, but see [Route Data](./route_data.md)
- any number of child routes
Routes can be wrapped in vectors and lists and `nil` routes are ignored.
@ -11,43 +14,38 @@ Paths can have path-parameters (`:id`) or catch-all-parameters (`*path`). Parame
Simple route:
```clj
["/ping"]
["/ping" {:handler ping}]
```
Two routes:
Two routes with more data:
```clj
[["/ping"]
["/pong"]]
[["/ping" {:handler ping
:cost 300}]
["/pong" {:handler pong
:tags #{:game}}]]
```
Routes with route arguments:
Routes with path parameters (see also [Coercion](../coercion/coercion.md) and [Ring Coercion](../ring/coercion.md)):
```clj
[["/ping" ::ping]
["/pong" {:name ::pong}]]
```
Routes with path parameters:
```clj
[["/users/:user-id"]
["/api/:version/ping"]]
[["/users/:user-id" {:handler get-user}]
["/api/:version/ping" {:handler ping-version}]]
```
```clj
[["/users/{user-id}"]
["/files/file-{number}.pdf"]]
[["/users/{user-id}" {:handler get-user}]
["/files/file-{number}.pdf" {:handler get-pdf}]]
```
Route with catch-all parameter:
```clj
["/public/*path"]
["/public/*path" {:handler get-file}]
```
```clj
["/public/{*path}"]
["/public/{*path}" {:handler get-file}]
```
Nested routes:
@ -55,9 +53,9 @@ Nested routes:
```clj
["/api"
["/admin" {:middleware [::admin]}
["" ::admin]
["/db" ::db]]
["/ping" ::ping]]
["" {:name ::admin}]
["/db" {:name ::db}]]
["/ping" {:name ::ping}]]
```
Same routes flattened:
@ -77,31 +75,31 @@ Reitit does not apply any encoding to your paths. If you need that, you must enc
Normal path-parameters (`:id`) can start anywhere in the path string, but have to end either to slash `/` (currently hardcoded) or to an end of path string:
```clj
[["/api/:version"]
["/files/file-:number"]
["/user/:user-id/orders"]]
[["/api/:version" {...}]
["/files/file-:number" {...}]
["/user/:user-id/orders" {...}]]
```
Bracket path-parameters can start and stop anywhere in the path-string, the following character is used as a terminator.
```clj
[["/api/{version}"]
["/files/{name}.{extension}"]
["/user/{user-id}/orders"]]
[["/api/{version}" {...}]
["/files/{name}.{extension}" {...}]
["/user/{user-id}/orders" {...}]]
```
Having multiple terminators after a bracket path-path parameter with identical path prefix will cause a compile-time error at router creation:
```clj
[["/files/file-{name}.pdf"] ;; terminator \.
["/files/file-{name}-{version}.pdf"]] ;; terminator \-
[["/files/file-{name}.pdf" {...}] ;; terminator \.
["/files/file-{name}-{version}.pdf" {...}]] ;; terminator \-
```
### Slash Free Routing
```clj
[["broker.{customer}.{device}.{*data}"]
["events.{target}.{type}"]]
[["broker.{customer}.{device}.{*data}" {...}]
["events.{target}.{type}" {...}]]
```
### Generating routes

View file

@ -73,9 +73,10 @@ Using `create` with options to create the coercion instead of `coercion`:
{:transformers {:body {:default reitit.coercion.malli/default-transformer-provider
:formats {"application/json" reitit.coercion.malli/json-transformer-provider}}
:string {:default reitit.coercion.malli/string-transformer-provider}
:response {:default reitit.coercion.malli/default-transformer-provider}}
:response {:default reitit.coercion.malli/default-transformer-provider
:formats {"application/json" reitit.coercion.malli/json-transformer-provider}}}
;; set of keys to include in error messages
:error-keys #{:type :coercion :in :schema :value :errors :humanized #_:transformed}
:error-keys #{:type :coercion :in #_:schema :value #_:errors :humanized #_:transformed}
;; support lite syntax?
:lite true
;; schema identity function (default: close all map schemas)
@ -87,7 +88,37 @@ Using `create` with options to create the coercion instead of `coercion`:
;; strip-extra-keys (affects only predefined transformers)
:strip-extra-keys true
;; add/set default values
;; Can be false, true or a map of options to pass to malli.transform/default-value-transformer,
;; for example {:malli.transform/add-optional-keys true}
:default-values true
;; encode-error
:encode-error nil
;; malli options
:options nil})
```
## Configuring humanize error messages
Malli humanized error messages can be configured using `:options :errors`:
```clj
(reitit.coercion.malli/create
{:options
{:errors (assoc malli.error/default-errors
:malli.core/missing-key {:error/message {:en "MISSING"}})}})
```
See the malli docs for more info.
## Custom registry
Malli registry can be configured conveniently via `:options :registry`:
```clj
(require '[malli.core :as m])
(reitit.coercion.malli/create
{:options
{:registry {:registry (merge (m/default-schemas)
{:my-type :string})}}})
```

View file

@ -25,6 +25,14 @@ clojure-lsp clean-ns
The documentation lives under `doc` and it is hosted on [cljdoc](https://cljdoc.org). See their
documentation for [library authors](https://github.com/cljdoc/cljdoc/blob/master/doc/userguide/for-library-authors.adoc)
## Updating deps
* `lein ancient upgrade`
* Mention non-dev non-test dep upgrades in CHANGELOG.md
* `npm update --save`
* Make a PR, run CI
## Making a release
We use [Break Versioning][breakver]. Remember our promise: patch-level bumps never include breaking changes!
@ -32,29 +40,20 @@ We use [Break Versioning][breakver]. Remember our promise: patch-level bumps nev
[breakver]: https://github.com/ptaoussanis/encore/blob/master/BREAK-VERSIONING.md
```bash
# Check that you're using Java 8! Making the release with a newer Java version
# means that it is broken when used with Java 8.
java -version
# new version
# create a release commit
./scripts/set-version "1.0.0"
# create a release commit and a tag
git add -u
# !!! update the changelog
git add -u
git commit -m "Release 1.0.0"
git tag 1.0.0
# works
./scripts/lein-modules install
lein test
# deploy to clojars
CLOJARS_USERNAME=*** CLOJARS_PASSWORD=*** ./scripts/lein-modules do clean, deploy clojars
# push the commit and the tag
# push the commit
git push
git push --tags
# !!! check that tests pass on CI
```
* Remembor to update the changelog!
* Create a new release on github at <https://github.com/metosin/reitit/releases>
* This will trigger the automated release workflow <https://github.com/metosin/reitit/actions/workflows/release.yml>
* Announce the release at least on #reitit in Clojurians.

View file

@ -8,6 +8,10 @@ history events
- Stateful wrapper for easy use of history integration
- Optional [controller extension](./controllers.md)
You likely won't use `reitit.frontend` directly in your apps and instead you
will use the API documented in the browser integration docs, which wraps these
lower level functions.
## Core functions
`reitit.frontend` provides some useful functions wrapping core functions:
@ -23,7 +27,8 @@ enabled.
`match-by-name` and `match-by-name!` with optional `path-paramers` and
logging errors to `console.warn` instead of throwing errors to prevent
React breaking due to errors.
React breaking due to errors. These can also [encode query-parameters](./coercion.md)
using schema from match data.
## Next

View file

@ -13,6 +13,9 @@ There are also secondary functions following HTML5 History API:
`push-state` to navigate to new route adding entry to the history and
`replace-state` to change route without leaving previous entry in browser history.
See [coercion notes](./coercion.md) to see how frontend route parameters
can be decoded and encoded.
## Fragment router
Fragment is simple integration which stores the current route in URL fragment,
@ -62,7 +65,7 @@ event handler for page change events.
## History manipulation
Reitit doesn't include functions to manipulate the history stack, i.e.
Reitit doesn't include functions to manipulate the history stack, i.e.,
go back or forwards, but calling History API functions directly should work:
```

59
doc/frontend/coercion.md Normal file
View file

@ -0,0 +1,59 @@
# Frontend coercion
The Reitit frontend leverages [coercion](../coercion/coercion.md) for path,
query, and fragment parameters. The coercion uses the input schema defined
in the match data under `:parameters`.
## Behavior of Coercion
1. **Route Matching**
When matching a route from a path, the resulting match will include the
coerced values (if coercion is enabled) under `:parameters`. If coercion is
disabled, the parsed string values are stored in the same location.
The original un-coerced values are always available under `:path-params`,
`:query-params`, and `:fragment` (a single string).
2. **Creating Links and Navigating**
When generating a URL (`href`) or navigating (`push-state`, `replace-state`, `navigate`)
to a route, coercion can be
used to encode query-parameter values into strings. This happens before
Reitit performs basic URL encoding on the values. This feature is
especially useful for handling the encoding of specific types, such as
keywords or dates, into strings.
3. **Updating current query parameters**
When using `set-query` to modify current query parameters, Reitit frontend
first tries to find a match for the current path so the match can be used to
first decode query parameters and then to encode them. If the current path
doesn't match the routing tree, `set-query` keeps all the query parameter
values as strings.
## Notes
- **Value Encoding Support**: Only Malli supports value encoding.
- **Limitations**: Path parameters and fragment values are not encoded using
the match schema.
## Example
```cljs
(def router (r/router ["/"
["" ::frontpage]
["bar"
{:name ::bar
:coercion rcm/coercion
:parameters {:query [:map
[:q {:optional true}
[:keyword
{:decode/string (fn [s] (keyword (subs s 2)))
:encode/string (fn [k] (str "__" (name k)))}]]]}}]]))
(rfe/href ::bar {} {:q :hello})
;; Result "/bar?q=__hello", the :q value is first encoded
(rfe/push-state ::bar {} {:q :world})
;; Result "/bar?q=__world"
;; The current match will contain both the original value and parsed & decoded parameters:
;; {:query-params {:q "__world"}
;; :parameters {:query {:q :world}}}
```

View file

@ -1,7 +1,7 @@
# Default Interceptors
```clj
[metosin/reitit-interceptors "0.7.0-alpha8"]
[metosin/reitit-interceptors "0.10.0"]
```
Just like the [ring default middleware](../ring/default_middleware.md), but for interceptors.

View file

@ -5,7 +5,7 @@ Reitit has also support for [interceptors](http://pedestal.io/reference/intercep
## Reitit-http
```clj
[metosin/reitit-http "0.7.0-alpha8"]
[metosin/reitit-http "0.10.0"]
```
A module for http-routing using interceptors instead of middleware. Builds on top of the [`reitit-ring`](../ring/ring.md) module having all the same features.

View file

@ -3,7 +3,7 @@
[Pedestal](http://pedestal.io/) is a backend web framework for Clojure. `reitit-pedestal` provides an alternative routing engine for Pedestal.
```clj
[metosin/reitit-pedestal "0.7.0-alpha8"]
[metosin/reitit-pedestal "0.10.0"]
```
Why should one use reitit instead of the Pedestal [default routing](http://pedestal.io/reference/routing-quick-reference)?
@ -26,8 +26,8 @@ A minimalistic example on how to to swap the default-router with a reitit router
```clj
; [io.pedestal/pedestal.service "0.5.5"]
; [io.pedestal/pedestal.jetty "0.5.5"]
; [metosin/reitit-pedestal "0.7.0-alpha8"]
; [metosin/reitit "0.7.0-alpha8"]
; [metosin/reitit-pedestal "0.10.0"]
; [metosin/reitit "0.10.0"]
(require '[io.pedestal.http :as server])
(require '[reitit.pedestal :as pedestal])

View file

@ -1,7 +1,7 @@
# Sieppari
```clj
[metosin/reitit-sieppari "0.7.0-alpha8"]
[metosin/reitit-sieppari "0.10.0"]
```
[Sieppari](https://github.com/metosin/sieppari) is a new and fast interceptor implementation for Clojure, with pluggable async supporting [core.async](https://github.com/clojure/core.async), [Manifold](https://github.com/ztellman/manifold) and [Promesa](http://funcool.github.io/promesa/latest).

View file

@ -65,7 +65,7 @@ There is an extra option in http-router (actually, in the underlying interceptor
### Printing Context Diffs
```clj
[metosin/reitit-interceptors "0.7.0-alpha8"]
[metosin/reitit-interceptors "0.10.0"]
```
Using `reitit.http.interceptors.dev/print-context-diffs` transformation, the context diffs between each interceptor are printed out to the console. To use it, add the following router option:

BIN
doc/images/reitit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

View file

@ -37,7 +37,7 @@ Coercion can be attached to route data under `:coercion` key. There can be multi
Parameters are defined in route data under `:parameters` key. It's value should be a map of parameter `:type` -> Coercion Schema.
Responses are defined in route data under `:responses` key. It's value should be a map of http status code to a map which can contain `:body` key with Coercion Schema as value.
Responses are defined in route data under `:responses` key. It's value should be a map of http status code to a map which can contain `:body` key with Coercion Schema as value. Additionally, the key `:default` specifies the coercion for other status codes.
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`.
@ -54,7 +54,8 @@ Handlers can access the coerced parameters via the `:parameters` key in the requ
:parameters {:query {:x s/Int}
:body {:y s/Int}
:path {:z s/Int}}
:responses {200 {:body {:total PositiveInt}}}
:responses {200 {:body {:total PositiveInt}}
:default {:body {:error s/Str}}}
:handler (fn [{:keys [parameters]}]
(let [total (+ (-> parameters :query :x)
(-> parameters :body :y)
@ -62,6 +63,54 @@ Handlers can access the coerced parameters via the `:parameters` key in the requ
{:status 200
:body {:total total}}))})
```
### Nested parameter definitions
Parameters are accumulated recursively along the route tree, just like
other [route data](../basics/route_data.md). There is special case
handling for merging eg. malli `:map` schemas.
```clj
(def router
(reitit.ring/router
["/api" {:get {:parameters {:query [:map [:api-key :string]]}}}
["/project/:project-id" {:get {:parameters {:path [:map [:project-id :int]]}}}
["/task/:task-id" {:get {:parameters {:path [:map [:task-id :int]]
:query [:map [:details :boolean]]}
:handler (fn [req] (prn req))}}]]]
{:data {:coercion reitit.coercion.malli/coercion}}))
```
```clj
(-> (r/match-by-path router "/api/project/1/task/2") :result :get :data :parameters)
; {:query [:map
; {:closed true}
; [:api-key :string]
; [:details :boolean]],
; :path [:map
; {:closed true}
; [:project-id :int]
; [:task-id :int]]}
```
### Differences in behaviour for different parameters
All parameter coercions *except* `:body`:
1. Allow keys outside the schema (by opening up the schema using eg. `malli.util/open-schema`)
2. Keywordize the keys (ie. header & query parameter names) of the input before coercing
In contrast, the `:body` coercion:
1. Uses the specified schema
* depending on the coercion, it can be configured as open or closed, see specific coercion docs for details
2. Does not keywordize the keys of the input before coercion
* however, coercions like malli might do the keywordization when coercing json bodies, depending on configuration
This admittedly confusing behaviour is retained currently due to
backwards compatibility reasons. It can be configured by passing
option `:reitit.coercion/parameter-coercion` to `reitit.ring/router`
or `reitit.coercion/compile-request-coercers`. See also:
`reitit.coercion/default-parameter-coercion`.
## Coercion Middleware
@ -171,11 +220,32 @@ is:
"application/edn" {:schema {:x s/Int}}
:default {:schema {:ww s/Int}}}}}
:handler ...}}]]
{:data {:middleware [rrc/coerce-exceptions-middleware
rrc/coerce-request-middleware
rrc/coerce-response-middleware]}})))
{:data {:muuntaja muuntaja.core/instance
:middleware [reitit.ring.middleware.muuntaja/format-middleware
reitit.ring.coercion/coerce-exceptions-middleware
reitit.ring.coercion/coerce-request-middleware
reitit.ring.coercion/coerce-response-middleware]}})))
```
The resolution logic for response coercers is:
1. Get the response status, or `:default` from the `:responses` map
2. From this map, get use the first of these to coerce:
1. `:content <content-type> :schema`
2. `:content :default :schema`
3. `:body`
3. If nothing was found, do not coerce
To select the response content-type, you can either:
1. Let muuntaja pick the content-type based on things like the request Accept header
- This is what most users want
2. Set `:muuntaja/content-type` in the response to pick an explicit content type
3. Set the `"Content-Type"` header in the response
- This disables muuntaja, so you need to encode your response body in some other way!
- This is not compatible with response schema checking, since coercion won't know what to do with the already-encoded response body.
4. Use the `:extract-response-format` option to inject your own logic. See `reitit.coercion/extract-response-format-default` for the default.
See also the [muuntaja content negotiation](./content_negotiation.md) docs.
## Pretty printing spec errors
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:

View file

@ -27,8 +27,8 @@ To demonstrate the two approaches, below is the response coercion middleware wri
coercion (-> match :data :coercion)
opts (-> match :data :opts)]
(if (and coercion responses)
(let [coercers (response-coercers coercion responses opts)]
(coerce-response coercers request response))
(let [coercer (response-coercer coercion responses opts)]
(coercer request response))
response)))
([request respond raise]
(let [method (:request-method request)
@ -37,8 +37,8 @@ To demonstrate the two approaches, below is the response coercion middleware wri
coercion (-> match :data :coercion)
opts (-> match :data :opts)]
(if (and coercion responses)
(let [coercers (response-coercers coercion responses opts)]
(handler request #(respond (coerce-response coercers request %))))
(let [coercer (response-coercer coercion responses opts)]
(handler request #(respond (coercer request %))))
(handler request respond raise))))))
```
@ -60,13 +60,13 @@ To demonstrate the two approaches, below is the response coercion middleware wri
:spec ::rs/responses
:compile (fn [{:keys [coercion responses]} opts]
(if (and coercion responses)
(let [coercers (coercion/response-coercers coercion responses opts)]
(let [coercer (coercion/response-coercer coercion responses opts)]
(fn [handler]
(fn
([request]
(coercion/coerce-response coercers request (handler request)))
(coercer request (handler request)))
([request respond raise]
(handler request #(respond (coercion/coerce-response coercers request %)) raise)))))))})
(handler request #(respond (coercer request %)) raise)))))))})
```
It has 50% less code, it's much easier to reason about and is much faster.

View file

@ -2,6 +2,8 @@
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.
For the basics of reitit middleware, [read this first](ring.md#middleware).
Reitit defines middleware as data:
1. A middleware can be defined as first-class data entries

View file

@ -1,7 +1,7 @@
# Default Middleware
```clj
[metosin/reitit-middleware "0.7.0-alpha8"]
[metosin/reitit-middleware "0.10.0"]
```
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.
@ -17,8 +17,6 @@ Any Ring middleware can be used with `reitit-ring`, but using data-driven middle
`reitit.ring.middleware.parameters/parameters-middleware` to capture query- and form-params. Wraps
`ring.middleware.params/wrap-params`.
**NOTE**: This middleware will be factored into two parts: a query-parameters middleware and a Muuntaja format responsible for the the `application/x-www-form-urlencoded` body format. cf. https://github.com/metosin/reitit/issues/134
## Exception Handling
See [Exception Handling with Ring](exceptions.md).

View file

@ -1,7 +1,7 @@
# Exception Handling with Ring
```clj
[metosin/reitit-middleware "0.7.0-alpha8"]
[metosin/reitit-middleware "0.10.0"]
```
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.

View file

@ -2,7 +2,7 @@
The `:middleware` syntax in `reitit-ring` also supports Keywords. Keywords are looked up from the Middleware Registry, which is a map of `keyword => IntoMiddleware`. Middleware registry should be stored under key `:reitit.middleware/registry` in the router options. If a middleware keyword isn't found in the registry, router creation fails fast with a descriptive error message.
## Examples
## Examples
Application using middleware defined in the Middleware Registry:
@ -52,6 +52,20 @@ Router creation fails fast if the registry doesn't contain the middleware:
;| :bonus | reitit.ring_test$wrap_bonus@59fddabb |
```
Middleware defined in the registry can also be used on the `ring-handler` level:
```clj
(def app
(ring/ring-handler
(ring/router
["/api"
["/bonus" {:get (fn [{:keys [bonus]}]
{:status 200, :body {:bonus bonus}})}]]
{::middleware/registry {:bonus wrap-bonus}})
nil
{:middleware [[:bonus 15]]}))
```
## When to use the registry?
Middleware as Keywords helps to keep the routes (all but handlers) as literal data (i.e. data that evaluates to itself), enabling the routes to be persisted in external formats like EDN-files and databases. Duct is a good example, where the [middleware can be referenced from EDN-files](https://github.com/duct-framework/duct/wiki/Configuration). It should be easy to make Duct configuration a Middleware Registry in `reitit-ring`.

View file

@ -5,6 +5,7 @@
Reitit can generate [OpenAPI 3.1.0](https://spec.openapis.org/oas/v3.1.0)
documentation. The feature works similarly to [Swagger documentation](swagger.md).
The main example is [examples/openapi](../../examples/openapi).
The
[ring-malli-swagger](../../examples/ring-malli-swagger)
and
@ -34,52 +35,6 @@ Coercion keys also contribute to the docs:
| :request | optional description of body parameters, possibly per content-type
| :responses | optional descriptions of responses, in a format defined by coercion
## Annotating schemas
You can use malli properties, schema-tools data or spec-tools data to
annotate your models with examples, descriptions and defaults that
show up in the OpenAPI spec.
Malli:
```clj
["/plus"
{:post
{:parameters
{:body [:map
[:x
{:title "X parameter"
:description "Description for X parameter"
:json-schema/default 42}
int?]
[:y int?]]}}}]
```
Schema:
```clj
["/plus"
{:post
{:parameters
{:body {:x (schema-tools.core/schema s/Num {:description "Description for X parameter"
:openapi/example 13
:openapi/default 42})
:y int?}}}}]
```
Spec:
```clj
["/plus"
{:post
{:parameters
{:body (spec-tools.data-spec/spec ::foo
{:x (schema-tools.core/spec {:spec int?
:description "Description for X parameter"
:openapi/example 13
:openapi/default 42})
:y int?}}}}}]
```
## Per-content-type coercions
@ -91,8 +46,8 @@ openapi example](../../examples/openapi).
```clj
["/pizza"
{:get {:summary "Fetch a pizza | Multiple content-types, multiple examples"
:responses {200 {:content {"application/json" {:description "Fetch a pizza as json"
:schema [:map
:responses {200 {:description "Fetch a pizza as json or EDN"
:content {"application/json" {:schema [:map
[:color :keyword]
[:pineapple :boolean]]
:examples {:white {:description "White pizza with pineapple"
@ -101,8 +56,7 @@ openapi example](../../examples/openapi).
:red {:description "Red pizza"
:value {:color :red
:pineapple false}}}}
"application/edn" {:description "Fetch a pizza as edn"
:schema [:map
"application/edn" {:schema [:map
[:color :keyword]
[:pineapple :boolean]]
:examples {:red {:description "Red pizza with pineapple"
@ -115,16 +69,6 @@ and `:openapi/response-content-types` keys, which must contain vector of
supported content types. If there is no Muuntaja instance, and these keys are
not defined, the content types will default to `["application/json"]`.
## Custom OpenAPI data
The `:openapi` route data key can be used to add top-level or
route-level information to the generated OpenAPI spec. This is useful
for providing `"securitySchemes"` or other OpenAPI keys that are not
generated automatically by reitit.
See [the openapi example](../../examples/openapi) for a working
example of `"securitySchemes"`.
## OpenAPI spec
Serving the OpenAPI specification is handled by
@ -148,6 +92,123 @@ If you need to post-process the generated spec, just wrap the handler with a cus
## Swagger-ui
[Swagger-UI](https://github.com/swagger-api/swagger-ui) is a user interface to visualize and interact with the Swagger specification. To make things easy, there is a pre-integrated version of the swagger-ui as a separate module.
[Swagger-UI](https://github.com/swagger-api/swagger-ui) is a user interface to visualize and interact with the Swagger specification. To make things easy, there is a pre-integrated version of the swagger-ui as a separate module. See `reitit.swagger-ui/create-swagger-ui-handle`
Note: you need Swagger-UI 5 for OpenAPI 3.1 support. As of 2023-03-10, a v5.0.0-alpha.0 is out.
## Finetuning the OpenAPI output
There are a number of ways you can specify extra data that gets
included in the OpenAPI spec.
### Custom OpenAPI data
The `:openapi` route data key can be used to add top-level or
route-level information to the generated OpenAPI spec.
A straightforward use case is adding `"externalDocs"`:
```clj
["/account"
{:get {:summary "Fetch an account | Recursive schemas using malli registry, link to external docs"
:openapi {:externalDocs {:description "The reitit repository"
:url "https://github.com/metosin/reitit"}}
...}}]
```
In a more complex use case is providing `"securitySchemes"`. See
[the openapi example](../../examples/openapi) for a working example of
`"securitySchemes"`. See also the
[OpenAPI docs](https://spec.openapis.org/oas/v3.1.0.html#security-scheme-object)
### Annotating schemas
You can use malli properties, schema-tools data or spec-tools data to
annotate your models with examples, descriptions and defaults that
show up in the OpenAPI spec.
This approach lets you add additional keys to the
[OpenAPI Schema Objects](https://spec.openapis.org/oas/v3.1.0.html#schema-object).
The most common ones are default and example values for parameters.
Malli:
```clj
["/plus"
{:post
{:parameters
{:body [:map
[:x
{:title "X parameter"
:description "Description for X parameter"
:json-schema/deprecated true
:json-schema/default 42}
int?]
[:y int?]]}}}]
```
Schema:
```clj
["/plus"
{:post
{:parameters
{:body {:x (schema-tools.core/schema s/Num {:description "Description for X parameter"
:openapi/deprecated true
:openapi/example 13
:openapi/default 42})
:y int?}}}}]
```
Spec:
```clj
["/plus"
{:post
{:parameters
{:body (spec-tools.data-spec/spec ::foo
{:x (schema-tools.core/spec {:spec int?
:description "Description for X parameter"
:openapi/deprecated true
:openapi/example 13
:openapi/default 42})
:y int?}}}}}]
```
### Adding examples
Adding request/response examples have been mentioned above a couple of times
above. Here's a summary of the different ways to do it:
1. Add an example to the schema object using a `:openapi/example`
(schema, spec) or `:json-schema/example` (malli) key in your
schema/spec/malli model metadata. See the examples above.
2. Use `:example` (a single example) or `:examples` (named examples)
with per-content-type coercion.
**Caveat!** When adding examples for query parameters (or headers),
you must add the examples to the individual parameters, not the map
schema surrounding them. This is due to limitations in how OpenAPI
represents query parameters.
```clj
;; Wrong!
{:parameters {:query [:map
{:json-schema/example {:a 1}}
[:a :int]]}}
;; Right!
{:parameters {:query [:map
[:a {:json-schema/example 1} :int]]}}
```
### Named schemas
OpenAPI supports reusable schema objects that can be referred to with
the `"$ref": "#/components/schemas/Foo"` json-schema syntax. This is
useful when you have multiple endpoints that use the same schema. It
can also make OpenAPI-based code nicer for consumers of your API.
These schemas are also rendered in their own section in Swagger UI.
Reusable schema objects are generated for Malli `:ref`s and vars. The
[openapi example](../../examples/openapi) showcases this.
Currently (as of 0.7.2), reusable schema objects are **not** generated
for Plumatic Schema or Spec.

View file

@ -5,7 +5,7 @@
Read more about the [Ring Concepts](https://github.com/ring-clojure/ring/wiki/Concepts).
```clj
[metosin/reitit-ring "0.7.0-alpha8"]
[metosin/reitit-ring "0.10.0"]
```
## `reitit.ring/router`
@ -141,7 +141,7 @@ Name-based reverse routing:
# Middleware
Middleware can be mounted using a `:middleware` key - either to top-level or under request method submap. Its value should be a vector of `reitit.middleware/IntoMiddleware` values. These include:
Middleware can be mounted using a `:middleware` key in [Route Data](../basics/route_data.md) - either to top-level or under request method submap. Its value should be a vector of `reitit.middleware/IntoMiddleware` values. These include:
1. normal ring middleware function `handler -> request -> response`
2. vector of middleware function `[handler args*] -> request -> response` and it's arguments
@ -194,11 +194,56 @@ Top-level middleware, applied before any routing is done:
(def app
(ring/ring-handler
(ring/router
["/api" {:middleware [[mw :api]]}
["/api" {:middleware [[wrap :api]]}
["/get" {:get handler}]])
nil
{:middleware [[mw :top]]}))
{:middleware [[wrap :top]]}))
(app {:request-method :get, :uri "/api/get"})
; {:status 200, :body [:top :api :ok]}
```
Same middleware for all routes, using [top-level route data](route_data.md#top-level-route-data):
```clj
(def app
(ring/ring-handler
(ring/router
["/api"
["/get" {:get handler
:middleware [[wrap :specific]]}]]
{:data {:middleware [[wrap :generic]]}})))
(app {:request-method :get, :uri "/api/get"})
; {:status 200, :body [:generic :specific :handler]}
```
## Execution order
Here's a full example that shows the execution order of the middleware
using all of the above techniques:
```clj
(def app
(ring/ring-handler
(ring/router
["/api" {:middleware [[wrap :3-parent]]}
["/get" {:get handler
:middleware [[wrap :4-route]]}]]
{:data {:middleware [[wrap :2-top-level-route-data]]}})
nil
{:middleware [[wrap :1-top]]}))
(app {:request-method :get, :uri "/api/get"})
; {:status 200, :body [:1-top :2-top-level-route-data :3-parent :4-route :handler]}
```
## Which method should I use for defining middleware?
- If you have middleware that you want to apply to the default handler (second argument of `ring/ring-handler`), use _top-level middleware_
- If you have a generic middleware, that doesn't depend on the route, use _top-level middleware_ or _top-level route data_
- If you are using top-level route data anyway for some other reasons, it might be clearest to have all the middleware there. This is what most of the reitit examples do.
- If you want to apply a middleware to only a couple of routes, use _nested middleware_ (ie. _route data_)
- If you want a middleware to apply to all routes, but use route-specific data, you need _top-level route data_ combined with [Compiling Middleware](compiling_middleware.md)
- This is what many reitit features like [Ring Coercion](coercion.md) do. Check the examples & docs for the reitit features you want to use!

View file

@ -54,14 +54,16 @@ This way, they are only served if none of the actual routes have matched.
`reitit.ring/create-file-handler` and `reitit.ring/create-resource-handler` take optionally an options map to configure how the files are being served.
| key | description |
| -------------------|-------------|
| :parameter | optional name of the wildcard parameter, defaults to unnamed keyword `:`
| :root | optional resource root, defaults to `\"public\"`
| :path | path to mount the handler to. Required when mounted outside of a router, does not work inside a router.
| :loader | optional class loader to resolve the resources
| :index-files | optional vector of index-files to look in a resource directory, defaults to `[\"index.html\"]`
| :not-found-handler | optional handler function to use if the requested resource is missing (404 Not Found)
| key | description |
| --------------------|-------------|
| :parameter | optional name of the wildcard parameter, defaults to unnamed keyword `:`
| :root | optional resource root, defaults to `\"public\"`
| :path | path to mount the handler to. Required when mounted outside of a router, does not work inside a router.
| :loader | optional class loader to resolve the resources
| :index-files | optional vector of index-files to look in a resource directory, defaults to `[\"index.html\"]`
| :index-redirect? | optional boolean: if true (default false), redirect to index file, if false serve it directly
| :canonicalize-uris? | optional boolean: if true (default), try to serve index files for non directory paths (paths that end with slash)
| :not-found-handler | optional handler function to use if the requested resource is missing (404 Not Found)
### TODO

View file

@ -1,7 +1,7 @@
# Swagger Support
```
[metosin/reitit-swagger "0.7.0-alpha8"]
[metosin/reitit-swagger "0.10.0"]
```
Reitit supports [Swagger2](https://swagger.io/) documentation, thanks to [schema-tools](https://github.com/metosin/schema-tools) and [spec-tools](https://github.com/metosin/spec-tools). Documentation is extracted from route definitions, coercion `:parameters` and `:responses` and from a set of new documentation keys.
@ -47,7 +47,7 @@ If you need to post-process the generated spec, just wrap the handler with a cus
[Swagger-ui](https://github.com/swagger-api/swagger-ui) is a user interface to visualize and interact with the Swagger specification. To make things easy, there is a pre-integrated version of the swagger-ui as a separate module.
```
[metosin/reitit-swagger-ui "0.7.0-alpha8"]
[metosin/reitit-swagger-ui "0.10.0"]
```
`reitit.swagger-ui/create-swagger-ui-handler` can be used to create a ring-handler to serve the swagger-ui. It accepts the following options:
@ -286,7 +286,18 @@ Example with:
; ("/common/ping" "/one/ping" "/two/ping" "/two/deep/ping")
```
### TODO
## Reusable schema definitions
Swagger supports having reusable schema definitions under the
`"definitions"` key. These can be reused in different parts of
swagger.json using the `"$ref": "#/definitions/Foo"` syntax. These
definitions are also rendered in their own section in Swagger UI.
Reusable schema objects are generated for Malli `:ref`s and vars.
Currently (as of 0.7.2), reusable schema objects are **not** generated
for Plumatic Schema or Spec.
## TODO
* ClojureScript
* example for [Macchiato](https://github.com/macchiato-framework)

View file

@ -59,7 +59,7 @@ There is an extra option in the Ring router (actually, in the underlying middlew
### Printing Request Diffs
```clj
[metosin/reitit-middleware "0.7.0-alpha8"]
[metosin/reitit-middleware "0.10.0"]
```
Using `reitit.ring.middleware.dev/print-request-diffs` transformation, the request diffs between each middleware are printed out to the console. To use it, add the following router option:

View file

@ -2,6 +2,6 @@
:description "Reitit Buddy Auth App"
:dependencies [[org.clojure/clojure "1.11.2"]
[ring/ring-jetty-adapter "1.12.1"]
[metosin/reitit "0.7.0-alpha8"]
[metosin/reitit "0.10.0"]
[buddy "2.0.0"]]
:repl-options {:init-ns example.server})

View file

@ -10,9 +10,9 @@
[ring "1.12.1"]
[hiccup "1.0.5"]
[org.clojure/clojurescript "1.11.132"]
[metosin/reitit "0.7.0-alpha8"]
[metosin/reitit-schema "0.7.0-alpha8"]
[metosin/reitit-frontend "0.7.0-alpha8"]
[metosin/reitit "0.10.0"]
[metosin/reitit-schema "0.10.0"]
[metosin/reitit-frontend "0.10.0"]
[cljsjs/react "17.0.2-0"]
[cljsjs/react-dom "17.0.2-0"]
;; Just for pretty printting the match

View file

@ -10,9 +10,9 @@
[ring "1.12.1"]
[hiccup "1.0.5"]
[org.clojure/clojurescript "1.11.132"]
[metosin/reitit "0.7.0-alpha8"]
[metosin/reitit-schema "0.7.0-alpha8"]
[metosin/reitit-frontend "0.7.0-alpha8"]
[metosin/reitit "0.10.0"]
[metosin/reitit-schema "0.10.0"]
[metosin/reitit-frontend "0.10.0"]
[cljsjs/react "17.0.2-0"]
[cljsjs/react-dom "17.0.2-0"]
;; Just for pretty printting the match

View file

@ -10,9 +10,9 @@
[ring "1.12.1"]
[hiccup "1.0.5"]
[org.clojure/clojurescript "1.10.520"]
[metosin/reitit "0.7.0-alpha8"]
[metosin/reitit-spec "0.7.0-alpha8"]
[metosin/reitit-frontend "0.7.0-alpha8"]
[metosin/reitit "0.10.0"]
[metosin/reitit-spec "0.10.0"]
[metosin/reitit-frontend "0.10.0"]
[cljsjs/react "17.0.2-0"]
[cljsjs/react-dom "17.0.2-0"]
;; Just for pretty printting the match

View file

@ -10,9 +10,9 @@
[ring "1.12.1"]
[hiccup "1.0.5"]
[org.clojure/clojurescript "1.11.132"]
[metosin/reitit "0.7.0-alpha8"]
[metosin/reitit-malli "0.7.0-alpha8"]
[metosin/reitit-frontend "0.7.0-alpha8"]
[metosin/reitit "0.10.0"]
[metosin/reitit-malli "0.10.0"]
[metosin/reitit-frontend "0.10.0"]
[cljsjs/react "17.0.2-0"]
[cljsjs/react-dom "17.0.2-0"]
;; Just for pretty printting the match

View file

@ -10,9 +10,9 @@
[ring "1.12.1"]
[hiccup "1.0.5"]
[org.clojure/clojurescript "1.11.132"]
[metosin/reitit "0.7.0-alpha8"]
[metosin/reitit-spec "0.7.0-alpha8"]
[metosin/reitit-frontend "0.7.0-alpha8"]
[metosin/reitit "0.10.0"]
[metosin/reitit-spec "0.10.0"]
[metosin/reitit-frontend "0.10.0"]
[cljsjs/react "17.0.2-0"]
[cljsjs/react-dom "17.0.2-0"]
;; Just for pretty printting the match

View file

@ -1,7 +1,7 @@
(defproject frontend-re-frame "0.1.0-SNAPSHOT"
:dependencies [[org.clojure/clojure "1.11.2"]
[org.clojure/clojurescript "1.11.132"]
[metosin/reitit "0.7.0-alpha8"]
[metosin/reitit "0.10.0"]
[reagent "1.2.0"]
[re-frame "0.10.6"]
[cljsjs/react "17.0.2-0"]

View file

@ -1,6 +1,5 @@
(ns frontend-re-frame.core
(:require [re-frame.core :as re-frame]
[reagent.core :as reagent]
[reagent.dom :as rd]
[reitit.core :as r]
[reitit.coercion.spec :as rss]

View file

@ -10,9 +10,9 @@
[ring "1.12.1"]
[hiccup "1.0.5"]
[org.clojure/clojurescript "1.11.132"]
[metosin/reitit "0.7.0-alpha8"]
[metosin/reitit-spec "0.7.0-alpha8"]
[metosin/reitit-frontend "0.7.0-alpha8"]
[metosin/reitit "0.10.0"]
[metosin/reitit-spec "0.10.0"]
[metosin/reitit-frontend "0.10.0"]
[cljsjs/react "17.0.2-0"]
[cljsjs/react-dom "17.0.2-0"]
;; Just for pretty printting the match

View file

@ -3,6 +3,6 @@
:dependencies [[org.clojure/clojure "1.11.2"]
[ring/ring-jetty-adapter "1.12.1"]
[aleph "0.7.1"]
[metosin/reitit "0.7.0-alpha8"]
[metosin/reitit "0.10.0"]
[metosin/ring-swagger-ui "5.9.0"]]
:repl-options {:init-ns example.server})

View file

@ -5,5 +5,5 @@
[funcool/promesa "11.0.678"]
[manifold "0.4.2"]
[ring/ring-jetty-adapter "1.12.1"]
[metosin/reitit "0.7.0-alpha8"]]
[metosin/reitit "0.10.0"]]
:repl-options {:init-ns example.server})

View file

@ -2,4 +2,4 @@
:description "Reitit coercion with vanilla ring"
:dependencies [[org.clojure/clojure "1.11.2"]
[ring/ring-jetty-adapter "1.12.1"]
[metosin/reitit "0.7.0-alpha8"]])
[metosin/reitit "0.10.0"]])

View file

@ -3,7 +3,8 @@
:dependencies [[org.clojure/clojure "1.11.2"]
[metosin/jsonista "0.3.8"]
[ring/ring-jetty-adapter "1.12.1"]
[metosin/reitit "0.7.0-alpha8"]
[metosin/ring-swagger-ui "5.9.0"]]
[metosin/reitit "0.10.0"]
[metosin/ring-swagger-ui "5.9.0"]
[org.slf4j/slf4j-simple "2.0.9"]]
:repl-options {:init-ns example.server}
:profiles {:dev {:dependencies [[ring/ring-mock "0.4.0"]]}})

View file

@ -12,7 +12,6 @@
[reitit.ring.middleware.multipart :as multipart]
[reitit.ring.middleware.parameters :as parameters]
[ring.adapter.jetty :as jetty]
[malli.core :as malli]
[muuntaja.core :as m]))
(def Transaction
@ -50,45 +49,60 @@
["/pizza"
{:get {:summary "Fetch a pizza | Multiple content-types, multiple examples"
:responses {200 {:content {"application/json" {:description "Fetch a pizza as json"
:schema [:map
:responses {200 {:description "Fetch a pizza as json or EDN"
:content {"application/json" {:schema [:map
[:format [:enum :json]]
[:color :keyword]
[:pineapple :boolean]]
:examples {:white {:description "White pizza with pineapple"
:value {:color :white
:value {:format :json
:color :white
:pineapple true}}
:red {:description "Red pizza"
:value {:color :red
:value {:format :json
:color :red
:pineapple false}}}}
"application/edn" {:description "Fetch a pizza as edn"
:schema [:map
"application/edn" {:schema [:map
[:format [:enum :edn]]
[:color :keyword]
[:pineapple :boolean]]
:examples {:red {:description "Red pizza with pineapple"
:value (pr-str {:color :red :pineapple true})}}}}}}
:value (pr-str {:format :edn :color :red :pineapple true})}}}}}}
:handler (fn [_request]
{:status 200
:body {:color :red
:pineapple true}})}
:post {:summary "Create a pizza | Multiple content-types, multiple examples"
:request {:content {"application/json" {:description "Create a pizza using json"
:schema [:map
(rand-nth [{:status 200
:muuntaja/content-type "application/json"
:body {:format :json
:color :red
:pineapple true}}
{:status 200
:muuntaja/content-type "application/edn"
:body {:format :edn
:color :red
:pineapple true}}]))}
:post {:summary "Create a pizza | Multiple content-types, multiple examples | Default response schema"
:request {:description "Create a pizza using json or EDN"
:content {"application/json" {:schema [:map
[:color :keyword]
[:pineapple :boolean]]
:examples {:purple {:value {:color :purple
:pineapple false}}}}
"application/edn" {:description "Create a pizza using EDN"
:schema [:map
"application/edn" {:schema [:map
[:color :keyword]
[:pineapple :boolean]]
:examples {:purple {:value (pr-str {:color :purple
:pineapple false})}}}}}
:responses {200 {:content {:default {:description "Success"
:schema [:map [:success :boolean]]
:example {:success true}}}}}
:responses {200 {:description "Success"
:content {:default {:schema [:map [:success :boolean]]
:example {:success true}}}}
:default {:description "Not success"
:content {:default {:schema [:map [:error :string]]
:example {:error "error"}}}}}
:handler (fn [_request]
{:status 200
:body {:success true}})}}]
(if (< (Math/random) 0.5)
{:status 200
:body {:success true}}
{:status 500
:body {:error "an error happened"}}))}}]
["/contact"
@ -99,6 +113,10 @@
:json-schema/default 30
:json-schema/example 10}
int?]
[:charset {:title "Which charset to use?"
:optional true
:json-schema/deprecated true}
string?]
[:email {:title "Email address to search for"
:json-schema/format "email"}
string?]]}
@ -114,9 +132,11 @@
:email "heidi@alps.ch"}]})}}]
["/account"
{:get {:summary "Fetch an account | Recursive schemas using malli registry"
{:get {:summary "Fetch an account | Recursive schemas using malli registry, link to external docs"
:parameters {:query #'AccountId}
:responses {200 {:content {:default {:schema #'Account}}}}
:openapi {:externalDocs {:description "The reitit repository"
:url "https://github.com/metosin/reitit"}}
:handler (fn [_request]
{:status 200
:body {:bank "MiniBank"
@ -127,6 +147,32 @@
{:from "0003"
:amount -6.5}]}})}}]
["/complex"
{:post {:summary "Complex schema with :multi, :enum, :tuple etc."
:request {:content
{:default
{:schema [:map
[:vector-of-tuples [:vector [:tuple :string :int]]]
[:regex [:re "[0-9]+"]]
[:enum [:enum 1 3 5 42]]
[:multi [:multi {:dispatch :type}
["literal" [:map
[:type [:= "literal"]]
[:value [:or :int :string]]]]
["reference" [:map
[:type [:= "reference"]]
[:description :string]
[:ref :uuid]]]]]]
:example {:vector-of-tuples [["a" 1] ["b" 2]]
:regex "01234"
:enum 5
:multi {:type "literal"
:value "x"}}}}}
:responses {200 {:content {:default {:schema [:map-of :keyword :any]}}}}
:handler (fn [request]
{:status 200
:body (get-in request [:parameters :request])})}}]
["/secure"
{:tags #{"secure"}
:openapi {:security [{"auth" []}]}}

View file

@ -3,7 +3,7 @@
:dependencies [[org.clojure/clojure "1.11.2"]
[io.pedestal/pedestal.service "0.6.3"]
[io.pedestal/pedestal.jetty "0.6.3"]
[metosin/reitit-malli "0.7.0-alpha8"]
[metosin/reitit-pedestal "0.7.0-alpha8"]
[metosin/reitit "0.7.0-alpha8"]]
[metosin/reitit-malli "0.10.0"]
[metosin/reitit-pedestal "0.10.0"]
[metosin/reitit "0.10.0"]]
:repl-options {:init-ns server})

View file

@ -3,6 +3,6 @@
:dependencies [[org.clojure/clojure "1.11.2"]
[io.pedestal/pedestal.service "0.6.3"]
[io.pedestal/pedestal.jetty "0.6.3"]
[metosin/reitit-pedestal "0.7.0-alpha8"]
[metosin/reitit "0.7.0-alpha8"]]
[metosin/reitit-pedestal "0.10.0"]
[metosin/reitit "0.10.0"]]
:repl-options {:init-ns example.server})

View file

@ -3,6 +3,6 @@
:dependencies [[org.clojure/clojure "1.11.2"]
[io.pedestal/pedestal.service "0.6.3"]
[io.pedestal/pedestal.jetty "0.6.3"]
[metosin/reitit-pedestal "0.7.0-alpha8"]
[metosin/reitit "0.7.0-alpha8"]]
[metosin/reitit-pedestal "0.10.0"]
[metosin/reitit "0.10.0"]]
:repl-options {:init-ns example.server})

View file

@ -2,5 +2,5 @@
:description "Reitit Ring App"
:dependencies [[org.clojure/clojure "1.11.2"]
[ring/ring-jetty-adapter "1.12.1"]
[metosin/reitit "0.7.0-alpha8"]]
[metosin/reitit "0.10.0"]]
:repl-options {:init-ns example.server})

View file

@ -2,7 +2,7 @@
:description "Reitit Ring App with Integrant"
:dependencies [[org.clojure/clojure "1.11.2"]
[ring/ring-jetty-adapter "1.12.1"]
[metosin/reitit "0.7.0-alpha8"]
[metosin/reitit "0.10.0"]
[integrant "0.8.1"]]
:main example.server
:repl-options {:init-ns user}

View file

@ -3,6 +3,6 @@
:dependencies [[org.clojure/clojure "1.11.2"]
[metosin/jsonista "0.3.8"]
[ring/ring-jetty-adapter "1.12.1"]
[metosin/reitit "0.7.0-alpha8"]]
[metosin/reitit "0.10.0"]]
:repl-options {:init-ns example.server}
:profiles {:dev {:dependencies [[ring/ring-mock "0.4.0"]]}})

View file

@ -3,7 +3,7 @@
:dependencies [[org.clojure/clojure "1.11.2"]
[metosin/jsonista "0.3.8"]
[ring/ring-jetty-adapter "1.12.1"]
[metosin/reitit "0.7.0-alpha8"]
[metosin/reitit "0.10.0"]
[metosin/ring-swagger-ui "5.9.0"]]
:repl-options {:init-ns example.server}
:profiles {:dev {:dependencies [[ring/ring-mock "0.4.0"]]}})

View file

@ -2,7 +2,7 @@
:description "Reitit Ring App with Swagger"
:dependencies [[org.clojure/clojure "1.11.2"]
[ring/ring-jetty-adapter "1.12.1"]
[metosin/reitit "0.7.0-alpha8"]
[metosin/reitit "0.10.0"]
[metosin/ring-swagger-ui "5.9.0"]]
:repl-options {:init-ns example.server}
:profiles {:dev {:dependencies [[ring/ring-mock "0.4.0"]]}})

18
karma.conf.js Normal file
View file

@ -0,0 +1,18 @@
module.exports = function (config) {
config.set({
browsers: ['ChromeHeadless'],
// The directory where the output file lives
basePath: 'target/karma',
// The file itself
files: ['ci.js'],
frameworks: ['cljs-test'],
reporters: ['progress'],
plugins: ['karma-cljs-test', 'karma-chrome-launcher'],
colors: true,
logLevel: config.LOG_INFO,
client: {
args: ["shadow.test.karma.init"],
singleRun: true
},
})
};

View file

@ -1,4 +1,4 @@
(defproject metosin/reitit-core "0.7.0-alpha8"
(defproject metosin/reitit-core "0.10.0"
:description "Snappy data-driven router for Clojure(Script)"
:url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License"
@ -10,5 +10,5 @@
:parent-project {:path "../../project.clj"
:inherit [:deploy-repositories :managed-dependencies]}
:java-source-paths ["java-src"]
:javac-options ["-Xlint:unchecked" "-target" "1.8" "-source" "1.8"]
:javac-options ["-Xlint:unchecked" "-target" "11" "-source" "11"]
:dependencies [[meta-merge]])

View file

@ -1,5 +1,6 @@
(ns reitit.coercion
(:require [clojure.walk :as walk]
(:require [#?(:clj reitit.walk :cljs clojure.walk) :as walk]
[reitit.core :as r]
[reitit.impl :as impl])
#?(:clj
(:import (java.io Writer))))
@ -19,7 +20,8 @@
(-open-model [this model] "Returns a new model which allows extra keys in maps")
(-encode-error [this error] "Converts error in to a serializable format")
(-request-coercer [this type model] "Returns a `value format => value` request coercion function")
(-response-coercer [this model] "Returns a `value format => value` response coercion function"))
(-response-coercer [this model] "Returns a `value format => value` response coercion function")
(-query-string-coercer [this model] "Returns a `value => value` query string coercion function"))
#?(:clj
(defmethod print-method ::coercion [coercion ^Writer w]
@ -128,29 +130,6 @@
(request-coercion-failed! result coercion value in request serialize-failed-result)
result)))))))
(defn extract-response-format-default [request _]
(-> request :muuntaja/response :format))
(defn response-coercer [coercion {:keys [content body]} {:keys [extract-response-format serialize-failed-result]
:or {extract-response-format extract-response-format-default}}]
(if coercion
(let [format->coercer (some->> (concat (when body
[[:default (-response-coercer coercion body)]])
(for [[format {:keys [schema]}] content, :when schema]
[format (-response-coercer coercion schema)]))
(filter second) (seq) (into (array-map)))]
(when format->coercer
(fn [request response]
(let [format (extract-response-format request response)
value (:body response)
coercer (or (format->coercer format)
(format->coercer :default)
-identity-coercer)
result (coercer value format)]
(if (error? result)
(response-coercion-failed! result coercion value request response serialize-failed-result)
result)))))))
(defn encode-error [data]
(-> data
(dissoc :request :response)
@ -163,12 +142,6 @@
(impl/fast-assoc acc k (coercer request)))
{} coercers))
(defn coerce-response [coercers request response]
(if response
(if-let [coercer (or (coercers (:status response)) (coercers :default))]
(impl/fast-assoc response :body (coercer request response))
response)))
(defn request-coercers
([coercion parameters opts]
(some->> (for [[k v] parameters, :when v]
@ -179,10 +152,45 @@
rcs (request-coercers coercion parameters (cond-> opts route-request (assoc ::skip #{:body})))]
(if (and crc rcs) (into crc (vec rcs)) (or crc rcs)))))
(defn response-coercers [coercion responses opts]
(some->> (for [[status model] responses]
[status (response-coercer coercion model opts)])
(filter second) (seq) (into {})))
(defn extract-response-format-default [request response]
(or (get-in response [:headers "Content-Type"])
(:muuntaja/content-type response)
(-> request :muuntaja/response :format)))
(defn -format->coercer [coercion {:keys [content body]} _opts]
(->> (concat (when body
[[:default (-response-coercer coercion body)]])
(for [[format {:keys [schema]}] content, :when schema]
[format (-response-coercer coercion schema)]))
(filter second) (into (array-map))))
(defn response-coercer [coercion responses {:keys [extract-response-format serialize-failed-result]
:or {extract-response-format extract-response-format-default}
:as opts}]
(when coercion
(let [status->format->coercer
(into {}
(for [[status model] responses]
(do
(when-not (or (= :default status) (int? status))
(throw (ex-info "Response status must be int or :default" {:status status})))
[status (-format->coercer coercion model opts)])))]
(when-not (every? empty? (vals status->format->coercer)) ;; fast path: return nil if there are no models to coerce
(fn [request response]
(let [format->coercer (or (status->format->coercer (:status response))
(status->format->coercer :default))
format (extract-response-format request response)
coercer (when format->coercer
(or (format->coercer format)
(format->coercer :default)))]
(if-not coercer
response
(let [value (:body response)
coerced (coercer (:body response) format)
result (if (error? coerced)
(response-coercion-failed! coerced coercion value request response serialize-failed-result)
coerced)]
(impl/fast-assoc response :body result)))))))))
(defn -compile-parameters [data coercion]
(impl/path-update data [[[:parameters any?] #(-compile-model coercion % nil)]]))
@ -219,3 +227,33 @@
[match]
(if-let [coercers (-> match :result :coerce)]
(coerce-request coercers match)))
(defn coerce-query-params
"Uses an input schema and coercion implementation from the given match to
encode query-parameters map.
If no match, no input schema or coercion implementation, just returns the
original parameters map."
[match query-params]
(when query-params
(let [coercion (-> match :data :coercion)
schema (when coercion
(-compile-model coercion (-> match :data :parameters :query) nil))
coercer (when (and schema coercion)
(-query-string-coercer coercion schema))]
(if coercer
(let [result (coercer query-params :default)]
(if (error? result)
(throw (ex-info "Query parameters coercion failed"
result))
result))
query-params))))
(defn match->path
"Create routing path from given match and optional query-parameters map.
Query-parameters are encoded using the input schema and coercion implementation."
([match]
(r/match->path match))
([match query-params]
(r/match->path match (coerce-query-params match query-params))))

View file

@ -46,7 +46,7 @@
(options [this])
(route-names [this])
(match-by-path [this path])
(match-by-name [this name] [this name path-params]))
(match-by-name [this name] [this name path-params] [this name path-params opts]))
(defn router? [x]
(satisfies? Router x))
@ -68,10 +68,12 @@
(:template match) (:required match) path-params)))))
(defn match->path
"Create routing path from given match and optional query-parameters map."
([match]
(match->path match nil))
([match query-params]
(some-> match :path (cond-> (seq query-params) (str "?" (impl/query-string query-params))))))
(some-> match :path (cond-> (seq query-params)
(str "?" (impl/query-string query-params))))))
;;
;; Different routers
@ -120,9 +122,11 @@
(match-by-name [_ name]
(if-let [match (impl/fast-get lookup name)]
(match nil)))
(match-by-name [_ name path-params]
(match-by-name [r name path-params]
(match-by-name r name path-params nil))
(match-by-name [_ name path-params opts]
(if-let [match (impl/fast-get lookup name)]
(match (impl/path-params path-params))))))))
(match (impl/path-params path-params opts))))))))
(defn lookup-router
"Creates a lookup-router from resolved routes and optional
@ -159,9 +163,11 @@
(match-by-name [_ name]
(if-let [match (impl/fast-get lookup name)]
(match nil)))
(match-by-name [_ name path-params]
(match-by-name [r name path-params]
(match-by-name r name path-params nil))
(match-by-name [_ name path-params opts]
(if-let [match (impl/fast-get lookup name)]
(match (impl/path-params path-params))))))))
(match (impl/path-params path-params opts))))))))
(defn trie-router
"Creates a special prefix-tree router from resolved routes and optional
@ -206,9 +212,11 @@
(match-by-name [_ name]
(if-let [match (impl/fast-get lookup name)]
(match nil)))
(match-by-name [_ name path-params]
(match-by-name [r name path-params]
(match-by-name r name path-params nil))
(match-by-name [_ name path-params opts]
(if-let [match (impl/fast-get lookup name)]
(match (impl/path-params path-params))))))))
(match (impl/path-params path-params opts))))))))
(defn single-static-path-router
"Creates a fast router of 1 static route(s) and optional
@ -236,8 +244,10 @@
(if (#?(:clj .equals :cljs =) p path) match))
(match-by-name [_ name]
(if (= n name) match))
(match-by-name [_ name path-params]
(if (= n name) (impl/fast-assoc match :path-params (impl/path-params path-params))))))))
(match-by-name [r name path-params]
(match-by-name r name path-params nil))
(match-by-name [_ name path-params opts]
(if (= n name) (impl/fast-assoc match :path-params (impl/path-params path-params opts))))))))
(defn mixed-router
"Creates two routers: [[lookup-router]] or [[single-static-path-router]] for
@ -266,9 +276,11 @@
(match-by-name [_ name]
(or (match-by-name static-router name)
(match-by-name wildcard-router name)))
(match-by-name [_ name path-params]
(or (match-by-name static-router name path-params)
(match-by-name wildcard-router name path-params)))))))
(match-by-name [r name path-params]
(match-by-name r name path-params nil))
(match-by-name [_ name path-params opts]
(or (match-by-name static-router name path-params opts)
(match-by-name wildcard-router name path-params opts)))))))
(defn quarantine-router
"Creates two routers: [[mixed-router]] for non-conflicting routes
@ -297,9 +309,11 @@
(match-by-name [_ name]
(or (match-by-name mixed-router name)
(match-by-name linear-router name)))
(match-by-name [_ name path-params]
(or (match-by-name mixed-router name path-params)
(match-by-name linear-router name path-params)))))))
(match-by-name [r name path-params]
(match-by-name r name path-params nil))
(match-by-name [_ name path-params opts]
(or (match-by-name mixed-router name path-params opts)
(match-by-name linear-router name path-params opts)))))))
;;
;; Creating Routers

View file

@ -1,52 +0,0 @@
(ns reitit.dependency
"Dependency resolution for middleware/interceptors."
(:require [reitit.exception :as exception]))
(defn- providers
"Map from provision key to provider. `get-provides` should return the provision keys of a dependent."
[get-provides nodes]
(reduce (fn [acc dependent]
(into acc
(map (fn [provide]
(when (contains? acc provide)
(exception/fail!
(str "multiple providers for: " provide)
{::multiple-providers provide}))
[provide dependent]))
(get-provides dependent)))
{} nodes))
(defn- get-provider
"Get the provider for `k`, throw if no provider can be found for it."
[providers k]
(if (contains? providers k)
(get providers k)
(exception/fail!
(str "provider missing for dependency: " k)
{::missing-provider k})))
(defn post-order
"Put `nodes` in post-order. Can also be described as a reverse topological sort.
`get-provides` and `get-requires` are callbacks that you can provide to compute the provide and depend
key sets of nodes, the defaults are `:provides` and `:requires`."
([nodes] (post-order :provides :requires nodes))
([get-provides get-requires nodes]
(let [providers-by-key (providers get-provides nodes)]
(letfn [(toposort [node path colors]
(case (get colors node)
:white (let [requires (get-requires node)
[nodes* colors] (toposort-seq (map (partial get-provider providers-by-key) requires)
(conj path node)
(assoc colors node :grey))]
[(conj nodes* node)
(assoc colors node :black)])
:grey (exception/fail! "circular dependency" {:cycle (drop-while #(not= % node) (conj path node))})
:black [() colors]))
(toposort-seq [nodes path colors]
(reduce (fn [[nodes* colors] node]
(let [[nodes** colors] (toposort node path colors)]
[(into nodes* nodes**) colors]))
[[] colors] nodes))]
(first (toposort-seq nodes [] (zipmap nodes (repeat :white))))))))

View file

@ -7,6 +7,10 @@
([type data]
(throw (ex-info (str type) {:type type, :data data}))))
(defn unsupported-protocol-method!
[method]
(fail! :unsupported-protocol-method {:method method}))
(defn get-message [e]
#?(:clj (.getMessage ^Exception e) :cljs (ex-message e)))

View file

@ -1,7 +1,6 @@
(ns ^:no-doc reitit.impl
#?(:cljs (:require-macros [reitit.impl]))
(:require [clojure.set :as set]
[clojure.string :as str]
(:require [clojure.string :as str]
[meta-merge.core :as mm]
[reitit.exception :as ex]
[reitit.trie :as trie])
@ -23,23 +22,39 @@
(defn -path-vals [m path-map]
(letfn [(-path-vals [l p m]
(reduce
(fn [l [k v]]
(reduce-kv
(fn [l k v]
(let [p' (conj p k)
f (-match p' path-map)]
(cond
f (conj l [p' (f v)])
(and (map? v) (seq v)) (-path-vals l p' v)
(and (map? v) (not (record? v)) (seq v)) (-path-vals l p' v)
:else (conj l [p' v]))))
l m))]
(-path-vals [] [] m)))
(defn -copy-meta [to from]
(letfn [(-with-meta [x m]
(try (with-meta x m) (catch #?(:clj Exception, :cljs js/Error) _ x)))
(-copy [l p m]
(reduce-kv
(fn [l k v]
(let [p' (conj p k)
m' (when (empty? (meta v)) (meta (get-in from p')))]
(cond
m' (update-in l p' -with-meta m')
(and (map? v) (not (record? v)) (seq v)) (-copy l p' v)
:else l)))
l m))]
(-copy to [] to)))
(defn -assoc-in-path-vals [c]
(reduce (partial apply assoc-in) {} c))
(defn path-update [m path-map]
(-> (-path-vals m path-map)
(-assoc-in-path-vals)))
(-assoc-in-path-vals)
(-copy-meta m)))
(defn accumulator? [x]
(-> x meta ::accumulator))
@ -182,9 +197,8 @@
(:path route)))
(defn throw-on-missing-path-params [template required path-params]
(when-not (every? #(contains? path-params %) required)
(let [defined (-> path-params keys set)
missing (set/difference required defined)]
(let [missing (set (remove #(get path-params %) required))]
(when-not (empty? missing)
(ex/fail!
(str "missing path-params for route " template " -> " missing)
{:path-params path-params, :required required}))))
@ -283,8 +297,11 @@
(defn path-params
"Convert parameters' values into URL-encoded strings, suitable for URL paths"
[params]
(maybe-map-values #(url-encode (into-string %)) params))
([params] (path-params params nil))
([params {:keys [url-encode?] :or {url-encode? true}}]
(if url-encode?
(maybe-map-values #(url-encode (into-string %)) params)
(maybe-map-values #(into-string %) params))))
(defn- query-parameter [k v]
(str (form-encode (into-string k))

View file

@ -37,8 +37,11 @@
;; Default data
;;
(defn -multi? [x]
(instance? #?(:clj clojure.lang.MultiFn :cljs cljs.core.MultiFn) x))
(s/def ::name keyword?)
(s/def ::handler (s/or :fn fn? :var var?))
(s/def ::handler (s/or :fn fn? :var var? :multi -multi?))
(s/def ::no-doc boolean?)
(s/def ::conflicting boolean?)
(s/def ::default-data

View file

@ -71,11 +71,17 @@
(and bracket? (= \{ c))
(let [^long to' (or (str/index-of s "}" to) (ex/fail! ::unclosed-brackets {:path s}))]
(if (= \* (get s (inc to)))
(cond
(= \* (get s (inc to)))
(recur (concat ss (-static from to) (-catch-all (inc to) to')) (long (inc to')) (long (inc to')))
(= \: (get s (inc to)))
(recur (concat ss (-static from to) (-wild (inc to) to')) (long (inc to')) (long (inc to')))
:else
(recur (concat ss (-static from to) (-wild to to')) (long (inc to')) (long (inc to')))))
(and colon? (= \: c))
(and colon? (= \: c) (not= \{ (get s (dec to))))
(let [^long to' (or (str/index-of s "/" to) (count s))]
(if (= 1 (- to' to))
(recur ss from (inc to))

View file

@ -0,0 +1,58 @@
(ns ^:no-doc reitit.walk)
(defprotocol IKeywordize
(-keywordize [coll]))
(defn keywordize-keys [m] (-keywordize m))
(defn- keywordize-kv
[m k v]
(assoc! m (if (string? k) (keyword k) (-keywordize k)) (-keywordize v)))
(defn- -keywordize-kvreducible
[m]
(persistent! (reduce-kv keywordize-kv (transient (empty m)) m)))
(def ^:private keywordize-xf (map keywordize-keys))
(defn- -keywordize-default
[coll]
(into (empty coll) keywordize-xf coll))
(doseq [type [clojure.lang.PersistentHashSet
clojure.lang.PersistentVector
clojure.lang.PersistentQueue
clojure.lang.PersistentStructMap
clojure.lang.PersistentTreeSet]]
(extend type IKeywordize {:-keywordize -keywordize-default}))
(doseq [type [clojure.lang.PersistentArrayMap
clojure.lang.PersistentHashMap
clojure.lang.PersistentTreeMap]]
(extend type IKeywordize {:-keywordize -keywordize-kvreducible}))
(defn- -keywordize-map
[m]
(let [f (fn [[k v]] (if (string? k) [(keyword k) v] [k v]))]
(into {} (map f) m)))
(doseq [type [clojure.lang.IPersistentMap]]
(extend type IKeywordize {:-keywordize -keywordize-map}))
(extend-protocol IKeywordize
Object
(-keywordize [x] x)
nil
(-keywordize [_] nil)
clojure.lang.MapEntry
(-keywordize [e] (clojure.lang.MapEntry/create
(-keywordize (.key e))
(-keywordize (.val e))))
clojure.lang.ISeq
(-keywordize [coll] (map -keywordize coll))
clojure.lang.PersistentList
(-keywordize [coll] (apply list (map -keywordize coll)))
clojure.lang.PersistentList$EmptyList
(-keywordize [x] x)
clojure.lang.IRecord
(-keywordize [r] (reduce (fn [r x] (conj r (-keywordize x))) r r)))

View file

@ -1,4 +1,4 @@
(defproject metosin/reitit-dev "0.7.0-alpha8"
(defproject metosin/reitit-dev "0.10.0"
:description "Snappy data-driven router for Clojure(Script)"
:url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License"

View file

@ -174,7 +174,7 @@
;; TODO: this is hack, but seems to work and is safe.
(defn source-str [[target _ file line]]
(try
(if (and (not= 1 line))
(if (not= 1 line)
(let [file-name (str/replace file #"(.*?)\.\S[^\.]+" "$1")
target-name (name target)
ns (str (subs target-name 0 (or (str/index-of target-name (str file-name "$")) 0)) file-name)]
@ -190,7 +190,7 @@
(color :title message " ")
(color :title-dark (repeat-str "-" between) " ")
(color :title source) " "
(color :title-dark (str "--"))]))
(color :title-dark "--")]))
(defn footer [{:keys [width]}]
(color :title-dark (repeat-str "-" width)))

View file

@ -1,4 +1,4 @@
(defproject metosin/reitit-frontend "0.7.0-alpha8"
(defproject metosin/reitit-frontend "0.10.0"
:description "Reitit: Clojurescript frontend routing core"
:url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License"

View file

@ -15,7 +15,7 @@
(defn query-params
"Given goog.Uri, read query parameters into a Clojure map."
[^goog.Uri uri]
(let [q (.getQueryData uri)]
(let [^goog.Uri.QueryData q (.getQueryData uri)]
(->> q
(.getKeys)
(map (juxt keyword #(query-param q %)))
@ -40,12 +40,18 @@
(defn
^{:see-also ["reitit.core/match->path"]}
match->path
"Create routing path from given match and optional query-string map and
optional fragment string."
[match query-params fragment]
(when-let [path (r/match->path match query-params)]
(cond-> path
(and fragment (seq fragment)) (str "#" (impl/form-encode fragment)))))
"Create routing path from given match and optional query-parameters map and
optional fragment string.
Query-parameters are encoded using the input schema and coercion implementation."
([match]
(match->path match nil nil))
([match query-params]
(match->path match query-params nil))
([match query-params fragment]
(when-let [path (coercion/match->path match query-params)]
(cond-> path
(and fragment (seq fragment)) (str "#" (impl/form-encode fragment))))))
(defn match-by-path
"Given routing tree and current path, return match with possibly
@ -54,7 +60,7 @@
:on-coercion-error - a sideeffecting fn of `match exception -> nil`"
([router path] (match-by-path router path nil))
([router path {:keys [on-coercion-error]}]
(let [uri (.parse goog.Uri path)
(let [^goog.Uri uri (.parse goog.Uri path)
coerce! (if on-coercion-error
(fn [match]
(try (coercion/coerce! match)

View file

@ -48,9 +48,10 @@
The URL is formatted using Reitit frontend history handler, so using it with
anchor element href will correctly trigger route change event.
Note: currently collections in query-parameters are encoded as field-value
pairs separated by &, i.e. \"?a=1&a=2\", if you want to encode them
differently, convert the collections to strings first."
By default currently collections in query parameters are encoded as field-value
pairs separated by &, i.e. \"?a=1&a=2\". To encode them differently, you can
either use Malli coercion to encode values, or just turn the values to strings
before calling the function."
([name]
(rfh/href @history name nil nil nil))
([name path-params]
@ -69,9 +70,10 @@
Will also trigger on-navigate callback on Reitit frontend History handler.
Note: currently collections in query parameters are encoded as field-value
pairs separated by &, i.e. \"?a=1&a=2\", if you want to encode them
differently, convert the collections to strings first.
By default currently collections in query parameters are encoded as field-value
pairs separated by &, i.e. \"?a=1&a=2\". To encode them differently, you can
either use Malli coercion to encode values, or just turn the values to strings
before calling the function.
See also:
https://developer.mozilla.org/en-US/docs/Web/API/History/pushState"
@ -93,9 +95,10 @@
Will also trigger on-navigate callback on Reitit frontend History handler.
Note: currently collections in query-parameters are encoded as field-value
pairs separated by &, i.e. \"?a=1&a=2\", if you want to encode them
differently, convert the collections to strings first.
By default currently collections in query parameters are encoded as field-value
pairs separated by &, i.e. \"?a=1&a=2\". To encode them differently, you can
either use Malli coercion to encode values, or just turn the values to strings
before calling the function.
See also:
https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState"
@ -122,9 +125,10 @@
Will also trigger on-navigate callback on Reitit frontend History handler.
Note: currently collections in query-parameters are encoded as field-value
pairs separated by &, i.e. \"?a=1&a=2\", if you want to encode them
differently, convert the collections to strings first.
By default currently collections in query parameters are encoded as field-value
pairs separated by &, i.e. \"?a=1&a=2\". To encode them differently, you can
either use Malli coercion to encode values, or just turn the values to strings
before calling the function.
See also:
https://developer.mozilla.org/en-US/docs/Web/API/History/pushState
@ -142,8 +146,11 @@
New query params can be given as a map, or a function taking
the old params and returning the new modified params.
Note: The query parameter values aren't coereced, so the
update fn will see string values for all query params."
The current path is matched against the routing tree, and the match data
(schema, coercion) is used to encode the query parameters.
If the current path doesn't match any route, the query parameters
are parsed from the path without coercion and new values
are also stored without coercion encoding."
([new-query-or-update-fn]
(rfh/set-query @history new-query-or-update-fn))
([new-query-or-update-fn {:keys [replace] :as opts}]

View file

@ -65,7 +65,7 @@
(defn- event-target
"Read event's target from composed path to get shadow dom working,
fallback to target property if not available"
[event]
[^goog.events.BrowserEvent event]
(let [original-event (.getBrowserEvent event)]
(if (exists? (.-composedPath original-event))
(aget (.composedPath original-event) 0)
@ -76,9 +76,9 @@
should be ignored. This logic will ignore the event
if anchor href matches the route tree, and in this case
the page location is updated using History API."
[router e el uri]
[router e el ^goog.Uri uri]
(let [current-domain (if (exists? js/location)
(.getDomain (.parse goog.Uri js/location)))]
(.getDomain ^goog.Uri (.parse goog.Uri js/location)))]
(and (or (and (not (.hasScheme uri)) (not (.hasDomain uri)))
(= current-domain (.getDomain uri)))
(not (.-altKey e))
@ -110,7 +110,7 @@
ignore-anchor-click (fn [e]
;; Returns the next matching ancestor of event target
(when-let [el (closest-by-tag (event-target e) "a")]
(let [uri (.parse goog.Uri (.-href el))]
(let [^goog.Uri uri (.parse goog.Uri (.-href el))]
(when (ignore-anchor-click-predicate router e el uri)
(.preventDefault e)
(let [path (str (.getPath uri)
@ -187,9 +187,10 @@
The URL is formatted using Reitit frontend history handler, so using it with
anchor element href will correctly trigger route change event.
Note: currently collections in query parameters are encoded as field-value
pairs separated by &, i.e. \"?a=1&a=2\", if you want to encode them
differently, convert the collections to strings first."
By default currently collections in query parameters are encoded as field-value
pairs separated by &, i.e. \"?a=1&a=2\". To encode them differently, you can
either use Malli coercion to encode values, or just turn the values to strings
before calling the function."
([history name]
(href history name nil))
([history name path-params]
@ -208,9 +209,10 @@
Will also trigger on-navigate callback on Reitit frontend History handler.
Note: currently collections in query-parameters are encoded as field-value
pairs separated by &, i.e. \"?a=1&a=2\", if you want to encode them
differently, convert the collections to strings first.
By default currently collections in query parameters are encoded as field-value
pairs separated by &, i.e. \"?a=1&a=2\". To encode them differently, you can
either use Malli coercion to encode values, or just turn the values to strings
before calling the function.
See also:
https://developer.mozilla.org/en-US/docs/Web/API/History/pushState"
@ -236,9 +238,10 @@
Will also trigger on-navigate callback on Reitit frontend History handler.
Note: currently collections in query-parameters are encoded as field-value
pairs separated by &, i.e. \"?a=1&a=2\", if you want to encode them
differently, convert the collections to strings first.
By default currently collections in query parameters are encoded as field-value
pairs separated by &, i.e. \"?a=1&a=2\". To encode them differently, you can
either use Malli coercion to encode values, or just turn the values to strings
before calling the function.
See also:
https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState"
@ -264,9 +267,10 @@
Will also trigger on-navigate callback on Reitit frontend History handler.
Note: currently collections in query-parameters are encoded as field-value
pairs separated by &, i.e. \"?a=1&a=2\", if you want to encode them
differently, convert the collections to strings first.
By default currently collections in query parameters are encoded as field-value
pairs separated by &, i.e. \"?a=1&a=2\". To encode them differently, you can
either use Malli coercion to encode values, or just turn the values to strings
before calling the function.
See also:
https://developer.mozilla.org/en-US/docs/Web/API/History/pushState
@ -289,13 +293,22 @@
New query params can be given as a map, or a function taking
the old params and returning the new modified params.
Note: The query parameter values aren't coereced, so the
update fn will see string values for all query params."
The current path is matched against the routing tree, and the match data
(schema, coercion) is used to encode the query parameters.
If the current path doesn't match any route, the query parameters
are parsed from the path without coercion and new values
are also stored without coercion encoding."
([history new-query-or-update-fn]
(set-query history new-query-or-update-fn nil))
([history new-query-or-update-fn {:keys [replace] :as opts}]
(let [current-path (-get-path history)
new-path (rf/set-query-params current-path new-query-or-update-fn)]
match (rf/match-by-path (:router history) current-path)
new-path (if match
(let [query-params (if (fn? new-query-or-update-fn)
(new-query-or-update-fn (:query (:parameters match)))
new-query-or-update-fn)]
(rf/match->path match query-params (:fragment (:parameters match))))
(rf/set-query-params current-path new-query-or-update-fn))]
(if replace
(.replaceState js/window.history nil "" (-href history new-path))
(.pushState js/window.history nil "" (-href history new-path)))

View file

@ -1,4 +1,4 @@
(defproject metosin/reitit-http "0.7.0-alpha8"
(defproject metosin/reitit-http "0.10.0"
:description "Reitit: HTTP routing with interceptors"
:url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License"

View file

@ -22,12 +22,12 @@
compile (fn [[path data] opts scope]
(interceptor/compile-result [path data] opts scope))
->endpoint (fn [p d m s]
(let [d (ring/-compile-coercion d)]
(let [compiled (compile [p d] opts s)]
(-> compiled
(map->Endpoint)
(assoc :path p)
(assoc :method m)))))
(let [d (ring/-compile-coercion d)
compiled (compile [p d] opts s)]
(-> compiled
(map->Endpoint)
(assoc :path p)
(assoc :method m))))
->methods (fn [any? data]
(reduce
(fn [acc method]
@ -133,7 +133,7 @@
(interceptor/queue executor))
router-opts (-> (r/options router)
(assoc ::interceptor/queue (partial interceptor/queue executor))
(dissoc :data) ; data is already merged into routes
(dissoc :data :path) ; data and path already take effect in routes
(cond-> (seq interceptors)
(update-in [:data :interceptors] (partial into (vec interceptors)))))
router (reitit.http/router (r/routes router) router-opts) ;; will re-compile the interceptors

View file

@ -41,11 +41,11 @@
(not responses) {}
;; mount
:else
(if-let [coercers (coercion/response-coercers coercion responses opts)]
(if-let [coercer (coercion/response-coercer coercion responses opts)]
{:leave (fn [ctx]
(let [request (:request ctx)
response (:response ctx)
response (coercion/coerce-response coercers request response)]
response (coercer request response)]
(assoc ctx :response response)))}
{})))})

View file

@ -1,4 +1,4 @@
(defproject metosin/reitit-interceptors "0.7.0-alpha8"
(defproject metosin/reitit-interceptors "0.10.0"
:description "Reitit, common interceptors bundled"
:url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License"

View file

@ -1,4 +1,4 @@
(defproject metosin/reitit-malli "0.7.0-alpha8"
(defproject metosin/reitit-malli "0.10.0"
:description "Reitit: Malli coercion"
:url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License"

View file

@ -30,7 +30,7 @@
(mt/transformer
(if strip-extra-keys (mt/strip-extra-keys-transformer))
transformer
(if default-values (mt/default-value-transformer))))))
(if default-values (mt/default-value-transformer (if (map? default-values) default-values {})))))))
(def string-transformer-provider (-provider (mt/string-transformer)))
(def json-transformer-provider (-provider (mt/json-transformer)))
@ -76,6 +76,22 @@
(assoc error :transformed transformed))))
value))))))))
(defn- -query-string-coercer
"Create coercer for query-parameters, always allows extra params and does
encoding using string-transformer."
[schema string-transformer-provider options]
(let [;; Always allow extra paramaters on query-parameters encoding
open-schema (mu/open-schema schema)
;; Do not remove extra keys
string-transformer (if (satisfies? TransformationProvider string-transformer-provider)
(-transformer string-transformer-provider (assoc options :strip-extra-keys false))
string-transformer-provider)
encoder (m/encoder open-schema options string-transformer)]
(fn [value format]
(if encoder
(encoder value)
value))))
;;
;; public api
;;
@ -99,7 +115,9 @@
:enabled true
;; strip-extra-keys (affects only predefined transformers)
:strip-extra-keys true
;; add/set default values
;; add/set default values.
;; Can be false, true or a map of options to pass to malli.transform/default-value-transformer,
;; for example {:malli.transform/add-optional-keys true}
:default-values true
;; encode-error
:encode-error nil
@ -112,6 +130,9 @@
([opts]
(let [{:keys [transformers lite compile options error-keys encode-error] :as opts} (merge default-options opts)
show? (fn [key] (contains? error-keys key))
;; Query-string-coercer needs to construct transfomer without strip-extra-keys so it will
;; use the transformer-provider directly.
string-transformer-provider (:default (:string transformers))
transformers (walk/prewalk #(if (satisfies? TransformationProvider %) (-transformer % opts) %) transformers)
compile (if lite (fn [schema options]
(compile (binding [l/*options* options] (l/schema schema)) options))
@ -126,7 +147,10 @@
;; For :parameters we need to output an object schema with actual :properties.
;; The caller will iterate through the properties and add them individually to the openapi doc.
;; Thus, we deref to get the actual [:map ..] instead of some ref-schema.
(json-schema/transform (m/deref model) (merge opts options))
(let [should-be-map (m/deref model)]
(when-not (= :map (m/type should-be-map))
(println "WARNING: Unsupported schema for OpenAPI (expected :map schema)" (select-keys options [:in :parameter]) should-be-map))
(json-schema/transform should-be-map (merge opts options)))
(json-schema/transform model (merge opts options)))
(throw
(ex-info
@ -164,7 +188,8 @@
(-open-model [_ schema] schema)
(-encode-error [_ error]
(cond-> error
(show? :humanized) (assoc :humanized (me/humanize error {:wrap :message}))
(show? :humanized) (assoc :humanized (me/humanize error (cond-> {:wrap :message}
options (merge options))))
(show? :schema) (update :schema edn/write-string opts)
(show? :errors) (-> (me/with-error-messages opts)
(update :errors (partial map #(update % :schema edn/write-string opts))))
@ -173,6 +198,8 @@
(-request-coercer [_ type schema]
(-coercer schema type transformers :decode opts))
(-response-coercer [_ schema]
(-coercer schema :response transformers :encode opts))))))
(-coercer schema :response transformers :encode opts))
(-query-string-coercer [_ schema]
(-query-string-coercer schema string-transformer-provider opts))))))
(def coercion (create default-options))

View file

@ -1,4 +1,4 @@
(defproject metosin/reitit-middleware "0.7.0-alpha8"
(defproject metosin/reitit-middleware "0.10.0"
:description "Reitit, common middleware bundled"
:url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License"

View file

@ -19,14 +19,17 @@
(recur (.getSuperclass sk) (conj ks sk))
ks)))
(defn- find-closest-ancestor [val m]
(or (get m val)
(some #(find-closest-ancestor % m) (parents val))))
(defn- call-error-handler [handlers error request]
(let [type (:type (ex-data error))
ex-class (class error)
error-handler (or (get handlers type)
(get handlers ex-class)
(some
(partial get handlers)
(descendants type))
(when-not (class? type)
(find-closest-ancestor type handlers))
(some
(partial get handlers)
(super-classes ex-class))
@ -66,6 +69,7 @@
"Default safe handler for any exception."
[^Exception e _]
{:status 500
:headers {}
:body {:type "exception"
:class (.getName (.getClass e))}})
@ -74,6 +78,7 @@
[status]
(fn [e _]
{:status status
:headers {}
:body (coercion/encode-error (ex-data e))}))
(defn http-response-handler
@ -138,6 +143,9 @@
4) Super Classes of exception
5) The ::default handler
Note! If the closest ancestor for `:type` is not unique, an
arbitrary one is picked.
Example:
(require '[reitit.ring.middleware.exception :as exception])

View file

@ -58,7 +58,10 @@
"Creates a Middleware to handle the multipart params, based on
ring.middleware.multipart-params, taking same options. Mounts only
if endpoint has `[:parameters :multipart]` defined. Publishes coerced
parameters into `[:parameters :multipart]` under request."
parameters into `[:parameters :multipart]` under request.
Note! You want to have multipart-middleware after coerce-request-middleware,
because coerce-request-middleware overwrites `:parameters`."
([]
(create-multipart-middleware nil))
([options]
@ -69,5 +72,8 @@
"Middleware to handle the multipart params, based on
ring.middleware.multipart-params, taking same options. Mounts only
if endpoint has `[:parameters :multipart]` defined. Publishes coerced
parameters into `[:parameters :multipart]` under request."
parameters into `[:parameters :multipart]` under request.
Note! You want to have multipart-middleware after coerce-request-middleware,
because coerce-request-middleware overwrites `:parameters`."
(create-multipart-middleware))

View file

@ -1,4 +1,4 @@
(defproject fi.metosin/reitit-openapi "0.7.0-alpha8"
(defproject fi.metosin/reitit-openapi "0.10.0"
:description "Reitit: OpenAPI-support"
:url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License"

View file

@ -76,14 +76,16 @@
(defn- openapi-path [path opts]
(-> path (trie/normalize opts) (str/replace #"\{\*" "{")))
(def ^:private form-content-type "application/x-www-form-urlencoded")
(defn -get-apidocs-openapi
[coercion {:keys [request muuntaja parameters responses openapi/request-content-types openapi/response-content-types]} definitions]
(let [{:keys [body multipart]} parameters
parameters (dissoc parameters :request :body :multipart)
(let [{:keys [body form multipart]} parameters
parameters (dissoc parameters :request :body :form :multipart)
->content (fn [data schema]
(merge
{:schema schema}
(select-keys data [:description :examples])
(select-keys data [:example :examples])
(:openapi data)))
->schema-object (fn [model opts]
(let [result (coercion/-get-model-apidocs
@ -108,11 +110,11 @@
(merge {:in (name in)
:name k
:required (required? k)
:schema schema}
(select-keys schema [:description])))
:schema (dissoc schema :description :deprecated)}
(select-keys schema [:description :deprecated])))
(into []))})
(when body
;; body uses a single schema to describe every :requestBody
;; :body uses a single schema to describe every :requestBody
;; the schema-object transformer should be able to transform into distinct content-types
{:requestBody {:content (into {}
(map (fn [content-type]
@ -122,8 +124,15 @@
[content-type {:schema schema}])))
request-content-types)}})
(when form
;; :form is similar to any other body, but the content type must be application/x-www-form-urlencoded
{:requestBody {:content {form-content-type {:schema (->schema-object form
{:in :requestBody
:type :schema
:content-type form-content-type})}}}})
(when request
;; request allow to different :requestBody per content-type
;; :request allows different :requestBody per content-type
{:requestBody
(merge
(select-keys request [:description])
@ -197,20 +206,23 @@
accept-route (fn [route]
(-> route second :openapi :id (or ::default) (trie/into-set) (set/intersection ids) seq))
definitions (volatile! {})
transform-endpoint (fn [[method {{:keys [coercion no-doc openapi] :as data} :data
middleware :middleware
interceptors :interceptors}]]
(if (and data (not no-doc))
[method
(meta-merge
(apply meta-merge (keep (comp :openapi :data) middleware))
(apply meta-merge (keep (comp :openapi :data) interceptors))
(if coercion
(-get-apidocs-openapi coercion data definitions))
(select-keys data [:tags :summary :description])
(strip-top-level-keys openapi))]))
transform-endpoint (fn [path [method {{:keys [coercion no-doc openapi] :as data} :data
middleware :middleware
interceptors :interceptors}]]
(try
(if (and data (not no-doc))
[method
(meta-merge
(apply meta-merge (keep (comp :openapi :data) middleware))
(apply meta-merge (keep (comp :openapi :data) interceptors))
(if coercion
(-get-apidocs-openapi coercion data definitions))
(select-keys data [:tags :summary :description])
(strip-top-level-keys openapi))])
(catch Throwable t
(throw (ex-info "While building openapi docs" {:path path :method method} t)))))
transform-path (fn [[p _ c]]
(if-let [endpoint (some->> c (keep transform-endpoint) (seq) (into {}))]
(if-let [endpoint (some->> c (keep (partial transform-endpoint p)) (seq) (into {}))]
[(openapi-path p (r/options router)) endpoint]))
map-in-order #(->> % (apply concat) (apply array-map))
paths (->> router (r/compiled-routes) (filter accept-route) (map transform-path) map-in-order)]

View file

@ -1,4 +1,4 @@
(defproject metosin/reitit-pedestal "0.7.0-alpha8"
(defproject metosin/reitit-pedestal "0.10.0"
:description "Reitit + Pedestal Integration"
:url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License"

View file

@ -2,10 +2,10 @@
(:require [io.pedestal.http :as http]
[io.pedestal.interceptor :as interceptor]
[io.pedestal.interceptor.chain :as chain]
[reitit.exception :as ex]
[reitit.http]
[reitit.interceptor])
(:import (java.lang.reflect Method)
(reitit.interceptor Executor)))
(:import (java.lang.reflect Method)))
;; TODO: variadic
(defn- arities [f]
@ -46,12 +46,16 @@
(def pedestal-executor
(reify
Executor
reitit.interceptor/Executor
(queue [_ interceptors]
(->> interceptors
(map (fn [{::interceptor/keys [handler] :as interceptor}]
(or handler interceptor)))
(keep ->interceptor)))
(execute [_ _ _]
(ex/unsupported-protocol-method! 'reitit.interceptor/execute))
(execute [_ _ _ _ _]
(ex/unsupported-protocol-method! 'reitit.interceptor/execute))
(enqueue [_ context interceptors]
(chain/enqueue context interceptors))))

View file

@ -1,4 +1,4 @@
(defproject metosin/reitit-ring "0.7.0-alpha8"
(defproject metosin/reitit-ring "0.10.0"
:description "Reitit: Ring routing"
:url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License"

View file

@ -97,6 +97,23 @@
{:no-doc true
:handler default-options-handler})
(defn- comp-handlers
"Compose two ring handlers such that if the first has an empty response
the second will be invoked."
[handler1 handler2]
(let [single-arity (fn [request]
(or (handler1 request) (handler2 request)))
multi-arity (fn [request respond raise]
(handler1 request (fn [response]
(if response
(respond response)
(handler2 request respond raise))) raise))]
(fn
([request]
(single-arity request))
([request respond raise]
(multi-arity request respond raise)))))
;;
;; public api
;;
@ -135,19 +152,11 @@
(defn routes
"Create a ring handler by combining several handlers into one."
[& handlers]
(if-let [single-arity (some->> handlers (keep identity) (seq) (apply some-fn))]
(fn
([request]
(single-arity request))
([request respond raise]
(letfn [(f [handlers]
(if (seq handlers)
(let [handler (first handlers)
respond' #(if % (respond %) (f (rest handlers)))]
(handler request respond' raise))
(respond nil)))]
(f handlers))))))
{:arglists '([& handlers])}
([& [handler1 handler2 & handlers]]
(cond (seq handlers) (reduce routes (routes handler1 handler2) handlers)
(and handler1 handler2) (comp-handlers handler1 handler2)
:else (or handler1 handler2))))
(defn redirect-trailing-slash-handler
"A ring handler that redirects a missing path if there is an
@ -164,7 +173,8 @@
(letfn [(maybe-redirect [{:keys [query-string] :as request} path]
(if (and (seq path) (r/match-by-path (::r/router request) path))
{:status (if (= (:request-method request) :get) 301 308)
:headers {"Location" (if query-string (str path "?" query-string) path)}
:headers {"Location" (let [path (str/replace-first path #"^/+" "/")] ; Locations starting with // redirect to another hostname. Avoid these due to security implications.
(if query-string (str path "?" query-string) path))}
:body ""}))
(redirect-handler [request]
(let [uri (:uri request)]
@ -218,18 +228,21 @@
;; TODO: ring.middleware.head/wrap-head
;; TODO: handle etags
(defn -create-file-or-resource-handler
[response-fn {:keys [parameter root path loader allow-symlinks? index-files paths not-found-handler]
[response-fn {:keys [parameter root path loader allow-symlinks? index-files index-redirect? canonicalize-uris? paths not-found-handler mime-types]
:or {parameter (keyword "")
root "public"
index-files ["index.html"]
paths (constantly nil)
not-found-handler (if path
(constantly nil)
(constantly {:status 404, :body "", :headers {}}))}}]
index-redirect? false
canonicalize-uris? true
paths (constantly nil)}}]
(let [options {:root root
:loader loader
:index-files? false
:allow-symlinks? allow-symlinks?}
not-found-handler (or not-found-handler
(if path
(constantly nil)
(constantly {:status 404, :body "", :headers {}})))
path-size (count path)
create (fn [handler]
(fn
@ -238,16 +251,27 @@
join-paths (fn [& paths]
(str/replace (str/replace (str/join "/" paths) #"([/]+)" "/") #"/$" ""))
response (fn [path]
(if-let [response (or (paths (join-paths "/" path))
(response-fn path options))]
(response/content-type response (mime-type/ext-mime-type path))))
(when-let [response (or (paths (join-paths "/" path))
(response-fn path options))]
(if-let [content-type (mime-type/ext-mime-type path mime-types)]
(response/content-type response content-type)
response)))
path-or-index-response (fn [path uri]
(or (response path)
(loop [[file & files] index-files]
(if file
(if (response (join-paths path file))
(response/redirect (join-paths uri file))
(recur files))))))
(when (or canonicalize-uris? (str/ends-with? uri "/"))
(loop [[file & files] index-files]
(if file
(if-let [resp (response (join-paths path file))]
(cond
index-redirect?
(response/redirect (join-paths uri file))
(not (str/ends-with? uri "/"))
(response/redirect (str uri "/"))
:else
resp)
(recur files)))))))
handler (if path
(fn [request]
(let [uri (impl/url-decode (:uri request))]
@ -265,14 +289,18 @@
(defn create-resource-handler
"A ring handler for serving classpath resources, configured via options:
| key | description |
| -------------------|-------------|
| :parameter | optional name of the wildcard parameter, defaults to unnamed keyword `:`
| :root | optional resource root, defaults to `\"public\"`
| :path | path to mount the handler to. Required when mounted outside of a router, does not work inside a router.
| :loader | optional class loader to resolve the resources
| :index-files | optional vector of index-files to look in a resource directory, defaults to `[\"index.html\"]`
| :not-found-handler | optional handler function to use if the requested resource is missing (404 Not Found)"
| key | description |
| --------------------|-------------|
| :parameter | optional name of the wildcard parameter, defaults to unnamed keyword `:`
| :root | optional resource root, defaults to `\"public\"`
| :path | path to mount the handler to. Required when mounted outside of a router, does not work inside a router.
| :loader | optional class loader to resolve the resources
| :index-files | optional vector of index-files to look in a resource directory, defaults to `[\"index.html\"]`
| :index-redirect? | optional boolean: if true (default false), redirect to index file, if false serve it directly
| :canonicalize-uris? | optional boolean: if true (default), try to serve index files for non directory paths (paths that end with slash)
| :not-found-handler | optional handler function to use if the requested resource is missing (404 Not Found)
| :mime-types | optional map of filename extensions to mime-types that will be used to guess the content type in addition to the ones defined in ring.util.mime-type/default-mime-types
| :allow-symlinks? | allow symlinks that lead to paths outside the root classpath directories, defaults to false"
([]
(create-resource-handler nil))
([opts]
@ -282,14 +310,18 @@
(defn create-file-handler
"A ring handler for serving file resources, configured via options:
| key | description |
| -------------------|-------------|
| :parameter | optional name of the wildcard parameter, defaults to unnamed keyword `:`
| :root | optional resource root, defaults to `\"public\"`
| :path | path to mount the handler to. Required when mounted outside of a router, does not work inside a router.
| :loader | optional class loader to resolve the resources
| :index-files | optional vector of index-files to look in a resource directory, defaults to `[\"index.html\"]`
| :not-found-handler | optional handler function to use if the requested resource is missing (404 Not Found)"
| key | description |
| --------------------|-------------|
| :parameter | optional name of the wildcard parameter, defaults to unnamed keyword `:`
| :root | optional resource root, defaults to `\"public\"`
| :path | path to mount the handler to. Required when mounted outside of a router, does not work inside a router.
| :loader | optional class loader to resolve the resources
| :index-files | optional vector of index-files to look in a resource directory, defaults to `[\"index.html\"]`
| :index-redirect? | optional boolean: if true (default false), redirect to index file, if false serve it directly
| :canonicalize-uris? | optional boolean: if true (default), try to serve index files for non directory paths (paths that end with slash)
| :not-found-handler | optional handler function to use if the requested resource is missing (404 Not Found)
| :mime-types | optional map of filename extensions to mime-types that will be used to guess the content type in addition to the ones defined in ring.util.mime-type/default-mime-types
| :allow-symlinks? | allow symlinks that lead to paths outside the root classpath directories, defaults to false"
([]
(create-file-handler nil))
([opts]
@ -341,7 +373,7 @@
([router default-handler {:keys [middleware inject-match? inject-router?]
:or {inject-match? true, inject-router? true}}]
(let [default-handler (or default-handler (fn ([_]) ([_ respond _] (respond nil))))
wrap (if middleware (partial middleware/chain middleware) identity)
wrap (if middleware #(middleware/chain middleware % nil (r/options router)) identity)
enrich-request (create-enrich-request inject-match? inject-router?)
enrich-default-request (create-enrich-default-request inject-router?)]
(with-meta
@ -363,7 +395,10 @@
result (:result match)
handler (-> result method :handler (or default-handler))
request (enrich-request request path-params match router)]
((routes handler default-handler) request respond raise))
(handler request (fn [response]
(if response
(respond response)
(default-handler request respond raise))) raise))
(default-handler (enrich-default-request request router) respond raise))
nil)))
{::r/router router}))))

View file

@ -11,6 +11,7 @@
nil)]
(respond
{:status status
:headers {}
:body (coercion/encode-error data)})
(raise e))))
@ -57,13 +58,13 @@
(not responses) {}
;; mount
:else
(if-let [coercers (coercion/response-coercers coercion responses opts)]
(if-let [coercer (coercion/response-coercer coercion responses opts)]
(fn [handler]
(fn
([request]
(coercion/coerce-response coercers request (handler request)))
(coercer request (handler request)))
([request respond raise]
(handler request #(respond (coercion/coerce-response coercers request %)) raise))))
(handler request #(respond (coercer request %)) raise))))
{})))})
(def coerce-exceptions-middleware

View file

@ -1,4 +1,4 @@
(defproject metosin/reitit-schema "0.7.0-alpha8"
(defproject metosin/reitit-schema "0.10.0"
:description "Reitit: Plumatic Schema coercion"
:url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License"

View file

@ -101,6 +101,8 @@
value))))
(-response-coercer [this schema]
(if (coerce-response? schema)
(coercion/-request-coercer this :response schema)))))
(coercion/-request-coercer this :response schema)))
(-query-string-coercer [this schema]
nil)))
(def coercion (create default-options))

View file

@ -1,4 +1,4 @@
(defproject metosin/reitit-sieppari "0.7.0-alpha8"
(defproject metosin/reitit-sieppari "0.10.0"
:description "Reitit: Sieppari Interceptors"
:url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License"

View file

@ -1,5 +1,6 @@
(ns reitit.interceptor.sieppari
(:require [reitit.interceptor :as interceptor]
(:require [reitit.exception :as ex]
[reitit.interceptor :as interceptor]
[sieppari.core :as sieppari]
[sieppari.queue :as queue]))
@ -15,4 +16,6 @@
(execute [_ interceptors request]
(sieppari/execute interceptors request))
(execute [_ interceptors request respond raise]
(sieppari/execute interceptors request respond raise))))
(sieppari/execute interceptors request respond raise))
(enqueue [_ _ _]
(ex/unsupported-protocol-method! 'reitit.interceptor/enqueue))))

View file

@ -1,4 +1,4 @@
(defproject metosin/reitit-spec "0.7.0-alpha8"
(defproject metosin/reitit-spec "0.10.0"
:description "Reitit: clojure.spec coercion"
:url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License"

View file

@ -148,6 +148,8 @@
value))))
(-response-coercer [this spec]
(if (coerce-response? spec)
(coercion/-request-coercer this :response spec)))))
(coercion/-request-coercer this :response spec)))
(-query-string-coercer [this spec]
nil)))
(def coercion (create default-options))

View file

@ -1,4 +1,4 @@
(defproject metosin/reitit-swagger-ui "0.7.0-alpha8"
(defproject metosin/reitit-swagger-ui "0.10.0"
:description "Reitit: Swagger-ui support"
:url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License"

View file

@ -1,4 +1,4 @@
(defproject metosin/reitit-swagger "0.7.0-alpha8"
(defproject metosin/reitit-swagger "0.10.0"
:description "Reitit: Swagger-support"
:url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License"

View file

@ -1,4 +1,4 @@
(defproject metosin/reitit "0.7.0-alpha8"
(defproject metosin/reitit "0.10.0"
:description "Snappy data-driven router for Clojure(Script)"
:url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License"

3986
package-lock.json generated

File diff suppressed because it is too large Load diff

Some files were not shown because too many files have changed in this diff Show more