Compare commits

...

284 commits

Author SHA1 Message Date
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
Tommi Reiman
bed41fa46c test with all LTS versions 2024-04-30 11:15:15 +03:00
Tommi Reiman
2be0dbbb2a 0.7.0-alpha8 2024-04-30 11:12:39 +03:00
Tommi Reiman
710eb69e8c 0.7.0-alpha8 2024-04-30 11:11:25 +03:00
Joel Kaasinen
275b789ca9
Merge pull request #677 from metosin/improve-swagger-var-tests
test: add response schema to reitit.swagger-test/malli-var-test
2024-04-26 08:59:23 +03:00
Joel Kaasinen
18550cd297 test: add response schema to reitit.swagger-test/malli-var-test 2024-04-22 09:08:37 +03:00
Joel Kaasinen
15790f3028
Merge pull request #676 from metosin/update-validator
update openapi-schema-validator, fix openapi requestBody description
2024-04-22 08:48:59 +03:00
Joel Kaasinen
3296323059 test: use string refs in openapi-malli-tests
... in order to pass the validator, which disallows "/"
2024-04-22 08:31:51 +03:00
Joel Kaasinen
037763561e fix: location of openapi :requestBody :description
:description should be under :requestBody, not under :content. Thanks
to openapi-schema-validator for noticing this.

Also fix examples in openapi-malli-tests to make the resulting schema
valid.
2024-04-22 08:31:51 +03:00
Joel Kaasinen
c84433352e deps: bump @seriousme/openapi-schema-validator 2024-04-22 08:31:51 +03:00
Joel Kaasinen
c8c8c0eb03
Merge pull request #673 from metosin/malli-vars
Generate correct OpenAPI $ref schemas for malli var and ref schemas
2024-04-22 08:31:22 +03:00
Joel Kaasinen
a06b2c98a7 doc: update CHANGELOG.md 2024-04-22 08:17:37 +03:00
Joel Kaasinen
3cb387747b doc: use malli vars in examples/openapi 2024-04-22 08:00:27 +03:00
Joel Kaasinen
57fc00a45e test: tests for openapi + malli refs/vars 2024-04-22 08:00:27 +03:00
Joel Kaasinen
ce52b26329 test: actually assert something in openapi-malli-tests
(is (= x) y) strikes again
2024-04-22 08:00:27 +03:00
Joel Kaasinen
337d94823a feat: support ref schemas in openapi parameters
e.g. {:parameters {:query #'MyVar}}
2024-04-22 08:00:27 +03:00
Joel Kaasinen
288b701d4e feat: openapi #/components/schemas
collect definitions when traversing the models, and put them in the
right place for openapi

depends on :malli.json-schema/definitions-path support
2024-04-22 08:00:27 +03:00
Joel Kaasinen
c2372473d0 test: test for malli vars + swagger 2024-04-22 08:00:27 +03:00
Joel Kaasinen
066c54b1d2
Merge pull request #675 from metosin/0.7.0-alpha8
0.7.0-alpha8
2024-04-22 07:55:36 +03:00
Tommi Reiman
7432ef9bb9 require java11 2024-04-20 16:42:36 +03:00
Tommi Reiman
fa24dcd29a update deps 2024-04-20 16:38:41 +03:00
Joel Kaasinen
bbaab0b8f8
Merge pull request #674 from metosin/bump-examples
Bump deps on examples
2024-04-19 16:53:24 +03:00
Martín Varela
81b9464f68 deps: bump deps for frontend example
Also fix frontend example to work with current reagent version
2024-04-19 16:25:45 +03:00
Joel Kaasinen
1b7fc0fc58 deps: bump the deps on some examples 2024-04-19 14:32:08 +03:00
Joel Kaasinen
13e8dd86e5 deps: bump deps on some examples 2024-04-19 14:32:08 +03:00
Joel Kaasinen
06c0fd8566 fix: ns name in examples/pedestal-malli-swagger, bump deps 2024-04-19 14:32:08 +03:00
Joel Kaasinen
cd6c23823c deps: bump the deps of some examples 2024-04-19 14:32:08 +03:00
Joel Kaasinen
ff76f5d888 deps: update deps on some examples 2024-04-19 14:32:08 +03:00
Joel Kaasinen
cc9863c95f deps: bump deps in examples/buddy-auth 2024-04-19 14:32:08 +03:00
Joel Kaasinen
59df1f995b deps: bump clojure version in examples 2024-04-19 14:32:08 +03:00
Joel Kaasinen
f41d555b62
Merge pull request #671 from metosin/fix-example
fix: remove unsupported coercions when generating swagger
2024-04-19 10:32:25 +03:00
Joel Kaasinen
a69cfdac41
Merge pull request #666 from velios/patch-1
Update ring-swagger-ui for support openapi 3.1.0 version
2024-04-19 10:21:08 +03:00
Joel Kaasinen
b6c5b69ffe
Merge pull request #659 from frenchy64/escaped-double-quote-doc
Fix formatting of #'router docstring
2024-04-19 10:18:32 +03:00
Joel Kaasinen
8ce2de3631 doc: reitit requires clojure 1.11
now that we're using update-vals
2024-04-19 10:16:13 +03:00
Joel Kaasinen
ff957661e5 doc: update CHANGELOG.md 2024-04-19 10:16:13 +03:00
Joel Kaasinen
01b476b342 fix: remove unsupported coercions when generating swagger
If we don't remove them, :responses :content gets passed out verbatim
in the swagger.json, breaking stuff.

In particular, fixes the swagger.json in
examples/reitit-malli-swagger. Reported broken in #669.
2024-04-19 10:16:13 +03:00
Joel Kaasinen
fbec1e2ecc
Merge pull request #668 from metosin/bump-muuntaja
deps: bump muuntaja
2024-03-15 12:06:11 +02:00
Joel Kaasinen
00b5487cc0 deps: bump muuntaja 2024-03-15 12:01:08 +02:00
Joel Kaasinen
fb2f4b2ee9 doc: update CHANGELOG.md 2024-03-15 10:36:15 +02:00
Joel Kaasinen
c67a748915
Merge pull request #585 from djblue/var-handler
Allow var handlers
2024-03-15 10:35:22 +02:00
Joel Kaasinen
d24b501281 doc: handlers can be vars 2024-03-15 09:31:48 +02:00
Joel Kaasinen
5d30a73bad feat: reitit.core/Expand for Vars 2024-03-15 09:21:51 +02:00
Joel Kaasinen
659e96e780 test: handler can be a var 2024-03-15 09:18:54 +02:00
Juho Teperi
2fe448c3d8 Add frontend-malli example 2024-02-26 11:12:54 +02:00
velios
c295e645c5
Update ring-swagger-ui dependency
The need for such a change is that reitit-openapi module creates openapi.js of 3.1.0 version, but current reitit-swagger-ui can't draw it, because dependency through root to ring-swagger-ui . ring-swagger-ui > 5.x.x version can draw openapi.js 3.1.0 version correctly
2024-02-24 13:41:31 +01:00
Martín Varela
ca434f9c05
Merge pull request #663 from metosin/openapi-exp
#636 Adds level-1 Muuntaja support for OpenAPI3
2024-02-09 12:19:15 +02:00
Martín Varela
0e8d635e44 fix: added muuntaja dependency for the openapi module 2024-02-09 12:14:35 +02:00
Martín Varela
6c9b280fa2 doc: add notice about OpenAPI support being clj only 2024-02-09 12:12:58 +02:00
Martín Varela
cb1c5e8748 made openapi clj, not cljc 2024-02-09 12:12:58 +02:00
Martín Varela
e7be6327d4 doc: make :default stand out as special 2024-02-09 11:52:00 +02:00
Martín Varela
d2c00026e6 doc: Update docs for fetching content types from Muuntaja instance 2024-02-09 11:49:44 +02:00
Martín Varela
ed280f9a33 feature: fetch openapi content types from muuntaja
(level 1 integration in #636)
2024-02-09 11:49:44 +02:00
Martín Varela
f1e6d37dcf fix: don't output :default in openapi request body 2024-02-09 11:49:44 +02:00
Martín Varela
98f3eb0a72
Merge pull request #664 from metosin/fix-swagger-tests
Fix: swagger tests and CI
2024-02-09 11:48:59 +02:00
Martín Varela
82c714d0cc fix: pin version for openapi-schema-validator on CI 2024-02-09 11:42:19 +02:00
Martín Varela
982ac3ec72 Fix: account for changed malli encoder behavior that was breaking ring-coercion-test 2024-02-09 11:37:34 +02:00
Martín Varela
f99a76886e Fix: fix swagger ring tests, malli keys are now strings, not keywords 2024-02-09 11:21:31 +02:00
Tommi Reiman
989ab72a58 use latest malli.
test fail, but they did so earlier. FIX
2024-01-16 14:59:53 +02:00
Ambrose Bonnaire-Sergeant
5444bad439 Fix formatting of #'router docstring
Escaped double quotes breaks the clojure.repl/doc output.
2024-01-12 13:50:18 -06:00
Tommi Reiman
7e00de835d
Merge pull request #615 from nimitmaru/patch-1
Update README.md - fixed link
2024-01-08 08:04:47 +02:00
Juho Teperi
620d0c2711
Merge pull request #655 from stig/fix-link-to-jira
Fix links to Jira in the documentation
2023-10-30 12:34:48 +02:00
Juho Teperi
8d2d295a60
Merge pull request #654 from stig/effect->affect
Correct "effects" to "affects" in comments & docs
2023-10-30 12:32:57 +02:00
Stig Brautaset
0fff06ec6b
Correct "effects" to "affects" in comments & docs
Their usage is commonly confused, but "affect" is usually a verb and
"effect" is usually a noun. In this case we want the verb. See also
https://www.merriam-webster.com/grammar/affect-vs-effect-usage-difference
2023-10-13 21:10:20 +01:00
Stig Brautaset
f4a8013388
Fix link to Jira in the documentation
The existing links didn't work for me, but the new links do.
2023-10-09 14:03:36 +01:00
Nimit Maru
24669cf58f
Update README.md
ring router link bugfix
2023-05-01 17:46:34 -04:00
Chris Badahdah
999f6c3dbd Allow var handlers 2023-01-31 16:04:28 -07:00
132 changed files with 4349 additions and 3880 deletions

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

@ -8,30 +8,40 @@ jobs:
build-clj:
strategy:
matrix:
# Supported Java versions: LTS releases 8 and 11 and the latest release
jdk: [8, 11, 15]
# Supported Java versions: LTS releases and latest
jdk: [11, 17, 21]
name: 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
# Install openapi-schema-validator for openapi-tests
# Uses node version from the ubuntu-latest
- name: Install dependencies
run: npm ci
- name: Run tests
run: ./scripts/test.sh clj
@ -39,33 +49,37 @@ jobs:
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
@ -73,7 +87,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
@ -81,13 +96,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,184 @@ We use [Break Versioning][breakver]. The version numbers follow a `<major>.<mino
[breakver]: https://github.com/ptaoussanis/encore/blob/master/BREAK-VERSIONING.md
## UNRELEASED
* 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)
## 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
* Fix swagger generation when unsupported coercions are present [#671](https://github.com/metosin/reitit/pull/671)
* Generate correct OpenAPI $ref schemas for malli var and ref schemas [#673](https://github.com/metosin/reitit/pull/673)
* Updated dependencies:
```clojure
[metosin/malli "0.16.1"] is available but we use "0.13.0"
[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/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.15.1"
[com.fasterxml.jackson.core/jackson-databind "2.17.0"] is available but we use "2.15.1"
[ring/ring-core "1.12.1"] is available but we use "1.10.0"
[metosin/ring-swagger-ui "5.9.0"] is available but we use "4.19.1"
```
## 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:
@ -22,20 +198,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
@ -47,29 +223,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:
@ -80,10 +256,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)
@ -109,23 +285,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/)
* `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,11 +66,15 @@ There is [#reitit](https://clojurians.slack.com/messages/reitit/) in [Clojurians
All main modules bundled:
```clj
[metosin/reitit "0.7.0-alpha7"]
[metosin/reitit "0.9.2"]
```
Optionally, the parts can be required separately.
Reitit requires Clojure 1.11 and Java 11.
Reitit is tested with the LTS releases Java 11, 17 and 21.
## Quick start
```clj
@ -91,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])
@ -106,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-alpha7"]
[metosin/reitit "0.9.2"]
```
Optionally, the parts can be required separately.

View file

@ -147,3 +147,19 @@ Let's apply a small change to our ```ns3```. We'll replace our router by two dif
And there you have it, dynamic during dev, performance at production. We have it all !
## Var handlers
You can use a var instead of a function as a `:handler`. This will
allow you to modify the handler function without rebuilding the reitit
router.
For example:
```clj
(def router
(ring/router
["/ping" {:get #'my-ns/handler}]))
```
Now you can reload `my-ns` or redefine `my-ns/handler` and the router
will use the new definition automatically.

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-alpha7"]
[metosin/reitit-dev "0.9.2"]
```
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

@ -11,7 +11,8 @@ Route data is the key feature of reitit. Routes can have any map-like data attac
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.

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

@ -10,7 +10,7 @@ The [clojure.spec](https://clojure.org/guides/spec) library specifies the struct
For simple specs (core predicates, `spec-tools.core/spec`, `s/and`, `s/or`, `s/coll-of`, `s/keys`, `s/map-of`, `s/nillable` and `s/every`), the transformation is inferred using [spec-walker](https://github.com/metosin/spec-tools#spec-walker) and is automatic. To support all specs (like regex-specs), specs need to be wrapped into [Spec Records](https://github.com/metosin/spec-tools/blob/master/README.md#spec-records).
There are [CLJ-2116](https://dev.clojure.org/jira/browse/CLJ-2116) and [CLJ-2251](https://dev.clojure.org/jira/browse/CLJ-2251) that would help solve this elegantly. Go vote 'em up.
There are [CLJ-2116](https://clojure.atlassian.net/browse/CLJ-2116) and [CLJ-2251](https://clojure.atlassian.net/browse/CLJ-2251) that would help solve this elegantly. Go vote 'em up.
## Example

View file

@ -1,6 +1,6 @@
# Data-spec Coercion
[Data-specs](https://github.com/metosin/spec-tools#data-specs) is alternative, macro-free syntax to define `clojure.spec`s. As a bonus, supports the [runtime transformations via conforming](https://dev.clojure.org/jira/browse/CLJ-2116) out-of-the-box.
[Data-specs](https://github.com/metosin/spec-tools#data-specs) is alternative, macro-free syntax to define `clojure.spec`s. As a bonus, supports the [runtime transformations via conforming](https://clojure.atlassian.net/browse/CLJ-2116) out-of-the-box.
```clj
(require '[reitit.coercion.spec])

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)
@ -84,10 +85,40 @@ Using `create` with options to create the coercion instead of `coercion`:
:validate true
;; top-level short-circuit to disable request & response coercion
:enabled true
;; strip-extra-keys (effects only predefined transformers)
;; 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
# !!! 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-alpha7"]
[metosin/reitit-interceptors "0.9.2"]
```
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-alpha7"]
[metosin/reitit-http "0.9.2"]
```
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-alpha7"]
[metosin/reitit-pedestal "0.9.2"]
```
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-alpha7"]
; [metosin/reitit "0.7.0-alpha7"]
; [metosin/reitit-pedestal "0.9.2"]
; [metosin/reitit "0.9.2"]
(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-alpha7"]
[metosin/reitit-sieppari "0.9.2"]
```
[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-alpha7"]
[metosin/reitit-interceptors "0.9.2"]
```
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)
@ -63,6 +64,36 @@ Handlers can access the coerced parameters via the `:parameters` key in the requ
: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]]}
```
## Coercion Middleware
Defining a coercion for a route data doesn't do anything, as it's just data. We have to attach some code to apply the actual coercion. We can use the middleware from `reitit.ring.coercion`:
@ -171,11 +202,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-alpha7"]
[metosin/reitit-middleware "0.9.2"]
```
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-alpha7"]
[metosin/reitit-middleware "0.9.2"]
```
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

@ -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
@ -19,12 +20,12 @@ The following route data keys contribute to the generated swagger specification:
| key | description |
| ---------------|-------------|
| :openapi | map of any openapi data. Can contain keys like `:deprecated`.
| :openapi/request-content-types | vector of supported request content types. Defaults to `["application/json"]`. Only needed if you use the [:request :content :default] coercion.
| :openapi/response-content-types | vector of supported response content types. Defaults to `["application/json"]`. Only needed if you use the [:response nnn :content :default] coercion.
| :no-doc | optional boolean to exclude endpoint from api docs
| :tags | optional set of string or keyword tags for an endpoint api docs
| :summary | optional short string summary of an endpoint
| :description | optional long description of an endpoint. Supports http://spec.commonmark.org/
| :openapi/request-content-types | See the Per-content-type-coercions section below.
| :openapi/response-content-types |See the Per-content-type-coercions section below. vector of supported response content types. Defaults to `["application/json"]`. Only needed if you use the [:response nnn :content :default] coercion.
Coercion keys also contribute to the docs:
@ -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,31 +56,28 @@ 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"
:value (pr-str {:color :red :pineapple true})}}}}}}
```
## 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"`.
The special `:default` content types map to the content types supported by the Muuntaja
instance. You can override these by using the `:openapi/request-content-types`
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"]`.
## OpenAPI spec
Serving the OpenAPI specification is handled by `reitit.openapi/create-openapi-handler`. It takes no arguments and returns a ring handler which collects at request-time data from all routes and returns an OpenAPI specification as Clojure data, to be encoded by a response formatter.
Serving the OpenAPI specification is handled by
`reitit.openapi/create-openapi-handler`. It takes no arguments and returns a
ring handler which collects at request-time data from all routes and returns an
OpenAPI specification as Clojure data, to be encoded by a response formatter.
You can use the `:openapi` route data key of the `create-openapi-handler` route to populate the top level of the OpenAPI spec.
You can use the `:openapi` route data key of the `create-openapi-handler` route
to populate the top level of the OpenAPI spec.
Example:
@ -140,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-alpha7"]
[metosin/reitit-ring "0.9.2"]
```
## `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-alpha7"]
[metosin/reitit-swagger "0.9.2"]
```
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-alpha7"]
[metosin/reitit-swagger-ui "0.9.2"]
```
`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-alpha7"]
[metosin/reitit-middleware "0.9.2"]
```
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

@ -7,6 +7,13 @@
## frontend-prompt
## frontend-re-frame
## frontend
Frontend example with clojure.spec coercion.
## frontend-malli
Frontend example with Malli coercion.
## http-swagger
Coercion with Spec and Swagger generation.

View file

@ -1,7 +1,7 @@
(defproject ring-example "0.1.0-SNAPSHOT"
(defproject buddy-auth "0.1.0-SNAPSHOT"
:description "Reitit Buddy Auth App"
:dependencies [[org.clojure/clojure "1.10.1"]
[ring/ring-jetty-adapter "1.8.1"]
[metosin/reitit "0.7.0-alpha7"]
:dependencies [[org.clojure/clojure "1.11.2"]
[ring/ring-jetty-adapter "1.12.1"]
[metosin/reitit "0.9.2"]
[buddy "2.0.0"]]
:repl-options {:init-ns example.server})

View file

@ -1,23 +1,25 @@
(defproject frontend "0.1.0-SNAPSHOT"
(defproject frontend-auth "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.10.0"]
:dependencies [[org.clojure/clojure "1.11.2"]
[ring-server "0.5.0"]
[reagent "0.8.1"]
[ring "1.7.1"]
[reagent "1.2.0"]
[ring "1.12.1"]
[hiccup "1.0.5"]
[org.clojure/clojurescript "1.10.439"]
[metosin/reitit "0.7.0-alpha7"]
[metosin/reitit-schema "0.7.0-alpha7"]
[metosin/reitit-frontend "0.7.0-alpha7"]
[org.clojure/clojurescript "1.11.132"]
[metosin/reitit "0.9.2"]
[metosin/reitit-schema "0.9.2"]
[metosin/reitit-frontend "0.9.2"]
[cljsjs/react "17.0.2-0"]
[cljsjs/react-dom "17.0.2-0"]
;; Just for pretty printting the match
[fipp "0.6.14"]]
:plugins [[lein-cljsbuild "1.1.7"]
[lein-figwheel "0.5.18"]]
:plugins [[lein-cljsbuild "1.1.8"]
[lein-figwheel "0.5.20"]]
:source-paths []
:resource-paths ["resources" "target/cljsbuild"]

View file

@ -1,5 +1,6 @@
(ns frontend.core
(:require [reagent.core :as r]
[reagent.dom :as rd]
[reitit.frontend :as rf]
[reitit.frontend.easy :as rfe]
[reitit.frontend.controllers :as rfc]
@ -147,6 +148,6 @@
(assoc state :match (assoc new-match :controllers (rfc/apply-controllers (:controllers (:match state)) new-match)))
(assoc state :match new-match))))))
{:use-fragment true})
(r/render [main-view] (.getElementById js/document "app")))
(rd/render [main-view] (.getElementById js/document "app")))
(init!)

View file

@ -1,23 +1,25 @@
(defproject frontend "0.1.0-SNAPSHOT"
(defproject frontend-controllers "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.10.0"]
:dependencies [[org.clojure/clojure "1.11.2"]
[ring-server "0.5.0"]
[reagent "0.8.1"]
[ring "1.7.1"]
[reagent "1.2.0"]
[ring "1.12.1"]
[hiccup "1.0.5"]
[org.clojure/clojurescript "1.10.439"]
[metosin/reitit "0.7.0-alpha7"]
[metosin/reitit-schema "0.7.0-alpha7"]
[metosin/reitit-frontend "0.7.0-alpha7"]
[org.clojure/clojurescript "1.11.132"]
[metosin/reitit "0.9.2"]
[metosin/reitit-schema "0.9.2"]
[metosin/reitit-frontend "0.9.2"]
[cljsjs/react "17.0.2-0"]
[cljsjs/react-dom "17.0.2-0"]
;; Just for pretty printting the match
[fipp "0.6.14"]]
:plugins [[lein-cljsbuild "1.1.7"]
[lein-figwheel "0.5.18"]]
:plugins [[lein-cljsbuild "1.1.8"]
[lein-figwheel "0.5.20"]]
:source-paths []
:resource-paths ["resources" "target/cljsbuild"]

View file

@ -1,5 +1,6 @@
(ns frontend.core
(:require [reagent.core :as r]
[reagent.dom :as rd]
[reitit.frontend :as rf]
[reitit.frontend.easy :as rfe]
[reitit.frontend.controllers :as rfc]
@ -88,6 +89,6 @@
(if new-match
(assoc new-match :controllers (rfc/apply-controllers (:controllers old-match) new-match))))))
{:use-fragment true})
(r/render [current-page] (.getElementById js/document "app")))
(rd/render [current-page] (.getElementById js/document "app")))
(init!)

View file

@ -1,24 +1,26 @@
(defproject frontend "0.1.0-SNAPSHOT"
(defproject frontend-links "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.10.0"]
:dependencies [[org.clojure/clojure "1.11.2"]
[ring-server "0.5.0"]
[reagent "0.8.1"]
[ring "1.7.1"]
[reagent "1.2.0"]
[ring "1.12.1"]
[hiccup "1.0.5"]
[org.clojure/clojurescript "1.10.520"]
[metosin/reitit "0.7.0-alpha7"]
[metosin/reitit-spec "0.7.0-alpha7"]
[metosin/reitit-frontend "0.7.0-alpha7"]
[metosin/reitit "0.9.2"]
[metosin/reitit-spec "0.9.2"]
[metosin/reitit-frontend "0.9.2"]
[cljsjs/react "17.0.2-0"]
[cljsjs/react-dom "17.0.2-0"]
;; Just for pretty printting the match
[fipp "0.6.14"]]
:plugins [[lein-cljsbuild "1.1.7"]
[lein-figwheel "0.5.18"]
[cider/cider-nrepl "0.21.1"]]
:plugins [[lein-cljsbuild "1.1.8"]
[lein-figwheel "0.5.20"]
[cider/cider-nrepl "0.47.1"]]
:repl-options {:nrepl-middleware [cider.piggieback/wrap-cljs-repl]}

View file

@ -2,6 +2,7 @@
(:require [clojure.string :as string]
[fipp.edn :as fedn]
[reagent.core :as r]
[reagent.dom :as rd]
[reitit.coercion.spec :as rss]
[reitit.frontend :as rf]
[reitit.frontend.easy :as rfe]
@ -137,7 +138,7 @@
(fn [m] (reset! current-match m))
;; set to false to enable HistoryAPI
{:use-fragment true})
(r/render [current-page] (.getElementById js/document "app")))
(rd/render [current-page] (.getElementById js/document "app")))
(init!)

View file

@ -0,0 +1,13 @@
# reitit-frontend example
## Usage
```clj
> lein figwheel
```
Go with browser to http://localhost:3449
## License
Copyright © Metosin Oy and collaborators

View file

@ -0,0 +1 @@
../../../modules/reitit-core

View file

@ -0,0 +1 @@
../../../modules/reitit-frontend

View file

@ -0,0 +1 @@
../../../modules/reitit-schema

View file

@ -0,0 +1,57 @@
(defproject frontend-malli "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.10.1"]
[ring-server "0.5.0"]
[reagent "1.2.0"]
[ring "1.12.1"]
[hiccup "1.0.5"]
[org.clojure/clojurescript "1.11.132"]
[metosin/reitit "0.9.2"]
[metosin/reitit-malli "0.9.2"]
[metosin/reitit-frontend "0.9.2"]
[cljsjs/react "17.0.2-0"]
[cljsjs/react-dom "17.0.2-0"]
;; Just for pretty printting the match
[fipp "0.6.23"]]
:plugins [[lein-cljsbuild "1.1.8"]
[lein-figwheel "0.5.20"]]
:source-paths []
:resource-paths ["resources" "target/cljsbuild"]
:profiles {:dev {:dependencies [[binaryage/devtools "1.0.2"]]}}
:cljsbuild
{:builds
[{:id "app"
:figwheel true
:source-paths ["src"]
:watch-paths ["src" "checkouts/reitit-frontend/src"]
:compiler {:main "frontend.core"
:asset-path "/js/out"
:output-to "target/cljsbuild/public/js/app.js"
:output-dir "target/cljsbuild/public/js/out"
:source-map true
:optimizations :none
:pretty-print true
:preloads [devtools.preload]
:aot-cache true}}
{:id "min"
:source-paths ["src"]
:compiler {:output-to "target/cljsbuild/public/js/app.js"
:output-dir "target/cljsbuild/public/js"
:source-map "target/cljsbuild/public/js/app.js.map"
:optimizations :advanced
:pretty-print false
:aot-cache true}}]}
:figwheel {:http-server-root "public"
:server-port 3449
:nrepl-port 7002
;; Server index.html for all routes for HTML5 routing
:ring-handler backend.server/handler})

View file

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<title>Reitit frontend example</title>
</head>
<body>
<div id="app"></div>
<script src="/js/app.js"></script>
</body>
</html>

View file

@ -0,0 +1,10 @@
(ns backend.server
(:require [ring.util.response :as resp]
[ring.middleware.content-type :as content-type]))
(def handler
(-> (fn [request]
(or (resp/resource-response (:uri request) {:root "public"})
(-> (resp/resource-response "index.html" {:root "public"})
(resp/content-type "text/html"))))
content-type/wrap-content-type))

View file

@ -0,0 +1,84 @@
(ns frontend.core
(:require [reagent.core :as r]
[reagent.dom :as rd]
[reitit.frontend :as rf]
[reitit.frontend.easy :as rfe]
[reitit.coercion.malli :as rsm]
[fipp.edn :as fedn]))
(defn home-page []
[:div
[:h2 "Welcome to frontend"]
[:button
{:type "button"
:on-click #(rfe/push-state ::item {:id 3})}
"Item 3"]
[:button
{:type "button"
:on-click #(rfe/replace-state ::item {:id 4})}
"Replace State Item 4"]])
(defn about-page []
[:div
[:h2 "About frontend"]
[:ul
[:li [:a {:href "http://google.com"} "external link"]]
[:li [:a {:href (rfe/href ::foobar)} "Missing route"]]
[:li [:a {:href (rfe/href ::item)} "Missing route params"]]]
[:div
{:content-editable true
:suppressContentEditableWarning true}
[:p "Link inside contentEditable element is ignored."]
[:a {:href (rfe/href ::frontpage)} "Link"]]])
(defn item-page [match]
(let [{:keys [path query]} (:parameters match)
{:keys [id]} path]
[:div
[:h2 "Selected item " id]
(if (:foo query)
[:p "Optional foo query param: " (:foo query)])]))
(defonce match (r/atom nil))
(defn current-page []
[:div
[:ul
[:li [:a {:href (rfe/href ::frontpage)} "Frontpage"]]
[:li [:a {:href (rfe/href ::about)} "About"]]
[:li [:a {:href (rfe/href ::item {:id 1})} "Item 1"]]
[:li [:a {:href (rfe/href ::item {:id 2} {:foo "bar"})} "Item 2"]]]
(if @match
(let [view (:view (:data @match))]
[view @match]))
[:pre (with-out-str (fedn/pprint @match))]])
(def routes
[["/"
{:name ::frontpage
:view home-page}]
["/about"
{:name ::about
:view about-page}]
["/item/:id"
{:name ::item
:view item-page
:parameters {:path [:map
[:id :int]]
:query [:map
[:foo {:optional true} :keyword]]}}]])
(defn init! []
(rfe/start!
(rf/router routes {:data {:coercion rsm/coercion}})
(fn [m] (reset! match m))
;; set to false to enable HistoryAPI
{:use-fragment true})
(rd/render [current-page] (.getElementById js/document "app")))
(init!)

View file

@ -1,24 +1,27 @@
(defproject frontend "0.1.0-SNAPSHOT"
(defproject frontend-prompt "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.10.0"]
:dependencies [[org.clojure/clojure "1.11.2"]
[ring-server "0.5.0"]
[reagent "0.8.1"]
[ring "1.7.1"]
[reagent "1.2.0"]
[ring "1.12.1"]
[hiccup "1.0.5"]
[org.clojure/clojurescript "1.10.520"]
[metosin/reitit "0.7.0-alpha7"]
[metosin/reitit-spec "0.7.0-alpha7"]
[metosin/reitit-frontend "0.7.0-alpha7"]
[org.clojure/clojurescript "1.11.132"]
[metosin/reitit "0.9.2"]
[metosin/reitit-spec "0.9.2"]
[metosin/reitit-frontend "0.9.2"]
[cljsjs/react "17.0.2-0"]
[cljsjs/react-dom "17.0.2-0"]
;; Just for pretty printting the match
[fipp "0.6.14"]]
[fipp "0.6.23"]]
:plugins [[lein-cljsbuild "1.1.8"]
[lein-figwheel "0.5.20"]
[cider/cider-nrepl "0.47.1"]]
:plugins [[lein-cljsbuild "1.1.7"]
[lein-figwheel "0.5.18"]
[cider/cider-nrepl "0.21.1"]]
:repl-options {:nrepl-middleware [cider.piggieback/wrap-cljs-repl]}

View file

@ -1,6 +1,7 @@
(ns frontend.core
(:require [fipp.edn :as fedn]
[reagent.core :as r]
[reagent.dom :as rd]
[reitit.coercion.spec :as rss]
[reitit.frontend :as rf]
[reitit.frontend.easy :as rfe]))
@ -63,6 +64,6 @@
on-navigate
;; set to false to enable HistoryAPI
{:use-fragment true})
(r/render [current-page] (.getElementById js/document "app")))
(rd/render [current-page] (.getElementById js/document "app")))
(init!)

View file

@ -1,13 +1,15 @@
(defproject frontend-re-frame "0.1.0-SNAPSHOT"
:dependencies [[org.clojure/clojure "1.10.0"]
[org.clojure/clojurescript "1.10.520"]
[metosin/reitit "0.7.0-alpha7"]
[reagent "0.8.1"]
[re-frame "0.10.6"]]
:dependencies [[org.clojure/clojure "1.11.2"]
[org.clojure/clojurescript "1.11.132"]
[metosin/reitit "0.9.2"]
[reagent "1.2.0"]
[re-frame "0.10.6"]
[cljsjs/react "17.0.2-0"]
[cljsjs/react-dom "17.0.2-0"]]
:plugins [[lein-cljsbuild "1.1.7"]
[lein-figwheel "0.5.18"]
[cider/cider-nrepl "0.21.1"]]
:plugins [[lein-cljsbuild "1.1.8"]
[lein-figwheel "0.5.20"]
[cider/cider-nrepl "0.47.1"]]
:repl-options {:nrepl-middleware [cider.piggieback/wrap-cljs-repl]}
:min-lein-version "2.5.3"

View file

@ -1,6 +1,7 @@
(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]
[reitit.frontend :as rf]
@ -143,7 +144,7 @@
(re-frame/dispatch-sync [::initialize-db])
(dev-setup)
(init-routes!) ;; Reset routes on figwheel reload
(reagent/render [router-component {:router router}]
(rd/render [router-component {:router router}]
(.getElementById js/document "app")))
(init)

View file

@ -4,15 +4,17 @@
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.10.1"]
:dependencies [[org.clojure/clojure "1.11.2"]
[ring-server "0.5.0"]
[reagent "0.10.0"]
[ring "1.8.1"]
[reagent "1.2.0"]
[ring "1.12.1"]
[hiccup "1.0.5"]
[org.clojure/clojurescript "1.10.773"]
[metosin/reitit "0.7.0-alpha7"]
[metosin/reitit-spec "0.7.0-alpha7"]
[metosin/reitit-frontend "0.7.0-alpha7"]
[org.clojure/clojurescript "1.11.132"]
[metosin/reitit "0.9.2"]
[metosin/reitit-spec "0.9.2"]
[metosin/reitit-frontend "0.9.2"]
[cljsjs/react "17.0.2-0"]
[cljsjs/react-dom "17.0.2-0"]
;; Just for pretty printting the match
[fipp "0.6.23"]]

View file

@ -1,5 +1,6 @@
(ns frontend.core
(:require [reagent.core :as r]
[reagent.dom :as rd]
[reitit.frontend :as rf]
[reitit.frontend.easy :as rfe]
[reitit.coercion.spec :as rss]
@ -77,6 +78,6 @@
(fn [m] (reset! match m))
;; set to false to enable HistoryAPI
{:use-fragment true})
(r/render [current-page] (.getElementById js/document "app")))
(rd/render [current-page] (.getElementById js/document "app")))
(init!)

View file

@ -1,8 +1,8 @@
(defproject ring-example "0.1.0-SNAPSHOT"
(defproject http-swagger "0.1.0-SNAPSHOT"
:description "Reitit Http App with Swagger"
:dependencies [[org.clojure/clojure "1.11.1"]
[ring/ring-jetty-adapter "1.7.1"]
[aleph "0.4.7-alpha5"]
[metosin/reitit "0.7.0-alpha7"]
:dependencies [[org.clojure/clojure "1.11.2"]
[ring/ring-jetty-adapter "1.12.1"]
[aleph "0.7.1"]
[metosin/reitit "0.9.2"]
[metosin/ring-swagger-ui "5.9.0"]]
:repl-options {:init-ns example.server})

View file

@ -1,9 +1,9 @@
(defproject ring-example "0.1.0-SNAPSHOT"
(defproject http "0.1.0-SNAPSHOT"
:description "Reitit Ring App with Swagger"
:dependencies [[org.clojure/clojure "1.10.0"]
[org.clojure/core.async "0.4.490"]
[funcool/promesa "1.9.0"]
[manifold "0.1.8"]
[ring/ring-jetty-adapter "1.7.1"]
[metosin/reitit "0.7.0-alpha7"]]
:dependencies [[org.clojure/clojure "1.11.2"]
[org.clojure/core.async "1.6.681"]
[funcool/promesa "11.0.678"]
[manifold "0.4.2"]
[ring/ring-jetty-adapter "1.12.1"]
[metosin/reitit "0.9.2"]]
:repl-options {:init-ns example.server})

View file

@ -1,5 +1,5 @@
(defproject just-coercion-with-ring "0.1.0-SNAPSHOT"
:description "Reitit coercion with vanilla ring"
:dependencies [[org.clojure/clojure "1.10.0"]
[ring/ring-jetty-adapter "1.7.1"]
[metosin/reitit "0.7.0-alpha7"]])
:dependencies [[org.clojure/clojure "1.11.2"]
[ring/ring-jetty-adapter "1.12.1"]
[metosin/reitit "0.9.2"]])

View file

@ -1,9 +1,9 @@
(defproject openapi "0.1.0-SNAPSHOT"
:description "Reitit OpenAPI example"
:dependencies [[org.clojure/clojure "1.10.0"]
[metosin/jsonista "0.2.6"]
[ring/ring-jetty-adapter "1.7.1"]
[metosin/reitit "0.7.0-alpha7"]
:dependencies [[org.clojure/clojure "1.11.2"]
[metosin/jsonista "0.3.8"]
[ring/ring-jetty-adapter "1.12.1"]
[metosin/reitit "0.9.2"]
[metosin/ring-swagger-ui "5.9.0"]]
:repl-options {:init-ns example.server}
:profiles {:dev {:dependencies [[ring/ring-mock "0.3.2"]]}})
:profiles {:dev {:dependencies [[ring/ring-mock "0.4.0"]]}})

View file

@ -12,8 +12,28 @@
[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
[:map
[:amount :double]
[:from :string]])
(def AccountId
[:map
[:bank :string]
[:id :string]])
(def Account
[:map
[:bank :string]
[:id :string]
[:balance :double]
[:transactions [:vector #'Transaction]]])
(def app
(ring/ring-handler
(ring/router
@ -30,47 +50,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})}}}}}
;; Need to list content types explicitly because we use :default in :responses
:openapi/response-content-types ["application/json" "application/edn"]
: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"
@ -81,6 +114,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?]]}
@ -91,8 +128,51 @@
[:email {:json-schema/example "heidi@alps.ch"}
string?]]]}}}}
:handler (fn [_request]
[{:name "Heidi"
:email "heidi@alps.ch"}])}}]
{:status 200
:body [{:name "Heidi"
:email "heidi@alps.ch"}]})}}]
["/account"
{: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"
:id "0001"
:balance 13.5
:transactions [{:from "0002"
:amount 20.0}
{: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]}}}}
:handler (fn [request]
{:status 200
:body (:body request)})}}]
["/secure"
{:tags #{"secure"}

View file

@ -1,9 +1,9 @@
(defproject pedestal-malli-swagger-example "0.1.0-SNAPSHOT"
:description "Reitit-http with pedestal"
:dependencies [[org.clojure/clojure "1.11.1"]
[io.pedestal/pedestal.service "0.5.5"]
[io.pedestal/pedestal.jetty "0.5.5"]
[metosin/reitit-malli "0.7.0-alpha7"]
[metosin/reitit-pedestal "0.7.0-alpha7"]
[metosin/reitit "0.7.0-alpha7"]]
: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.9.2"]
[metosin/reitit-pedestal "0.9.2"]
[metosin/reitit "0.9.2"]]
:repl-options {:init-ns server})

View file

@ -1,4 +1,4 @@
(ns example.server
(ns server
(:require [clojure.java.io :as io]
[io.pedestal.http.route]
[reitit.interceptor]

View file

@ -1,8 +1,8 @@
(defproject ring-example "0.1.0-SNAPSHOT"
:description "Reitit-http with pedestal"
:dependencies [[org.clojure/clojure "1.11.1"]
[io.pedestal/pedestal.service "0.5.5"]
[io.pedestal/pedestal.jetty "0.5.5"]
[metosin/reitit-pedestal "0.7.0-alpha7"]
[metosin/reitit "0.7.0-alpha7"]]
: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.9.2"]
[metosin/reitit "0.9.2"]]
:repl-options {:init-ns example.server})

View file

@ -1,8 +1,8 @@
(defproject ring-example "0.1.0-SNAPSHOT"
(defproject pedestal-example "0.1.0-SNAPSHOT"
:description "Reitit-http with pedestal"
:dependencies [[org.clojure/clojure "1.10.0"]
[io.pedestal/pedestal.service "0.5.5"]
[io.pedestal/pedestal.jetty "0.5.5"]
[metosin/reitit-pedestal "0.7.0-alpha7"]
[metosin/reitit "0.7.0-alpha7"]]
: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.9.2"]
[metosin/reitit "0.9.2"]]
:repl-options {:init-ns example.server})

View file

@ -1,6 +1,6 @@
(defproject ring-example "0.1.0-SNAPSHOT"
:description "Reitit Ring App"
:dependencies [[org.clojure/clojure "1.10.0"]
[ring/ring-jetty-adapter "1.7.1"]
[metosin/reitit "0.7.0-alpha7"]]
:dependencies [[org.clojure/clojure "1.11.2"]
[ring/ring-jetty-adapter "1.12.1"]
[metosin/reitit "0.9.2"]]
:repl-options {:init-ns example.server})

View file

@ -1,10 +1,10 @@
(defproject ring-integrant-example "0.1.0-SNAPSHOT"
:description "Reitit Ring App with Integrant"
:dependencies [[org.clojure/clojure "1.10.1"]
[ring/ring-jetty-adapter "1.7.1"]
[metosin/reitit "0.7.0-alpha7"]
[integrant "0.7.0"]]
:dependencies [[org.clojure/clojure "1.11.2"]
[ring/ring-jetty-adapter "1.12.1"]
[metosin/reitit "0.9.2"]
[integrant "0.8.1"]]
:main example.server
:repl-options {:init-ns user}
:profiles {:dev {:dependencies [[integrant/repl "0.3.1"]]
:profiles {:dev {:dependencies [[integrant/repl "0.3.3"]]
:source-paths ["dev"]}})

View file

@ -1,8 +1,8 @@
(defproject ring-example "0.1.0-SNAPSHOT"
(defproject ring-malli-lite-swagger "0.1.0-SNAPSHOT"
:description "Reitit Ring App with Swagger"
:dependencies [[org.clojure/clojure "1.11.1"]
[metosin/jsonista "0.2.6"]
[ring/ring-jetty-adapter "1.7.1"]
[metosin/reitit "0.7.0-alpha7"]]
:dependencies [[org.clojure/clojure "1.11.2"]
[metosin/jsonista "0.3.8"]
[ring/ring-jetty-adapter "1.12.1"]
[metosin/reitit "0.9.2"]]
:repl-options {:init-ns example.server}
:profiles {:dev {:dependencies [[ring/ring-mock "0.3.2"]]}})
:profiles {:dev {:dependencies [[ring/ring-mock "0.4.0"]]}})

View file

@ -83,7 +83,7 @@
:error-keys #{#_:type :coercion :in :schema :value :errors :humanized #_:transformed}
;; schema identity function (default: close all map schemas)
:compile mu/closed-schema
;; strip-extra-keys (effects only predefined transformers)
;; strip-extra-keys (affects only predefined transformers)
:strip-extra-keys true
;; add/set default values
:default-values true

View file

@ -1,9 +1,9 @@
(defproject ring-example "0.1.0-SNAPSHOT"
(defproject ring-malli-swagger "0.1.0-SNAPSHOT"
:description "Reitit Ring App with Swagger"
:dependencies [[org.clojure/clojure "1.11.1"]
[metosin/jsonista "0.2.6"]
[ring/ring-jetty-adapter "1.7.1"]
[metosin/reitit "0.7.0-alpha7"]
:dependencies [[org.clojure/clojure "1.11.2"]
[metosin/jsonista "0.3.8"]
[ring/ring-jetty-adapter "1.12.1"]
[metosin/reitit "0.9.2"]
[metosin/ring-swagger-ui "5.9.0"]]
:repl-options {:init-ns example.server}
:profiles {:dev {:dependencies [[ring/ring-mock "0.3.2"]]}})
:profiles {:dev {:dependencies [[ring/ring-mock "0.4.0"]]}})

View file

@ -123,7 +123,7 @@
:error-keys #{#_:type :coercion :in :schema :value :errors :humanized #_:transformed}
;; schema identity function (default: close all map schemas)
:compile mu/closed-schema
;; strip-extra-keys (effects only predefined transformers)
;; strip-extra-keys (affects only predefined transformers)
:strip-extra-keys true
;; add/set default values
:default-values true

View file

@ -1,8 +1,8 @@
(defproject ring-example "0.1.0-SNAPSHOT"
:description "Reitit Ring App with Swagger"
:dependencies [[org.clojure/clojure "1.11.1"]
[ring/ring-jetty-adapter "1.7.1"]
[metosin/reitit "0.7.0-alpha7"]
:dependencies [[org.clojure/clojure "1.11.2"]
[ring/ring-jetty-adapter "1.12.1"]
[metosin/reitit "0.9.2"]
[metosin/ring-swagger-ui "5.9.0"]]
:repl-options {:init-ns example.server}
:profiles {:dev {:dependencies [[ring/ring-mock "0.3.2"]]}})
: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-alpha7"
(defproject metosin/reitit-core "0.9.2"
: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 (str "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

@ -28,6 +28,10 @@
:cljs function)
(expand [this _] {:handler this})
#?(:clj clojure.lang.Var
:cljs cljs.core.Var)
(expand [this _] {:handler this})
nil
(expand [_ _]))
@ -64,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

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

@ -23,23 +23,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 +198,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}))))

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 fn?)
(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

@ -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-alpha7"
(defproject metosin/reitit-dev "0.9.2"
:description "Snappy data-driven router for Clojure(Script)"
:url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License"

View file

@ -1,4 +1,4 @@
(defproject metosin/reitit-frontend "0.7.0-alpha7"
(defproject metosin/reitit-frontend "0.9.2"
: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-alpha7"
(defproject metosin/reitit-http "0.9.2"
:description "Reitit: HTTP routing with interceptors"
:url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License"

View file

@ -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-alpha7"
(defproject metosin/reitit-interceptors "0.9.2"
: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-alpha7"
(defproject metosin/reitit-malli "0.9.2"
: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
;;
@ -97,9 +113,11 @@
:validate true
;; top-level short-circuit to disable request & response coercion
:enabled true
;; strip-extra-keys (effects only predefined transformers)
;; 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))
@ -122,7 +143,15 @@
(-get-options [_] opts)
(-get-model-apidocs [this specification model options]
(case specification
:openapi (json-schema/transform model (merge opts options))
:openapi (if (= :parameter (:type options))
;; 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.
(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
(str "Can't produce Malli apidocs for " specification)
@ -159,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))))
@ -168,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-alpha7"
(defproject metosin/reitit-middleware "0.9.2"
:description "Reitit, common middleware bundled"
:url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License"

View file

@ -19,6 +19,9 @@
(recur (.getSuperclass sk) (conj ks sk))
ks)))
(defn- descendants-safe [type]
(when-not (class? type) (descendants type)))
(defn- call-error-handler [handlers error request]
(let [type (:type (ex-data error))
ex-class (class error)
@ -26,7 +29,7 @@
(get handlers ex-class)
(some
(partial get handlers)
(descendants type))
(descendants-safe type))
(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

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-alpha7"
(defproject fi.metosin/reitit-openapi "0.9.2"
:description "Reitit: OpenAPI-support"
:url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License"
@ -9,4 +9,5 @@
:plugins [[lein-parent "0.3.9"]]
:parent-project {:path "../../project.clj"
:inherit [:deploy-repositories :managed-dependencies]}
:dependencies [[metosin/reitit-core]])
:dependencies [[metosin/reitit-core]
[metosin/muuntaja]])

View file

@ -3,6 +3,7 @@
[clojure.spec.alpha :as s]
[clojure.string :as str]
[meta-merge.core :refer [meta-merge]]
[muuntaja.core :as m]
[reitit.coercion :as coercion]
[reitit.core :as r]
[reitit.trie :as trie]))
@ -75,18 +76,30 @@
(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 parameters responses openapi/request-content-types openapi/response-content-types]
:or {request-content-types ["application/json"]
response-content-types ["application/json"]}}]
(let [{:keys [body multipart]} parameters
parameters (dissoc parameters :request :body :multipart)
[coercion {:keys [request muuntaja parameters responses openapi/request-content-types openapi/response-content-types]} definitions]
(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 #(coercion/-get-model-apidocs coercion :openapi %1 %2)]
->schema-object (fn [model opts]
(let [result (coercion/-get-model-apidocs
coercion :openapi model
(assoc opts :malli.json-schema/definitions-path "#/components/schemas/"))]
(when-let [d (:definitions result)]
(vswap! definitions merge d))
(dissoc result :definitions)))
request-content-types (or request-content-types
(when muuntaja (m/decodes muuntaja))
["application/json"])
response-content-types (or response-content-types
(when muuntaja (m/encodes muuntaja))
["application/json"])]
(merge
(when (seq parameters)
{:parameters
@ -97,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]
@ -111,26 +124,34 @@
[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
{:content (merge
(select-keys request [:description])
(when-let [{:keys [schema] :as data} (coercion/get-default request)]
(into {}
(map (fn [content-type]
(let [schema (->schema-object schema {:in :requestBody
:type :schema
:content-type content-type})]
[content-type (->content data schema)])))
request-content-types))
(into {}
(map (fn [[content-type {:keys [schema] :as data}]]
(let [schema (->schema-object schema {:in :requestBody
:type :schema
:content-type content-type})]
[content-type (->content data schema)])))
(:content request)))}})
(merge
(select-keys request [:description])
{:content (merge
(when-let [{:keys [schema] :as data} (coercion/get-default request)]
(into {}
(map (fn [content-type]
(let [schema (->schema-object schema {:in :requestBody
:type :schema
:content-type content-type})]
[content-type (->content data schema)])))
request-content-types))
(into {}
(map (fn [[content-type {:keys [schema] :as data}]]
(let [schema (->schema-object schema {:in :requestBody
:type :schema
:content-type content-type})]
[content-type (->content data schema)])))
(dissoc (:content request) :default)))})})
(when multipart
{:requestBody
{:content
@ -184,27 +205,32 @@
:x-id ids}))
accept-route (fn [route]
(-> route second :openapi :id (or ::default) (trie/into-set) (set/intersection ids) seq))
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))
(select-keys data [:tags :summary :description])
(strip-top-level-keys openapi))]))
definitions (volatile! {})
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)]
{:status 200
:body (meta-merge openapi {:paths paths})}))
:body (cond-> (meta-merge openapi {:paths paths})
(seq @definitions) (assoc-in [:components :schemas] @definitions))}))
([req res raise]
(try
(res (create-openapi req))
(catch #?(:clj Exception :cljs :default) e
(catch Exception e
(raise e))))))

View file

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

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