mirror of
https://github.com/metosin/reitit.git
synced 2026-02-26 19:02:22 +00:00
Compare commits
838 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c1544c3ce | ||
|
|
0724c0c5a0 | ||
|
|
7051c99e99 | ||
|
|
4c8a69c616 | ||
|
|
e3306e1876 | ||
|
|
e6e1bfd5c4 | ||
|
|
8391fafbe2 | ||
|
|
97bfafa907 | ||
|
|
71a777b4fa | ||
|
|
a6b68cc3d6 | ||
|
|
248200aad3 | ||
|
|
bf18586d75 | ||
|
|
373ea9bb62 | ||
|
|
334a42e03d | ||
|
|
5ac4e65284 | ||
|
|
dbac18546b | ||
|
|
69b23c49b9 | ||
|
|
e1d5789f40 | ||
|
|
d27454efdc | ||
|
|
1d4473e1f4 | ||
|
|
75faf709e2 | ||
|
|
2c87d90bda | ||
|
|
8907dfc5f5 | ||
|
|
63429a2d1e | ||
|
|
8721c7ae37 | ||
|
|
e3180e4d6a | ||
|
|
2597d14125 | ||
|
|
54a040f136 | ||
|
|
e4c53a64e2 | ||
|
|
218f05972e | ||
|
|
e342ac5401 | ||
|
|
e6137cb47a | ||
|
|
3191d9ee59 | ||
|
|
579eb28a50 | ||
|
|
20735730c9 | ||
|
|
aa6c1ac460 | ||
|
|
c113bded4e | ||
|
|
ed6397cd05 | ||
|
|
c3a152a44e | ||
|
|
c0bc789863 | ||
|
|
78aba57d2d | ||
|
|
451b286f1d | ||
|
|
eb06404f1e | ||
|
|
af7313bd9b | ||
|
|
ea58100fec | ||
|
|
a4576cc622 | ||
|
|
9d88d92241 | ||
|
|
d16aac673e | ||
|
|
dede2db213 | ||
|
|
1dc961f661 | ||
|
|
2ce9850de6 | ||
|
|
0bc30e9361 | ||
|
|
9b26d5c0fd | ||
|
|
e671f78741 | ||
|
|
55f8d98bde | ||
|
|
342bae3ffe | ||
|
|
ae52000b29 | ||
|
|
39c5ae86a4 | ||
|
|
7fb9c27e46 | ||
|
|
f4da07c222 | ||
|
|
d25dca19f6 | ||
|
|
1b02662c78 | ||
|
|
36af88b65e | ||
|
|
152c598858 | ||
|
|
6d9632e85e | ||
|
|
5ff8ba2e3e | ||
|
|
c684c83c99 | ||
|
|
defebb0f1f | ||
|
|
54c0935078 | ||
|
|
cde050f964 | ||
|
|
560c6d7969 | ||
|
|
abe95bfc17 | ||
|
|
d2f44b8015 | ||
|
|
ef9dd495be | ||
|
|
9509e40dae | ||
|
|
67918a3f9c | ||
|
|
45951aa82e | ||
|
|
1cdca2e3d5 | ||
|
|
2f22838820 | ||
|
|
d809291553 | ||
|
|
4e572e86d6 | ||
|
|
10700e0ca2 | ||
|
|
3e0f1d3188 | ||
|
|
f26dc1ab19 | ||
|
|
01766ee211 | ||
|
|
79627aea7b | ||
|
|
4d284385ec | ||
|
|
31fa0376cf | ||
|
|
05bc331397 | ||
|
|
7520d20f12 | ||
|
|
210f20e714 | ||
|
|
aad054ef39 | ||
|
|
653db7efa3 | ||
|
|
5025ca3a75 | ||
|
|
55c30af979 | ||
|
|
9ee8e364f3 | ||
|
|
ad9cd31168 | ||
|
|
99267d2943 | ||
|
|
ecf22d5c4a | ||
|
|
1803643f1f | ||
|
|
f247751409 | ||
|
|
48fdb692ef | ||
|
|
b9cef492f8 | ||
|
|
524d0d4d57 | ||
|
|
20c28ecc7c | ||
|
|
3793a7b23b | ||
|
|
ef64fc50b8 | ||
|
|
15987d9952 | ||
|
|
2bf8aa98a7 | ||
|
|
832d9ebe95 | ||
|
|
4b7a596ece | ||
|
|
0454e8914f | ||
|
|
7a77c9f86b | ||
|
|
a8b4bc0d2d | ||
|
|
9db58f21dc | ||
|
|
9797725ae8 | ||
|
|
3c6139950c | ||
|
|
9534f6df8b | ||
|
|
a390180975 | ||
|
|
f038fe1941 | ||
|
|
8058cecae0 | ||
|
|
dd835e73a8 | ||
|
|
78cf477b88 | ||
|
|
2d1d0cc5d2 | ||
|
|
75a5e816a9 | ||
|
|
e16c95c5b3 | ||
|
|
91fa60324f | ||
|
|
6dd9cb1b7c | ||
|
|
fa28489b63 | ||
|
|
9849ed5ebb | ||
|
|
a103411bf7 | ||
|
|
a57662693c | ||
|
|
44d54cc9f2 | ||
|
|
3342e77538 | ||
|
|
8fb44467a0 | ||
|
|
c2feb5b983 | ||
|
|
9fb0b7233a | ||
|
|
89c05932dc | ||
|
|
2f1defe3cd | ||
|
|
f50feff63c | ||
|
|
34e6db0d3f | ||
|
|
04cb70f1f7 | ||
|
|
58195eed68 | ||
|
|
d5d46d5b0b | ||
|
|
f0fc440425 | ||
|
|
30fd739fa9 | ||
|
|
de0e1f4462 | ||
|
|
34b6bb9349 | ||
|
|
3fcd6cfd73 | ||
|
|
e5483cb1fc | ||
|
|
481c653139 | ||
|
|
5ca22193d0 | ||
|
|
4eb29d3ed9 | ||
|
|
dfc5a4ef67 | ||
|
|
7e9116f77e | ||
|
|
7ae118fbb5 | ||
|
|
ce6d9e26cd | ||
|
|
7ae2bfafc2 | ||
|
|
1b37c87aa2 | ||
|
|
f60a7ad902 | ||
|
|
1ba77a7267 | ||
|
|
21e5840f13 | ||
|
|
5f10465533 | ||
|
|
dba8d159cc | ||
|
|
5829e1c656 | ||
|
|
1819fa5d75 | ||
|
|
25dd0abcaf | ||
|
|
a19b6034dd | ||
|
|
0256642f64 | ||
|
|
b1d066246a | ||
|
|
b19dda8d5d | ||
|
|
0a7b50a730 | ||
|
|
bf82533028 | ||
|
|
0370750a3f | ||
|
|
ea88b06206 | ||
|
|
f1e3ed88ea | ||
|
|
ada41ec7dd | ||
|
|
1abff4937c | ||
|
|
cc1cd114e4 | ||
|
|
e86662561f | ||
|
|
059f93aee8 | ||
|
|
fc132f3a92 | ||
|
|
431242c926 | ||
|
|
0f9414847a | ||
|
|
86e04414ba | ||
|
|
c89b6bbe31 | ||
|
|
f6b8054669 | ||
|
|
4f4d05fe65 | ||
|
|
702e7b8972 | ||
|
|
d11deb3473 | ||
|
|
e2c63d6579 | ||
|
|
86871a6a55 | ||
|
|
923bafdc9b | ||
|
|
afc8945d78 | ||
|
|
610586f0d3 | ||
|
|
5a811421db | ||
|
|
c96b22bc5f | ||
|
|
21a967145f | ||
|
|
494aa29cdb | ||
|
|
02d4f797ca | ||
|
|
0158dbb54b | ||
|
|
4aff73305f | ||
|
|
f5bb9ce70e | ||
|
|
337e6c39c7 | ||
|
|
3136948453 | ||
|
|
c155ee7160 | ||
|
|
ff99ab3ff9 | ||
|
|
a40d9893fb | ||
|
|
ed78552a72 | ||
|
|
dd1d507d90 | ||
|
|
2ac6ee84df | ||
|
|
400a2be0fe | ||
|
|
65ff2ee22d | ||
|
|
f1ec7bbe8e | ||
|
|
f466271e15 | ||
|
|
d926ef7591 | ||
|
|
c9848bd6e4 | ||
|
|
8b0c8a3c18 | ||
|
|
bffe360c6d | ||
|
|
734fca7d4a | ||
|
|
5a2ae56991 | ||
|
|
d8a8bce272 | ||
|
|
517ae8ffc3 | ||
|
|
78cc54d3a8 | ||
|
|
c94ecf5ca7 | ||
|
|
4eab67a8db | ||
|
|
7dfc0e5fca | ||
|
|
61783e4c81 | ||
|
|
59642e51f1 | ||
|
|
dcb7258caf | ||
|
|
7ab6021630 | ||
|
|
c48b6a3704 | ||
|
|
a0467d52cd | ||
|
|
5589328a3c | ||
|
|
af2fc137d5 | ||
|
|
be35375387 | ||
|
|
d88b693e26 | ||
|
|
e8c3035254 | ||
|
|
aec024a943 | ||
|
|
49e8d887da | ||
|
|
ee67a746d4 | ||
|
|
72dadb3c76 | ||
|
|
e0cc8b4cd8 | ||
|
|
c792a6b794 | ||
|
|
129de37d3e | ||
|
|
77f0798c06 | ||
|
|
eb4c231c23 | ||
|
|
2da94f733d | ||
|
|
d136975154 | ||
|
|
411bf39c7d | ||
|
|
2f3fc21c84 | ||
|
|
4e85cb14c5 | ||
|
|
2da5d8bb7d | ||
|
|
4d4307ef57 | ||
|
|
fbfd7ad5f3 | ||
|
|
509b4dd232 | ||
|
|
877c45af90 | ||
|
|
bed41fa46c | ||
|
|
2be0dbbb2a | ||
|
|
710eb69e8c | ||
|
|
275b789ca9 | ||
|
|
18550cd297 | ||
|
|
15790f3028 | ||
|
|
3296323059 | ||
|
|
037763561e | ||
|
|
c84433352e | ||
|
|
c8c8c0eb03 | ||
|
|
a06b2c98a7 | ||
|
|
3cb387747b | ||
|
|
57fc00a45e | ||
|
|
ce52b26329 | ||
|
|
337d94823a | ||
|
|
288b701d4e | ||
|
|
c2372473d0 | ||
|
|
066c54b1d2 | ||
|
|
7432ef9bb9 | ||
|
|
fa24dcd29a | ||
|
|
bbaab0b8f8 | ||
|
|
81b9464f68 | ||
|
|
1b7fc0fc58 | ||
|
|
13e8dd86e5 | ||
|
|
06c0fd8566 | ||
|
|
cd6c23823c | ||
|
|
ff76f5d888 | ||
|
|
cc9863c95f | ||
|
|
59df1f995b | ||
|
|
f41d555b62 | ||
|
|
a69cfdac41 | ||
|
|
b6c5b69ffe | ||
|
|
8ce2de3631 | ||
|
|
ff957661e5 | ||
|
|
01b476b342 | ||
|
|
fbec1e2ecc | ||
|
|
00b5487cc0 | ||
|
|
fb2f4b2ee9 | ||
|
|
c67a748915 | ||
|
|
d24b501281 | ||
|
|
5d30a73bad | ||
|
|
659e96e780 | ||
|
|
2fe448c3d8 | ||
|
|
c295e645c5 | ||
|
|
ca434f9c05 | ||
|
|
0e8d635e44 | ||
|
|
6c9b280fa2 | ||
|
|
cb1c5e8748 | ||
|
|
e7be6327d4 | ||
|
|
d2c00026e6 | ||
|
|
ed280f9a33 | ||
|
|
f1e6d37dcf | ||
|
|
98f3eb0a72 | ||
|
|
82c714d0cc | ||
|
|
982ac3ec72 | ||
|
|
f99a76886e | ||
|
|
989ab72a58 | ||
|
|
5444bad439 | ||
|
|
7e00de835d | ||
|
|
620d0c2711 | ||
|
|
8d2d295a60 | ||
|
|
0fff06ec6b | ||
|
|
f4a8013388 | ||
|
|
15e0c95cb6 | ||
|
|
53327b3147 | ||
|
|
92a746f803 | ||
|
|
d4a85c40e6 | ||
|
|
8115fb225f | ||
|
|
8eb3b75a80 | ||
|
|
bdadb0fa8d | ||
|
|
90f3708e16 | ||
|
|
67a6658754 | ||
|
|
c1a82ad8ab | ||
|
|
6a39dcd6ec | ||
|
|
2778775565 | ||
|
|
8a22696a37 | ||
|
|
f0f19c2dae | ||
|
|
35264dc130 | ||
|
|
ccc2b5636e | ||
|
|
6360fa8ba0 | ||
|
|
bad798d90d | ||
|
|
b4c0936207 | ||
|
|
aee0caa5c8 | ||
|
|
b0b9f8cbee | ||
|
|
557c89acdd | ||
|
|
5352fd4f99 | ||
|
|
b206fc79b3 | ||
|
|
721cf7f321 | ||
|
|
abd84de68d | ||
|
|
7352358662 | ||
|
|
68adf50362 | ||
|
|
f2e6d335f0 | ||
|
|
ee462c9981 | ||
|
|
497da675b9 | ||
|
|
df5f75eb89 | ||
|
|
aef74c6e6a | ||
|
|
68371c2c05 | ||
|
|
cdbfef2d21 | ||
|
|
241c8367e3 | ||
|
|
b316840ea0 | ||
|
|
f1d26791fc | ||
|
|
5d0bce1242 | ||
|
|
0323409cd5 | ||
|
|
7b41882e6c | ||
|
|
38547e4ad2 | ||
|
|
c6541de1b5 | ||
|
|
76a08a2322 | ||
|
|
85ebb343ed | ||
|
|
d5021e549a | ||
|
|
4d1d469686 | ||
|
|
25aee5ed22 | ||
|
|
7b88125f5e | ||
|
|
55854f6652 | ||
|
|
e4c75c7354 | ||
|
|
7b4127b0f1 | ||
|
|
8af89c05cb | ||
|
|
233ac19914 | ||
|
|
6f111bce2e | ||
|
|
051452231a | ||
|
|
f943b025cb | ||
|
|
ee298ec362 | ||
|
|
803ed0933a | ||
|
|
b0c810a981 | ||
|
|
05cbed815f | ||
|
|
adef7ad06e | ||
|
|
226ca889b6 | ||
|
|
d8e9819e0a | ||
|
|
81dfe45b72 | ||
|
|
12f0970e39 | ||
|
|
73422e8da0 | ||
|
|
60719f03c7 | ||
|
|
5acb1a7ffe | ||
|
|
e93f365a0b | ||
|
|
4a182588b4 | ||
|
|
5b7b0a7b4e | ||
|
|
a032abc910 | ||
|
|
cf5906030a | ||
|
|
ac410507f2 | ||
|
|
2db9ee328c | ||
|
|
0728154751 | ||
|
|
b1404ada6d | ||
|
|
93a4246682 | ||
|
|
1b5287724e | ||
|
|
d17c97780e | ||
|
|
499f84be21 | ||
|
|
77e2b567e6 | ||
|
|
9f58bb22e3 | ||
|
|
3e6c3f589f | ||
|
|
9ac713f0e5 | ||
|
|
ce06214014 | ||
|
|
3f265888a4 | ||
|
|
550ea6da58 | ||
|
|
4d0e40f135 | ||
|
|
d45dd151b7 | ||
|
|
1827c1294b | ||
|
|
b3383b0396 | ||
|
|
2e555a1453 | ||
|
|
86af5d8724 | ||
|
|
1983a4bb64 | ||
|
|
45f9f0f21e | ||
|
|
6b378ffbf5 | ||
|
|
c443adbfca | ||
|
|
b56c15b64c | ||
|
|
2e8e9265d9 | ||
|
|
4ac973ba31 | ||
|
|
88d7caf013 | ||
|
|
ed1230d1cf | ||
|
|
59812a350f | ||
|
|
0b6ed62738 | ||
|
|
5adc5ffba0 | ||
|
|
e1f9cfb286 | ||
|
|
e204d4ff1b | ||
|
|
9f6565f097 | ||
|
|
b9f189b3f7 | ||
|
|
526bac39c2 | ||
|
|
253f379fd0 | ||
|
|
f14808b8a3 | ||
|
|
28e9cc01e5 | ||
|
|
bafc9b757f | ||
|
|
bcbdd05a6f | ||
|
|
8db7598141 | ||
|
|
f015b6669a | ||
|
|
3336880b01 | ||
|
|
a3ab5714cb | ||
|
|
24669cf58f | ||
|
|
937768651e | ||
|
|
6d9d7a09b0 | ||
|
|
be0d066f5d | ||
|
|
5227e65029 | ||
|
|
ca9852a318 | ||
|
|
75ebeaf6cd | ||
|
|
182524baac | ||
|
|
f237b0942e | ||
|
|
0c643409a6 | ||
|
|
1a73ba952e | ||
|
|
4b33c6d203 | ||
|
|
3e78667244 | ||
|
|
33f7b1ccd7 | ||
|
|
6e7acaedee | ||
|
|
8174296fe7 | ||
|
|
56e6dbe8d8 | ||
|
|
2a3e382df1 | ||
|
|
e2217887e3 | ||
|
|
dad8f530a6 | ||
|
|
48bbdba8ed | ||
|
|
f78116e346 | ||
|
|
a558365252 | ||
|
|
e3e93eaffb | ||
|
|
dd724f0d0e | ||
|
|
ea3d031acc | ||
|
|
fc12af4ea8 | ||
|
|
ade24cebc9 | ||
|
|
76b5dfa1e0 | ||
|
|
f1890048e1 | ||
|
|
74a56800e2 | ||
|
|
6a6492b547 | ||
|
|
2818378b11 | ||
|
|
0f9b624124 | ||
|
|
bae6e6b8dd | ||
|
|
8272b651e3 | ||
|
|
d8e28e153b | ||
|
|
389f4a29da | ||
|
|
9a99ed96b2 | ||
|
|
bf8d0ba1ef | ||
|
|
6c3db02163 | ||
|
|
224acf930e | ||
|
|
b5c9ee274d | ||
|
|
83b747c7c6 | ||
|
|
de2d810b7c | ||
|
|
acbcec1ed9 | ||
|
|
60f53d7e7c | ||
|
|
1c65f533cf | ||
|
|
f322597c04 | ||
|
|
8bf4b5c6a6 | ||
|
|
d0ff64df57 | ||
|
|
bcd12d9f31 | ||
|
|
41c4d78823 | ||
|
|
5107658c25 | ||
|
|
66e7c9e1c8 | ||
|
|
0c82ce0e4d | ||
|
|
6f72acdf32 | ||
|
|
23b2719be5 | ||
|
|
25b75c877a | ||
|
|
814c8b88e2 | ||
|
|
bbaf4b27f7 | ||
|
|
11f47527f1 | ||
|
|
9b50baca0c | ||
|
|
20cafa3d9b | ||
|
|
7defd98808 | ||
|
|
304b77cb7d | ||
|
|
3fa50ea0f6 | ||
|
|
8c87fef7b6 | ||
|
|
ae55b6628c | ||
|
|
2a789557f2 | ||
|
|
50c1af9a5b | ||
|
|
9ae3cd0824 | ||
|
|
8df8bf06cc | ||
|
|
52b7402575 | ||
|
|
2cc6e33654 | ||
|
|
16145dbdce | ||
|
|
11534551da | ||
|
|
4f31304a1a | ||
|
|
229171c7e3 | ||
|
|
8e099febdd | ||
|
|
2596f25411 | ||
|
|
df0d4c4935 | ||
|
|
4c990fb44f | ||
|
|
c3a3ca9f95 | ||
|
|
7842160656 | ||
|
|
2d60702769 | ||
|
|
b149c8c5af | ||
|
|
c8d679c6b3 | ||
|
|
8f48cdc96c | ||
|
|
aec611f8b7 | ||
|
|
ae138c6dec | ||
|
|
47f1ee0c84 | ||
|
|
310dcd0e99 | ||
|
|
e490f5df05 | ||
|
|
2494f702d9 | ||
|
|
83c31e35bc | ||
|
|
f03134e215 | ||
|
|
b6c046318b | ||
|
|
e175dc76c9 | ||
|
|
999f6c3dbd | ||
|
|
d1bb44a88f | ||
|
|
0648296315 | ||
|
|
e5bd123740 | ||
|
|
744326d6af | ||
|
|
24b2808886 | ||
|
|
3fd20f2294 | ||
|
|
e75c833fe1 | ||
|
|
358c447698 | ||
|
|
d0a9fd196b | ||
|
|
8e1e7e62ca | ||
|
|
1247e7ff25 | ||
|
|
6a396d1315 | ||
|
|
a290b22679 | ||
|
|
034dc9cfbd | ||
|
|
d1e02fd0a1 | ||
|
|
f27c2fc2aa | ||
|
|
98a4d9b447 | ||
|
|
3ec5acc7a1 | ||
|
|
22fb9810a7 | ||
|
|
0870b20a05 | ||
|
|
4d1b00edfa | ||
|
|
c2c267f4d8 | ||
|
|
8087522b82 | ||
|
|
42e988e518 | ||
|
|
23c20dc44e | ||
|
|
af441e6154 | ||
|
|
26a581298a | ||
|
|
8654bf3277 | ||
|
|
b918c5f3ba | ||
|
|
ffe8846de1 | ||
|
|
7e05f4931e | ||
|
|
f449bf848d | ||
|
|
24f38e0dfa | ||
|
|
8398c98595 | ||
|
|
bc4443a935 | ||
|
|
1dbca52002 | ||
|
|
544e264ffb | ||
|
|
8e5fd8a9db | ||
|
|
edee97a550 | ||
|
|
3d5e0b154c | ||
|
|
eebc2a3df0 | ||
|
|
148fa2167b | ||
|
|
e84495585c | ||
|
|
c69b4cde3a | ||
|
|
c576b47634 | ||
|
|
fc73d02e01 | ||
|
|
4e14b1f05e | ||
|
|
25a051b003 | ||
|
|
aeab5b96a6 | ||
|
|
3dff4c84aa | ||
|
|
38d3fb0dda | ||
|
|
4e40d3e2c9 | ||
|
|
e6bd4f2e57 | ||
|
|
37d9d490ca | ||
|
|
98944a2b50 | ||
|
|
b12c433652 | ||
|
|
40bf16857f | ||
|
|
b282e32b73 | ||
|
|
1ecfd1ae02 | ||
|
|
9e4b420fc8 | ||
|
|
ea38b60b28 | ||
|
|
053ac5b961 | ||
|
|
54ae50525c | ||
|
|
43d6f52208 | ||
|
|
f0405adc02 | ||
|
|
ae73d031b9 | ||
|
|
650ff3d6b3 | ||
|
|
b0602d60c9 | ||
|
|
f9841363c5 | ||
|
|
1e5fb601da | ||
|
|
7033abc530 | ||
|
|
acfc48faa1 | ||
|
|
382661a6c4 | ||
|
|
a2150436d3 | ||
|
|
20b37b7d43 | ||
|
|
0ac3a14805 | ||
|
|
0a872d8f3a | ||
|
|
2f31f658cc | ||
|
|
198cfda00d | ||
|
|
f3dee769fb | ||
|
|
2aba5610c7 | ||
|
|
45fbe5eaa2 | ||
|
|
40eb09b2a9 | ||
|
|
d0f7126491 | ||
|
|
88170bc495 | ||
|
|
060e593414 | ||
|
|
bdcb1eb5b1 | ||
|
|
5d4c886d35 | ||
|
|
b7cc420fde | ||
|
|
07c39fc2df | ||
|
|
5973bc0f26 | ||
|
|
ad90a2788e | ||
|
|
673f6dec72 | ||
|
|
fce79e903b | ||
|
|
93e288bf34 | ||
|
|
53c2e4ed6b | ||
|
|
5bd4aec775 | ||
|
|
22461f638e | ||
|
|
9eded42ba1 | ||
|
|
a7f3cd15ab | ||
|
|
f537be56b7 | ||
|
|
153eaaf2b0 | ||
|
|
59f4bf285a | ||
|
|
9f798c63c9 | ||
|
|
143bf06290 | ||
|
|
c0ff024b05 | ||
|
|
6c835e6e09 | ||
|
|
f3a686026f | ||
|
|
751ba17b75 | ||
|
|
04225b0eff | ||
|
|
d0f1abe115 | ||
|
|
0223fbc091 | ||
|
|
9160aa0f2b | ||
|
|
1737e19214 | ||
|
|
a83285f39d | ||
|
|
ef0aeaa7c4 | ||
|
|
ac68f0f726 | ||
|
|
8694d312f8 | ||
|
|
db84daca95 | ||
|
|
f8a8930375 | ||
|
|
10747acb00 | ||
|
|
fd12b3c6f2 | ||
|
|
2747428dea | ||
|
|
38f2bd4812 | ||
|
|
5486174722 | ||
|
|
57e1d1668d | ||
|
|
020c424b4e | ||
|
|
20b7cabed7 | ||
|
|
33afa9f999 | ||
|
|
7a1cc78a80 | ||
|
|
478ee18a32 | ||
|
|
056c70d269 | ||
|
|
9ba73524f3 | ||
|
|
38ec679207 | ||
|
|
de5bdeba19 | ||
|
|
287a01d4f3 | ||
|
|
6202e802c9 | ||
|
|
1297cfd902 | ||
|
|
f212edfcd6 | ||
|
|
1494641ab8 | ||
|
|
caa571cd66 | ||
|
|
2638041e0c | ||
|
|
51a26ed052 | ||
|
|
1b74373911 | ||
|
|
1b583c1cc2 | ||
|
|
40efc2d9d4 | ||
|
|
522356ba71 | ||
|
|
02004cac8d | ||
|
|
1ab075bd35 | ||
|
|
388f825d4d | ||
|
|
ac9ff806df | ||
|
|
d0e83fed41 | ||
|
|
b8110f1e40 | ||
|
|
722bd8950f | ||
|
|
1e76ea8114 | ||
|
|
26c4ebc645 | ||
|
|
c5bb467402 | ||
|
|
5831f2f4f6 | ||
|
|
ddbbe1a0da | ||
|
|
a3aa5df111 | ||
|
|
c212a7a9fa | ||
|
|
1992198b58 | ||
|
|
8a205002a8 | ||
|
|
349c838de4 | ||
|
|
c033f10e4e | ||
|
|
fb5eb1ff98 | ||
|
|
7520cd425c | ||
|
|
8567550ad9 | ||
|
|
6f2e181335 | ||
|
|
b6d9707b53 | ||
|
|
0c93cfb1f9 | ||
|
|
1f18cbe747 | ||
|
|
39fc17c5fd | ||
|
|
5824d9eeef | ||
|
|
88897a2264 | ||
|
|
07dfb91d96 | ||
|
|
60fee31733 | ||
|
|
ff55f85677 | ||
|
|
902b33f004 | ||
|
|
e64490f473 | ||
|
|
d80f1038f6 | ||
|
|
a94b6db06e | ||
|
|
03f029e536 | ||
|
|
f43a8311fc | ||
|
|
e7cec06b71 | ||
|
|
d478ec1d23 | ||
|
|
36673d4674 | ||
|
|
eb10af92c4 | ||
|
|
496e6b6fc7 | ||
|
|
0ecc2fb9a7 | ||
|
|
8cca1fe40b | ||
|
|
b4099169be | ||
|
|
d663342fbb | ||
|
|
8be1b16cc7 | ||
|
|
12d3dcabec | ||
|
|
7fb720ef36 | ||
|
|
a3b251449b | ||
|
|
c539c53ae2 | ||
|
|
e095cd2efa | ||
|
|
3a6985eb71 | ||
|
|
d1b8e1b98b | ||
|
|
fb771a33bd | ||
|
|
2fc893bf6f | ||
|
|
abfe810700 | ||
|
|
bf3242a6f4 | ||
|
|
e27d5c0213 | ||
|
|
0b2c4d0d8e | ||
|
|
dbf3751815 | ||
|
|
f2f7d3a428 | ||
|
|
ff647f3a2c | ||
|
|
422e97ffed | ||
|
|
c3df762b48 | ||
|
|
c0a76c6648 | ||
|
|
8c3ad99276 | ||
|
|
9ce7e6593c | ||
|
|
b4d1b36c8d | ||
|
|
21df83c804 | ||
|
|
2ed5b48067 | ||
|
|
845240d330 | ||
|
|
b51374fec2 | ||
|
|
d57851c2d3 | ||
|
|
61945adddf | ||
|
|
d7ed4f32af | ||
|
|
15812930a3 | ||
|
|
f037b803b6 | ||
|
|
5825694153 | ||
|
|
39ec264da8 | ||
|
|
c4afca9417 | ||
|
|
58f9871747 | ||
|
|
5651b4648a | ||
|
|
390cdb1e4e | ||
|
|
0c85b3d54b | ||
|
|
dea8894610 | ||
|
|
208eeeb5c5 | ||
|
|
5e290db106 | ||
|
|
b809b6328d | ||
|
|
03c5b04384 | ||
|
|
fbff819909 | ||
|
|
48e0c7f883 | ||
|
|
21fbad22a0 | ||
|
|
f95016ad4e | ||
|
|
755bc131de | ||
|
|
31cee66548 | ||
|
|
2d16c2f172 | ||
|
|
720b1f9524 | ||
|
|
8ae0a4ccd4 | ||
|
|
2f17f09856 | ||
|
|
218cd4be69 | ||
|
|
d2bc642ac6 | ||
|
|
37fc06b081 | ||
|
|
afd0af83be | ||
|
|
612555cdc0 | ||
|
|
aa833ac200 | ||
|
|
486e3928c5 | ||
|
|
9affebbb73 | ||
|
|
9fe64a333f | ||
|
|
11a419d8b9 | ||
|
|
43e1a520d6 | ||
|
|
274d527d2f | ||
|
|
d3c21e3c6c | ||
|
|
807b75f7c0 | ||
|
|
ce635609c7 | ||
|
|
56cbd67abd | ||
|
|
e6e6bd3e27 | ||
|
|
06cc5090d5 | ||
|
|
6703a02baa | ||
|
|
15df0c5005 | ||
|
|
0ffef07cbf | ||
|
|
c1c3cd26b4 | ||
|
|
87d7e2e3c9 | ||
|
|
9ca88d7165 | ||
|
|
0cfe5b4ea5 | ||
|
|
cf45940715 | ||
|
|
3a73ddab51 | ||
|
|
962ffc6fb5 | ||
|
|
2ff255eb89 | ||
|
|
bb4a7502fb | ||
|
|
e30e739a15 | ||
|
|
aa3d38e971 | ||
|
|
da7cef232c | ||
|
|
681c994f7f | ||
|
|
f2e0470ecd | ||
|
|
4e9537cecf | ||
|
|
57da6fa5ad | ||
|
|
e649ed22b9 | ||
|
|
f41006c8bb | ||
|
|
ea5ec93793 | ||
|
|
a2d8208600 | ||
|
|
28962e75df | ||
|
|
9e42140a28 | ||
|
|
e27f2449b9 | ||
|
|
efe07e3d5d | ||
|
|
6e332e46f1 | ||
|
|
3e8eaa48d7 | ||
|
|
50f0120e8c | ||
|
|
9a0395837c | ||
|
|
20a3864482 |
230 changed files with 11927 additions and 6056 deletions
|
|
@ -1,130 +0,0 @@
|
||||||
version: 2.1
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test-clj:
|
|
||||||
working_directory: ~/test
|
|
||||||
parameters:
|
|
||||||
image-name:
|
|
||||||
type: string
|
|
||||||
docker:
|
|
||||||
- image: << parameters.image-name >>
|
|
||||||
steps:
|
|
||||||
- checkout
|
|
||||||
- restore_cache:
|
|
||||||
keys:
|
|
||||||
- 'v1-clj-{{ checksum "project.clj" }}'
|
|
||||||
- 'v1-clj-'
|
|
||||||
- 'v1-test-'
|
|
||||||
- run:
|
|
||||||
name: Install modules
|
|
||||||
command: ./scripts/lein-modules install
|
|
||||||
- run:
|
|
||||||
name: Run tests
|
|
||||||
command: ./scripts/test.sh clj
|
|
||||||
- run:
|
|
||||||
name: Install curl if missing
|
|
||||||
command: apt update && apt install -y curl
|
|
||||||
- run:
|
|
||||||
name: Verify cljdoc.edn
|
|
||||||
command: curl -fsSL https://raw.githubusercontent.com/cljdoc/cljdoc/master/script/verify-cljdoc-edn | bash -s doc/cljdoc.edn
|
|
||||||
- store_test_results:
|
|
||||||
# path must be a directory under which there a subdirectories that
|
|
||||||
# contain the JUnit XML files.
|
|
||||||
path: ~/test/target/results
|
|
||||||
# - run:
|
|
||||||
# name: Run coverage
|
|
||||||
# command: ./scripts/submit-to-coveralls.sh clj
|
|
||||||
- save_cache:
|
|
||||||
key: 'v1-clj-{{ checksum "project.clj" }}'
|
|
||||||
paths:
|
|
||||||
- ~/.m2
|
|
||||||
- ~/.cljs/.aot_cache
|
|
||||||
|
|
||||||
test-cljs:
|
|
||||||
working_directory: ~/test
|
|
||||||
docker:
|
|
||||||
- image: circleci/clojure:lein-2.8.1-node-browsers
|
|
||||||
steps:
|
|
||||||
- checkout
|
|
||||||
- restore_cache:
|
|
||||||
keys:
|
|
||||||
- 'v1-cljs-{{ checksum "project.clj" }}-{{ checksum "package.json" }}'
|
|
||||||
- 'v1-cljs-'
|
|
||||||
- run:
|
|
||||||
name: Install npm dependencies
|
|
||||||
command: npm install
|
|
||||||
- run:
|
|
||||||
name: Install modules
|
|
||||||
command: ./scripts/lein-modules install
|
|
||||||
- run:
|
|
||||||
name: Run tests
|
|
||||||
command: ./scripts/test.sh cljs
|
|
||||||
- store_test_results:
|
|
||||||
path: ~/test/target/results
|
|
||||||
- save_cache:
|
|
||||||
key: 'v1-cljs-{{ checksum "project.clj" }}-{{ checksum "package.json" }}'
|
|
||||||
paths:
|
|
||||||
- ~/.m2
|
|
||||||
- ~/test/node_modules
|
|
||||||
|
|
||||||
build-docs:
|
|
||||||
working_directory: ~/build
|
|
||||||
docker:
|
|
||||||
- image: circleci/node:latest
|
|
||||||
steps:
|
|
||||||
- checkout
|
|
||||||
- run: rm package.json package-lock.json
|
|
||||||
- restore_cache:
|
|
||||||
keys:
|
|
||||||
- 'v1-gitbook-{{ checksum "book.json" }}'
|
|
||||||
- 'v1-gitbook-'
|
|
||||||
- run:
|
|
||||||
name: "Install GitBook"
|
|
||||||
command: npm install gitbook-cli && ./node_modules/.bin/gitbook install
|
|
||||||
- run:
|
|
||||||
name: "Clone gh-pages"
|
|
||||||
command: git clone --branch gh-pages git@github.com:metosin/reitit.git ~/gh-pages
|
|
||||||
- run:
|
|
||||||
name: Build the documentation
|
|
||||||
command: |
|
|
||||||
./node_modules/.bin/gitbook build
|
|
||||||
cp -r _book/* ~/gh-pages/
|
|
||||||
- add_ssh_keys:
|
|
||||||
fingerprints:
|
|
||||||
- "2d:eb:be:af:53:33:36:01:40:61:81:9d:76:84:8e:83"
|
|
||||||
- deploy:
|
|
||||||
name: Upload the documentation
|
|
||||||
command: |
|
|
||||||
cd ~/gh-pages
|
|
||||||
git config user.name "Automatic build"
|
|
||||||
git config user.email "noreply@metosin.fi"
|
|
||||||
git add -A
|
|
||||||
git commit -m "Build book from commit $CIRCLE_SHA1 [skip ci]"
|
|
||||||
git push
|
|
||||||
- save_cache:
|
|
||||||
key: 'v1-gitbook-{{ checksum "book.json" }}'
|
|
||||||
paths:
|
|
||||||
- node_modules
|
|
||||||
|
|
||||||
workflows:
|
|
||||||
version: 2
|
|
||||||
test-and-build-docs:
|
|
||||||
jobs:
|
|
||||||
- test-clj:
|
|
||||||
name: jdk8
|
|
||||||
image-name: clojure:openjdk-8-lein-2.9.1
|
|
||||||
- test-clj:
|
|
||||||
name: jdk11
|
|
||||||
image-name: clojure:openjdk-11-lein-2.9.1
|
|
||||||
- test-clj:
|
|
||||||
name: jdk13
|
|
||||||
image-name: clojure:openjdk-13-lein-2.9.1
|
|
||||||
- test-clj:
|
|
||||||
name: jdk14
|
|
||||||
image-name: clojure:openjdk-14-lein-2.9.1
|
|
||||||
- test-cljs
|
|
||||||
- build-docs:
|
|
||||||
filters:
|
|
||||||
branches:
|
|
||||||
only:
|
|
||||||
- master
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
{;;:skip-comments true
|
{;;:skip-comments true
|
||||||
:lint-as {potemkin/def-derived-map clojure.core/defrecord}
|
:lint-as {potemkin/def-derived-map clojure.core/defrecord
|
||||||
:linters {:if {:level :off}
|
clojure.test.check.clojure-test/defspec clojure.test/deftest}
|
||||||
|
:linters {:missing-else-branch {:level :off}
|
||||||
:unused-binding {:level :off}
|
:unused-binding {:level :off}
|
||||||
:unused-referred-var {:exclude {clojure.test [deftest testing is are]
|
:unused-referred-var {:exclude {clojure.test [deftest testing is are]
|
||||||
cljs.test [deftest testing is are]}}}}
|
cljs.test [deftest testing is are]}}}}
|
||||||
|
|
|
||||||
1
.clj-kondo/module_config.edn
Normal file
1
.clj-kondo/module_config.edn
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{:config-paths ["../../../.clj-kondo"]}
|
||||||
26
.github/workflows/release.yml
vendored
Normal file
26
.github/workflows/release.yml
vendored
Normal 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 }}"
|
||||||
118
.github/workflows/testsuite.yml
vendored
Normal file
118
.github/workflows/testsuite.yml
vendored
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
name: testsuite
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
pull_request:
|
||||||
|
branches: [master]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-clj:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
# Supported Java versions: LTS releases and latest
|
||||||
|
jdk: [11, 17, 21, 25]
|
||||||
|
clojure: [11, 12]
|
||||||
|
|
||||||
|
name: Clojure ${{ matrix.clojure }} (Java ${{ matrix.jdk }})
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Cache dependencies
|
||||||
|
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@v4
|
||||||
|
with:
|
||||||
|
distribution: temurin
|
||||||
|
java-version: ${{ matrix.jdk }}
|
||||||
|
|
||||||
|
- name: Setup Clojure
|
||||||
|
uses: DeLaGuardo/setup-clojure@13.1
|
||||||
|
with:
|
||||||
|
lein: 2.9.5
|
||||||
|
clj-kondo: 2025.12.23
|
||||||
|
|
||||||
|
# Install openapi-schema-validator for openapi-tests
|
||||||
|
# Uses node version from the ubuntu-latest
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Run Linter
|
||||||
|
run: ./lint.sh
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: ./scripts/test.sh clj${{ matrix.clojure }}
|
||||||
|
|
||||||
|
build-cljs:
|
||||||
|
name: ClojureScript
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Cache dependencies
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.m2/repository
|
||||||
|
key: ${{ runner.os }}-cljs-${{ hashFiles('**/project.clj') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-cljs-
|
||||||
|
|
||||||
|
- name: Setup Java
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: temurin
|
||||||
|
java-version: 21
|
||||||
|
|
||||||
|
- name: Setup Clojure
|
||||||
|
uses: DeLaGuardo/setup-clojure@13.1
|
||||||
|
with:
|
||||||
|
lein: 2.9.5
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
cache: npm
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: ./scripts/test.sh cljs
|
||||||
|
|
||||||
|
lint:
|
||||||
|
name: Lint cljdoc.edn
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- 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
|
||||||
|
|
||||||
|
check-cljdoc:
|
||||||
|
name: Check cljdoc analysis
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Clojure
|
||||||
|
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
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -13,3 +13,6 @@ pom.xml.asc
|
||||||
/_book
|
/_book
|
||||||
figwheel_server.log
|
figwheel_server.log
|
||||||
/.idea
|
/.idea
|
||||||
|
.clj-kondo
|
||||||
|
.shadow-cljs
|
||||||
|
.cache
|
||||||
|
|
|
||||||
3
.lsp/config.edn
Normal file
3
.lsp/config.edn
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{:cljfmt {:indents {for-all [[:inner 0]]
|
||||||
|
are [[:inner 0]]}}
|
||||||
|
:clean {:ns-inner-blocks-indentation :same-line}}
|
||||||
558
CHANGELOG.md
558
CHANGELOG.md
|
|
@ -12,6 +12,564 @@ We use [Break Versioning][breakver]. The version numbers follow a `<major>.<mino
|
||||||
|
|
||||||
[breakver]: https://github.com/ptaoussanis/encore/blob/master/BREAK-VERSIONING.md
|
[breakver]: https://github.com/ptaoussanis/encore/blob/master/BREAK-VERSIONING.md
|
||||||
|
|
||||||
|
## UNRELEASED
|
||||||
|
|
||||||
|
* **FIX** redirect-trailing-slash-handler won't make external redirects. [#776](https://github.com/metosin/reitit/pull/776)
|
||||||
|
* Allow colons in bracket parameter syntax. [#770](https://github.com/metosin/reitit/pull/770)
|
||||||
|
* Add `url-encode?` option to `match-by-name`. [#778](https://github.com/metosin/reitit/pull/778)
|
||||||
|
|
||||||
|
## 0.10.0 (2026-01-09)
|
||||||
|
|
||||||
|
* Improve & document how response schemas get picked in per-content-type coercion. See [docs](./doc/ring/coercion.md#per-content-type-coercion). [#745](https://github.com/metosin/reitit/issues/745).
|
||||||
|
* **BREAKING** Remove unused `reitit.dependency` ns. [#763](https://github.com/metosin/reitit/pull/763)
|
||||||
|
* Support passing options to malli humanize. See [docs](./doc/coercion/malli_coercion.md). [#467](https://github.com/metosin/reitit/issues/467)
|
||||||
|
* **FIX** Handling of ex-type keyword hierarchies in create-exception-middleware. [#768](https://github.com/metosin/reitit/issues/768)
|
||||||
|
* Updated dependencies:
|
||||||
|
|
||||||
|
```
|
||||||
|
[metosin/malli "0.20.0"] is available but we use "0.19.2"
|
||||||
|
[com.fasterxml.jackson.core/jackson-core "2.20.1"] is available but we use "2.20.0"
|
||||||
|
[com.fasterxml.jackson.core/jackson-databind "2.20.1"] is available but we use "2.20.0"
|
||||||
|
[org.clojure/core.rrb-vector "0.2.1"] is available but we use "0.2.0"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 0.9.2 (2025-10-28)
|
||||||
|
|
||||||
|
* Allow multimethods as handlers when validating [#755](https://github.com/metosin/reitit/pull/755)
|
||||||
|
* Improve error reporting when generating OpenAPI fails [#754](https://github.com/metosin/reitit/pull/754)
|
||||||
|
* Allow middleware registry to be used when defining middleware in `ring-handler`. See [docs](./doc/ring/middleware_registry.md). [#739](https://github.com/metosin/reitit/pull/739)
|
||||||
|
* Allow passing options (eg. `:malli.transform/add-optional-keys`) to malli's `default-value-transformer`. See [docs](./doc/coercion/malli_coercion.md). [#756](https://github.com/metosin/reitit/pull/756)
|
||||||
|
* **FIX**: `match-by-name!` returning `nil` instead of throwing an exception for some partial matches [#758](https://github.com/metosin/reitit/issues/758)
|
||||||
|
* Updated dependencies:
|
||||||
|
|
||||||
|
```
|
||||||
|
[com.fasterxml.jackson.core/jackson-core "2.20.0"] is available but we use "2.18.2"
|
||||||
|
[com.fasterxml.jackson.core/jackson-databind "2.20.0"] is available but we use "2.18.2"
|
||||||
|
[compojure "1.7.2"] is available but we use "1.7.1"
|
||||||
|
[fipp "0.6.29"] is available but we use "0.6.27"
|
||||||
|
[metosin/malli "0.19.2"] is available but we use "0.18.0"
|
||||||
|
[ring "1.15.3"] is available but we use "1.14.1"
|
||||||
|
[ring/ring-core "1.15.3"] is available but we use "1.14.1"
|
||||||
|
[ring/ring-defaults "0.7.0"] is available but we use "0.6.0"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 0.9.1 (2025-05-27)
|
||||||
|
|
||||||
|
* **FIX**: response coercion threw an exception for unlisted HTTP status codes if there was no `:default`. Broken in 0.9.0. [#742](https://github.com/metosin/reitit/issues/742)
|
||||||
|
|
||||||
|
## 0.9.0 (2025-05-23)
|
||||||
|
|
||||||
|
* Improvements to mime type handling in `create-file-handler` and `create-resource-handler` [#733](https://github.com/metosin/reitit/pull/733)
|
||||||
|
* New `:mime-types` option to configure a map from file extension to mime type
|
||||||
|
* Don't set Content-Type header at all if mime type is not known
|
||||||
|
* Fix location of OpenAPI deprecated metadata [#714](https://github.com/metosin/reitit/pull/714)
|
||||||
|
* **BREAKING** Fix & clarify `:responses :default` and `:content :default` handling. See [docs](./doc/ring/coercion.md). [#735](https://github.com/metosin/reitit/pull/735)
|
||||||
|
* Summary: If `:responses <status>` is present, `:responses :default` is not used, even if `:responses <status>` defines no schema.
|
||||||
|
* Should not break normal use, but might cause surprises related to defaults applying/not applying
|
||||||
|
* **NOTE** This release depends on malli 0.18.0, which changes the format of OpenAPI & Swagger named schemas from `foo.bar/quux` to `foo.bar.quux`
|
||||||
|
|
||||||
|
## 0.8.0 (2025-03-28)
|
||||||
|
|
||||||
|
**[compare](https://github.com/metosin/reitit/compare/0.7.2..0.8.0)**
|
||||||
|
|
||||||
|
* **BREAKING**: throw error if `:responses` keys are not integers [#667](https://github.com/metosin/reitit/issues/667)
|
||||||
|
* **BREAKING**: Java 8 is no longer supported (Ring-core requires Apache Commons FileUpload which now requires Java 11)
|
||||||
|
* File and resource handlers (`create-file-handler` and `create-resource-handler`)
|
||||||
|
* **BREAKING**: New default is to redirect from `dir` path to `dir/` and serve the index file (if found) on the path ending with `/`
|
||||||
|
* For example the Swagger UI handler now serves the index from `/api-docs/` instead of redirecting to `/api-docs/index.html` (both work)
|
||||||
|
* Mostly this is a visual change, though if you have unit tests checking for response status or redirect, those could break
|
||||||
|
* New option `:index-redirect?` (default false) allows enable redirecting to the index file, e.g. `dir` -> `dir/index.html` (same as the old default)
|
||||||
|
* New option `:canonicalize-uris?` (default true) enables redirect from `dir` to `dir/` if the index file exists for the path
|
||||||
|
* Without this option `dir` would return 404 and `dir/` and `dir/index.html` would return the file
|
||||||
|
* Changes in 0.8.0-alpha1
|
||||||
|
* Updated dependencies:
|
||||||
|
|
||||||
|
```
|
||||||
|
[fipp "0.6.27"] is available but we use "0.6.26"
|
||||||
|
[metosin/jsonista "0.3.13"] is available but we use "0.3.10"
|
||||||
|
[metosin/malli "0.17.0"] is available but we use "0.16.4"
|
||||||
|
[metosin/muuntaja "0.6.11"] is available but we use "0.6.10"
|
||||||
|
[metosin/ring-swagger-ui "5.20.0"] is available but we use "5.9.0"
|
||||||
|
[ring/ring-core "1.14.1"] is available but we use "1.12.2"
|
||||||
|
[ring/ring-defaults "0.6.0"] is available but we use "0.5.0"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 0.8.0-alpha1 (2025-01-31)
|
||||||
|
|
||||||
|
**[compare](https://github.com/metosin/reitit/compare/0.7.2..0.8.0-alpha1)**
|
||||||
|
|
||||||
|
* Improve OpenAPI docs, plus don't emit `:description` in the wrong place [#702](https://github.com/metosin/reitit/pull/702)
|
||||||
|
* Support reitit.walk for all IPersitentMap implementations, fixes coercion with
|
||||||
|
aleph 0.7.2 [#700](https://github.com/metosin/reitit/issues/700), [#701](https://github.com/metosin/reitit/pull/701)
|
||||||
|
* *POTENTIALLY BREAKING* The frontend functions (href, push/replace-state, navigate, set-query) now
|
||||||
|
encode query-string values using configured coercion when possible (only Malli supports encoding).
|
||||||
|
[#716](https://github.com/metosin/reitit/pull/716)
|
||||||
|
- You can use this to encode query parameter values before they are URL-encoded. This works for DateTimes, collections etc.
|
||||||
|
- In most cases this shouldn't break existing uses, but it is possible even without
|
||||||
|
a custom encoding function, the default Malli string-transformer could encode some values differently
|
||||||
|
then previously.
|
||||||
|
|
||||||
|
## 0.7.2 (2024-09-02)
|
||||||
|
|
||||||
|
**[compare](https://github.com/metosin/reitit/compare/0.7.1..0.7.2)**
|
||||||
|
|
||||||
|
* Speed up routes and inline it in code ring handler [#693](https://github.com/metosin/reitit/pull/693) [#693](https://github.com/metosin/reitit/pull/696)
|
||||||
|
* Fix: Can't get descendants of classes [#555](https://github.com/metosin/reitit/issues/555)
|
||||||
|
* Faster keywordize [#506](https://github.com/metosin/reitit/pull/506)
|
||||||
|
* Updated dependencies:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
[metosin/jsonista "0.3.10"] is available but we use "0.3.9"
|
||||||
|
[metosin/malli "0.16.4"] is available but we use "0.16.2"
|
||||||
|
[com.fasterxml.jackson.core/jackson-core "2.17.2"] is available but we use "2.17.1"
|
||||||
|
[com.fasterxml.jackson.core/jackson-databind "2.17.2"] is available but we use "2.17.1"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 0.7.1 (2024-06-30)
|
||||||
|
|
||||||
|
**[compare](https://github.com/metosin/reitit/compare/0.7.0..0.7.1)**
|
||||||
|
|
||||||
|
* FIX: Route data maps ignore meta-merge options in 0.7.0, breaking compatibility [#679](https://github.com/metosin/reitit/issues/679)
|
||||||
|
* FIX: Clojure record in route data is converted to a plain map [#686](https://github.com/metosin/reitit/issues/686)
|
||||||
|
* Add arities 1 and 2 to rf/match->path [#685](https://github.com/metosin/reitit/pull/685)
|
||||||
|
* Updated dependencies:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
[ring/ring-core "1.12.2"] is available but we use "1.12.1"
|
||||||
|
[metosin/malli "0.16.2"] is available but we use "0.16.1"
|
||||||
|
[metosin/jsonista "0.3.9"] is available but we use "0.3.8"
|
||||||
|
[metosin/spec-tools "0.10.7"] is available but we use "0.10.6"
|
||||||
|
[com.fasterxml.jackson.core/jackson-core "2.17.1"] is available but we use "2.17.0"
|
||||||
|
[com.fasterxml.jackson.core/jackson-databind "2.17.1"] is available but we use "2.17.0"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 0.7.0 (2024-04-30)
|
||||||
|
|
||||||
|
**[compare](https://github.com/metosin/reitit/compare/0.6.0..0.7.0)**
|
||||||
|
|
||||||
|
The OpenAPI3 release, Year in the making - the changes span over multiple repositories.
|
||||||
|
|
||||||
|
* Openapi3 support, see the [docs](https://github.com/metosin/reitit/blob/master/doc/ring/openapi.md)
|
||||||
|
* Fetch OpenAPI content types from Muuntaja [#636](https://github.com/metosin/reitit/issues/636)
|
||||||
|
* OpenAPI 3 parameter descriptions get populated from malli/spec/schema descriptions. [#612](https://github.com/metosin/reitit/issues/612)
|
||||||
|
* Generate correct OpenAPI $ref schemas for malli var and ref schemas [#673](https://github.com/metosin/reitit/pull/673)
|
||||||
|
* new syntax for `:request` and `:response` per-content-type coercions. See [coercion.md](doc/ring/coercion.md). [#627](https://github.com/metosin/reitit/issues/627)
|
||||||
|
* [#84](https://github.com/metosin/reitit/issues/84)
|
||||||
|
* Handlers can be vars [#585](https://github.com/metosin/reitit/pull/585)
|
||||||
|
* Fix swagger generation when unsupported coercions are present [#671](https://github.com/metosin/reitit/pull/671)
|
||||||
|
* **BREAKING**: require Clojure 1.11, drop support for Clojure 1.10
|
||||||
|
* **BREAKING**: `compile-request-coercers` returns a map with `:data` and `:coerce` instead of plain `:coerce` function
|
||||||
|
* **BREAKING**: Parameter and Response schemas are merged into the route data vector - so they can be properly merged into the compiled result, fixes [#422](https://github.com/metosin/reitit/issues/422) - merging multiple schemas together works with `Malli` and `Schema`, partially with `data-spec` but not with `spec`.
|
||||||
|
* Fixed some module dependencies so Cljdoc can properly analyze all the modules
|
||||||
|
* Fix reading fragment string on `Html5History` initialization
|
||||||
|
* Add fragment string parameter to reitit-frontend functions ([#604](https://github.com/metosin/reitit/pull/604))
|
||||||
|
* Frontend: provide easy way to update current query params. [#600](https://github.com/metosin/reitit/issues/600)
|
||||||
|
|
||||||
|
* Updated dependencies:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
[metosin/malli "0.16.1"] is available but we use "0.10.1"
|
||||||
|
[metosin/muuntaja "0.6.10"] is available but we use "0.6.8"
|
||||||
|
[metosin/spec-tools "0.10.6"] is available but we use "0.10.5"
|
||||||
|
[metosin/schema-tools "0.13.1"] is available but we use "0.13.0"
|
||||||
|
[metosin/jsonista "0.3.8"] is available but we use "0.3.7"
|
||||||
|
[com.fasterxml.jackson.core/jackson-core "2.17.0"] is available but we use "2.14.2"
|
||||||
|
[com.fasterxml.jackson.core/jackson-databind "2.17.0"] is available but we use "2.14.2"
|
||||||
|
[ring/ring-core "1.12.1"] is available but we use "1.9.6"
|
||||||
|
[metosin/ring-swagger-ui "5.9.0"] is available but we use "4.15.5"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 0.7.0-alpha8 (2024-04-30)
|
||||||
|
|
||||||
|
**[compare](https://github.com/metosin/reitit/compare/0.7.0-alpha7...0.7.0-alpha8)**
|
||||||
|
|
||||||
|
* Handlers can be vars [#585](https://github.com/metosin/reitit/pull/585)
|
||||||
|
* Fetch OpenAPI content types from Muuntaja [#636](https://github.com/metosin/reitit/issues/636)
|
||||||
|
* **BREAKING** OpenAPI support is now clj only
|
||||||
|
* 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:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
[metosin/ring-swagger-ui "4.19.1"] is available but we use "4.18.1"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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**
|
||||||
|
|
||||||
|
## 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
|
||||||
|
* Updated dependencies:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
[metosin/schema-tools "0.13.1"] is available but we use "0.13.0"
|
||||||
|
[com.fasterxml.jackson.core/jackson-core "2.15.1"] is available but we use "2.14.2"
|
||||||
|
[com.fasterxml.jackson.core/jackson-databind "2.15.1"] is available but we use "2.14.2"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 0.7.0-alpha4 (2023-05-17)
|
||||||
|
|
||||||
|
**[compare](https://github.com/metosin/reitit/compare/0.7.0-alpha3...0.7.0-alpha4)**
|
||||||
|
|
||||||
|
* OpenAPI 3 parameter descriptions get populated from malli/spec/schema descriptions. [#612](https://github.com/metosin/reitit/issues/612)
|
||||||
|
|
||||||
|
## 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))
|
||||||
|
|
||||||
|
## 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:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
[metosin/ring-swagger-ui "4.18.1"] is available but we use "4.15.5"
|
||||||
|
[metosin/malli "0.11.0"] is available but we use "0.10.1"
|
||||||
|
[ring/ring-core "1.10.0"] is available but we use "1.9.6"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
* FIX: redirect-trailing-slash-handler strips query-params [#565](https://github.com/metosin/reitit/issues/565)
|
||||||
|
* **BREAKING**: Drop tests for Clojure 1.9, run tests with 1.10 & 1.11
|
||||||
|
* NEW option `:meta-merge` on a router for custom merge strategy on route data
|
||||||
|
* Swagger: support operationId in generated swagger json [#452](https://github.com/metosin/reitit/pull/452) & [#569](https://github.com/metosin/reitit/pull/569)
|
||||||
|
* Update documentation and link to the startrek project [#578](https://github.com/metosin/reitit/pull/578)
|
||||||
|
* Upgrade jackson for CVE-2022-42003 and CVE-2022-42004 [#577](https://github.com/metosin/reitit/pull/577)
|
||||||
|
* Improved coercion errors perf [#576](https://github.com/metosin/reitit/pull/576)
|
||||||
|
* Add example for Reitit + Pedestal + Malli coercion [#572](https://github.com/metosin/reitit/pull/572)
|
||||||
|
* Handle empty seq as empty string in query-string [#566](https://github.com/metosin/reitit/pull/566)
|
||||||
|
* Polish pedestal chains when printing context diffs [#557](https://github.com/metosin/reitit/pull/557)
|
||||||
|
* Updated dependencies:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
[metosin/ring-swagger-ui "4.15.5"] is available but we use "4.3.0"
|
||||||
|
[metosin/jsonista "0.3.7"] is available but we use "0.3.5"
|
||||||
|
[metosin/malli "0.10.1"] is available but we use "0.8.2"
|
||||||
|
[fipp "0.6.26"] is available but we use "0.6.25"
|
||||||
|
[ring/ring-core "1.9.6"] is available but we use "1.9.5"
|
||||||
|
[com.fasterxml.jackson.core/jackson-core "2.14.2"] is available but we use "2.14.1"
|
||||||
|
[com.fasterxml.jackson.core/jackson-databind "2.14.2"] is available but we use "2.14.1"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
|
||||||
|
## 0.5.17 (2022-03-10)
|
||||||
|
|
||||||
|
**[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)**
|
||||||
|
|
||||||
|
* Support for [Malli Lite Syntax](https://github.com/metosin/malli#lite) in coercion (enabled by default):
|
||||||
|
|
||||||
|
```clj
|
||||||
|
["/add/:id" {:post {:parameters {:path {:id int?}
|
||||||
|
:query {:a (l/optional int?)}
|
||||||
|
:body {:id int?
|
||||||
|
:data {:id (l/maybe int?)
|
||||||
|
:orders (l/map-of uuid? {:name string?})}}}
|
||||||
|
:responses {200 {:body {:total pos-int?}}
|
||||||
|
500 {:description "fail"}}}}]
|
||||||
|
```
|
||||||
|
|
||||||
|
* Improved Reitit-frontend function docstrings
|
||||||
|
|
||||||
|
* Updated deps:
|
||||||
|
|
||||||
|
```clj
|
||||||
|
[metosin/ring-swagger-ui "4.3.0"] is available but we use "3.46.0"
|
||||||
|
[metosin/jsonista "0.3.5"] is available but we use "0.3.3"
|
||||||
|
[metosin/malli "0.8.2"] is available but we use "0.5.1"
|
||||||
|
[com.fasterxml.jackson.core/jackson-core "2.13.1"] is available but we use "2.12.4"
|
||||||
|
[com.fasterxml.jackson.core/jackson-databind "2.13.1"] is available but we use "2.12.4"
|
||||||
|
[fipp "0.6.25"] is available but we use "0.6.24"
|
||||||
|
[expound "0.9.0"] is available but we use "0.8.9"
|
||||||
|
[ring/ring-core "1.9.5"] is available but we use "1.9.4"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 0.5.15 (2021-08-05)
|
||||||
|
|
||||||
|
**[compare](https://github.com/metosin/reitit/compare/0.5.14...0.5.15)**
|
||||||
|
|
||||||
|
* recompiled with Java8
|
||||||
|
|
||||||
|
## 0.5.14 (2021-08-03)
|
||||||
|
|
||||||
|
**[compare](https://github.com/metosin/reitit/compare/0.5.13...0.5.14)**
|
||||||
|
|
||||||
|
* updated deps:
|
||||||
|
|
||||||
|
```clj
|
||||||
|
[metosin/ring-swagger-ui "3.46.0"] is available but we use "3.36.0"
|
||||||
|
[metosin/jsonista "0.3.3"] is available but we use "0.3.1"
|
||||||
|
[metosin/malli "0.5.1"] is available but we use "0.3.0"
|
||||||
|
[com.fasterxml.jackson.core/jackson-core "2.12.4"] is available but we use "2.12.1"
|
||||||
|
[com.fasterxml.jackson.core/jackson-databind "2.12.4"] is available but we use "2.12.1"
|
||||||
|
[fipp "0.6.24"] is available but we use "0.6.23"
|
||||||
|
[ring/ring-core "1.9.4"] is available but we use "1.9.1"
|
||||||
|
[io.pedestal/pedestal.service "0.5.9"] is available but we use "0.5.8"
|
||||||
|
```
|
||||||
|
|
||||||
|
### `reitit-ring`
|
||||||
|
|
||||||
|
* Fixes `reitit.ring/create-resource-handler` and `reitit.ring/create-file-handler` to support URL-escaped characters. [#484](https://github.com/metosin/reitit/issues/484). PR [#489](https://github.com/metosin/reitit/pull/489).
|
||||||
|
|
||||||
|
### `reitit-malli`
|
||||||
|
|
||||||
|
* FIX: Malli response coercision seems to do nothing at all [#498](https://github.com/metosin/reitit/pull/501)
|
||||||
|
|
||||||
|
### `reitit-pedestal`
|
||||||
|
|
||||||
|
* Enrich request for pedestal/routing-interceptor default-queue [#495](https://github.com/metosin/reitit/pull/495)
|
||||||
|
|
||||||
|
## 0.5.13 (2021-04-23)
|
||||||
|
|
||||||
|
**[compare](https://github.com/metosin/reitit/compare/0.5.12...0.5.13)**
|
||||||
|
|
||||||
|
* updated deps:
|
||||||
|
|
||||||
|
```clj
|
||||||
|
[metosin/malli "0.3.0"] is available but we use "0.2.1"
|
||||||
|
[metosin/schema-tools "0.12.3"] is available but we use "0.12.2"
|
||||||
|
[ring/ring-core "1.9.1"] is available but we use "1.9.0"
|
||||||
|
[metosin/schema-tools "0.12.3"] is available but we use "0.12.2"
|
||||||
|
[expound "0.8.9"] is available but we use "0.8.7"
|
||||||
|
[ring "1.9.1"] is available but we use "1.9.0"
|
||||||
|
```
|
||||||
|
|
||||||
|
### `reitit-ring`
|
||||||
|
|
||||||
|
* Make reitit.ring/create-resource-handler's `:not-found-handler` work when used outside of a router. [#464](https://github.com/metosin/reitit/issues/464). PR [#471](https://github.com/metosin/reitit/pull/471) by Kari Marttila and Metosin Maintenance Mob.
|
||||||
|
|
||||||
|
## 0.5.12 (2021-02-01)
|
||||||
|
|
||||||
|
**[compare](https://github.com/metosin/reitit/compare/0.5.11...0.5.12)**
|
||||||
|
|
||||||
|
* updated deps:
|
||||||
|
|
||||||
|
```
|
||||||
|
[metosin/spec-tools "0.10.5"] is available but we use "0.10.4"
|
||||||
|
[metosin/jsonista "0.3.1"] is available but we use "0.3.0"
|
||||||
|
[metosin/muuntaja "0.6.8"] is available but we use "0.6.7"
|
||||||
|
[ring/ring-core "1.9.0"] is available but we use "1.8.2"
|
||||||
|
[metosin/muuntaja "0.6.8"] is available but we use "0.6.7"
|
||||||
|
[com.fasterxml.jackson.core/jackson-core "2.12.1"] is available but we use "2.12.0"
|
||||||
|
[com.fasterxml.jackson.core/jackson-databind "2.12.1"] is available but we use "2.12.0"
|
||||||
|
```
|
||||||
|
|
||||||
|
* Allow whitespace as separator in route paths. [#411](https://github.com/metosin/reitit/issues/411). PR [#466](https://github.com/metosin/reitit/pull/466) by Kimmo Koskinen and Metosin Maintenance Mob.
|
||||||
|
|
||||||
|
## 0.5.11 (2020-12-27)
|
||||||
|
|
||||||
|
**[compare](https://github.com/metosin/reitit/compare/0.5.10...0.5.11)**
|
||||||
|
|
||||||
|
* updated deps:
|
||||||
|
|
||||||
|
```clj
|
||||||
|
[metosin/ring-swagger-ui "3.36.0"] is available but we use "3.25.3"
|
||||||
|
[metosin/jsonista "0.3.0"] is available but we use "0.2.7"
|
||||||
|
[com.fasterxml.jackson.core/jackson-core "2.12.0"] is available but we use "2.11.2"
|
||||||
|
[com.fasterxml.jackson.core/jackson-databind "2.12.0"] is available but we use "2.11.2"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 0.5.10 (2020-10-22)
|
||||||
|
|
||||||
|
**[compare](https://github.com/metosin/reitit/compare/0.5.9...0.5.10)**
|
||||||
|
|
||||||
|
* updated deps:
|
||||||
|
|
||||||
|
```clj
|
||||||
|
[metosin/malli "0.2.1] is available but we use "0.2.0"
|
||||||
|
```
|
||||||
|
|
||||||
|
### `reitit-malli`
|
||||||
|
|
||||||
|
* fix [#445](https://github.com/metosin/reitit/issues/445): Malli response coercion failing for `[:sequential string?]` if the response body is an empty vector
|
||||||
|
|
||||||
|
## 0.5.9 (2020-10-19)
|
||||||
|
|
||||||
|
**[compare](https://github.com/metosin/reitit/compare/0.5.8...0.5.9)**
|
||||||
|
|
||||||
|
### `reitit-frontend`
|
||||||
|
|
||||||
|
- `reitit.frontend.easy/start!` now correctly removes old event listeners
|
||||||
|
when called repeatedly (e.g. with hot code reload workflow)
|
||||||
|
([#438](https://github.com/metosin/reitit/pull/438))
|
||||||
|
|
||||||
|
## 0.5.8 (2020-10-19)
|
||||||
|
|
||||||
|
**[compare](https://github.com/metosin/reitit/compare/0.5.7...0.5.8)**
|
||||||
|
|
||||||
|
* Add `:conflicting` to route data spec [#444](https://github.com/metosin/reitit/pull/444).
|
||||||
|
|
||||||
|
## 0.5.7 (2020-10-18)
|
||||||
|
|
||||||
|
**[compare](https://github.com/metosin/reitit/compare/0.5.6...0.5.7)**
|
||||||
|
|
||||||
|
* updated deps:
|
||||||
|
|
||||||
|
```clj
|
||||||
|
[metosin/malli "0.2.0"] is available but we use "0.0.1-20200924.063109-27"
|
||||||
|
[com.fasterxml.jackson.core/jackson-core "2.11.3"] is available but we use "2.11.2"
|
||||||
|
[com.fasterxml.jackson.core/jackson-databind "2.11.3"] is available but we use "2.11.2"
|
||||||
|
[expound "0.8.6"] is available but we use "0.8.5"
|
||||||
|
[ring/ring-core "1.8.2"] is available but we use "1.8.1"
|
||||||
|
```
|
||||||
|
|
||||||
|
### `reitit-ring`
|
||||||
|
|
||||||
|
* Fix resource handler path matching [#443](https://github.com/metosin/reitit/pull/443)
|
||||||
|
* Automatically publish Swagger `:consumes` for `:form` params, fixes [#217](https://github.com/metosin/reitit/issues/217).
|
||||||
|
|
||||||
|
## 0.5.6 (2020-09-26)
|
||||||
|
|
||||||
|
**[compare](https://github.com/metosin/reitit/compare/0.5.5...0.5.6)**
|
||||||
|
|
||||||
|
* updated deps:
|
||||||
|
|
||||||
|
```clj
|
||||||
|
[metosin/malli "0.0.1-20200924.063109-27"] is available but we use "0.0.1-20200715.082439-21"
|
||||||
|
[metosin/spec-tools "0.10.4"] is available but we use "0.10.3"
|
||||||
|
[metosin/jsonista "0.2.7"] is available but we use "0.2.6"
|
||||||
|
[com.fasterxml.jackson.core/jackson-core "2.11.2"] is available but we use "2.11.0"
|
||||||
|
[com.fasterxml.jackson.core/jackson-databind "2.11.2"] is available but we use "2.11.0"
|
||||||
|
```
|
||||||
|
|
||||||
|
### `reitit-malli`
|
||||||
|
|
||||||
|
* `:map-of` keys in JSON are correctly decoded using string-decoders
|
||||||
|
* new `:encode-error` option in coercion:
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(def coercion
|
||||||
|
(reitit.coercion.malli/create
|
||||||
|
{:encode-error (fn [error] {:errors (:humanized error)})}))
|
||||||
|
; results in... => {:status 400, :body {:errors {:x ["missing required key"]}}}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 0.5.5 (2020-07-15)
|
||||||
|
|
||||||
|
**[compare](https://github.com/metosin/reitit/compare/0.5.4...0.5.5)**
|
||||||
|
|
||||||
|
* recompile with Java8
|
||||||
|
|
||||||
|
```clj
|
||||||
|
[metosin/malli "0.0.1-20200715.082439-21"] is available but we use "0.0.1-20200713.080243-20"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 0.5.4 (2020-07-13)
|
||||||
|
|
||||||
|
**[compare](https://github.com/metosin/reitit/compare/0.5.3...0.5.4)**
|
||||||
|
|
||||||
|
```clj
|
||||||
|
[metosin/malli "0.0.1-20200713.080243-20"] is available but we use "0.0.1-20200709.163702-18"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 0.5.3 (2020-07-09)
|
||||||
|
|
||||||
|
**[compare](https://github.com/metosin/reitit/compare/0.5.2...0.5.3)**
|
||||||
|
|
||||||
|
```clj
|
||||||
|
[metosin/malli "0.0.1-20200709.163702-18"] is available but we use "0.0.1-20200525.162645-15"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 0.5.2 (2020-05-27)
|
||||||
|
|
||||||
|
**[compare](https://github.com/metosin/reitit/compare/0.5.1...0.5.2)**
|
||||||
|
|
||||||
|
```clj
|
||||||
|
[metosin/malli "0.0.1-20200525.162645-15"] is available but we use "0.0.1-20200404.091302-14"
|
||||||
|
```
|
||||||
|
|
||||||
|
### `reitit-malli`
|
||||||
|
|
||||||
|
* Fixed coercion with `:and` and `:or`, fixes [#407](https://github.com/metosin/reitit/issues/407).
|
||||||
|
* New options to `reitit.coercion.malli/create`:
|
||||||
|
* `:validate` - boolean to indicate whether validation is enabled (true)
|
||||||
|
* `:enabled` - boolean to indicate whether coercion (and validation) is enabled (true)
|
||||||
|
|
||||||
|
### `reitit-swagger`
|
||||||
|
|
||||||
|
* If no `:responses` are defined for an endpoint, add `{:responses {:default {:description ""}}}` to make swagger spec valid, fixes [#403](https://github.com/metosin/reitit/issues/403) by [胡雨軒 Петр](https://github.com/piotr-yuxuan).
|
||||||
|
|
||||||
|
### `reitit-ring`
|
||||||
|
|
||||||
|
* Coercion middleware will not to mount if the selected `:coercion` is not enabled for the given `:parameters`, e.g. "just api-docs"
|
||||||
|
|
||||||
|
### `reitit-http`
|
||||||
|
|
||||||
|
* Coercion interceptor will not to mount if the selected `:coercion` is not enabled for the given `:parameters`, e.g. "just api-docs"
|
||||||
|
|
||||||
|
## 0.5.1 (2020-05-18)
|
||||||
|
|
||||||
|
**[compare](https://github.com/metosin/reitit/compare/0.5.0...0.5.1)**
|
||||||
|
|
||||||
|
```clj
|
||||||
|
[metosin/sieppari "0.0.0-alpha13"] is available but we use "0.0.0-alpha10"
|
||||||
|
```
|
||||||
|
|
||||||
|
* new sieppari NOT to include all it's dev dependencies, fixes [#402](https://github.com/metosin/reitit/issues/402)
|
||||||
|
* re-compile with Java8
|
||||||
|
|
||||||
## 0.5.0 (2020-05-17)
|
## 0.5.0 (2020-05-17)
|
||||||
|
|
||||||
* **NOTE** Due to [issues with Jackson versioning](https://clojureverse.org/t/depending-on-the-right-versions-of-jackson-libraries/5111), you might get errors after updating as [Cheshire still uses older version](https://github.com/dakrone/cheshire/pull/164) as is most likely as a transitive dependency via 3rd party libs. To resolve issues (with Leiningen), you can either:
|
* **NOTE** Due to [issues with Jackson versioning](https://clojureverse.org/t/depending-on-the-right-versions-of-jackson-libraries/5111), you might get errors after updating as [Cheshire still uses older version](https://github.com/dakrone/cheshire/pull/164) as is most likely as a transitive dependency via 3rd party libs. To resolve issues (with Leiningen), you can either:
|
||||||
|
|
|
||||||
10
Justfile
10
Justfile
|
|
@ -1,10 +0,0 @@
|
||||||
help:
|
|
||||||
@just --list
|
|
||||||
|
|
||||||
# Initializes lint
|
|
||||||
init-lint:
|
|
||||||
clj-kondo --lint $(lein classpath)
|
|
||||||
|
|
||||||
# Lints the project
|
|
||||||
lint:
|
|
||||||
./lint.sh
|
|
||||||
134
README.md
134
README.md
|
|
@ -1,17 +1,23 @@
|
||||||
# reitit [](https://circleci.com/gh/metosin/reitit) [](https://cljdoc.org/jump/release/metosin/reitit) [](https://clojurians.slack.com/messages/reitit/)
|
# reitit
|
||||||
|
|
||||||
|
[](https://github.com/metosin/reitit/actions)
|
||||||
|
[](https://cljdoc.org/d/metosin/reitit/)
|
||||||
|
[](https://clojars.org/metosin/reitit)
|
||||||
|
[](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).
|
A fast data-driven router for Clojure(Script).
|
||||||
|
|
||||||
* Simple data-driven [route syntax](https://metosin.github.io/reitit/basics/route_syntax.html)
|
* Simple data-driven [route syntax](https://cljdoc.org/d/metosin/reitit/CURRENT/doc/basics/route-syntax/)
|
||||||
* Route [conflict resolution](https://metosin.github.io/reitit/basics/route_conflicts.html)
|
* Route [conflict resolution](https://cljdoc.org/d/metosin/reitit/CURRENT/doc/basics/route-conflicts/)
|
||||||
* First-class [route data](https://metosin.github.io/reitit/basics/route_data.html)
|
* First-class [route data](https://cljdoc.org/d/metosin/reitit/CURRENT/doc/basics/route-data/)
|
||||||
* Bi-directional routing
|
* Bi-directional routing
|
||||||
* [Pluggable coercion](https://metosin.github.io/reitit/coercion/coercion.html) ([malli](https://github.com/metosin/malli), [schema](https://github.com/plumatic/schema) & [clojure.spec](https://clojure.org/about/spec))
|
* [Pluggable coercion](https://cljdoc.org/d/metosin/reitit/CURRENT/doc/coercion/coercion-explained) ([malli](https://github.com/metosin/malli), [schema](https://github.com/plumatic/schema) & [clojure.spec](https://clojure.org/about/spec))
|
||||||
* Helpers for [ring](https://metosin.github.io/reitit/ring/ring.html), [http](https://metosin.github.io/reitit/http/interceptors.html), [pedestal](https://metosin.github.io/reitit/http/pedestal.html) & [frontend](https://metosin.github.io/reitit/frontend/basics.html)
|
* Helpers for [ring](https://cljdoc.org/d/metosin/reitit/CURRENT/doc/ring/ring-router), [http](https://cljdoc.org/d/metosin/reitit/CURRENT/doc/http/interceptors/), [pedestal](https://cljdoc.org/d/metosin/reitit/CURRENT/doc/http/pedestal/) & [frontend](https://cljdoc.org/d/metosin/reitit/CURRENT/doc/frontend/basics/)
|
||||||
* Friendly [Error Messages](https://metosin.github.io/reitit/basics/error_messages.html)
|
* Friendly [Error Messages](https://cljdoc.org/d/metosin/reitit/CURRENT/doc/basics/error-messages/)
|
||||||
* Extendable
|
* Extendable
|
||||||
* Modular
|
* Modular
|
||||||
* [Fast](https://metosin.github.io/reitit/performance.html)
|
* [Fast](https://cljdoc.org/d/metosin/reitit/CURRENT/doc/misc/performance)
|
||||||
|
|
||||||
Presentations:
|
Presentations:
|
||||||
* [Reitit, The Ancient Art of Data-Driven](https://www.slideshare.net/mobile/metosin/reitit-clojurenorth-2019-141438093), Clojure/North 2019, [video](https://youtu.be/cSntRGAjPiM)
|
* [Reitit, The Ancient Art of Data-Driven](https://www.slideshare.net/mobile/metosin/reitit-clojurenorth-2019-141438093), Clojure/North 2019, [video](https://youtu.be/cSntRGAjPiM)
|
||||||
|
|
@ -20,26 +26,36 @@ Presentations:
|
||||||
* [Data-Driven Ring with Reitit](https://www.metosin.fi/blog/reitit-ring/)
|
* [Data-Driven Ring with Reitit](https://www.metosin.fi/blog/reitit-ring/)
|
||||||
* [Reitit, Data-Driven Routing with Clojure(Script)](https://www.metosin.fi/blog/reitit/)
|
* [Reitit, Data-Driven Routing with Clojure(Script)](https://www.metosin.fi/blog/reitit/)
|
||||||
|
|
||||||
## [Full Documentation](https://metosin.github.io/reitit/)
|
**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.
|
There is [#reitit](https://clojurians.slack.com/messages/reitit/) in [Clojurians Slack](http://clojurians.net/) for discussion & help.
|
||||||
|
|
||||||
## Main Modules
|
## Main Modules
|
||||||
|
|
||||||
* `reitit` - all bundled
|
* `metosin/reitit` - all bundled
|
||||||
* `reitit-core` - the routing core
|
* `metosin/reitit-core` - the routing core
|
||||||
* `reitit-ring` - a [ring router](https://metosin.github.io/reitit/ring/ring.html)
|
* `metosin/reitit-ring` - a [ring router](https://cljdoc.org/d/metosin/reitit/CURRENT/doc/ring/ring-router/)
|
||||||
* `reitit-middleware` - [common middleware](https://metosin.github.io/reitit/ring/default_middleware.html)
|
* `metosin/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
|
* `metosin/reitit-spec` [clojure.spec](https://clojure.org/about/spec) coercion
|
||||||
* `reitit-malli` [malli](https://github.com/metosin/malli) coercion
|
* `metosin/reitit-malli` [malli](https://github.com/metosin/malli) coercion
|
||||||
* `reitit-schema` [Schema](https://github.com/plumatic/schema) coercion
|
* `metosin/reitit-schema` [Schema](https://github.com/plumatic/schema) coercion
|
||||||
* `reitit-swagger` [Swagger2](https://swagger.io/) apidocs
|
* `fi.metosin/reitit-openapi` [OpenAPI](https://www.openapis.org/) apidocs *
|
||||||
* `reitit-swagger-ui` Integrated [Swagger UI](https://github.com/swagger-api/swagger-ui)
|
* `metosin/reitit-swagger` [Swagger2](https://swagger.io/) apidocs
|
||||||
* `reitit-frontend` Tools for [frontend routing]((https://metosin.github.io/reitit/frontend/basics.html))
|
* `metosin/reitit-swagger-ui` Integrated [Swagger UI](https://github.com/swagger-api/swagger-ui)
|
||||||
* `reitit-http` http-routing with Interceptors
|
* `metosin/reitit-frontend` Tools for [frontend routing]((https://cljdoc.org/d/metosin/reitit/CURRENT/doc/frontend/basics/))
|
||||||
* `reitit-interceptors` - [common interceptors](https://metosin.github.io/reitit/http/default_interceptors.html)
|
* `metosin/reitit-http` http-routing with Interceptors
|
||||||
* `reitit-sieppari` support for [Sieppari](https://github.com/metosin/sieppari)
|
* `metosin/reitit-interceptors` - [common interceptors](https://cljdoc.org/d/metosin/reitit/CURRENT/doc/http/default-interceptors/)
|
||||||
* `reitit-dev` - development utilities
|
* `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
|
## Extra modules
|
||||||
|
|
||||||
|
|
@ -50,11 +66,15 @@ There is [#reitit](https://clojurians.slack.com/messages/reitit/) in [Clojurians
|
||||||
All main modules bundled:
|
All main modules bundled:
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
[metosin/reitit "0.5.0"]
|
[metosin/reitit "0.10.0"]
|
||||||
```
|
```
|
||||||
|
|
||||||
Optionally, the parts can be required separately.
|
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, 21 and 25
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
|
|
@ -85,22 +105,29 @@ Optionally, the parts can be required separately.
|
||||||
A Ring routing app with input & output coercion using [data-specs](https://github.com/metosin/spec-tools/blob/master/README.md#data-specs).
|
A Ring routing app with input & output coercion using [data-specs](https://github.com/metosin/spec-tools/blob/master/README.md#data-specs).
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
|
(require '[muuntaja.core :as m])
|
||||||
(require '[reitit.ring :as ring])
|
(require '[reitit.ring :as ring])
|
||||||
(require '[reitit.coercion.spec])
|
(require '[reitit.coercion.spec])
|
||||||
(require '[reitit.ring.coercion :as rrc])
|
(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])
|
||||||
|
|
||||||
(def app
|
(def app
|
||||||
(ring/ring-handler
|
(ring/ring-handler
|
||||||
(ring/router
|
(ring/router
|
||||||
["/api"
|
["/api"
|
||||||
["/math" {:get {:parameters {:query {:x int?, :y int?}}
|
["/math" {:get {:parameters {:query {:x int?, :y int?}}
|
||||||
:responses {200 {:body {:total pos-int?}}}
|
:responses {200 {:body {:total int?}}}
|
||||||
:handler (fn [{{{:keys [x y]} :query} :parameters}]
|
:handler (fn [{{{:keys [x y]} :query} :parameters}]
|
||||||
{:status 200
|
{:status 200
|
||||||
:body {:total (+ x y)}})}}]]
|
:body {:total (+ x y)}})}}]]
|
||||||
;; router data effecting all routes
|
;; router data affecting all routes
|
||||||
{:data {:coercion reitit.coercion.spec/coercion
|
{:data {:coercion reitit.coercion.spec/coercion
|
||||||
:middleware [rrc/coerce-exceptions-middleware
|
:muuntaja m/instance
|
||||||
|
: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
|
rrc/coerce-request-middleware
|
||||||
rrc/coerce-response-middleware]}})))
|
rrc/coerce-response-middleware]}})))
|
||||||
```
|
```
|
||||||
|
|
@ -108,35 +135,40 @@ A Ring routing app with input & output coercion using [data-specs](https://githu
|
||||||
Valid request:
|
Valid request:
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
(app {:request-method :get
|
(-> (app {:request-method :get
|
||||||
:uri "/api/math"
|
:uri "/api/math"
|
||||||
:query-params {:x "1", :y "2"}})
|
:query-params {:x "1", :y "2"}})
|
||||||
|
(update :body slurp))
|
||||||
; {:status 200
|
; {:status 200
|
||||||
; :body {:total 3}}
|
; :body "{\"total\":3}"
|
||||||
|
; :headers {"Content-Type" "application/json; charset=utf-8"}}
|
||||||
```
|
```
|
||||||
|
|
||||||
Invalid request:
|
Invalid request:
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
(app {:request-method :get
|
(-> (app {:request-method :get
|
||||||
:uri "/api/math"
|
:uri "/api/math"
|
||||||
:query-params {:x "1", :y "a"}})
|
:query-params {:x "1", :y "a"}})
|
||||||
;{:status 400,
|
(update :body jsonista.core/read-value))
|
||||||
; :body {:type :reitit.coercion/request-coercion,
|
; {:status 400
|
||||||
; :coercion :spec,
|
; :headers {"Content-Type" "application/json; charset=utf-8"}
|
||||||
; :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}})",
|
; :body {"spec" "(spec-tools.core/spec {:spec (clojure.spec.alpha/keys :req-un [:spec$8974/x :spec$8974/y]), :type :map, :leaf? false})"
|
||||||
; :problems [{:path [:y],
|
; "value" {"x" "1"
|
||||||
; :pred "clojure.core/int?",
|
; "y" "a"}
|
||||||
; :val "a",
|
; "problems" [{"via" ["spec$8974/y"]
|
||||||
; :via [:$spec20745/y],
|
; "path" ["y"]
|
||||||
; :in [:y]}],
|
; "pred" "clojure.core/int?"
|
||||||
; :value {:x "1", :y "a"},
|
; "in" ["y"]
|
||||||
; :in [:request :query-params]}}
|
; "val" "a"}]
|
||||||
|
; "type" "reitit.coercion/request-coercion"
|
||||||
|
; "coercion" "spec"
|
||||||
|
; "in" ["request" "query-params"]}}
|
||||||
```
|
```
|
||||||
|
|
||||||
## More examples
|
## More examples
|
||||||
|
|
||||||
* [`reitit-ring` with coercion, swagger and default middleware](https://github.com/metosin/reitit/blob/master/examples/ring-swagger/src/example/server.clj)
|
* [`reitit-ring` with coercion, swagger and default middleware](https://github.com/metosin/reitit/blob/master/examples/ring-malli-swagger/src/example/server.clj)
|
||||||
* [`reitit-frontend`, the easy way](https://github.com/metosin/reitit/blob/master/examples/frontend/src/frontend/core.cljs)
|
* [`reitit-frontend`, the easy way](https://github.com/metosin/reitit/blob/master/examples/frontend/src/frontend/core.cljs)
|
||||||
* [`reitit-frontend` with Keechma-style controllers](https://github.com/metosin/reitit/blob/master/examples/frontend-controllers/src/frontend/core.cljs)
|
* [`reitit-frontend` with Keechma-style controllers](https://github.com/metosin/reitit/blob/master/examples/frontend-controllers/src/frontend/core.cljs)
|
||||||
* [`reitit-http` with Pedestal](https://github.com/metosin/reitit/blob/master/examples/pedestal/src/example/server.clj)
|
* [`reitit-http` with Pedestal](https://github.com/metosin/reitit/blob/master/examples/pedestal/src/example/server.clj)
|
||||||
|
|
@ -144,9 +176,23 @@ Invalid request:
|
||||||
|
|
||||||
All examples are in https://github.com/metosin/reitit/tree/master/examples
|
All examples are in https://github.com/metosin/reitit/tree/master/examples
|
||||||
|
|
||||||
|
## External resources
|
||||||
|
* Simple web application using Ring/Reitit and Integrant: https://github.com/PrestanceDesign/usermanager-reitit-integrant-example
|
||||||
|
* A simple Clojure backend using Reitit to serve up a RESTful API: [startrek](https://github.com/dharrigan/startrek). Technologies include:
|
||||||
|
* [Donut System](https://github.com/donut-party/system)
|
||||||
|
* [next-jdbc](https://github.com/seancorfield/next-jdbc)
|
||||||
|
* [JUXT Clip](https://github.com/juxt/clip)
|
||||||
|
* [Flyway](https://github.com/flyway/flyway)
|
||||||
|
* [HoneySQL](https://github.com/seancorfield/honeysql)
|
||||||
|
* [Babashka](https://babashka.org)
|
||||||
|
* https://www.learnreitit.com/
|
||||||
|
* Lipas, liikuntapalvelut: https://github.com/lipas-liikuntapaikat/lipas
|
||||||
|
* Implementation of the Todo-Backend API spec, using Clojure, Ring/Reitit and next-jdbc: https://github.com/PrestanceDesign/todo-backend-clojure-reitit
|
||||||
|
* Ping CRM, a single page app written in Clojure Ring, Reitit, Integrant and next.jdbc: https://github.com/prestancedesign/clojure-inertia-pingcrm-demo
|
||||||
|
|
||||||
## More info
|
## More info
|
||||||
|
|
||||||
[Check out the full documentation!](https://metosin.github.io/reitit/)
|
[Check out the full documentation!](https://cljdoc.org/d/metosin/reitit/CURRENT/)
|
||||||
|
|
||||||
Join [#reitit](https://clojurians.slack.com/messages/reitit/) channel in [Clojurians slack](http://clojurians.net/).
|
Join [#reitit](https://clojurians.slack.com/messages/reitit/) channel in [Clojurians slack](http://clojurians.net/).
|
||||||
|
|
||||||
|
|
@ -163,6 +209,6 @@ Roadmap is mostly written in [issues](https://github.com/metosin/reitit/issues).
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Copyright © 2017-2020 [Metosin Oy](http://www.metosin.fi)
|
Copyright © 2017-2023 [Metosin Oy](http://www.metosin.fi)
|
||||||
|
|
||||||
Distributed under the Eclipse Public License, the same as Clojure.
|
Distributed under the Eclipse Public License, the same as Clojure.
|
||||||
|
|
|
||||||
26
bb.edn
Normal file
26
bb.edn
Normal 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"))}}}
|
||||||
3
dev-resources/public/site.webmanifest
Normal file
3
dev-resources/public/site.webmanifest
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"name": "Example"
|
||||||
|
}
|
||||||
1
dev-resources/public/with space.txt
Normal file
1
dev-resources/public/with space.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
hello
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
* Route [conflict resolution](./basics/route_conflicts.md)
|
* Route [conflict resolution](./basics/route_conflicts.md)
|
||||||
* First-class [route data](./basics/route_data.md)
|
* First-class [route data](./basics/route_data.md)
|
||||||
* Bi-directional routing
|
* Bi-directional routing
|
||||||
* [Pluggable coercion](./coercion/coercion.md) ([schema](https://github.com/plumatic/schema) & [clojure.spec](https://clojure.org/about/spec))
|
* [Pluggable coercion](./coercion/coercion.md) ([schema](https://github.com/plumatic/schema), [clojure.spec](https://clojure.org/about/spec), [malli](https://github.com/metosin/malli))
|
||||||
* Helpers for [ring](./ring/ring.md), [http](./http/interceptors.md), [pedestal](./http/pedestal.md) & [frontend](./frontend/basics.md)
|
* Helpers for [ring](./ring/ring.md), [http](./http/interceptors.md), [pedestal](./http/pedestal.md) & [frontend](./frontend/basics.md)
|
||||||
* Friendly [Error Messages](./basics/error_messages.md)
|
* Friendly [Error Messages](./basics/error_messages.md)
|
||||||
* Extendable
|
* Extendable
|
||||||
|
|
@ -24,6 +24,7 @@ There is [#reitit](https://clojurians.slack.com/messages/reitit/) in [Clojurians
|
||||||
* `reitit-spec` [clojure.spec](https://clojure.org/about/spec) coercion
|
* `reitit-spec` [clojure.spec](https://clojure.org/about/spec) coercion
|
||||||
* `reitit-schema` [Schema](https://github.com/plumatic/schema) coercion
|
* `reitit-schema` [Schema](https://github.com/plumatic/schema) coercion
|
||||||
* `reitit-swagger` [Swagger2](https://swagger.io/) apidocs
|
* `reitit-swagger` [Swagger2](https://swagger.io/) apidocs
|
||||||
|
* `reitit-openapi` OpenAPI 3 apidocs
|
||||||
* `reitit-swagger-ui` Integrated [Swagger UI](https://github.com/swagger-api/swagger-ui).
|
* `reitit-swagger-ui` Integrated [Swagger UI](https://github.com/swagger-api/swagger-ui).
|
||||||
* `reitit-frontend` Tools for [frontend routing](frontend/basics.md)
|
* `reitit-frontend` Tools for [frontend routing](frontend/basics.md)
|
||||||
* `reitit-http` http-routing with Pedestal-style Interceptors
|
* `reitit-http` http-routing with Pedestal-style Interceptors
|
||||||
|
|
@ -40,7 +41,7 @@ There is [#reitit](https://clojurians.slack.com/messages/reitit/) in [Clojurians
|
||||||
All bundled:
|
All bundled:
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
[metosin/reitit "0.5.0"]
|
[metosin/reitit "0.10.0"]
|
||||||
```
|
```
|
||||||
|
|
||||||
Optionally, the parts can be required separately.
|
Optionally, the parts can be required separately.
|
||||||
|
|
@ -110,9 +111,9 @@ Reverse-routing:
|
||||||
; :path "/api/orders/2"}
|
; :path "/api/orders/2"}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Ring-router
|
## Ring router
|
||||||
|
|
||||||
Ring-router adds support for `:handler` functions, `:middleware` and routing based on `:request-method`. It also supports pluggable parameter coercion (`clojure.spec`), data-driven middleware, route and middleware compilation, dynamic extensions and more.
|
A Ring router function adds support for `:handler` functions, `:middleware` and routing based on `:request-method`. It also supports pluggable parameter coercion (`clojure.spec`), data-driven middleware, route and middleware compilation, dynamic extensions and more.
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
(require '[reitit.ring :as ring])
|
(require '[reitit.ring :as ring])
|
||||||
|
|
@ -139,7 +140,7 @@ Routing:
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
(app {:request-method :get, :uri "/api/admin/users"})
|
(app {:request-method :get, :uri "/api/admin/users"})
|
||||||
; {:status 200, :body "ok", :wrap (:api :admin}
|
; {:status 200, :body "ok", :wrap (:api :admin)}
|
||||||
|
|
||||||
(app {:request-method :put, :uri "/api/admin/users"})
|
(app {:request-method :put, :uri "/api/admin/users"})
|
||||||
; nil
|
; nil
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,11 @@
|
||||||
* [Plumatic Schema](coercion/schema_coercion.md)
|
* [Plumatic Schema](coercion/schema_coercion.md)
|
||||||
* [Clojure.spec](coercion/clojure_spec_coercion.md)
|
* [Clojure.spec](coercion/clojure_spec_coercion.md)
|
||||||
* [Data-specs](coercion/data_spec_coercion.md)
|
* [Data-specs](coercion/data_spec_coercion.md)
|
||||||
|
* [Malli](coercion/malli_coercion.md)
|
||||||
|
|
||||||
## Ring
|
## Ring
|
||||||
|
|
||||||
* [Ring-router](ring/ring.md)
|
* [Ring Router](ring/ring.md)
|
||||||
* [Reverse-routing](ring/reverse_routing.md)
|
* [Reverse-routing](ring/reverse_routing.md)
|
||||||
* [Default handler](ring/default_handler.md)
|
* [Default handler](ring/default_handler.md)
|
||||||
* [Slash handler](ring/slash_handler.md)
|
* [Slash handler](ring/slash_handler.md)
|
||||||
|
|
@ -40,6 +41,7 @@
|
||||||
* [Route Data Validation](ring/route_data_validation.md)
|
* [Route Data Validation](ring/route_data_validation.md)
|
||||||
* [Compiling Middleware](ring/compiling_middleware.md)
|
* [Compiling Middleware](ring/compiling_middleware.md)
|
||||||
* [Swagger Support](ring/swagger.md)
|
* [Swagger Support](ring/swagger.md)
|
||||||
|
* [OpenAPI Support](ring/openapi.md)
|
||||||
* [RESTful form methods](ring/RESTful_form_methods.md)
|
* [RESTful form methods](ring/RESTful_form_methods.md)
|
||||||
|
|
||||||
## HTTP
|
## HTTP
|
||||||
|
|
|
||||||
|
|
@ -143,10 +143,10 @@ As the `Match` contains all the route data, we can create a new matching functio
|
||||||
(require '[clojure.string :as str])
|
(require '[clojure.string :as str])
|
||||||
|
|
||||||
(defn recursive-match-by-path [router path]
|
(defn recursive-match-by-path [router path]
|
||||||
(if-let [match (r/match-by-path router path)]
|
(when-let [match (r/match-by-path router path)]
|
||||||
(if-let [subrouter (-> match :data :router)]
|
(if-let [subrouter (-> match :data :router)]
|
||||||
(let [subpath (subs path (str/last-index-of (:template match) "/"))]
|
(let [subpath (subs path (str/last-index-of (:template match) "/"))]
|
||||||
(if-let [submatch (recursive-match-by-path subrouter subpath)]
|
(when-let [submatch (recursive-match-by-path subrouter subpath)]
|
||||||
(cons match submatch)))
|
(cons match submatch)))
|
||||||
(list match))))
|
(list match))))
|
||||||
```
|
```
|
||||||
|
|
@ -206,10 +206,10 @@ First, we need to modify our matching function to support router references:
|
||||||
(deref x) x))
|
(deref x) x))
|
||||||
|
|
||||||
(defn recursive-match-by-path [router path]
|
(defn recursive-match-by-path [router path]
|
||||||
(if-let [match (r/match-by-path (<< router) path)]
|
(when-let [match (r/match-by-path (<< router) path)]
|
||||||
(if-let [subrouter (-> match :data :router <<)]
|
(if-let [subrouter (-> match :data :router <<)]
|
||||||
(let [subpath (subs path (str/last-index-of (:template match) "/"))]
|
(let [subpath (subs path (str/last-index-of (:template match) "/"))]
|
||||||
(if-let [submatch (recursive-match-by-path subrouter subpath)]
|
(when-let [submatch (recursive-match-by-path subrouter subpath)]
|
||||||
(cons match submatch)))
|
(cons match submatch)))
|
||||||
(list match))))
|
(list match))))
|
||||||
```
|
```
|
||||||
|
|
@ -405,9 +405,53 @@ All the beer-routes now match in constant time.
|
||||||
|-----------------|---------|-----------------------
|
|-----------------|---------|-----------------------
|
||||||
| `/beers/sahti` | 40ns | static
|
| `/beers/sahti` | 40ns | static
|
||||||
|
|
||||||
|
### Wrapping a swappable ring handler
|
||||||
|
|
||||||
|
In order for a ring handler to be recomposed, we can wrap it into a handler that dereferences it on request.
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(defn deref-handler [rf]
|
||||||
|
(fn
|
||||||
|
([request] (@rf request))
|
||||||
|
([request respond raise] (@rf request respond raise))))
|
||||||
|
```
|
||||||
|
|
||||||
|
A simplified beer router version that creates a ring-handler.
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(defn create-ring-handler [beers]
|
||||||
|
(ring/ring-handler
|
||||||
|
(ring/router
|
||||||
|
[["/beers"
|
||||||
|
(when (seq beers)
|
||||||
|
(for [beer beers]
|
||||||
|
[(str "/" beer)
|
||||||
|
{:get (fn [_] {:status 200 :body beer})}]))]])))
|
||||||
|
|
||||||
|
(def ring-handler
|
||||||
|
(atom (create-ring-handler nil)))
|
||||||
|
|
||||||
|
(defn reset-router! [beers]
|
||||||
|
(reset! ring-handler (create-ring-handler beers)))
|
||||||
|
```
|
||||||
|
|
||||||
|
We don't have any matching routes yet.
|
||||||
|
|
||||||
|
```clj
|
||||||
|
((deref-handler ring-handler) {:request-method :get :uri "/beers/lager"})
|
||||||
|
; nil
|
||||||
|
```
|
||||||
|
|
||||||
|
But we can add them later.
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(reset-router! ["lager"])
|
||||||
|
((deref-handler ring-handler) {:request-method :get :uri "/beers/lager"})
|
||||||
|
; {:status 200, :body "lager"}
|
||||||
|
```
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
* add an example how to do dynamic routing with `reitit-ring`
|
|
||||||
* maybe create a `recursive-router` into a separate ns with all `Router` functions implemented correctly? maybe not...
|
* maybe create a `recursive-router` into a separate ns with all `Router` functions implemented correctly? maybe not...
|
||||||
* add `reitit.core/merge-routes` to effectively merge routes with route data
|
* add `reitit.core/merge-routes` to effectively merge routes with route data
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
Routers can be configured via options. The following options are available for the `reitit.core/router`:
|
Routers can be configured via options. The following options are available for the `reitit.core/router`:
|
||||||
|
|
||||||
| key | description
|
| key | description
|
||||||
|--------------|-------------
|
|-----------------|-------------
|
||||||
| `:path` | Base-path for routes
|
| `:path` | Base-path for routes
|
||||||
| `:routes` | Initial resolved routes (default `[]`)
|
| `:routes` | Initial resolved routes (default `[]`)
|
||||||
| `:data` | Initial route data (default `{}`)
|
| `:data` | Initial route data (default `{}`)
|
||||||
|
|
@ -15,4 +15,8 @@ Routers can be configured via options. The following options are available for t
|
||||||
| `:validate` | Function of `routes opts => ()` to validate route (data) via side-effects
|
| `:validate` | Function of `routes opts => ()` to validate route (data) via side-effects
|
||||||
| `:conflicts` | Function of `{route #{route}} => ()` to handle conflicting routes
|
| `:conflicts` | Function of `{route #{route}} => ()` to handle conflicting routes
|
||||||
| `:exception` | Function of `Exception => Exception ` to handle creation time exceptions (default `reitit.exception/exception`)
|
| `:exception` | Function of `Exception => Exception ` to handle creation time exceptions (default `reitit.exception/exception`)
|
||||||
|
| `:meta-merge` | Function of `left right => merged` to merge route-data (default `meta-merge.core/meta-merge`)
|
||||||
|
| `:update-paths` | Sequence of Vectors with elements `update-path` and `function`, used to preprocess route data
|
||||||
| `:router` | Function of `routes opts => router` to override the actual router implementation
|
| `:router` | Function of `routes opts => router` to override the actual router implementation
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 !
|
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.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Different Routers
|
# Different Routers
|
||||||
|
|
||||||
Reitit ships with several different implementations for the `Router` protocol, originally based on the [Pedestal](https://github.com/pedestal/pedestal/tree/master/route) implementation. `router` function selects the most suitable implementation by inspecting the expanded routes. The implementation can be set manually using `:router` option, see [configuring routers](advanced/configuring_routers.md).
|
Reitit ships with several different implementations for the `Router` protocol, originally based on the [Pedestal](https://github.com/pedestal/pedestal/tree/master/route) implementation. `router` function selects the most suitable implementation by inspecting the expanded routes. The implementation can be set manually using `:router` option, see [configuring routers](configuring_routers.md).
|
||||||
|
|
||||||
| router | description |
|
| router | description |
|
||||||
| ------------------------------|-------------|
|
| ------------------------------|-------------|
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ The default exception formatting uses `reitit.exception/exception`. It produces
|
||||||
## Pretty Errors
|
## Pretty Errors
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
[metosin/reitit-dev "0.5.0"]
|
[metosin/reitit-dev "0.10.0"]
|
||||||
```
|
```
|
||||||
|
|
||||||
For human-readable and developer-friendly exception messages, there is `reitit.dev.pretty/exception` (in the `reitit-dev` module). It is inspired by the lovely errors messages of [ELM](https://elm-lang.org/blog/compiler-errors-for-humans) and [ETA](https://twitter.com/jyothsnasrin/status/1037703436043603968) and uses [fipp](https://github.com/brandonbloom/fipp), [expound](https://github.com/bhb/expound) and [spell-spec](https://github.com/bhauman/spell-spec) for most of heavy lifting.
|
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.
|
||||||
|
|
@ -51,4 +51,4 @@ See the [validating route data](route_data_validation.md) page.
|
||||||
|
|
||||||
## Runtime Exception
|
## Runtime Exception
|
||||||
|
|
||||||
See [Exception Handling with Ring](exceptions.md).
|
See [Exception Handling with Ring](../ring/exceptions.md).
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,17 @@ Path-parameters are automatically coerced into strings, with the help of (curren
|
||||||
; :path-params {:id "1"}}
|
; :path-params {:id "1"}}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
In case you want to do something like generate a template path for documentation, you can disable url-encoding:
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(r/match-by-name router ::user {:id "<id goes here>"} {:url-encode? false})
|
||||||
|
; #reitit.core.Match{:template "/api/user/:id"
|
||||||
|
; :data {:name :user/user}
|
||||||
|
; :path "/api/user/<id goes here>"
|
||||||
|
; :result nil
|
||||||
|
; :path-params {:id "<id goes here>"}}
|
||||||
|
```
|
||||||
|
|
||||||
There is also an exception throwing version:
|
There is also an exception throwing version:
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
|
|
|
||||||
|
|
@ -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.
|
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
|
```clj
|
||||||
(require '[reitit.core :as r])
|
(require '[reitit.core :as r])
|
||||||
|
|
@ -79,11 +80,13 @@ Resolved route tree:
|
||||||
; :name :user/ping}]
|
; :name :user/ping}]
|
||||||
; ["/api/admin/users" {:interceptors [::api]
|
; ["/api/admin/users" {:interceptors [::api]
|
||||||
; :roles #{:admin}
|
; :roles #{:admin}
|
||||||
; :name ::users} nil]
|
; :name ::users}]
|
||||||
; ["/api/admin/db" {:interceptors [::api ::db]
|
; ["/api/admin/db" {:interceptors [::api ::db]
|
||||||
; :roles #{:db-admin}}]]
|
; :roles #{:db-admin}}]]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
See also [nested parameter definitions for coercions](../ring/coercion.md#nested-parameter-definitions)
|
||||||
|
|
||||||
## Route Data Fragments
|
## 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.
|
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.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
# Route Syntax
|
# 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.
|
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:
|
Simple route:
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
["/ping"]
|
["/ping" {:handler ping}]
|
||||||
```
|
```
|
||||||
|
|
||||||
Two routes:
|
Two routes with more data:
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
[["/ping"]
|
[["/ping" {:handler ping
|
||||||
["/pong"]]
|
: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
|
```clj
|
||||||
[["/ping" ::ping]
|
[["/users/:user-id" {:handler get-user}]
|
||||||
["/pong" {:name ::pong}]]
|
["/api/:version/ping" {:handler ping-version}]]
|
||||||
```
|
|
||||||
|
|
||||||
Routes with path parameters:
|
|
||||||
|
|
||||||
```clj
|
|
||||||
[["/users/:user-id"]
|
|
||||||
["/api/:version/ping"]]
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
[["/users/{user-id}"]
|
[["/users/{user-id}" {:handler get-user}]
|
||||||
["/files/file-{number}.pdf"]]
|
["/files/file-{number}.pdf" {:handler get-pdf}]]
|
||||||
```
|
```
|
||||||
|
|
||||||
Route with catch-all parameter:
|
Route with catch-all parameter:
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
["/public/*path"]
|
["/public/*path" {:handler get-file}]
|
||||||
```
|
```
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
["/public/{*path}"]
|
["/public/{*path}" {:handler get-file}]
|
||||||
```
|
```
|
||||||
|
|
||||||
Nested routes:
|
Nested routes:
|
||||||
|
|
@ -55,9 +53,9 @@ Nested routes:
|
||||||
```clj
|
```clj
|
||||||
["/api"
|
["/api"
|
||||||
["/admin" {:middleware [::admin]}
|
["/admin" {:middleware [::admin]}
|
||||||
["" ::admin]
|
["" {:name ::admin}]
|
||||||
["/db" ::db]]
|
["/db" {:name ::db}]]
|
||||||
["/ping" ::ping]]
|
["/ping" {:name ::ping}]]
|
||||||
```
|
```
|
||||||
|
|
||||||
Same routes flattened:
|
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:
|
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
|
```clj
|
||||||
[["/api/:version"]
|
[["/api/:version" {...}]
|
||||||
["/files/file-:number"]
|
["/files/file-:number" {...}]
|
||||||
["/user/:user-id/orders"]]
|
["/user/:user-id/orders" {...}]]
|
||||||
```
|
```
|
||||||
|
|
||||||
Bracket path-parameters can start and stop anywhere in the path-string, the following character is used as a terminator.
|
Bracket path-parameters can start and stop anywhere in the path-string, the following character is used as a terminator.
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
[["/api/{version}"]
|
[["/api/{version}" {...}]
|
||||||
["/files/{name}.{extension}"]
|
["/files/{name}.{extension}" {...}]
|
||||||
["/user/{user-id}/orders"]]
|
["/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:
|
Having multiple terminators after a bracket path-path parameter with identical path prefix will cause a compile-time error at router creation:
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
[["/files/file-{name}.pdf"] ;; terminator \.
|
[["/files/file-{name}.pdf" {...}] ;; terminator \.
|
||||||
["/files/file-{name}-{version}.pdf"]] ;; terminator \-
|
["/files/file-{name}-{version}.pdf" {...}]] ;; terminator \-
|
||||||
```
|
```
|
||||||
|
|
||||||
### Slash Free Routing
|
### Slash Free Routing
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
[["broker.{customer}.{device}.{*data}"]
|
[["broker.{customer}.{device}.{*data}" {...}]
|
||||||
["events.{target}.{type}"]]
|
["events.{target}.{type}" {...}]]
|
||||||
```
|
```
|
||||||
|
|
||||||
### Generating routes
|
### Generating routes
|
||||||
|
|
@ -132,7 +130,7 @@ Routes are just data, so it's easy to create them programmatically:
|
||||||
|
|
||||||
### Explicit path-parameter syntax
|
### Explicit path-parameter syntax
|
||||||
|
|
||||||
Router options `:syntax` allows the path-parameter syntax to be explicitely defined. It takes a keyword or set of keywords as a value. Valid values are `:colon` and `:bracket`. Default value is `#{:colon :bracket}`.
|
Router options `:syntax` allows the path-parameter syntax to be explicitly defined. It takes a keyword or set of keywords as a value. Valid values are `:colon` and `:bracket`. Default value is `#{:colon :bracket}`.
|
||||||
|
|
||||||
With defaults:
|
With defaults:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,14 +63,6 @@ Route names:
|
||||||
; [:user/ping :user/user]
|
; [:user/ping :user/user]
|
||||||
```
|
```
|
||||||
|
|
||||||
The compiled route tree:
|
|
||||||
|
|
||||||
```clj
|
|
||||||
(r/routes router)
|
|
||||||
; [["/api/ping" {:name :user/ping} nil]
|
|
||||||
; ["/api/user/:id" {:name :user/user} nil]]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Composing
|
### Composing
|
||||||
|
|
||||||
As routes are defined as plain data, it's easy to merge multiple route trees into a single router
|
As routes are defined as plain data, it's easy to merge multiple route trees into a single router
|
||||||
|
|
@ -85,9 +77,10 @@ As routes are defined as plain data, it's easy to merge multiple route trees int
|
||||||
["/ping" ::ping]
|
["/ping" ::ping]
|
||||||
["/db" ::db]])
|
["/db" ::db]])
|
||||||
|
|
||||||
(r/router
|
(def router
|
||||||
|
(r/router
|
||||||
[admin-routes
|
[admin-routes
|
||||||
user-routes])
|
user-routes]))
|
||||||
```
|
```
|
||||||
|
|
||||||
Merged route tree:
|
Merged route tree:
|
||||||
|
|
@ -109,6 +102,6 @@ When router is created, the following steps are done:
|
||||||
* route arguments are expanded (via `:expand` option)
|
* route arguments are expanded (via `:expand` option)
|
||||||
* routes are coerced (via `:coerce` options)
|
* routes are coerced (via `:coerce` options)
|
||||||
* route tree is compiled (via `:compile` options)
|
* route tree is compiled (via `:compile` options)
|
||||||
* [route conflicts](advanced/route_conflicts.md) are resolved (via `:conflicts` options)
|
* [route conflicts](route_conflicts.md) are resolved (via `:conflicts` options)
|
||||||
* optionally, route data is validated (via `:validate` options)
|
* optionally, route data is validated (via `:validate` options)
|
||||||
* [router implementation](../advanced/different_routers.md) is automatically selected (or forced via `:router` options) and created
|
* [router implementation](../advanced/different_routers.md) is automatically selected (or forced via `:router` options) and created
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,8 @@
|
||||||
metosin/reitit-swagger-ui
|
metosin/reitit-swagger-ui
|
||||||
metosin/reitit-frontend
|
metosin/reitit-frontend
|
||||||
metosin/reitit-sieppari
|
metosin/reitit-sieppari
|
||||||
metosin/reitit-pedestal]
|
metosin/reitit-pedestal
|
||||||
|
fi.metosin/reitit-openapi]
|
||||||
:cljdoc.doc/tree
|
:cljdoc.doc/tree
|
||||||
[["Introduction" {:file "doc/README.md"}]
|
[["Introduction" {:file "doc/README.md"}]
|
||||||
["Basics" {}
|
["Basics" {}
|
||||||
|
|
@ -28,9 +29,10 @@
|
||||||
["Coercion Explained" {:file "doc/coercion/coercion.md"}]
|
["Coercion Explained" {:file "doc/coercion/coercion.md"}]
|
||||||
["Plumatic Schema" {:file "doc/coercion/schema_coercion.md"}]
|
["Plumatic Schema" {:file "doc/coercion/schema_coercion.md"}]
|
||||||
["Clojure.spec" {:file "doc/coercion/clojure_spec_coercion.md"}]
|
["Clojure.spec" {:file "doc/coercion/clojure_spec_coercion.md"}]
|
||||||
["Data-specs" {:file "doc/coercion/data_spec_coercion.md"}]]
|
["Data-specs" {:file "doc/coercion/data_spec_coercion.md"}]
|
||||||
|
["Malli" {:file "doc/coercion/malli_coercion.md"}]]
|
||||||
["Ring" {}
|
["Ring" {}
|
||||||
["Ring-router" {:file "doc/ring/ring.md"}]
|
["Ring Router" {:file "doc/ring/ring.md"}]
|
||||||
["Reverse-routing" {:file "doc/ring/reverse_routing.md"}]
|
["Reverse-routing" {:file "doc/ring/reverse_routing.md"}]
|
||||||
["Default handler" {:file "doc/ring/default_handler.md"}]
|
["Default handler" {:file "doc/ring/default_handler.md"}]
|
||||||
["Slash handler" {:file "doc/ring/slash_handler.md"}]
|
["Slash handler" {:file "doc/ring/slash_handler.md"}]
|
||||||
|
|
@ -46,6 +48,7 @@
|
||||||
["Route Data Validation" {:file "doc/ring/route_data_validation.md"}]
|
["Route Data Validation" {:file "doc/ring/route_data_validation.md"}]
|
||||||
["Compiling Middleware" {:file "doc/ring/compiling_middleware.md"}]
|
["Compiling Middleware" {:file "doc/ring/compiling_middleware.md"}]
|
||||||
["Swagger Support" {:file "doc/ring/swagger.md"}]
|
["Swagger Support" {:file "doc/ring/swagger.md"}]
|
||||||
|
["OpenAPI Support" {:file "doc/ring/openapi.md"}]
|
||||||
["RESTful form methods" {:file "doc/ring/RESTful_form_methods.md"}]]
|
["RESTful form methods" {:file "doc/ring/RESTful_form_methods.md"}]]
|
||||||
["HTTP" {}
|
["HTTP" {}
|
||||||
["Interceptors" {:file "doc/http/interceptors.md"}]
|
["Interceptors" {:file "doc/http/interceptors.md"}]
|
||||||
|
|
|
||||||
|
|
@ -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).
|
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
|
## Example
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Data-spec Coercion
|
# 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
|
```clj
|
||||||
(require '[reitit.coercion.spec])
|
(require '[reitit.coercion.spec])
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,10 @@
|
||||||
|
|
||||||
[Malli](https://github.com/metosin/malli) is data-driven Schema library for Clojure/Script.
|
[Malli](https://github.com/metosin/malli) is data-driven Schema library for Clojure/Script.
|
||||||
|
|
||||||
|
## Default Syntax
|
||||||
|
|
||||||
|
By default, [Vector Syntax](https://github.com/metosin/malli#vector-syntax) is used:
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
(require '[reitit.coercion.malli])
|
(require '[reitit.coercion.malli])
|
||||||
(require '[reitit.coercion :as coercion])
|
(require '[reitit.coercion :as coercion])
|
||||||
|
|
@ -13,7 +17,7 @@
|
||||||
:coercion reitit.coercion.malli/coercion
|
:coercion reitit.coercion.malli/coercion
|
||||||
:parameters {:path [:map
|
:parameters {:path [:map
|
||||||
[:company string?]
|
[:company string?]
|
||||||
[:user-id int?]]}]
|
[:user-id int?]]}}]
|
||||||
{:compile coercion/compile-request-coercers}))
|
{:compile coercion/compile-request-coercers}))
|
||||||
|
|
||||||
(defn match-by-path-and-coerce! [path]
|
(defn match-by-path-and-coerce! [path]
|
||||||
|
|
@ -43,3 +47,78 @@ Failing coercion:
|
||||||
(match-by-path-and-coerce! "/metosin/users/ikitommi")
|
(match-by-path-and-coerce! "/metosin/users/ikitommi")
|
||||||
; => ExceptionInfo Request coercion failed...
|
; => ExceptionInfo Request coercion failed...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Lite Syntax
|
||||||
|
|
||||||
|
Same using [Lite Syntax](https://github.com/metosin/malli#lite):
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(def router
|
||||||
|
(r/router
|
||||||
|
["/:company/users/:user-id" {:name ::user-view
|
||||||
|
:coercion reitit.coercion.malli/coercion
|
||||||
|
:parameters {:path {:company string?
|
||||||
|
:user-id int?}}}]
|
||||||
|
{:compile coercion/compile-request-coercers}))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuring coercion
|
||||||
|
|
||||||
|
Using `create` with options to create the coercion instead of `coercion`:
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(require '[malli.util :as mu])
|
||||||
|
|
||||||
|
(reitit.coercion.malli/create
|
||||||
|
{: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
|
||||||
|
: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}
|
||||||
|
;; support lite syntax?
|
||||||
|
:lite true
|
||||||
|
;; schema identity function (default: close all map schemas)
|
||||||
|
:compile mu/closed-schema
|
||||||
|
;; validate request & response
|
||||||
|
:validate true
|
||||||
|
;; top-level short-circuit to disable request & response coercion
|
||||||
|
:enabled true
|
||||||
|
;; 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})}}})
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -13,30 +13,47 @@
|
||||||
./scripts/test.sh cljs
|
./scripts/test.sh cljs
|
||||||
```
|
```
|
||||||
|
|
||||||
## Documentation
|
## Formatting
|
||||||
|
|
||||||
The documentation is built with [gitbook](https://toolchain.gitbook.com). To preview your changes locally:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install -g gitbook-cli
|
clojure-lsp format
|
||||||
gitbook install
|
clojure-lsp clean-ns
|
||||||
gitbook serve
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## To bump up version:
|
## Documentation
|
||||||
|
|
||||||
|
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!
|
We use [Break Versioning][breakver]. Remember our promise: patch-level bumps never include breaking changes!
|
||||||
|
|
||||||
[breakver]: https://github.com/ptaoussanis/encore/blob/master/BREAK-VERSIONING.md
|
[breakver]: https://github.com/ptaoussanis/encore/blob/master/BREAK-VERSIONING.md
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# new version
|
# create a release commit
|
||||||
./scripts/set-version "1.0.0"
|
./scripts/set-version "1.0.0"
|
||||||
./scripts/lein-modules install
|
|
||||||
|
|
||||||
# works
|
# !!! update the changelog
|
||||||
lein test
|
|
||||||
|
|
||||||
# deploy to clojars
|
git add -u
|
||||||
./scripts/lein-modules do clean, deploy clojars
|
git commit -m "Release 1.0.0"
|
||||||
|
|
||||||
|
# push the commit
|
||||||
|
git push
|
||||||
|
|
||||||
|
# !!! check that tests pass on CI
|
||||||
```
|
```
|
||||||
|
|
||||||
|
* 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.
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,13 @@ history events
|
||||||
- Stateful wrapper for easy use of history integration
|
- Stateful wrapper for easy use of history integration
|
||||||
- Optional [controller extension](./controllers.md)
|
- 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
|
## Core functions
|
||||||
|
|
||||||
`reitit.frontend` provides few useful functions wrapping core functions:
|
`reitit.frontend` provides some useful functions wrapping core functions:
|
||||||
|
|
||||||
`match-by-path` version which parses a URI using JavaScript, including
|
`match-by-path` version which parses a URI using JavaScript, including
|
||||||
query-string, and also [coerces the parameters](../coercion/coercion.md).
|
query-string, and also [coerces the parameters](../coercion/coercion.md).
|
||||||
|
|
@ -23,7 +27,8 @@ enabled.
|
||||||
|
|
||||||
`match-by-name` and `match-by-name!` with optional `path-paramers` and
|
`match-by-name` and `match-by-name!` with optional `path-paramers` and
|
||||||
logging errors to `console.warn` instead of throwing errors to prevent
|
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
|
## Next
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,19 @@
|
||||||
|
|
||||||
Reitit includes two browser history integrations.
|
Reitit includes two browser history integrations.
|
||||||
|
|
||||||
Functions follow HTML5 History API: `push-state` to change route, `replace-state`
|
Main functions are `navigate` and `set-query`. Navigate is used to navigate
|
||||||
to change route without leaving previous entry in browser history.
|
to named routes, and the options parameter can be used to control all
|
||||||
|
parameters and if `pushState` or `replaceState` should be used to control
|
||||||
|
browser history stack. The `set-query` function can be used to change
|
||||||
|
or modify query parameters for the current route, it takes either map of
|
||||||
|
new query params or function from old params to the new params.
|
||||||
|
|
||||||
|
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 router
|
||||||
|
|
||||||
|
|
@ -30,6 +41,7 @@ event handling:
|
||||||
```clj
|
```clj
|
||||||
(rfe/start!
|
(rfe/start!
|
||||||
router
|
router
|
||||||
|
on-navigate-fn
|
||||||
{:use-fragment false
|
{:use-fragment false
|
||||||
:ignore-anchor-click? (fn [router e el uri]
|
:ignore-anchor-click? (fn [router e el uri]
|
||||||
;; Add additional check on top of the default checks
|
;; Add additional check on top of the default checks
|
||||||
|
|
@ -53,7 +65,7 @@ event handler for page change events.
|
||||||
|
|
||||||
## History manipulation
|
## 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:
|
go back or forwards, but calling History API functions directly should work:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
|
||||||
59
doc/frontend/coercion.md
Normal file
59
doc/frontend/coercion.md
Normal 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}}}
|
||||||
|
```
|
||||||
|
|
@ -77,9 +77,9 @@ route data is merged. Consider this route tree:
|
||||||
```cljs
|
```cljs
|
||||||
["/" {:controllers [{:start (fn [_] (js/console.log "root start"))}]}
|
["/" {:controllers [{:start (fn [_] (js/console.log "root start"))}]}
|
||||||
["/item/:id"
|
["/item/:id"
|
||||||
{:controllers [{:params (fn [match] (get-in match [:path-params :id]))
|
{:controllers [{:parameters {:path [:id]}
|
||||||
:start (fn [item-id] (js/console.log "item start" item-id))
|
:start (fn [parameters] (js/console.log "item start" (-> parameters :path :id)))
|
||||||
:stop (fn [item-id] (js/console.log "item stop" item-id))}]}]]
|
:stop (fn [parameters] (js/console.log "item stop" (-> parameters :path :id)))}]}]]
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# Default Interceptors
|
# Default Interceptors
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
[metosin/reitit-interceptors "0.5.0"]
|
[metosin/reitit-interceptors "0.10.0"]
|
||||||
```
|
```
|
||||||
|
|
||||||
Just like the [ring default middleware](../ring/default_middleware.md), but for interceptors.
|
Just like the [ring default middleware](../ring/default_middleware.md), but for interceptors.
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
# Interceptors
|
# Interceptors
|
||||||
|
|
||||||
Reitit also support for [interceptors](http://pedestal.io/reference/interceptors) as an alternative to using middleware. Basic interceptor handling is implemented in `reitit.interceptor` package. There is no interceptor executor shipped, but you can use libraries like [Pedestal Interceptor](https://github.com/pedestal/pedestal/tree/master/interceptor) or [Sieppari](https://github.com/metosin/sieppari) to execute the chains.
|
Reitit has also support for [interceptors](http://pedestal.io/reference/interceptors) as an alternative to using middleware. Basic interceptor handling is implemented in `reitit.interceptor` package. There is no interceptor executor shipped, but you can use libraries like [Pedestal Interceptor](https://github.com/pedestal/pedestal/tree/master/interceptor) or [Sieppari](https://github.com/metosin/sieppari) to execute the chains.
|
||||||
|
|
||||||
## Reitit-http
|
## Reitit-http
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
[metosin/reitit-http "0.5.0"]
|
[metosin/reitit-http "0.10.0"]
|
||||||
```
|
```
|
||||||
|
|
||||||
An 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.
|
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.
|
||||||
|
|
||||||
The differences:
|
The differences:
|
||||||
|
|
||||||
* `:interceptors` key in used in route data instead of `:middleware`
|
* `:interceptors` key used in route data instead of `:middleware`
|
||||||
* `reitit.http/http-router` requires an extra option `:executor` of type `reitit.interceptor/Executor` to execute the interceptor chain
|
* `reitit.http/http-router` requires an extra option `:executor` of type `reitit.interceptor/Executor` to execute the interceptor chain
|
||||||
* optionally, a routing interceptor can be used - it enqueues the matched interceptors into the context. See `reitit.http/routing-interceptor` for details.
|
* optionally, a routing interceptor can be used - it enqueues the matched interceptors into the context. See `reitit.http/routing-interceptor` for details.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
[Pedestal](http://pedestal.io/) is a backend web framework for Clojure. `reitit-pedestal` provides an alternative routing engine for Pedestal.
|
[Pedestal](http://pedestal.io/) is a backend web framework for Clojure. `reitit-pedestal` provides an alternative routing engine for Pedestal.
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
[metosin/reitit-pedestal "0.5.0"]
|
[metosin/reitit-pedestal "0.10.0"]
|
||||||
```
|
```
|
||||||
|
|
||||||
Why should one use reitit instead of the Pedestal [default routing](http://pedestal.io/reference/routing-quick-reference)?
|
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
|
```clj
|
||||||
; [io.pedestal/pedestal.service "0.5.5"]
|
; [io.pedestal/pedestal.service "0.5.5"]
|
||||||
; [io.pedestal/pedestal.jetty "0.5.5"]
|
; [io.pedestal/pedestal.jetty "0.5.5"]
|
||||||
; [metosin/reitit-pedestal "0.5.0"]
|
; [metosin/reitit-pedestal "0.10.0"]
|
||||||
; [metosin/reitit "0.5.0"]
|
; [metosin/reitit "0.10.0"]
|
||||||
|
|
||||||
(require '[io.pedestal.http :as server])
|
(require '[io.pedestal.http :as server])
|
||||||
(require '[reitit.pedestal :as pedestal])
|
(require '[reitit.pedestal :as pedestal])
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# Sieppari
|
# Sieppari
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
[metosin/reitit-sieppari "0.5.0"]
|
[metosin/reitit-sieppari "0.10.0"]
|
||||||
```
|
```
|
||||||
|
|
||||||
[Sieppari](https://github.com/metosin/sieppari) is a new and fast interceptor implementation for Clojure, with pluggable async supporting [core.async](https://github.com/clojure/core.async), [Manifold](https://github.com/ztellman/manifold) and [Promesa](http://funcool.github.io/promesa/latest).
|
[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).
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ There is an extra option in http-router (actually, in the underlying interceptor
|
||||||
### Printing Context Diffs
|
### Printing Context Diffs
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
[metosin/reitit-interceptors "0.5.0"]
|
[metosin/reitit-interceptors "0.10.0"]
|
||||||
```
|
```
|
||||||
|
|
||||||
Using `reitit.http.interceptors.dev/print-context-diffs` transformation, the context diffs between each interceptor are printed out to the console. To use it, add the following router option:
|
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
BIN
doc/images/reitit.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 127 KiB |
|
|
@ -76,7 +76,7 @@ The routing sample taken from [bide](https://github.com/funcool/bide) README:
|
||||||
(r/match-by-path routes "/workspace/1/1")))
|
(r/match-by-path routes "/workspace/1/1")))
|
||||||
```
|
```
|
||||||
|
|
||||||
Based on the [perf tests](https://github.com/metosin/reitit/tree/master/perf-test/clj/reitit/perf/bide_perf_test.clj), the first (static path) lookup is 300-500x faster and the second (wildcard path) lookup is 18-110x faster that the other tested routing libs (Ataraxy, Bidi, Compojure and Pedestal).
|
Based on the [perf tests](https://github.com/metosin/reitit/blob/master/perf-test/clj/reitit/bide_perf_test.clj), the first (static path) lookup is 300-500x faster and the second (wildcard path) lookup is 18-110x faster that the other tested routing libs (Ataraxy, Bidi, Compojure and Pedestal).
|
||||||
|
|
||||||
But, the example is too simple for any real benchmark. Also, some of the libraries always match on the `:request-method` too and by doing so, do more work than just match by path. Compojure does most work also by invoking the handler.
|
But, the example is too simple for any real benchmark. Also, some of the libraries always match on the `:request-method` too and by doing so, do more work than just match by path. Compojure does most work also by invoking the handler.
|
||||||
|
|
||||||
|
|
@ -114,7 +114,7 @@ A quick poke to [the fast routers in Go](https://github.com/julienschmidt/go-htt
|
||||||
|
|
||||||
### Faster!
|
### Faster!
|
||||||
|
|
||||||
By default, `reitit.ring/ring-router`, `reitit.http/ring-router` and `reitit.http/routing-interceptor` inject both `Match` and `Router` into the request. You can remove the injections setting options `:inject-match?` and `:inject-router?` to `false`. This saves some tens of nanos (with the hw described above).
|
By default, `reitit.ring/router`, `reitit.http/router` and `reitit.http/routing-interceptor` inject both `Match` and `Router` into the request. You can remove the injections setting options `:inject-match?` and `:inject-router?` to `false`. This saves some tens of nanos (with the hw described above).
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
(require '[reitit.ring :as ring])
|
(require '[reitit.ring :as ring])
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,10 @@ We can do this with middleware in reitit like this:
|
||||||
```clj
|
```clj
|
||||||
(defn- hidden-method
|
(defn- hidden-method
|
||||||
[request]
|
[request]
|
||||||
(keyword
|
(some-> (or (get-in request [:form-params "_method"]) ;; look for "_method" field in :form-params
|
||||||
(or (get-in request [:form-params "_method"]) ;; look for "_method" field in :form-params
|
(get-in request [:multipart-params "_method"])) ;; or in :multipart-params
|
||||||
(get-in request [:multipart-params "_method"])))) ;; or in :multipart-params
|
clojure.string/lower-case
|
||||||
|
keyword))
|
||||||
|
|
||||||
(def wrap-hidden-method
|
(def wrap-hidden-method
|
||||||
{:name ::wrap-hidden-method
|
{:name ::wrap-hidden-method
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,14 @@ Basic coercion is explained in detail [in the Coercion Guide](../coercion/coerci
|
||||||
The following request parameters are currently supported:
|
The following request parameters are currently supported:
|
||||||
|
|
||||||
| type | request source |
|
| type | request source |
|
||||||
|-----------|------------------|
|
|--------------|--------------------------------------------------|
|
||||||
| `:query` | `:query-params` |
|
| `:query` | `:query-params` |
|
||||||
| `:body` | `:body-params` |
|
| `:body` | `:body-params` |
|
||||||
|
| `:request` | `:body-params`, allows per-content-type coercion |
|
||||||
| `:form` | `:form-params` |
|
| `:form` | `:form-params` |
|
||||||
| `:header` | `:header-params` |
|
| `:header` | `:header-params` |
|
||||||
| `:path` | `:path-params` |
|
| `:path` | `:path-params` |
|
||||||
|
| `:multipart` | `:multipart-params`, see [Default Middleware](default_middleware.md) |
|
||||||
|
|
||||||
To enable coercion, the following things need to be done:
|
To enable coercion, the following things need to be done:
|
||||||
|
|
||||||
|
|
@ -35,11 +37,11 @@ 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.
|
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`.
|
Below is an example with [Plumatic Schema](https://github.com/plumatic/schema). It defines schemas for `:query`, `:body` and `:path` parameters and for http 200 response `:body`.
|
||||||
|
|
||||||
Handler can access the coerced parameters can be read under `:parameters` key in the request.
|
Handlers can access the coerced parameters via the `:parameters` key in the request.
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
(require '[reitit.coercion.schema])
|
(require '[reitit.coercion.schema])
|
||||||
|
|
@ -52,7 +54,8 @@ Handler can access the coerced parameters can be read under `:parameters` key in
|
||||||
:parameters {:query {:x s/Int}
|
:parameters {:query {:x s/Int}
|
||||||
:body {:y s/Int}
|
:body {:y s/Int}
|
||||||
:path {:z 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]}]
|
:handler (fn [{:keys [parameters]}]
|
||||||
(let [total (+ (-> parameters :query :x)
|
(let [total (+ (-> parameters :query :x)
|
||||||
(-> parameters :body :y)
|
(-> parameters :body :y)
|
||||||
|
|
@ -60,6 +63,54 @@ Handler can access the coerced parameters can be read under `:parameters` key in
|
||||||
{:status 200
|
{:status 200
|
||||||
:body {:total total}}))})
|
:body {:total total}}))})
|
||||||
```
|
```
|
||||||
|
### Nested parameter definitions
|
||||||
|
|
||||||
|
Parameters are accumulated recursively along the route tree, just like
|
||||||
|
other [route data](../basics/route_data.md). There is special case
|
||||||
|
handling for merging eg. malli `:map` schemas.
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(def router
|
||||||
|
(reitit.ring/router
|
||||||
|
["/api" {:get {:parameters {:query [:map [:api-key :string]]}}}
|
||||||
|
["/project/:project-id" {:get {:parameters {:path [:map [:project-id :int]]}}}
|
||||||
|
["/task/:task-id" {:get {:parameters {:path [:map [:task-id :int]]
|
||||||
|
:query [:map [:details :boolean]]}
|
||||||
|
:handler (fn [req] (prn req))}}]]]
|
||||||
|
{:data {:coercion reitit.coercion.malli/coercion}}))
|
||||||
|
```
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(-> (r/match-by-path router "/api/project/1/task/2") :result :get :data :parameters)
|
||||||
|
; {:query [:map
|
||||||
|
; {:closed true}
|
||||||
|
; [:api-key :string]
|
||||||
|
; [:details :boolean]],
|
||||||
|
; :path [:map
|
||||||
|
; {:closed true}
|
||||||
|
; [:project-id :int]
|
||||||
|
; [:task-id :int]]}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Differences in behaviour for different parameters
|
||||||
|
|
||||||
|
All parameter coercions *except* `:body`:
|
||||||
|
|
||||||
|
1. Allow keys outside the schema (by opening up the schema using eg. `malli.util/open-schema`)
|
||||||
|
2. Keywordize the keys (ie. header & query parameter names) of the input before coercing
|
||||||
|
|
||||||
|
In contrast, the `:body` coercion:
|
||||||
|
|
||||||
|
1. Uses the specified schema
|
||||||
|
* depending on the coercion, it can be configured as open or closed, see specific coercion docs for details
|
||||||
|
2. Does not keywordize the keys of the input before coercion
|
||||||
|
* however, coercions like malli might do the keywordization when coercing json bodies, depending on configuration
|
||||||
|
|
||||||
|
This admittedly confusing behaviour is retained currently due to
|
||||||
|
backwards compatibility reasons. It can be configured by passing
|
||||||
|
option `:reitit.coercion/parameter-coercion` to `reitit.ring/router`
|
||||||
|
or `reitit.coercion/compile-request-coercers`. See also:
|
||||||
|
`reitit.coercion/default-parameter-coercion`.
|
||||||
|
|
||||||
## Coercion Middleware
|
## Coercion Middleware
|
||||||
|
|
||||||
|
|
@ -71,7 +122,7 @@ Defining a coercion for a route data doesn't do anything, as it's just data. We
|
||||||
|
|
||||||
### Full example
|
### Full example
|
||||||
|
|
||||||
Here's an full example for applying coercion with Reitit, Ring and Schema:
|
Here is a full example for applying coercion with Reitit, Ring and Schema:
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
(require '[reitit.ring.coercion :as rrc])
|
(require '[reitit.ring.coercion :as rrc])
|
||||||
|
|
@ -148,9 +199,56 @@ Invalid response:
|
||||||
; :in [:response :body]}}
|
; :in [:response :body]}}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Per-content-type coercion
|
||||||
|
|
||||||
|
You can also specify request and response body schemas per
|
||||||
|
content-type. These are also read by the [OpenAPI
|
||||||
|
feature](./openapi.md) when generating api docs. The syntax for this
|
||||||
|
is:
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(def app
|
||||||
|
(ring/ring-handler
|
||||||
|
(ring/router
|
||||||
|
["/api"
|
||||||
|
["/example" {:post {:coercion reitit.coercion.schema/coercion
|
||||||
|
:request {:content {"application/json" {:schema {:y s/Int}}
|
||||||
|
"application/edn" {:schema {:z s/Int}}
|
||||||
|
;; default if no content-type matches:
|
||||||
|
:default {:schema {:yy s/Int}}}}
|
||||||
|
:responses {200 {:content {"application/json" {:schema {:w s/Int}}
|
||||||
|
"application/edn" {:schema {:x s/Int}}
|
||||||
|
:default {:schema {:ww s/Int}}}}}
|
||||||
|
:handler ...}}]]
|
||||||
|
{: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
|
## Pretty printing spec errors
|
||||||
|
|
||||||
Spec problems are exposed as-is into request & response coercion errors, enabling pretty-printers like [expound](https://github.com/bhb/expound) to be used:
|
Spec problems are exposed as is in request & response coercion errors. Pretty-printers like [expound](https://github.com/bhb/expound) can be enabled like this:
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
(require '[reitit.ring :as ring])
|
(require '[reitit.ring :as ring])
|
||||||
|
|
@ -216,7 +314,7 @@ Spec problems are exposed as-is into request & response coercion errors, enablin
|
||||||
|
|
||||||
### Optimizations
|
### Optimizations
|
||||||
|
|
||||||
The coercion middleware are [compiled against a route](compiling_middleware.md). In the middleware compilation step the actual coercer implementations are constructed for the defined models. Also, the middleware doesn't mount itself if a route doesn't have `:coercion` and `:parameters` or `:responses` defined.
|
The coercion middlewares are [compiled against a route](compiling_middleware.md). In the middleware compilation step the actual coercer implementations are constructed for the defined models. Also, the middleware doesn't mount itself if a route doesn't have `:coercion` and `:parameters` or `:responses` defined.
|
||||||
|
|
||||||
We can query the compiled middleware chain for the routes:
|
We can query the compiled middleware chain for the routes:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
# Compiling Middleware
|
# Compiling Middleware
|
||||||
|
|
||||||
The [dynamic extensions](dynamic_extensions.md) is a easy way to extend the system. To enable fast lookups into route data, we can compile them into any shape (records, functions etc.) we want, enabling fast access at request-time.
|
The [dynamic extensions](dynamic_extensions.md) are an easy way to extend the system. To enable fast lookup of route data, we can compile them into any shape (records, functions etc.), enabling fast access at request-time.
|
||||||
|
|
||||||
But, we can do much better. As we know the exact route that middleware/interceptor is linked to, we can pass the (compiled) route information into the middleware at creation-time. It can do local reasoning: extract and transform relevant data just for it and pass the optimized data into the actual request-handler via a closure - yielding much faster runtime processing. Middleware can also decide not to mount itself by returning `nil`. Why mount a `wrap-enforce-roles` middleware for a route if there are no roles required for it?
|
But, we can do much better. As we know the exact route that a middleware/interceptor is linked to, we can pass the (compiled) route information into the middleware at creation-time. It can do local reasoning: Extract and transform relevant data just for it and pass the optimized data into the actual request-handler via a closure - yielding much faster runtime processing. A middleware can also decide not to mount itself by returning `nil`. (E.g. Why mount a `wrap-enforce-roles` middleware for a route if there are no roles required for it?)
|
||||||
|
|
||||||
To enable this we use [middleware records](data_driven_middleware.md) `:compile` key instead of the normal `:wrap`. `:compile` expects a function of `route-data router-opts => ?IntoMiddleware`.
|
To enable this we use [middleware records](data_driven_middleware.md) `:compile` key instead of the normal `:wrap`. `:compile` expects a function of `route-data router-opts => ?IntoMiddleware`.
|
||||||
|
|
||||||
To demonstrate the two approaches, below are response coercion middleware written as normal ring middleware function and as middleware record with `:compile`.
|
To demonstrate the two approaches, below is the response coercion middleware written as normal ring middleware function and as middleware record with `:compile`.
|
||||||
|
|
||||||
## Normal Middleware
|
## Normal Middleware
|
||||||
|
|
||||||
|
|
@ -27,8 +27,8 @@ To demonstrate the two approaches, below are response coercion middleware writte
|
||||||
coercion (-> match :data :coercion)
|
coercion (-> match :data :coercion)
|
||||||
opts (-> match :data :opts)]
|
opts (-> match :data :opts)]
|
||||||
(if (and coercion responses)
|
(if (and coercion responses)
|
||||||
(let [coercers (response-coercers coercion responses opts)]
|
(let [coercer (response-coercer coercion responses opts)]
|
||||||
(coerce-response coercers request response))
|
(coercer request response))
|
||||||
response)))
|
response)))
|
||||||
([request respond raise]
|
([request respond raise]
|
||||||
(let [method (:request-method request)
|
(let [method (:request-method request)
|
||||||
|
|
@ -37,8 +37,8 @@ To demonstrate the two approaches, below are response coercion middleware writte
|
||||||
coercion (-> match :data :coercion)
|
coercion (-> match :data :coercion)
|
||||||
opts (-> match :data :opts)]
|
opts (-> match :data :opts)]
|
||||||
(if (and coercion responses)
|
(if (and coercion responses)
|
||||||
(let [coercers (response-coercers coercion responses opts)]
|
(let [coercer (response-coercer coercion responses opts)]
|
||||||
(handler request #(respond (coerce-response coercers request %))))
|
(handler request #(respond (coercer request %))))
|
||||||
(handler request respond raise))))))
|
(handler request respond raise))))))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -60,13 +60,13 @@ To demonstrate the two approaches, below are response coercion middleware writte
|
||||||
:spec ::rs/responses
|
:spec ::rs/responses
|
||||||
:compile (fn [{:keys [coercion responses]} opts]
|
:compile (fn [{:keys [coercion responses]} opts]
|
||||||
(if (and coercion responses)
|
(if (and coercion responses)
|
||||||
(let [coercers (coercion/response-coercers coercion responses opts)]
|
(let [coercer (coercion/response-coercer coercion responses opts)]
|
||||||
(fn [handler]
|
(fn [handler]
|
||||||
(fn
|
(fn
|
||||||
([request]
|
([request]
|
||||||
(coercion/coerce-response coercers request (handler request)))
|
(coercer request (handler request)))
|
||||||
([request respond raise]
|
([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.
|
It has 50% less code, it's much easier to reason about and is much faster.
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
# Content Negotiation
|
# Content Negotiation
|
||||||
|
|
||||||
Wrapper for [Muuntaja](https://github.com/metosin/muuntaja) middleware for content-negotiation, request decoding and response encoding. Takes explicit configuration via `:muuntaja` key in route data. Emit's [swagger](swagger.md) `:produces` and `:consumes` definitions automatically based on the Muuntaja configuration.
|
Wrapper for [Muuntaja](https://github.com/metosin/muuntaja) middleware for content negotiation, request decoding and response encoding. Takes explicit configuration via `:muuntaja` key in route data. Emits [swagger](swagger.md) `:produces` and `:consumes` definitions automatically based on the Muuntaja configuration.
|
||||||
|
|
||||||
Negotiates a request body based on `Content-Type` header and response body based on `Accept`, `Accept-Charset` headers. Publishes the negotiation results as `:muuntaja/request` and `:muuntaja/response` keys into the request.
|
Negotiates a request body based on `Content-Type` header and response body based on `Accept` and `Accept-Charset` headers. Publishes the negotiation results as `:muuntaja/request` and `:muuntaja/response` keys into the request.
|
||||||
|
|
||||||
Decodes the request body into `:body-params` using the `:muuntaja/request` key in request if the `:body-params` doesn't already exist.
|
Decodes the request body into `:body-params` using the `:muuntaja/request` key in request if the `:body-params` doesn't already exist.
|
||||||
|
|
||||||
|
|
@ -84,10 +84,12 @@ Server: Jetty(9.2.21.v20170120)
|
||||||
<kikka>kukka</kikka>
|
<kikka>kukka</kikka>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You can also specify request and response schemas per content-type. See [Coercion](coercion.md) and [OpenAPI Support](openapi.md).
|
||||||
|
|
||||||
|
|
||||||
## Changing default parameters
|
## Changing default parameters
|
||||||
|
|
||||||
The current JSON formatter used by `reitit` already have the option to parse keys as `keyword` which is a sane default in Clojure. However, if you would like to parse all the `double` as `bigdecimal` you'd need to change an option of the [JSON formatter](https://github.com/metosin/jsonista)
|
The current JSON formatter used by `reitit` already has the option to parse keys as `keyword` which is a sane default in Clojure. However, if you would like to parse all the `double` as `bigdecimal` you'd need to change an option of the [JSON formatter](https://github.com/metosin/jsonista)
|
||||||
|
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
|
|
@ -102,7 +104,7 @@ The current JSON formatter used by `reitit` already have the option to parse key
|
||||||
|
|
||||||
Now you should change the `m/instance` installed in the router with the `new-muuntaja-instance`.
|
Now you should change the `m/instance` installed in the router with the `new-muuntaja-instance`.
|
||||||
|
|
||||||
You can find more options for [JSON](https://cljdoc.org/d/metosin/jsonista/0.2.5/api/jsonista.core#object-mapper) and [EDN].
|
Here you can find more options for [JSON](https://cljdoc.org/d/metosin/jsonista/0.2.5/api/jsonista.core#object-mapper) and EDN.
|
||||||
|
|
||||||
|
|
||||||
## Adding custom encoder
|
## Adding custom encoder
|
||||||
|
|
@ -125,14 +127,14 @@ The example below is from `muuntaja` explaining how to add a custom encoder to p
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Adding all together
|
## Putting it all together
|
||||||
|
|
||||||
If you inspect `m/default-options` it's only a map, therefore you can compose your new muuntaja instance with as many options as you need it.
|
If you inspect `m/default-options` you'll find it's only a map. This means you can compose your new muuntaja instance with as many options as you need.
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
(def new-muuntaja
|
(def new-muuntaja
|
||||||
(m/create
|
(m/create
|
||||||
(-> m/default-options
|
(-> m/default-options
|
||||||
(assoc-in [:formats "application/json" :decoder-opts :bigdecimals] true)
|
(assoc-in [:formats "application/json" :decoder-opts :bigdecimals] true)
|
||||||
(assoc-in [:formats "application/json" :encoder-opts :data-format] "yyyy-MM-dd"))))
|
(assoc-in [:formats "application/json" :encoder-opts :date-format] "yyyy-MM-dd"))))
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,21 @@
|
||||||
# Data-driven Middleware
|
# Data-driven Middleware
|
||||||
|
|
||||||
Ring [defines middleware](https://github.com/ring-clojure/ring/wiki/Concepts#middleware) as a function of type `handler & args => request => response`. It's relatively easy to understand and enables good performance. Downside is that the middleware-chain is just a opaque function, making things like debugging and composition hard. It's too easy to apply the middleware in wrong order.
|
Ring [defines middleware](https://github.com/ring-clojure/ring/wiki/Concepts#middleware) as a function of type `handler & args => request => response`. It is relatively easy to understand and allows for good performance. A downside is that the middleware chain is just a opaque function, making things like debugging and composition hard. It is too easy to apply the middlewares in wrong order.
|
||||||
|
|
||||||
|
For the basics of reitit middleware, [read this first](ring.md#middleware).
|
||||||
|
|
||||||
Reitit defines middleware as data:
|
Reitit defines middleware as data:
|
||||||
|
|
||||||
1. Middleware can be defined as first-class data entries
|
1. A middleware can be defined as first-class data entries
|
||||||
2. Middleware can be mounted as a [duct-style](https://github.com/duct-framework/duct/wiki/Configuration) vector (of middleware)
|
2. A middleware can be mounted as a [duct-style](https://github.com/duct-framework/duct/wiki/Configuration) vector (of middlewares)
|
||||||
4. Middleware can be optimized & [compiled](compiling_middleware.md) against an endpoint
|
4. A middleware can be optimized & [compiled](compiling_middleware.md) against an endpoint
|
||||||
3. Middleware chain can be transformed by the router
|
3. A middleware chain can be transformed by the router
|
||||||
|
|
||||||
## Middleware as data
|
## Middleware as data
|
||||||
|
|
||||||
All values in the `:middleware` vector in the route data are expanded into `reitit.middleware/Middleware` Records with using the `reitit.middleware/IntoMiddleware` Protocol. By default, functions, maps and `Middleware` records are allowed.
|
All values in the `:middleware` vector of route data are expanded into `reitit.middleware/Middleware` Records by using the `reitit.middleware/IntoMiddleware` Protocol. By default, functions, maps and `Middleware` records are allowed.
|
||||||
|
|
||||||
Records can have arbitrary keys, but the following keys have a special purpose:
|
Records can have arbitrary keys, but the following keys have special purpose:
|
||||||
|
|
||||||
| key | description |
|
| key | description |
|
||||||
| ---------------|-------------|
|
| ---------------|-------------|
|
||||||
|
|
@ -22,13 +24,13 @@ Records can have arbitrary keys, but the following keys have a special purpose:
|
||||||
| `:wrap` | The actual middleware function of `handler & args => request => response`
|
| `:wrap` | The actual middleware function of `handler & args => request => response`
|
||||||
| `:compile` | Middleware compilation function, see [compiling middleware](compiling_middleware.md).
|
| `:compile` | Middleware compilation function, see [compiling middleware](compiling_middleware.md).
|
||||||
|
|
||||||
Middleware Records are accessible in their raw form in the compiled route results, thus available for inventories, creating api-docs etc.
|
Middleware Records are accessible in their raw form in the compiled route results, and thus are available for inventories, creating api-docs, etc.
|
||||||
|
|
||||||
For the actual request processing, the Records are unwrapped into normal functions and composed into a middleware function chain, yielding zero runtime penalty.
|
For the actual request processing, the Records are unwrapped into normal functions and composed into a middleware function chain, yielding zero runtime penalty.
|
||||||
|
|
||||||
### Creating Middleware
|
### Creating Middleware
|
||||||
|
|
||||||
The following produce identical middleware runtime function.
|
The following examples produce identical middleware runtime functions.
|
||||||
|
|
||||||
### Function
|
### Function
|
||||||
|
|
||||||
|
|
@ -77,7 +79,7 @@ The following produce identical middleware runtime function.
|
||||||
:handler handler}}]])))
|
:handler handler}}]])))
|
||||||
```
|
```
|
||||||
|
|
||||||
All the middleware are applied correctly:
|
All the middlewares are applied correctly:
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
(app {:request-method :get, :uri "/api/ping"})
|
(app {:request-method :get, :uri "/api/ping"})
|
||||||
|
|
@ -86,7 +88,7 @@ All the middleware are applied correctly:
|
||||||
|
|
||||||
## Compiling middleware
|
## Compiling middleware
|
||||||
|
|
||||||
Middleware can be optimized against an endpoint using [middleware compilation](compiling_middleware.md).
|
Middlewares can be optimized against an endpoint using [middleware compilation](compiling_middleware.md).
|
||||||
|
|
||||||
## Ideas for the future
|
## Ideas for the future
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Default handler
|
# Default handler
|
||||||
|
|
||||||
By default, if no routes match, `nil` is returned, which is not valid response in Ring:
|
By default, if no routes match, `nil` is returned, which is not a valid response in Ring:
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
(require '[reitit.ring :as ring])
|
(require '[reitit.ring :as ring])
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
# Default Middleware
|
# Default Middleware
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
[metosin/reitit-middleware "0.5.0"]
|
[metosin/reitit-middleware "0.10.0"]
|
||||||
```
|
```
|
||||||
|
|
||||||
Any Ring middleware can be used with `reitit-ring`, but using data-driven middleware is preferred as they are easier to manage and in many cases, yield better performance. `reitit-middleware` contains a set of common ring middleware, lifted into data-driven middleware.
|
Any Ring middleware can be used with `reitit-ring`, but using data-driven middleware is preferred as they are easier to manage and in many cases yield better performance. `reitit-middleware` contains a set of common ring middleware, lifted into data-driven middleware.
|
||||||
|
|
||||||
* [Parameter Handling](#parameters-handling)
|
* [Parameter Handling](#parameters-handling)
|
||||||
* [Exception Handling](#exception-handling)
|
* [Exception Handling](#exception-handling)
|
||||||
|
|
@ -17,12 +17,14 @@ 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
|
`reitit.ring.middleware.parameters/parameters-middleware` to capture query- and form-params. Wraps
|
||||||
`ring.middleware.params/wrap-params`.
|
`ring.middleware.params/wrap-params`.
|
||||||
|
|
||||||
**NOTE**: 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.
|
|
||||||
|
|
||||||
## Exception Handling
|
## Exception Handling
|
||||||
|
|
||||||
See [Exception Handling with Ring](exceptions.md).
|
See [Exception Handling with Ring](exceptions.md).
|
||||||
|
|
||||||
|
## Content Negotiation
|
||||||
|
|
||||||
|
See [Content Negotiation](content_negotiation.md).
|
||||||
|
|
||||||
## Multipart Request Handling
|
## Multipart Request Handling
|
||||||
|
|
||||||
Wrapper for [Ring Multipart Middleware](https://github.com/ring-clojure/ring/blob/master/ring-core/src/ring/middleware/multipart_params.clj). Emits swagger `:consumes` definitions automatically.
|
Wrapper for [Ring Multipart Middleware](https://github.com/ring-clojure/ring/blob/master/ring-core/src/ring/middleware/multipart_params.clj). Emits swagger `:consumes` definitions automatically.
|
||||||
|
|
@ -55,4 +57,4 @@ Partial sample output:
|
||||||
|
|
||||||
## Example app
|
## Example app
|
||||||
|
|
||||||
See an example app with the default middleware in action: https://github.com/metosin/reitit/blob/master/examples/ring-swagger/src/example/server.clj.
|
See an example app with the default middleware in action: <https://github.com/metosin/reitit/blob/master/examples/ring-malli-swagger/src/example/server.clj>.
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
# Dynamic Extensions
|
# Dynamic Extensions
|
||||||
|
|
||||||
`ring-handler` injects the `Match` into a request and it can be extracted at runtime with `reitit.ring/get-match`. This can be used to build ad-hoc extensions to the system.
|
`ring-handler` injects the `Match` into a request and it can be extracted at runtime with `reitit.ring/get-match`. This can be used to build ad hoc extensions to the system.
|
||||||
|
|
||||||
Example middleware to guard routes based on user roles:
|
This example shows a middleware to guard routes based on user roles:
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
(require '[reitit.ring :as ring])
|
(require '[reitit.ring :as ring])
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
# Exception Handling with Ring
|
# Exception Handling with Ring
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
[metosin/reitit-middleware "0.5.0"]
|
[metosin/reitit-middleware "0.10.0"]
|
||||||
```
|
```
|
||||||
|
|
||||||
Exceptions thrown in router creation can be [handled with custom exception handler](../basics/error_messages.md). By default, exceptions thrown at runtime from a handler or a middleware are not caught by the `reitit.ring/ring-handler`. A good practise is a have an top-level exception handler to log and format the errors for clients.
|
Exceptions thrown in router creation can be [handled with custom exception handler](../basics/error_messages.md). By default, exceptions thrown at runtime from a handler or a middleware are not caught by the `reitit.ring/ring-handler`. A good practice is to have a top-level exception handler to log and format errors for clients.
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
(require '[reitit.ring.middleware.exception :as exception])
|
(require '[reitit.ring.middleware.exception :as exception])
|
||||||
|
|
@ -36,7 +36,7 @@ A preconfigured middleware using `exception/default-handlers`. Catches:
|
||||||
|
|
||||||
### `exception/create-exception-middleware`
|
### `exception/create-exception-middleware`
|
||||||
|
|
||||||
Creates the exception-middleware with custom options. Takes a map of `identifier => exception request => response` that is used to select the exception handler for the thrown/raised exception identifier. Exception identifier is either a `Keyword` or a Exception Class.
|
Creates the exception-middleware with custom options. Takes a map of `identifier => exception request => response` that is used to select the exception handler for the thrown/raised exception identifier. Exception identifier is either a `Keyword` or an Exception Class.
|
||||||
|
|
||||||
The following handlers are available by default:
|
The following handlers are available by default:
|
||||||
|
|
||||||
|
|
@ -55,7 +55,7 @@ The handler is selected from the options map by exception identifier in the foll
|
||||||
2) Class of exception
|
2) Class of exception
|
||||||
3) `:type` ancestors of exception ex-data
|
3) `:type` ancestors of exception ex-data
|
||||||
4) Super Classes of exception
|
4) Super Classes of exception
|
||||||
5) The ::default handler
|
5) The `::default` handler
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
;; type hierarchy
|
;; type hierarchy
|
||||||
|
|
@ -94,7 +94,7 @@ The handler is selected from the options map by exception identifier in the foll
|
||||||
(def app
|
(def app
|
||||||
(ring/ring-handler
|
(ring/ring-handler
|
||||||
(ring/router
|
(ring/router
|
||||||
["/fail" (fn [_] (throw (ex-info "fail" {:type ::failue})))]
|
["/fail" (fn [_] (throw (ex-info "fail" {:type ::failure})))]
|
||||||
{:data {:middleware [exception-middleware]}})))
|
{:data {:middleware [exception-middleware]}})))
|
||||||
|
|
||||||
(app {:request-method :get, :uri "/fail"})
|
(app {:request-method :get, :uri "/fail"})
|
||||||
|
|
@ -102,6 +102,6 @@ The handler is selected from the options map by exception identifier in the foll
|
||||||
; => {:status 500,
|
; => {:status 500,
|
||||||
; :body {:message "default"
|
; :body {:message "default"
|
||||||
; :exception clojure.lang.ExceptionInfo
|
; :exception clojure.lang.ExceptionInfo
|
||||||
; :data {:type :user/failue}
|
; :data {:type :user/failure}
|
||||||
; :uri "/fail"}}
|
; :uri "/fail"}}
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,20 @@ Router creation fails fast if the registry doesn't contain the middleware:
|
||||||
;| :bonus | reitit.ring_test$wrap_bonus@59fddabb |
|
;| :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?
|
## 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`.
|
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`.
|
||||||
|
|
|
||||||
214
doc/ring/openapi.md
Normal file
214
doc/ring/openapi.md
Normal file
|
|
@ -0,0 +1,214 @@
|
||||||
|
# OpenAPI Support
|
||||||
|
|
||||||
|
**Stability: alpha**
|
||||||
|
|
||||||
|
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
|
||||||
|
[ring-spec-swagger](../../examples/ring-spec-swagger)
|
||||||
|
examples also
|
||||||
|
have OpenAPI documentation.
|
||||||
|
|
||||||
|
## OpenAPI data
|
||||||
|
|
||||||
|
The following route data keys contribute to the generated swagger specification:
|
||||||
|
|
||||||
|
| key | description |
|
||||||
|
| ---------------|-------------|
|
||||||
|
| :openapi | map of any openapi data. Can contain keys like `:deprecated`.
|
||||||
|
| :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:
|
||||||
|
|
||||||
|
| key | description |
|
||||||
|
| --------------|-------------|
|
||||||
|
| :parameters | optional input parameters for a route, in a format defined by the coercion
|
||||||
|
| :request | optional description of body parameters, possibly per content-type
|
||||||
|
| :responses | optional descriptions of responses, in a format defined by coercion
|
||||||
|
|
||||||
|
|
||||||
|
## Per-content-type coercions
|
||||||
|
|
||||||
|
Use `:request` coercion (instead of `:body`) to unlock
|
||||||
|
per-content-type coercions. This also lets you specify multiple named
|
||||||
|
examples. See [Coercion](coercion.md) for more info. See also [the
|
||||||
|
openapi example](../../examples/openapi).
|
||||||
|
|
||||||
|
```clj
|
||||||
|
["/pizza"
|
||||||
|
{:get {:summary "Fetch a pizza | Multiple content-types, multiple examples"
|
||||||
|
: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"
|
||||||
|
:value {:color :white
|
||||||
|
:pineapple true}}
|
||||||
|
:red {:description "Red pizza"
|
||||||
|
:value {:color :red
|
||||||
|
:pineapple false}}}}
|
||||||
|
"application/edn" {:schema [:map
|
||||||
|
[:color :keyword]
|
||||||
|
[:pineapple :boolean]]
|
||||||
|
:examples {:red {:description "Red pizza with pineapple"
|
||||||
|
:value (pr-str {:color :red :pineapple true})}}}}}}
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
You can use the `:openapi` route data key of the `create-openapi-handler` route
|
||||||
|
to populate the top level of the OpenAPI spec.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
["/openapi.json"
|
||||||
|
{:get {:handler (openapi/create-openapi-handler)
|
||||||
|
:openapi {:info {:title "my nice api" :version "0.0.1"}}
|
||||||
|
:no-doc true}}]
|
||||||
|
```
|
||||||
|
|
||||||
|
If you need to post-process the generated spec, just wrap the handler with a custom `Middleware` or an `Interceptor`.
|
||||||
|
|
||||||
|
## 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. See `reitit.swagger-ui/create-swagger-ui-handle`
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
@ -5,14 +5,14 @@
|
||||||
Read more about the [Ring Concepts](https://github.com/ring-clojure/ring/wiki/Concepts).
|
Read more about the [Ring Concepts](https://github.com/ring-clojure/ring/wiki/Concepts).
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
[metosin/reitit-ring "0.5.0"]
|
[metosin/reitit-ring "0.10.0"]
|
||||||
```
|
```
|
||||||
|
|
||||||
## `reitit.ring/ring-router`
|
## `reitit.ring/router`
|
||||||
|
|
||||||
`ring-router` is a higher order router, which adds support for `:request-method` based routing, [handlers](https://github.com/ring-clojure/ring/wiki/Concepts#handlers) and [middleware](https://github.com/ring-clojure/ring/wiki/Concepts#middleware).
|
`reitit.ring/router` is a higher order router, which adds support for `:request-method` based routing, [handlers](https://github.com/ring-clojure/ring/wiki/Concepts#handlers) and [middleware](https://github.com/ring-clojure/ring/wiki/Concepts#middleware).
|
||||||
|
|
||||||
It accepts the following options:
|
It accepts the following options:
|
||||||
|
|
||||||
| key | description |
|
| key | description |
|
||||||
| ----------------------------------------|-------------|
|
| ----------------------------------------|-------------|
|
||||||
|
|
@ -33,7 +33,7 @@ Example router:
|
||||||
["/ping" {:get handler}]))
|
["/ping" {:get handler}]))
|
||||||
```
|
```
|
||||||
|
|
||||||
Match contains `:result` compiled by the `ring-router`:
|
Match contains `:result` compiled by `reitit.ring/router`:
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
(require '[reitit.core :as r])
|
(require '[reitit.core :as r])
|
||||||
|
|
@ -49,11 +49,11 @@ Match contains `:result` compiled by the `ring-router`:
|
||||||
|
|
||||||
## `reitit.ring/ring-handler`
|
## `reitit.ring/ring-handler`
|
||||||
|
|
||||||
Given a `ring-router`, optional default-handler & options, `ring-handler` function will return a valid ring handler supporting both synchronous and [asynchronous](https://www.booleanknot.com/blog/2016/07/15/asynchronous-ring.html) request handling. The following options are available:
|
Given a router from `reitit.ring/router`, optional default-handler & options, `ring-handler` function will return a valid ring handler supporting both synchronous and [asynchronous](https://www.booleanknot.com/blog/2016/07/15/asynchronous-ring.html) request handling. The following options are available:
|
||||||
|
|
||||||
| key | description |
|
| key | description |
|
||||||
| ------------------|-------------|
|
| ------------------|-------------|
|
||||||
| `:middleware` | Optional sequence of middleware that wrap the ring-handler"
|
| `:middleware` | Optional sequence of middlewares that wrap the ring-handler
|
||||||
| `:inject-match?` | Boolean to inject `match` into request under `:reitit.core/match` key (default true)
|
| `:inject-match?` | Boolean to inject `match` into request under `:reitit.core/match` key (default true)
|
||||||
| `:inject-router?` | Boolean to inject `router` into request under `:reitit.core/router` key (default true)
|
| `:inject-router?` | Boolean to inject `router` into request under `:reitit.core/router` key (default true)
|
||||||
|
|
||||||
|
|
@ -141,7 +141,7 @@ Name-based reverse routing:
|
||||||
|
|
||||||
# Middleware
|
# 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`
|
1. normal ring middleware function `handler -> request -> response`
|
||||||
2. vector of middleware function `[handler args*] -> request -> response` and it's arguments
|
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
|
(def app
|
||||||
(ring/ring-handler
|
(ring/ring-handler
|
||||||
(ring/router
|
(ring/router
|
||||||
["/api" {:middleware [[mw :api]]}
|
["/api" {:middleware [[wrap :api]]}
|
||||||
["/get" {:get handler}]])
|
["/get" {:get handler}]])
|
||||||
nil
|
nil
|
||||||
{:middleware [[mw :top]]}))
|
{:middleware [[wrap :top]]}))
|
||||||
|
|
||||||
(app {:request-method :get, :uri "/api/get"})
|
(app {:request-method :get, :uri "/api/get"})
|
||||||
; {:status 200, :body [:top :api :ok]}
|
; {: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!
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
# Static Resources (Clojure Only)
|
# Static Resources (Clojure Only)
|
||||||
|
|
||||||
Static resources can be served using `reitit.ring/create-resource-handler`. It takes optionally an options map and returns a ring handler to serve files from Classpath.
|
Static resources can be served by using the following two functions:
|
||||||
|
|
||||||
There are two options to serve the files.
|
* `reitit.ring/create-resource-handler`, which returns a Ring handler that serves files from classpath, and
|
||||||
|
* `reitit.ring/create-file-handler`, which returns a Ring handler that servers files from file system
|
||||||
|
|
||||||
|
There are two ways to mount the handlers.
|
||||||
|
The examples below use `reitit.ring/create-resource-handler`, but `reitit.ring/create-file-handler` works the same way.
|
||||||
|
|
||||||
## Internal routes
|
## Internal routes
|
||||||
|
|
||||||
|
|
@ -33,7 +37,9 @@ To serve static files with conflicting routes, e.g. `"/*"`, one needs to disable
|
||||||
|
|
||||||
## External routes
|
## External routes
|
||||||
|
|
||||||
A better way to serve files from conflicting paths, e.g. `"/*"`, is to serve them from the default-handler. One can compose multiple default locations using `ring-handler`. This way, they are only served if none of the actual routes have matched.
|
A better way to serve files from conflicting paths, e.g. `"/*"`, is to serve them from the default-handler.
|
||||||
|
One can compose multiple default locations using `reitit.ring/ring-handler`.
|
||||||
|
This way, they are only served if none of the actual routes have matched.
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
(ring/ring-handler
|
(ring/ring-handler
|
||||||
|
|
@ -46,21 +52,21 @@ A better way to serve files from conflicting paths, e.g. `"/*"`, is to serve the
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
`reitit.ring/create-resource-handler` takes optionally an options map to configure how the files are being served.
|
`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 |
|
| key | description |
|
||||||
| -----------------|-------------|
|
| --------------------|-------------|
|
||||||
| :parameter | optional name of the wildcard parameter, defaults to unnamed keyword `:`
|
| :parameter | optional name of the wildcard parameter, defaults to unnamed keyword `:`
|
||||||
| :root | optional resource root, defaults to `\"public\"`
|
| :root | optional resource root, defaults to `\"public\"`
|
||||||
| :path | optional path to mount the handler to. Works only if mounted outside of a router.
|
| :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
|
| :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-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)
|
| :not-found-handler | optional handler function to use if the requested resource is missing (404 Not Found)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### TODO
|
### TODO
|
||||||
|
|
||||||
* support for things like `:cache`, `:etag`, `:last-modified?`, and `:gzip`
|
* support for things like `:cache`, `:etag`, `:last-modified?`, and `:gzip`
|
||||||
* support for ClojureScript
|
* support for ClojureScript
|
||||||
* serve from file-system
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
# Swagger Support
|
# Swagger Support
|
||||||
|
|
||||||
```
|
```
|
||||||
[metosin/reitit-swagger "0.5.0"]
|
[metosin/reitit-swagger "0.10.0"]
|
||||||
```
|
```
|
||||||
|
|
||||||
Reitit supports [Swagger2](https://swagger.io/) documentation, thanks to [schema-tools](https://github.com/metosin/schema-tools) and [spec-tools](https://github.com/metosin/spec-tools). Documentation is extracted from route definitions, coercion `:parameters` and `:responses` and from a set of new documentation keys.
|
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.
|
||||||
|
|
||||||
To enable swagger-documentation for a ring-router:
|
See also: [OpenAPI support](openapi.md).
|
||||||
|
|
||||||
|
To enable swagger-documentation for a Ring router:
|
||||||
|
|
||||||
1. annotate your routes with swagger-data
|
1. annotate your routes with swagger-data
|
||||||
2. mount a swagger-handler to serve the swagger-spec
|
2. mount a swagger-handler to serve the swagger-spec
|
||||||
|
|
@ -23,6 +25,7 @@ The following route data keys contribute to the generated swagger specification:
|
||||||
| :tags | optional set of string or keyword tags for an endpoint api docs
|
| :tags | optional set of string or keyword tags for an endpoint api docs
|
||||||
| :summary | optional short string summary of an endpoint
|
| :summary | optional short string summary of an endpoint
|
||||||
| :description | optional long description of an endpoint. Supports http://spec.commonmark.org/
|
| :description | optional long description of an endpoint. Supports http://spec.commonmark.org/
|
||||||
|
| :operationId | optional string specifying the unique ID of an Operation
|
||||||
|
|
||||||
Coercion keys also contribute to the docs:
|
Coercion keys also contribute to the docs:
|
||||||
|
|
||||||
|
|
@ -44,10 +47,10 @@ 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.
|
[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.5.0"]
|
[metosin/reitit-swagger-ui "0.10.0"]
|
||||||
```
|
```
|
||||||
|
|
||||||
`reitit.swagger-ui/create-swagger-ui-hander` can be used to create a ring-handler to serve the swagger-ui. It accepts the following options:
|
`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:
|
||||||
|
|
||||||
| key | description |
|
| key | description |
|
||||||
| -----------------|-------------|
|
| -----------------|-------------|
|
||||||
|
|
@ -76,7 +79,7 @@ at the top-level to show responses for all endpoints.
|
||||||
* two routes
|
* two routes
|
||||||
* swagger-spec served from `"/swagger.json"`
|
* swagger-spec served from `"/swagger.json"`
|
||||||
* swagger-ui mounted to `"/api-docs"`
|
* swagger-ui mounted to `"/api-docs"`
|
||||||
* note that for real-world use, you need a [content-negation middleware][muuntaja] -
|
* note that for real-world use, you need a [content-negotiation middleware][muuntaja] -
|
||||||
see the next example
|
see the next example
|
||||||
|
|
||||||
[muuntaja]: ../ring/default_middleware.md#content-negotiation
|
[muuntaja]: ../ring/default_middleware.md#content-negotiation
|
||||||
|
|
@ -142,7 +145,7 @@ Another way to serve the swagger-ui is using the [default handler](default_handl
|
||||||
* missed routes are handled by `create-default-handler`
|
* missed routes are handled by `create-default-handler`
|
||||||
* served via [ring-jetty](https://github.com/ring-clojure/ring/tree/master/ring-jetty-adapter)
|
* served via [ring-jetty](https://github.com/ring-clojure/ring/tree/master/ring-jetty-adapter)
|
||||||
|
|
||||||
Whole example project is in [`/examples/ring-swagger`](https://github.com/metosin/reitit/tree/master/examples/ring-swagger).
|
Whole example project is in [`/examples/ring-spec-swagger`](https://github.com/metosin/reitit/tree/master/examples/ring-spec-swagger).
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
(ns example.server
|
(ns example.server
|
||||||
|
|
@ -283,7 +286,18 @@ Example with:
|
||||||
; ("/common/ping" "/one/ping" "/two/ping" "/two/deep/ping")
|
; ("/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
|
* ClojureScript
|
||||||
* example for [Macchiato](https://github.com/macchiato-framework)
|
* example for [Macchiato](https://github.com/macchiato-framework)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Transforming the Middleware Chain
|
# Transforming the Middleware Chain
|
||||||
|
|
||||||
There is an extra option in ring-router (actually, in the underlying middleware-router): `:reitit.middleware/transform` to transform the middleware chain per endpoint. Value should be a function or a vector of functions that get a vector of compiled middleware and should return a new vector of middleware.
|
There is an extra option in the Ring router (actually, in the underlying middleware-router): `:reitit.middleware/transform` to transform the middleware chain per endpoint. Value should be a function or a vector of functions that get a vector of compiled middleware and should return a new vector of middleware.
|
||||||
|
|
||||||
## Example Application
|
## Example Application
|
||||||
|
|
||||||
|
|
@ -59,7 +59,7 @@ There is an extra option in ring-router (actually, in the underlying middleware-
|
||||||
### Printing Request Diffs
|
### Printing Request Diffs
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
[metosin/reitit-middleware "0.5.0"]
|
[metosin/reitit-middleware "0.10.0"]
|
||||||
```
|
```
|
||||||
|
|
||||||
Using `reitit.ring.middleware.dev/print-request-diffs` transformation, the request diffs between each middleware are printed out to the console. To use it, add the following router option:
|
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:
|
||||||
|
|
@ -71,4 +71,3 @@ Using `reitit.ring.middleware.dev/print-request-diffs` transformation, the reque
|
||||||
Sample output:
|
Sample output:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
|
||||||
53
examples/README.md
Normal file
53
examples/README.md
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
# Examples
|
||||||
|
|
||||||
|
## buddy-auth
|
||||||
|
## frontend-auth
|
||||||
|
## frontend-controllers
|
||||||
|
## frontend-links
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
Same as ring-spec-swagger?
|
||||||
|
|
||||||
|
Async examples as extra.
|
||||||
|
|
||||||
|
## http
|
||||||
|
|
||||||
|
Async example.
|
||||||
|
|
||||||
|
## just-coercion-with-ring
|
||||||
|
|
||||||
|
Bad name.
|
||||||
|
|
||||||
|
Coercion example for spec, data-spec, Schema.
|
||||||
|
No Swagger generation or Malli!
|
||||||
|
|
||||||
|
Same as ring-example?
|
||||||
|
|
||||||
|
## pedestal-swagger
|
||||||
|
## pedestal
|
||||||
|
## ring-example
|
||||||
|
|
||||||
|
Coercion example for spec, data-spec, Schema.
|
||||||
|
No Swagger generation or Malli!
|
||||||
|
|
||||||
|
## ring-integrant
|
||||||
|
|
||||||
|
## ring-malli-swagger
|
||||||
|
|
||||||
|
Coercion with Malli and Swagger generation.
|
||||||
|
|
||||||
|
## ring-spec-swagger
|
||||||
|
|
||||||
|
Coercion with Spec and Swagger generation.
|
||||||
49
examples/buddy-auth/README.md
Normal file
49
examples/buddy-auth/README.md
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
# Buddy auth example
|
||||||
|
|
||||||
|
A sample project that shows how to use [Buddy] authentication with Reitit to implement simple authentication and authorization flows.
|
||||||
|
|
||||||
|
* Basic auth
|
||||||
|
* Token-based authorization with JWT tokens
|
||||||
|
|
||||||
|
[Buddy]: https://github.com/funcool/buddy
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Start a REPL:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
lein repl
|
||||||
|
```
|
||||||
|
|
||||||
|
Start the server:
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(start)
|
||||||
|
```
|
||||||
|
|
||||||
|
Take a look at the annotated example in [server.clj](src/example/server.clj).
|
||||||
|
|
||||||
|
You can also try some curl commands:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Let's first try without password - this should fail
|
||||||
|
curl http://localhost:3000/basic-auth
|
||||||
|
|
||||||
|
# With password, it should work
|
||||||
|
curl http://user1:kissa13@localhost:3000/basic-auth
|
||||||
|
|
||||||
|
# The response should look something like this:
|
||||||
|
#
|
||||||
|
# {"message":"Basic auth succeeded!","user":{"id":1,"roles":["admin","user"],
|
||||||
|
# "token":"eyJhbGciOiJIUzUxMiJ9.eyJpZCI6MSwicm9sZXMiOlsiYWRtaW4iLCJ1c2VyIl0sImV4cCI6MTU5NTU5NDcxNn0.lPFcLxWMFK4_dCLZs2crPB2rmvwO6f-uRsRYdhaWTAJHGKIQpP8anjbmnz6QlrS_RlI160FVzZohPlmkS9JfIQ"}}
|
||||||
|
#
|
||||||
|
# The value in the JSON field `token` is a JWT token. A new one is generated with every call and they expire in two hours.
|
||||||
|
|
||||||
|
# We can try token auth then. Copy the token from the response in the next command:
|
||||||
|
curl -H "Authorization: Token PASTE_YOUR_TOKEN_HERE" http://localhost:3000/token-auth
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Copyright © Metosin Oy and collaborators.
|
||||||
7
examples/buddy-auth/project.clj
Normal file
7
examples/buddy-auth/project.clj
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
(defproject buddy-auth "0.1.0-SNAPSHOT"
|
||||||
|
:description "Reitit Buddy Auth App"
|
||||||
|
:dependencies [[org.clojure/clojure "1.11.2"]
|
||||||
|
[ring/ring-jetty-adapter "1.12.1"]
|
||||||
|
[metosin/reitit "0.10.0"]
|
||||||
|
[buddy "2.0.0"]]
|
||||||
|
:repl-options {:init-ns example.server})
|
||||||
244
examples/buddy-auth/src/example/server.clj
Normal file
244
examples/buddy-auth/src/example/server.clj
Normal file
|
|
@ -0,0 +1,244 @@
|
||||||
|
(ns example.server
|
||||||
|
"This example demonstrates how to use Buddy authentication with Reitit
|
||||||
|
to implement simple authentication and authorization flows.
|
||||||
|
|
||||||
|
HTTP Basic authentication is used to authenticate with username and
|
||||||
|
password. HTTP Basic authentication middleware checks credentials
|
||||||
|
against a 'database' and if credentials are OK, a signed jwt-token
|
||||||
|
is created and returned. The token can be used to call endpoints
|
||||||
|
that require token authentication. Token payload contains users
|
||||||
|
roles that can be used for authorization.
|
||||||
|
|
||||||
|
NOTE: This example is not production-ready."
|
||||||
|
(:require [buddy.auth :as buddy-auth]
|
||||||
|
[buddy.auth.backends :as buddy-auth-backends]
|
||||||
|
[buddy.auth.backends.httpbasic :as buddy-auth-backends-httpbasic]
|
||||||
|
[buddy.auth.middleware :as buddy-auth-middleware]
|
||||||
|
[buddy.hashers :as buddy-hashers]
|
||||||
|
[buddy.sign.jwt :as jwt]
|
||||||
|
[muuntaja.core :as m]
|
||||||
|
[reitit.ring :as ring]
|
||||||
|
[reitit.ring.coercion :as coercion]
|
||||||
|
[reitit.ring.middleware.muuntaja :as muuntaja]
|
||||||
|
[ring.adapter.jetty :as jetty]
|
||||||
|
[ring.middleware.params :as params]))
|
||||||
|
|
||||||
|
(def db
|
||||||
|
"We use a simple map as a db here but in real-world you would
|
||||||
|
interface with a real data storage in `basic-auth` function."
|
||||||
|
{"user1"
|
||||||
|
{:id 1
|
||||||
|
:password (buddy-hashers/encrypt "kissa13")
|
||||||
|
:roles ["admin" "user"]}
|
||||||
|
"user2"
|
||||||
|
{:id 2
|
||||||
|
:password (buddy-hashers/encrypt "koira12")
|
||||||
|
:roles ["user"]}})
|
||||||
|
|
||||||
|
(def private-key
|
||||||
|
"Used for signing and verifying JWT-tokens In real world you'd read
|
||||||
|
this from an environment variable or some other configuration that's
|
||||||
|
not included in the source code."
|
||||||
|
"kana15")
|
||||||
|
|
||||||
|
(defn create-token
|
||||||
|
"Creates a signed jwt-token with user data as payload.
|
||||||
|
`valid-seconds` sets the expiration span."
|
||||||
|
[user & {:keys [valid-seconds] :or {valid-seconds 7200}}] ;; 2 hours
|
||||||
|
(let [payload (-> user
|
||||||
|
(select-keys [:id :roles])
|
||||||
|
(assoc :exp (.plusSeconds
|
||||||
|
(java.time.Instant/now) valid-seconds)))]
|
||||||
|
(jwt/sign payload private-key {:alg :hs512})))
|
||||||
|
|
||||||
|
(def token-backend
|
||||||
|
"Backend for verifying JWT-tokens."
|
||||||
|
(buddy-auth-backends/jws {:secret private-key :options {:alg :hs512}}))
|
||||||
|
|
||||||
|
(defn basic-auth
|
||||||
|
"Authentication function called from basic-auth middleware for each
|
||||||
|
request. The result of this function will be added to the request
|
||||||
|
under key :identity.
|
||||||
|
|
||||||
|
NOTE: Use HTTP Basic authentication always with HTTPS in real setups."
|
||||||
|
[db request {:keys [username password]}]
|
||||||
|
(let [user (get db username)]
|
||||||
|
(if (and user (buddy-hashers/check password (:password user)))
|
||||||
|
(-> user
|
||||||
|
(dissoc :password)
|
||||||
|
(assoc :token (create-token user)))
|
||||||
|
false)))
|
||||||
|
|
||||||
|
(defn create-basic-auth-backend
|
||||||
|
"Creates basic-auth backend to be used by basic-auth-middleware."
|
||||||
|
[db]
|
||||||
|
(buddy-auth-backends-httpbasic/http-basic-backend
|
||||||
|
{:authfn (partial basic-auth db)}))
|
||||||
|
|
||||||
|
(defn create-basic-auth-middleware
|
||||||
|
"Creates a middleware that authenticates requests using http-basic
|
||||||
|
authentication."
|
||||||
|
[db]
|
||||||
|
(let [backend (create-basic-auth-backend db)]
|
||||||
|
(fn [handler]
|
||||||
|
(buddy-auth-middleware/wrap-authentication handler backend))))
|
||||||
|
|
||||||
|
(defn token-auth-middleware
|
||||||
|
"Middleware used on routes requiring token authentication."
|
||||||
|
[handler]
|
||||||
|
(buddy-auth-middleware/wrap-authentication handler token-backend))
|
||||||
|
|
||||||
|
(defn admin-middleware
|
||||||
|
"Middleware used on routes requiring :admin role."
|
||||||
|
[handler]
|
||||||
|
(fn [request]
|
||||||
|
(if (-> request :identity :roles set (contains? "admin"))
|
||||||
|
(handler request)
|
||||||
|
{:status 403 :body {:error "Admin role required"}})))
|
||||||
|
|
||||||
|
(defn auth-middleware
|
||||||
|
"Middleware used in routes that require authentication. If request is
|
||||||
|
not authenticated a 401 unauthorized response will be
|
||||||
|
returned. Buddy checks if request key :identity is set to truthy
|
||||||
|
value by any previous middleware."
|
||||||
|
[handler]
|
||||||
|
(fn [request]
|
||||||
|
(if (buddy-auth/authenticated? request)
|
||||||
|
(handler request)
|
||||||
|
{:status 401 :body {:error "Unauthorized"}})))
|
||||||
|
|
||||||
|
(def routes
|
||||||
|
[["/no-auth"
|
||||||
|
[""
|
||||||
|
{:get (fn [_] {:status 200 :body {:message "No auth succeeded!"}})}]]
|
||||||
|
|
||||||
|
["/basic-auth"
|
||||||
|
[""
|
||||||
|
{:middleware [(create-basic-auth-middleware db) auth-middleware]
|
||||||
|
:get
|
||||||
|
(fn [req]
|
||||||
|
{:status 200
|
||||||
|
:body
|
||||||
|
{:message "Basic auth succeeded!"
|
||||||
|
:user (-> req :identity)}})}]]
|
||||||
|
|
||||||
|
["/token-auth"
|
||||||
|
[""
|
||||||
|
{:middleware [token-auth-middleware auth-middleware]
|
||||||
|
:get (fn [_] {:status 200 :body {:message "Token auth succeeded!"}})}]]
|
||||||
|
|
||||||
|
["/token-auth-with-admin-role"
|
||||||
|
[""
|
||||||
|
{:middleware [token-auth-middleware
|
||||||
|
auth-middleware
|
||||||
|
admin-middleware]
|
||||||
|
:get (fn [_] {:status 200 :body {:message "Token auth with admin role succeeded!"}})}]]])
|
||||||
|
|
||||||
|
(def app
|
||||||
|
(ring/ring-handler
|
||||||
|
(ring/router
|
||||||
|
routes
|
||||||
|
{:data
|
||||||
|
{:muuntaja m/instance
|
||||||
|
:middleware ; applied to all routes
|
||||||
|
[params/wrap-params
|
||||||
|
muuntaja/format-middleware
|
||||||
|
coercion/coerce-exceptions-middleware
|
||||||
|
coercion/coerce-request-middleware
|
||||||
|
coercion/coerce-response-middleware]}})
|
||||||
|
(ring/create-default-handler)))
|
||||||
|
|
||||||
|
(defn start []
|
||||||
|
(jetty/run-jetty #'app {:port 3000, :join? false})
|
||||||
|
(println "server running in port 3000"))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
;; Start server to try with real HTTP clients.
|
||||||
|
(start)
|
||||||
|
|
||||||
|
;; ...or just execute following sexps in the REPL. :)
|
||||||
|
|
||||||
|
(def headers {"accept" "application/edn"})
|
||||||
|
(def read-body (comp read-string slurp :body))
|
||||||
|
|
||||||
|
(-> {:headers headers :request-method :get :uri "/no-auth"}
|
||||||
|
app
|
||||||
|
read-body)
|
||||||
|
;; => {:message "No auth succeeded!"}
|
||||||
|
|
||||||
|
(-> {:headers headers :request-method :get :uri "/basic-auth"}
|
||||||
|
app
|
||||||
|
read-body)
|
||||||
|
;; => {:error "Unauthorized"}
|
||||||
|
|
||||||
|
(-> {:headers headers :request-method :get :uri "/token-auth"}
|
||||||
|
app
|
||||||
|
read-body)
|
||||||
|
;; => {:error "Unauthorized"}
|
||||||
|
|
||||||
|
(import java.util.Base64)
|
||||||
|
|
||||||
|
(defn ->base64
|
||||||
|
"Encodes a string as base64."
|
||||||
|
[s]
|
||||||
|
(.encodeToString (Base64/getEncoder) (.getBytes s)))
|
||||||
|
|
||||||
|
(defn basic-auth-headers [user pass]
|
||||||
|
(merge headers {:authorization (str "Basic " (->base64 (str user ":" pass)))}))
|
||||||
|
|
||||||
|
(def bad-creds (basic-auth-headers "juum" "joo"))
|
||||||
|
(-> {:headers bad-creds :request-method :get :uri "/basic-auth"}
|
||||||
|
app
|
||||||
|
read-body)
|
||||||
|
;; => {:error "Unauthorized"}
|
||||||
|
|
||||||
|
(def admin-creds (basic-auth-headers "user1" "kissa13"))
|
||||||
|
|
||||||
|
(-> {:headers admin-creds :request-method :get :uri "/basic-auth"}
|
||||||
|
app
|
||||||
|
read-body)
|
||||||
|
;; {:message "Basic auth succeeded!",
|
||||||
|
;; :user
|
||||||
|
;; {:id 1,
|
||||||
|
;; :roles [:admin :user],
|
||||||
|
;; :token
|
||||||
|
;; "eyJhbGciOiJIUzUxMiJ9.eyJp....."
|
||||||
|
|
||||||
|
(def admin-token
|
||||||
|
(-> {:headers admin-creds :request-method :get :uri "/basic-auth"}
|
||||||
|
app
|
||||||
|
read-body
|
||||||
|
:user
|
||||||
|
:token))
|
||||||
|
|
||||||
|
(def user-creds (basic-auth-headers "user2" "koira12"))
|
||||||
|
|
||||||
|
(def user-token
|
||||||
|
(-> {:headers user-creds :request-method :get :uri "/basic-auth"}
|
||||||
|
app
|
||||||
|
read-body
|
||||||
|
:user
|
||||||
|
:token))
|
||||||
|
|
||||||
|
(defn token-auth-headers [token]
|
||||||
|
(merge headers {:authorization (str "Token " token)}))
|
||||||
|
|
||||||
|
(def user-token-headers (token-auth-headers user-token))
|
||||||
|
|
||||||
|
(-> {:headers user-token-headers :request-method :get :uri "/token-auth"}
|
||||||
|
app
|
||||||
|
read-body)
|
||||||
|
;; => {:message "Token auth succeeded!"}
|
||||||
|
|
||||||
|
(-> {:headers user-token-headers :request-method :get :uri "/token-auth-with-admin-role"}
|
||||||
|
app
|
||||||
|
read-body)
|
||||||
|
;; => {:error "Admin role required"}
|
||||||
|
|
||||||
|
(def admin-token-headers (token-auth-headers admin-token))
|
||||||
|
|
||||||
|
(-> {:headers admin-token-headers :request-method :get :uri "/token-auth-with-admin-role"}
|
||||||
|
app
|
||||||
|
read-body)
|
||||||
|
;; => {:message "Token auth with admin role succeeded!"}
|
||||||
|
)
|
||||||
|
|
@ -1,23 +1,25 @@
|
||||||
(defproject frontend "0.1.0-SNAPSHOT"
|
(defproject frontend-auth "0.1.0-SNAPSHOT"
|
||||||
:description "FIXME: write description"
|
:description "FIXME: write description"
|
||||||
:url "http://example.com/FIXME"
|
:url "http://example.com/FIXME"
|
||||||
:license {:name "Eclipse Public License"
|
:license {:name "Eclipse Public License"
|
||||||
:url "http://www.eclipse.org/legal/epl-v10.html"}
|
: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"]
|
[ring-server "0.5.0"]
|
||||||
[reagent "0.8.1"]
|
[reagent "1.2.0"]
|
||||||
[ring "1.7.1"]
|
[ring "1.12.1"]
|
||||||
[hiccup "1.0.5"]
|
[hiccup "1.0.5"]
|
||||||
[org.clojure/clojurescript "1.10.439"]
|
[org.clojure/clojurescript "1.11.132"]
|
||||||
[metosin/reitit "0.5.0"]
|
[metosin/reitit "0.10.0"]
|
||||||
[metosin/reitit-schema "0.5.0"]
|
[metosin/reitit-schema "0.10.0"]
|
||||||
[metosin/reitit-frontend "0.5.0"]
|
[metosin/reitit-frontend "0.10.0"]
|
||||||
|
[cljsjs/react "17.0.2-0"]
|
||||||
|
[cljsjs/react-dom "17.0.2-0"]
|
||||||
;; Just for pretty printting the match
|
;; Just for pretty printting the match
|
||||||
[fipp "0.6.14"]]
|
[fipp "0.6.14"]]
|
||||||
|
|
||||||
:plugins [[lein-cljsbuild "1.1.7"]
|
:plugins [[lein-cljsbuild "1.1.8"]
|
||||||
[lein-figwheel "0.5.18"]]
|
[lein-figwheel "0.5.20"]]
|
||||||
|
|
||||||
:source-paths []
|
:source-paths []
|
||||||
:resource-paths ["resources" "target/cljsbuild"]
|
:resource-paths ["resources" "target/cljsbuild"]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
(ns frontend.core
|
(ns frontend.core
|
||||||
(:require [reagent.core :as r]
|
(:require [reagent.core :as r]
|
||||||
|
[reagent.dom :as rd]
|
||||||
[reitit.frontend :as rf]
|
[reitit.frontend :as rf]
|
||||||
[reitit.frontend.easy :as rfe]
|
[reitit.frontend.easy :as rfe]
|
||||||
[reitit.frontend.controllers :as rfc]
|
[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 (assoc new-match :controllers (rfc/apply-controllers (:controllers (:match state)) new-match)))
|
||||||
(assoc state :match new-match))))))
|
(assoc state :match new-match))))))
|
||||||
{:use-fragment true})
|
{:use-fragment true})
|
||||||
(r/render [main-view] (.getElementById js/document "app")))
|
(rd/render [main-view] (.getElementById js/document "app")))
|
||||||
|
|
||||||
(init!)
|
(init!)
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,25 @@
|
||||||
(defproject frontend "0.1.0-SNAPSHOT"
|
(defproject frontend-controllers "0.1.0-SNAPSHOT"
|
||||||
:description "FIXME: write description"
|
:description "FIXME: write description"
|
||||||
:url "http://example.com/FIXME"
|
:url "http://example.com/FIXME"
|
||||||
:license {:name "Eclipse Public License"
|
:license {:name "Eclipse Public License"
|
||||||
:url "http://www.eclipse.org/legal/epl-v10.html"}
|
: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"]
|
[ring-server "0.5.0"]
|
||||||
[reagent "0.8.1"]
|
[reagent "1.2.0"]
|
||||||
[ring "1.7.1"]
|
[ring "1.12.1"]
|
||||||
[hiccup "1.0.5"]
|
[hiccup "1.0.5"]
|
||||||
[org.clojure/clojurescript "1.10.439"]
|
[org.clojure/clojurescript "1.11.132"]
|
||||||
[metosin/reitit "0.5.0"]
|
[metosin/reitit "0.10.0"]
|
||||||
[metosin/reitit-schema "0.5.0"]
|
[metosin/reitit-schema "0.10.0"]
|
||||||
[metosin/reitit-frontend "0.5.0"]
|
[metosin/reitit-frontend "0.10.0"]
|
||||||
|
[cljsjs/react "17.0.2-0"]
|
||||||
|
[cljsjs/react-dom "17.0.2-0"]
|
||||||
;; Just for pretty printting the match
|
;; Just for pretty printting the match
|
||||||
[fipp "0.6.14"]]
|
[fipp "0.6.14"]]
|
||||||
|
|
||||||
:plugins [[lein-cljsbuild "1.1.7"]
|
:plugins [[lein-cljsbuild "1.1.8"]
|
||||||
[lein-figwheel "0.5.18"]]
|
[lein-figwheel "0.5.20"]]
|
||||||
|
|
||||||
:source-paths []
|
:source-paths []
|
||||||
:resource-paths ["resources" "target/cljsbuild"]
|
:resource-paths ["resources" "target/cljsbuild"]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
(ns frontend.core
|
(ns frontend.core
|
||||||
(:require [reagent.core :as r]
|
(:require [reagent.core :as r]
|
||||||
|
[reagent.dom :as rd]
|
||||||
[reitit.frontend :as rf]
|
[reitit.frontend :as rf]
|
||||||
[reitit.frontend.easy :as rfe]
|
[reitit.frontend.easy :as rfe]
|
||||||
[reitit.frontend.controllers :as rfc]
|
[reitit.frontend.controllers :as rfc]
|
||||||
|
|
@ -18,11 +19,18 @@
|
||||||
[:div
|
[:div
|
||||||
[:ul
|
[:ul
|
||||||
[:li [:a {:href (rfe/href ::item {:id 1})} "Item 1"]]
|
[:li [:a {:href (rfe/href ::item {:id 1})} "Item 1"]]
|
||||||
[:li [:a {:href (rfe/href ::item {:id 2} {:foo "bar"})} "Item 2"]]]
|
[:li [:a {:href (rfe/href ::item {:id 2} {:foo "bar"} "zzz")} "Item 2"]]]
|
||||||
(if id
|
(when id
|
||||||
[:h2 "Selected item " id])
|
[:h2 "Selected item " id])
|
||||||
(if (:foo query)
|
[:p "Query params: " [:pre (pr-str query)]]
|
||||||
[:p "Optional foo query param: " (:foo query)])]))
|
[:ul
|
||||||
|
[:li [:a {:on-click #(rfe/set-query {:a 1})} "set a=1"]]
|
||||||
|
[:li [:a {:on-click #(rfe/set-query {:a 2} {:replace true})} "set a=2 and replaceState"]]
|
||||||
|
[:li [:a {:on-click (fn [_] (rfe/set-query #(assoc % :foo "zzz")))} "add foo=zzz"]]]
|
||||||
|
[:button
|
||||||
|
{:on-click #(rfe/navigate ::item {:path-params {:id 3}
|
||||||
|
:query-params {:foo "aaa"}})}
|
||||||
|
"Navigate example, go to item 3"]]))
|
||||||
|
|
||||||
(defonce match (r/atom nil))
|
(defonce match (r/atom nil))
|
||||||
|
|
||||||
|
|
@ -31,9 +39,8 @@
|
||||||
[:ul
|
[:ul
|
||||||
[:li [:a {:href (rfe/href ::frontpage)} "Frontpage"]]
|
[:li [:a {:href (rfe/href ::frontpage)} "Frontpage"]]
|
||||||
[:li
|
[:li
|
||||||
[:a {:href (rfe/href ::item-list)} "Item list"]
|
[:a {:href (rfe/href ::item-list)} "Item list"]]]
|
||||||
]]
|
(when @match
|
||||||
(if @match
|
|
||||||
(let [view (:view (:data @match))]
|
(let [view (:view (:data @match))]
|
||||||
[view @match]))
|
[view @match]))
|
||||||
[:pre (with-out-str (fedn/pprint @match))]])
|
[:pre (with-out-str (fedn/pprint @match))]])
|
||||||
|
|
@ -63,7 +70,8 @@
|
||||||
["/:id"
|
["/:id"
|
||||||
{:name ::item
|
{:name ::item
|
||||||
:parameters {:path {:id s/Int}
|
:parameters {:path {:id s/Int}
|
||||||
:query {(s/optional-key :foo) s/Keyword}}
|
:query {(s/optional-key :a) s/Int
|
||||||
|
(s/optional-key :foo) s/Keyword}}
|
||||||
:controllers [{:parameters {:path [:id]}
|
:controllers [{:parameters {:path [:id]}
|
||||||
:start (fn [{:keys [path]}]
|
:start (fn [{:keys [path]}]
|
||||||
(js/console.log "start" "item controller" (:id path)))
|
(js/console.log "start" "item controller" (:id path)))
|
||||||
|
|
@ -81,6 +89,6 @@
|
||||||
(if new-match
|
(if new-match
|
||||||
(assoc new-match :controllers (rfc/apply-controllers (:controllers old-match) new-match))))))
|
(assoc new-match :controllers (rfc/apply-controllers (:controllers old-match) new-match))))))
|
||||||
{:use-fragment true})
|
{:use-fragment true})
|
||||||
(r/render [current-page] (.getElementById js/document "app")))
|
(rd/render [current-page] (.getElementById js/document "app")))
|
||||||
|
|
||||||
(init!)
|
(init!)
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,26 @@
|
||||||
(defproject frontend "0.1.0-SNAPSHOT"
|
(defproject frontend-links "0.1.0-SNAPSHOT"
|
||||||
:description "FIXME: write description"
|
:description "FIXME: write description"
|
||||||
:url "http://example.com/FIXME"
|
:url "http://example.com/FIXME"
|
||||||
:license {:name "Eclipse Public License"
|
:license {:name "Eclipse Public License"
|
||||||
:url "http://www.eclipse.org/legal/epl-v10.html"}
|
: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"]
|
[ring-server "0.5.0"]
|
||||||
[reagent "0.8.1"]
|
[reagent "1.2.0"]
|
||||||
[ring "1.7.1"]
|
[ring "1.12.1"]
|
||||||
[hiccup "1.0.5"]
|
[hiccup "1.0.5"]
|
||||||
[org.clojure/clojurescript "1.10.520"]
|
[org.clojure/clojurescript "1.10.520"]
|
||||||
[metosin/reitit "0.5.0"]
|
[metosin/reitit "0.10.0"]
|
||||||
[metosin/reitit-spec "0.5.0"]
|
[metosin/reitit-spec "0.10.0"]
|
||||||
[metosin/reitit-frontend "0.5.0"]
|
[metosin/reitit-frontend "0.10.0"]
|
||||||
|
[cljsjs/react "17.0.2-0"]
|
||||||
|
[cljsjs/react-dom "17.0.2-0"]
|
||||||
;; Just for pretty printting the match
|
;; Just for pretty printting the match
|
||||||
[fipp "0.6.14"]]
|
[fipp "0.6.14"]]
|
||||||
|
|
||||||
:plugins [[lein-cljsbuild "1.1.7"]
|
:plugins [[lein-cljsbuild "1.1.8"]
|
||||||
[lein-figwheel "0.5.18"]
|
[lein-figwheel "0.5.20"]
|
||||||
[cider/cider-nrepl "0.21.1"]]
|
[cider/cider-nrepl "0.47.1"]]
|
||||||
|
|
||||||
:repl-options {:nrepl-middleware [cider.piggieback/wrap-cljs-repl]}
|
:repl-options {:nrepl-middleware [cider.piggieback/wrap-cljs-repl]}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
(:require [clojure.string :as string]
|
(:require [clojure.string :as string]
|
||||||
[fipp.edn :as fedn]
|
[fipp.edn :as fedn]
|
||||||
[reagent.core :as r]
|
[reagent.core :as r]
|
||||||
|
[reagent.dom :as rd]
|
||||||
[reitit.coercion.spec :as rss]
|
[reitit.coercion.spec :as rss]
|
||||||
[reitit.frontend :as rf]
|
[reitit.frontend :as rf]
|
||||||
[reitit.frontend.easy :as rfe]
|
[reitit.frontend.easy :as rfe]
|
||||||
|
|
@ -137,7 +138,7 @@
|
||||||
(fn [m] (reset! current-match m))
|
(fn [m] (reset! current-match m))
|
||||||
;; set to false to enable HistoryAPI
|
;; set to false to enable HistoryAPI
|
||||||
{:use-fragment true})
|
{:use-fragment true})
|
||||||
(r/render [current-page] (.getElementById js/document "app")))
|
(rd/render [current-page] (.getElementById js/document "app")))
|
||||||
|
|
||||||
(init!)
|
(init!)
|
||||||
|
|
||||||
|
|
|
||||||
13
examples/frontend-malli/README.md
Normal file
13
examples/frontend-malli/README.md
Normal 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
|
||||||
1
examples/frontend-malli/checkouts/reitit-core
Symbolic link
1
examples/frontend-malli/checkouts/reitit-core
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../../../modules/reitit-core
|
||||||
1
examples/frontend-malli/checkouts/reitit-frontend
Symbolic link
1
examples/frontend-malli/checkouts/reitit-frontend
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../../../modules/reitit-frontend
|
||||||
1
examples/frontend-malli/checkouts/reitit-schema
Symbolic link
1
examples/frontend-malli/checkouts/reitit-schema
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../../../modules/reitit-schema
|
||||||
57
examples/frontend-malli/project.clj
Normal file
57
examples/frontend-malli/project.clj
Normal 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.10.0"]
|
||||||
|
[metosin/reitit-malli "0.10.0"]
|
||||||
|
[metosin/reitit-frontend "0.10.0"]
|
||||||
|
[cljsjs/react "17.0.2-0"]
|
||||||
|
[cljsjs/react-dom "17.0.2-0"]
|
||||||
|
;; Just for pretty printting the match
|
||||||
|
[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})
|
||||||
10
examples/frontend-malli/resources/public/index.html
Normal file
10
examples/frontend-malli/resources/public/index.html
Normal 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>
|
||||||
10
examples/frontend-malli/src/backend/server.clj
Normal file
10
examples/frontend-malli/src/backend/server.clj
Normal 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))
|
||||||
84
examples/frontend-malli/src/frontend/core.cljs
Normal file
84
examples/frontend-malli/src/frontend/core.cljs
Normal 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!)
|
||||||
|
|
@ -1,24 +1,27 @@
|
||||||
(defproject frontend "0.1.0-SNAPSHOT"
|
(defproject frontend-prompt "0.1.0-SNAPSHOT"
|
||||||
:description "FIXME: write description"
|
:description "FIXME: write description"
|
||||||
:url "http://example.com/FIXME"
|
:url "http://example.com/FIXME"
|
||||||
:license {:name "Eclipse Public License"
|
:license {:name "Eclipse Public License"
|
||||||
:url "http://www.eclipse.org/legal/epl-v10.html"}
|
: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"]
|
[ring-server "0.5.0"]
|
||||||
[reagent "0.8.1"]
|
[reagent "1.2.0"]
|
||||||
[ring "1.7.1"]
|
[ring "1.12.1"]
|
||||||
[hiccup "1.0.5"]
|
[hiccup "1.0.5"]
|
||||||
[org.clojure/clojurescript "1.10.520"]
|
[org.clojure/clojurescript "1.11.132"]
|
||||||
[metosin/reitit "0.5.0"]
|
[metosin/reitit "0.10.0"]
|
||||||
[metosin/reitit-spec "0.5.0"]
|
[metosin/reitit-spec "0.10.0"]
|
||||||
[metosin/reitit-frontend "0.5.0"]
|
[metosin/reitit-frontend "0.10.0"]
|
||||||
|
[cljsjs/react "17.0.2-0"]
|
||||||
|
[cljsjs/react-dom "17.0.2-0"]
|
||||||
;; Just for pretty printting the match
|
;; 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]}
|
:repl-options {:nrepl-middleware [cider.piggieback/wrap-cljs-repl]}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
(ns frontend.core
|
(ns frontend.core
|
||||||
(:require [fipp.edn :as fedn]
|
(:require [fipp.edn :as fedn]
|
||||||
[reagent.core :as r]
|
[reagent.core :as r]
|
||||||
|
[reagent.dom :as rd]
|
||||||
[reitit.coercion.spec :as rss]
|
[reitit.coercion.spec :as rss]
|
||||||
[reitit.frontend :as rf]
|
[reitit.frontend :as rf]
|
||||||
[reitit.frontend.easy :as rfe]))
|
[reitit.frontend.easy :as rfe]))
|
||||||
|
|
@ -63,6 +64,6 @@
|
||||||
on-navigate
|
on-navigate
|
||||||
;; set to false to enable HistoryAPI
|
;; set to false to enable HistoryAPI
|
||||||
{:use-fragment true})
|
{:use-fragment true})
|
||||||
(r/render [current-page] (.getElementById js/document "app")))
|
(rd/render [current-page] (.getElementById js/document "app")))
|
||||||
|
|
||||||
(init!)
|
(init!)
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
(defproject frontend-re-frame "0.1.0-SNAPSHOT"
|
(defproject frontend-re-frame "0.1.0-SNAPSHOT"
|
||||||
:dependencies [[org.clojure/clojure "1.10.0"]
|
:dependencies [[org.clojure/clojure "1.11.2"]
|
||||||
[org.clojure/clojurescript "1.10.520"]
|
[org.clojure/clojurescript "1.11.132"]
|
||||||
[metosin/reitit "0.5.0"]
|
[metosin/reitit "0.10.0"]
|
||||||
[reagent "0.8.1"]
|
[reagent "1.2.0"]
|
||||||
[re-frame "0.10.6"]]
|
[re-frame "0.10.6"]
|
||||||
|
[cljsjs/react "17.0.2-0"]
|
||||||
|
[cljsjs/react-dom "17.0.2-0"]]
|
||||||
|
|
||||||
:plugins [[lein-cljsbuild "1.1.7"]
|
:plugins [[lein-cljsbuild "1.1.8"]
|
||||||
[lein-figwheel "0.5.18"]
|
[lein-figwheel "0.5.20"]
|
||||||
[cider/cider-nrepl "0.21.1"]]
|
[cider/cider-nrepl "0.47.1"]]
|
||||||
|
|
||||||
:repl-options {:nrepl-middleware [cider.piggieback/wrap-cljs-repl]}
|
:repl-options {:nrepl-middleware [cider.piggieback/wrap-cljs-repl]}
|
||||||
:min-lein-version "2.5.3"
|
:min-lein-version "2.5.3"
|
||||||
|
|
@ -33,7 +35,7 @@
|
||||||
{:builds
|
{:builds
|
||||||
[{:id "dev"
|
[{:id "dev"
|
||||||
:source-paths ["src/cljs"]
|
:source-paths ["src/cljs"]
|
||||||
:figwheel {:on-jsload "frontend-re-frame.core/mount-root"}
|
:figwheel true
|
||||||
:compiler {:main frontend-re-frame.core
|
:compiler {:main frontend-re-frame.core
|
||||||
:output-to "resources/public/js/compiled/app.js"
|
:output-to "resources/public/js/compiled/app.js"
|
||||||
:output-dir "resources/public/js/compiled/out"
|
:output-dir "resources/public/js/compiled/out"
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,9 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset='utf-8'>
|
<meta charset='utf-8'>
|
||||||
|
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script src="js/compiled/app.js"></script>
|
<script src="js/compiled/app.js"></script>
|
||||||
<script>frontend_re_frame.core.init();</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,33 @@
|
||||||
(ns frontend-re-frame.core
|
(ns frontend-re-frame.core
|
||||||
(:require [re-frame.core :as re-frame]
|
(:require [re-frame.core :as re-frame]
|
||||||
[reagent.core :as reagent]
|
[reagent.dom :as rd]
|
||||||
[reitit.core :as r]
|
[reitit.core :as r]
|
||||||
[reitit.coercion.spec :as rss]
|
[reitit.coercion.spec :as rss]
|
||||||
[reitit.frontend :as rf]
|
[reitit.frontend :as rf]
|
||||||
[reitit.frontend.controllers :as rfc]
|
[reitit.frontend.controllers :as rfc]
|
||||||
[reitit.frontend.easy :as rfe]))
|
[reitit.frontend.easy :as rfe]))
|
||||||
|
|
||||||
|
;;; Effects ;;;
|
||||||
|
|
||||||
|
;; Triggering navigation from events.
|
||||||
|
|
||||||
|
(re-frame/reg-fx :push-state
|
||||||
|
(fn [route]
|
||||||
|
(apply rfe/push-state route)))
|
||||||
|
|
||||||
;;; Events ;;;
|
;;; Events ;;;
|
||||||
|
|
||||||
(re-frame/reg-event-db
|
(re-frame/reg-event-db ::initialize-db
|
||||||
::initialize-db
|
(fn [db _]
|
||||||
(fn [_ _]
|
(if db
|
||||||
{:current-route nil}))
|
db
|
||||||
|
{:current-route nil})))
|
||||||
|
|
||||||
(re-frame/reg-event-fx
|
(re-frame/reg-event-fx ::push-state
|
||||||
::navigate
|
(fn [_ [_ & route]]
|
||||||
(fn [db [_ & route]]
|
{:push-state route}))
|
||||||
;; See `navigate` effect in routes.cljs
|
|
||||||
{::navigate! route}))
|
|
||||||
|
|
||||||
(re-frame/reg-event-db
|
(re-frame/reg-event-db ::navigated
|
||||||
::navigated
|
|
||||||
(fn [db [_ new-match]]
|
(fn [db [_ new-match]]
|
||||||
(let [old-match (:current-route db)
|
(let [old-match (:current-route db)
|
||||||
controllers (rfc/apply-controllers (:controllers old-match) new-match)]
|
controllers (rfc/apply-controllers (:controllers old-match) new-match)]
|
||||||
|
|
@ -29,8 +35,7 @@
|
||||||
|
|
||||||
;;; Subscriptions ;;;
|
;;; Subscriptions ;;;
|
||||||
|
|
||||||
(re-frame/reg-sub
|
(re-frame/reg-sub ::current-route
|
||||||
::current-route
|
|
||||||
(fn [db]
|
(fn [db]
|
||||||
(:current-route db)))
|
(:current-route db)))
|
||||||
|
|
||||||
|
|
@ -41,7 +46,7 @@
|
||||||
[:h1 "This is home page"]
|
[:h1 "This is home page"]
|
||||||
[:button
|
[:button
|
||||||
;; Dispatch navigate event that triggers a (side)effect.
|
;; Dispatch navigate event that triggers a (side)effect.
|
||||||
{:on-click #(re-frame/dispatch [::navigate ::sub-page2])}
|
{:on-click #(re-frame/dispatch [::push-state ::sub-page2])}
|
||||||
"Go to sub-page 2"]])
|
"Go to sub-page 2"]])
|
||||||
|
|
||||||
(defn sub-page1 []
|
(defn sub-page1 []
|
||||||
|
|
@ -52,14 +57,6 @@
|
||||||
[:div
|
[:div
|
||||||
[:h1 "This is sub-page 2"]])
|
[:h1 "This is sub-page 2"]])
|
||||||
|
|
||||||
;;; Effects ;;;
|
|
||||||
|
|
||||||
;; Triggering navigation from events.
|
|
||||||
(re-frame/reg-fx
|
|
||||||
::navigate!
|
|
||||||
(fn [route]
|
|
||||||
(apply rfe/push-state route)))
|
|
||||||
|
|
||||||
;;; Routes ;;;
|
;;; Routes ;;;
|
||||||
|
|
||||||
(defn href
|
(defn href
|
||||||
|
|
@ -141,13 +138,12 @@
|
||||||
(enable-console-print!)
|
(enable-console-print!)
|
||||||
(println "dev mode")))
|
(println "dev mode")))
|
||||||
|
|
||||||
(defn mount-root []
|
(defn init []
|
||||||
(re-frame/clear-subscription-cache!)
|
(re-frame/clear-subscription-cache!)
|
||||||
(init-routes!) ;; Reset routes on figwheel reload
|
|
||||||
(reagent/render [router-component {:router router}]
|
|
||||||
(.getElementById js/document "app")))
|
|
||||||
|
|
||||||
(defn ^:export init []
|
|
||||||
(re-frame/dispatch-sync [::initialize-db])
|
(re-frame/dispatch-sync [::initialize-db])
|
||||||
(dev-setup)
|
(dev-setup)
|
||||||
(mount-root))
|
(init-routes!) ;; Reset routes on figwheel reload
|
||||||
|
(rd/render [router-component {:router router}]
|
||||||
|
(.getElementById js/document "app")))
|
||||||
|
|
||||||
|
(init)
|
||||||
|
|
|
||||||
|
|
@ -10,4 +10,4 @@ Go with browser to http://localhost:3449
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Copyright © 2018 Metosin Oy
|
Copyright © Metosin Oy and collaborators
|
||||||
|
|
|
||||||
|
|
@ -4,25 +4,27 @@
|
||||||
:license {:name "Eclipse Public License"
|
:license {:name "Eclipse Public License"
|
||||||
:url "http://www.eclipse.org/legal/epl-v10.html"}
|
: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"]
|
[ring-server "0.5.0"]
|
||||||
[reagent "0.8.1"]
|
[reagent "1.2.0"]
|
||||||
[ring "1.7.1"]
|
[ring "1.12.1"]
|
||||||
[hiccup "1.0.5"]
|
[hiccup "1.0.5"]
|
||||||
[org.clojure/clojurescript "1.10.439"]
|
[org.clojure/clojurescript "1.11.132"]
|
||||||
[metosin/reitit "0.5.0"]
|
[metosin/reitit "0.10.0"]
|
||||||
[metosin/reitit-spec "0.5.0"]
|
[metosin/reitit-spec "0.10.0"]
|
||||||
[metosin/reitit-frontend "0.5.0"]
|
[metosin/reitit-frontend "0.10.0"]
|
||||||
|
[cljsjs/react "17.0.2-0"]
|
||||||
|
[cljsjs/react-dom "17.0.2-0"]
|
||||||
;; Just for pretty printting the match
|
;; Just for pretty printting the match
|
||||||
[fipp "0.6.14"]]
|
[fipp "0.6.23"]]
|
||||||
|
|
||||||
:plugins [[lein-cljsbuild "1.1.7"]
|
:plugins [[lein-cljsbuild "1.1.8"]
|
||||||
[lein-figwheel "0.5.18"]]
|
[lein-figwheel "0.5.20"]]
|
||||||
|
|
||||||
:source-paths []
|
:source-paths []
|
||||||
:resource-paths ["resources" "target/cljsbuild"]
|
:resource-paths ["resources" "target/cljsbuild"]
|
||||||
|
|
||||||
:profiles {:dev {:dependencies [[binaryage/devtools "0.9.10"]]}}
|
:profiles {:dev {:dependencies [[binaryage/devtools "1.0.2"]]}}
|
||||||
|
|
||||||
:cljsbuild
|
:cljsbuild
|
||||||
{:builds
|
{:builds
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
(ns frontend.core
|
(ns frontend.core
|
||||||
(:require [reagent.core :as r]
|
(:require [reagent.core :as r]
|
||||||
|
[reagent.dom :as rd]
|
||||||
[reitit.frontend :as rf]
|
[reitit.frontend :as rf]
|
||||||
[reitit.frontend.easy :as rfe]
|
[reitit.frontend.easy :as rfe]
|
||||||
[reitit.coercion.spec :as rss]
|
[reitit.coercion.spec :as rss]
|
||||||
|
|
@ -77,6 +78,6 @@
|
||||||
(fn [m] (reset! match m))
|
(fn [m] (reset! match m))
|
||||||
;; set to false to enable HistoryAPI
|
;; set to false to enable HistoryAPI
|
||||||
{:use-fragment true})
|
{:use-fragment true})
|
||||||
(r/render [current-page] (.getElementById js/document "app")))
|
(rd/render [current-page] (.getElementById js/document "app")))
|
||||||
|
|
||||||
(init!)
|
(init!)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Http with Swagger example
|
# Http with Swagger/OpenAPI example
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
|
@ -7,6 +7,10 @@
|
||||||
(start)
|
(start)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- Swagger spec served at <http://localhost:3000/swagger.json>
|
||||||
|
- Openapi spec served at <http://localhost:3000/openapi.json>
|
||||||
|
- Swagger UI served at <http://localhost:3000/>
|
||||||
|
|
||||||
To test the endpoints using [httpie](https://httpie.org/):
|
To test the endpoints using [httpie](https://httpie.org/):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -20,4 +24,4 @@ http GET :3000/async results==1 seed==reitit
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Copyright © 2018 Metosin Oy
|
Copyright © 2018-2023 Metosin Oy
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
(defproject ring-example "0.1.0-SNAPSHOT"
|
(defproject http-swagger "0.1.0-SNAPSHOT"
|
||||||
:description "Reitit Http App with Swagger"
|
:description "Reitit Http App with Swagger"
|
||||||
:dependencies [[org.clojure/clojure "1.10.0"]
|
:dependencies [[org.clojure/clojure "1.11.2"]
|
||||||
[ring/ring-jetty-adapter "1.7.1"]
|
[ring/ring-jetty-adapter "1.12.1"]
|
||||||
[aleph "0.4.7-alpha5"]
|
[aleph "0.7.1"]
|
||||||
[metosin/reitit "0.5.0"]]
|
[metosin/reitit "0.10.0"]
|
||||||
|
[metosin/ring-swagger-ui "5.9.0"]]
|
||||||
:repl-options {:init-ns example.server})
|
:repl-options {:init-ns example.server})
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
[reitit.coercion.spec]
|
[reitit.coercion.spec]
|
||||||
[reitit.swagger :as swagger]
|
[reitit.swagger :as swagger]
|
||||||
[reitit.swagger-ui :as swagger-ui]
|
[reitit.swagger-ui :as swagger-ui]
|
||||||
|
[reitit.openapi :as openapi]
|
||||||
[reitit.http.coercion :as coercion]
|
[reitit.http.coercion :as coercion]
|
||||||
[reitit.dev.pretty :as pretty]
|
[reitit.dev.pretty :as pretty]
|
||||||
[reitit.interceptor.sieppari :as sieppari]
|
[reitit.interceptor.sieppari :as sieppari]
|
||||||
|
|
@ -42,11 +43,26 @@
|
||||||
[["/swagger.json"
|
[["/swagger.json"
|
||||||
{:get {:no-doc true
|
{:get {:no-doc true
|
||||||
:swagger {:info {:title "my-api"
|
:swagger {:info {:title "my-api"
|
||||||
:description "with reitit-http"}}
|
:description "swagger-docs with reitit-http"
|
||||||
|
:version "0.0.1"}
|
||||||
|
;; used in /secure APIs below
|
||||||
|
:securityDefinitions {"auth" {:type :apiKey
|
||||||
|
:in :header
|
||||||
|
:name "Example-Api-Key"}}}
|
||||||
:handler (swagger/create-swagger-handler)}}]
|
:handler (swagger/create-swagger-handler)}}]
|
||||||
|
["/openapi.json"
|
||||||
|
{:get {:no-doc true
|
||||||
|
:openapi {:info {:title "my-api"
|
||||||
|
:description "openapi3-docs with reitit-http"
|
||||||
|
:version "0.0.1"}
|
||||||
|
;; used in /secure APIs below
|
||||||
|
:components {:securitySchemes {"auth" {:type :apiKey
|
||||||
|
:in :header
|
||||||
|
:name "Example-Api-Key"}}}}
|
||||||
|
:handler (openapi/create-openapi-handler)}}]
|
||||||
|
|
||||||
["/files"
|
["/files"
|
||||||
{:swagger {:tags ["files"]}}
|
{:tags #{"files"}}
|
||||||
|
|
||||||
["/upload"
|
["/upload"
|
||||||
{:post {:summary "upload a file"
|
{:post {:summary "upload a file"
|
||||||
|
|
@ -60,7 +76,8 @@
|
||||||
["/download"
|
["/download"
|
||||||
{:get {:summary "downloads a file"
|
{:get {:summary "downloads a file"
|
||||||
:swagger {:produces ["image/png"]}
|
:swagger {:produces ["image/png"]}
|
||||||
:responses {200 {:description "image"}}
|
:responses {200 {:description "an image"
|
||||||
|
:content {"image/png" {:schema any?}}}}
|
||||||
:handler (fn [_]
|
:handler (fn [_]
|
||||||
{:status 200
|
{:status 200
|
||||||
:headers {"Content-Type" "image/png"}
|
:headers {"Content-Type" "image/png"}
|
||||||
|
|
@ -68,7 +85,7 @@
|
||||||
(io/resource "reitit.png"))})}}]]
|
(io/resource "reitit.png"))})}}]]
|
||||||
|
|
||||||
["/async"
|
["/async"
|
||||||
{:get {:swagger {:tags ["async"]}
|
{:get {:tags #{"async"}
|
||||||
:summary "fetches random users asynchronously over the internet"
|
:summary "fetches random users asynchronously over the internet"
|
||||||
:parameters {:query (s/keys :req-un [::results] :opt-un [::seed])}
|
:parameters {:query (s/keys :req-un [::results] :opt-un [::seed])}
|
||||||
:responses {200 {:body any?}}
|
:responses {200 {:body any?}}
|
||||||
|
|
@ -85,7 +102,7 @@
|
||||||
:body results})))}}]
|
:body results})))}}]
|
||||||
|
|
||||||
["/math"
|
["/math"
|
||||||
{:swagger {:tags ["math"]}}
|
{:tags #{"math"}}
|
||||||
|
|
||||||
["/plus"
|
["/plus"
|
||||||
{:get {:summary "plus with data-spec query parameters"
|
{:get {:summary "plus with data-spec query parameters"
|
||||||
|
|
@ -113,7 +130,22 @@
|
||||||
:responses {200 {:body (s/keys :req-un [::total])}}
|
:responses {200 {:body (s/keys :req-un [::total])}}
|
||||||
:handler (fn [{{{:keys [x y]} :body} :parameters}]
|
:handler (fn [{{{:keys [x y]} :body} :parameters}]
|
||||||
{:status 200
|
{:status 200
|
||||||
:body {:total (- x y)}})}}]]]
|
:body {:total (- x y)}})}}]]
|
||||||
|
["/secure"
|
||||||
|
{:tags #{"secure"}
|
||||||
|
:openapi {:security [{"auth" []}]}
|
||||||
|
:swagger {:security [{"auth" []}]}}
|
||||||
|
["/get"
|
||||||
|
{:get {:summary "endpoint authenticated with a header"
|
||||||
|
:responses {200 {:body {:secret string?}}
|
||||||
|
401 {:body {:error string?}}}
|
||||||
|
:handler (fn [request]
|
||||||
|
;; In a real app authentication would be handled by middleware
|
||||||
|
(if (= "secret" (get-in request [:headers "example-api-key"]))
|
||||||
|
{:status 200
|
||||||
|
:body {:secret "I am a marmot"}}
|
||||||
|
{:status 401
|
||||||
|
:body {:error "unauthorized"}}))}}]]]
|
||||||
|
|
||||||
{;:reitit.interceptor/transform dev/print-context-diffs ;; pretty context diffs
|
{;:reitit.interceptor/transform dev/print-context-diffs ;; pretty context diffs
|
||||||
;;:validate spec/validate ;; enable spec validation for route data
|
;;:validate spec/validate ;; enable spec validation for route data
|
||||||
|
|
@ -123,6 +155,8 @@
|
||||||
:muuntaja m/instance
|
:muuntaja m/instance
|
||||||
:interceptors [;; swagger feature
|
:interceptors [;; swagger feature
|
||||||
swagger/swagger-feature
|
swagger/swagger-feature
|
||||||
|
;; openapi feature
|
||||||
|
openapi/openapi-feature
|
||||||
;; query-params & form-params
|
;; query-params & form-params
|
||||||
(parameters/parameters-interceptor)
|
(parameters/parameters-interceptor)
|
||||||
;; content-negotiation
|
;; content-negotiation
|
||||||
|
|
@ -143,6 +177,9 @@
|
||||||
(swagger-ui/create-swagger-ui-handler
|
(swagger-ui/create-swagger-ui-handler
|
||||||
{:path "/"
|
{:path "/"
|
||||||
:config {:validatorUrl nil
|
:config {:validatorUrl nil
|
||||||
|
:urls [{:name "swagger", :url "swagger.json"}
|
||||||
|
{:name "openapi", :url "openapi.json"}]
|
||||||
|
:urls.primaryName "openapi"
|
||||||
:operationsSorter "alpha"}})
|
:operationsSorter "alpha"}})
|
||||||
(ring/create-default-handler))
|
(ring/create-default-handler))
|
||||||
{:executor sieppari/executor}))
|
{:executor sieppari/executor}))
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
(defproject ring-example "0.1.0-SNAPSHOT"
|
(defproject http "0.1.0-SNAPSHOT"
|
||||||
:description "Reitit Ring App with Swagger"
|
:description "Reitit Ring App with Swagger"
|
||||||
:dependencies [[org.clojure/clojure "1.10.0"]
|
:dependencies [[org.clojure/clojure "1.11.2"]
|
||||||
[org.clojure/core.async "0.4.490"]
|
[org.clojure/core.async "1.6.681"]
|
||||||
[funcool/promesa "1.9.0"]
|
[funcool/promesa "11.0.678"]
|
||||||
[manifold "0.1.8"]
|
[manifold "0.4.2"]
|
||||||
[ring/ring-jetty-adapter "1.7.1"]
|
[ring/ring-jetty-adapter "1.12.1"]
|
||||||
[metosin/reitit "0.5.0"]]
|
[metosin/reitit "0.10.0"]]
|
||||||
:repl-options {:init-ns example.server})
|
:repl-options {:init-ns example.server})
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
(defproject just-coercion-with-ring "0.1.0-SNAPSHOT"
|
(defproject just-coercion-with-ring "0.1.0-SNAPSHOT"
|
||||||
:description "Reitit coercion with vanilla ring"
|
:description "Reitit coercion with vanilla ring"
|
||||||
:dependencies [[org.clojure/clojure "1.10.0"]
|
:dependencies [[org.clojure/clojure "1.11.2"]
|
||||||
[ring/ring-jetty-adapter "1.7.1"]
|
[ring/ring-jetty-adapter "1.12.1"]
|
||||||
[metosin/reitit "0.5.0"]])
|
[metosin/reitit "0.10.0"]])
|
||||||
|
|
|
||||||
18
examples/openapi/README.md
Normal file
18
examples/openapi/README.md
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# OpenAPI 3 feature showcase
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```clj
|
||||||
|
> lein repl
|
||||||
|
(start)
|
||||||
|
```
|
||||||
|
|
||||||
|
- Swagger UI served at <http://localhost:3000/>
|
||||||
|
- Openapi spec served at <http://localhost:3000/openapi.json>
|
||||||
|
- See [src/example/server.clj](src/example/server.clj) for details
|
||||||
|
|
||||||
|
<img src="https://raw.githubusercontent.com/metosin/reitit/master/examples/openapi/openapi.png" />
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Copyright © 2023 Metosin Oy
|
||||||
BIN
examples/openapi/openapi.png
Normal file
BIN
examples/openapi/openapi.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 137 KiB |
10
examples/openapi/project.clj
Normal file
10
examples/openapi/project.clj
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
(defproject openapi "0.1.0-SNAPSHOT"
|
||||||
|
:description "Reitit OpenAPI example"
|
||||||
|
:dependencies [[org.clojure/clojure "1.11.2"]
|
||||||
|
[metosin/jsonista "0.3.8"]
|
||||||
|
[ring/ring-jetty-adapter "1.12.1"]
|
||||||
|
[metosin/reitit "0.10.0"]
|
||||||
|
[metosin/ring-swagger-ui "5.9.0"]
|
||||||
|
[org.slf4j/slf4j-simple "2.0.9"]]
|
||||||
|
:repl-options {:init-ns example.server}
|
||||||
|
:profiles {:dev {:dependencies [[ring/ring-mock "0.4.0"]]}})
|
||||||
227
examples/openapi/src/example/server.clj
Normal file
227
examples/openapi/src/example/server.clj
Normal file
|
|
@ -0,0 +1,227 @@
|
||||||
|
(ns example.server
|
||||||
|
(:require [reitit.ring :as ring]
|
||||||
|
[reitit.ring.spec]
|
||||||
|
[reitit.coercion.malli]
|
||||||
|
[reitit.openapi :as openapi]
|
||||||
|
[reitit.ring.malli]
|
||||||
|
[reitit.swagger-ui :as swagger-ui]
|
||||||
|
[reitit.ring.coercion :as coercion]
|
||||||
|
[reitit.dev.pretty :as pretty]
|
||||||
|
[reitit.ring.middleware.muuntaja :as muuntaja]
|
||||||
|
[reitit.ring.middleware.exception :as exception]
|
||||||
|
[reitit.ring.middleware.multipart :as multipart]
|
||||||
|
[reitit.ring.middleware.parameters :as parameters]
|
||||||
|
[ring.adapter.jetty :as jetty]
|
||||||
|
[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
|
||||||
|
[["/openapi.json"
|
||||||
|
{:get {:no-doc true
|
||||||
|
:openapi {:info {:title "my-api"
|
||||||
|
:description "openapi3 docs with [malli](https://github.com/metosin/malli) and reitit-ring"
|
||||||
|
:version "0.0.1"}
|
||||||
|
;; used in /secure APIs below
|
||||||
|
:components {:securitySchemes {"auth" {:type :apiKey
|
||||||
|
:in :header
|
||||||
|
:name "Example-Api-Key"}}}}
|
||||||
|
:handler (openapi/create-openapi-handler)}}]
|
||||||
|
|
||||||
|
["/pizza"
|
||||||
|
{:get {:summary "Fetch a pizza | Multiple content-types, multiple examples"
|
||||||
|
: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 {:format :json
|
||||||
|
:color :white
|
||||||
|
:pineapple true}}
|
||||||
|
:red {:description "Red pizza"
|
||||||
|
:value {:format :json
|
||||||
|
:color :red
|
||||||
|
:pineapple false}}}}
|
||||||
|
"application/edn" {:schema [:map
|
||||||
|
[:format [:enum :edn]]
|
||||||
|
[:color :keyword]
|
||||||
|
[:pineapple :boolean]]
|
||||||
|
:examples {:red {:description "Red pizza with pineapple"
|
||||||
|
:value (pr-str {:format :edn :color :red :pineapple true})}}}}}}
|
||||||
|
:handler (fn [_request]
|
||||||
|
(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" {:schema [:map
|
||||||
|
[:color :keyword]
|
||||||
|
[:pineapple :boolean]]
|
||||||
|
:examples {:purple {:value (pr-str {:color :purple
|
||||||
|
:pineapple false})}}}}}
|
||||||
|
: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]
|
||||||
|
(if (< (Math/random) 0.5)
|
||||||
|
{:status 200
|
||||||
|
:body {:success true}}
|
||||||
|
{:status 500
|
||||||
|
:body {:error "an error happened"}}))}}]
|
||||||
|
|
||||||
|
|
||||||
|
["/contact"
|
||||||
|
{:get {:summary "Search for a contact | Customizing via malli properties"
|
||||||
|
:parameters {:query [:map
|
||||||
|
[:limit {:title "How many results to return? Optional."
|
||||||
|
:optional true
|
||||||
|
: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?]]}
|
||||||
|
:responses {200 {:content {:default {:schema [:vector
|
||||||
|
[:map
|
||||||
|
[:name {:json-schema/example "Heidi"}
|
||||||
|
string?]
|
||||||
|
[:email {:json-schema/example "heidi@alps.ch"}
|
||||||
|
string?]]]}}}}
|
||||||
|
:handler (fn [_request]
|
||||||
|
{: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-of :keyword :any]}}}}
|
||||||
|
:handler (fn [request]
|
||||||
|
{:status 200
|
||||||
|
:body (get-in request [:parameters :request])})}}]
|
||||||
|
|
||||||
|
["/secure"
|
||||||
|
{:tags #{"secure"}
|
||||||
|
:openapi {:security [{"auth" []}]}}
|
||||||
|
["/get"
|
||||||
|
{:get {:summary "endpoint authenticated with a header"
|
||||||
|
:responses {200 {:body [:map [:secret :string]]}
|
||||||
|
401 {:body [:map [:error :string]]}}
|
||||||
|
:handler (fn [request]
|
||||||
|
;; In a real app authentication would be handled by middleware
|
||||||
|
(if (= "secret" (get-in request [:headers "example-api-key"]))
|
||||||
|
{:status 200
|
||||||
|
:body {:secret "I am a marmot"}}
|
||||||
|
{:status 401
|
||||||
|
:body {:error "unauthorized"}}))}}]]]
|
||||||
|
|
||||||
|
{;;:reitit.middleware/transform dev/print-request-diffs ;; pretty diffs
|
||||||
|
:validate reitit.ring.spec/validate
|
||||||
|
:exception pretty/exception
|
||||||
|
:data {:coercion reitit.coercion.malli/coercion
|
||||||
|
:muuntaja m/instance
|
||||||
|
:middleware [openapi/openapi-feature
|
||||||
|
;; query-params & form-params
|
||||||
|
parameters/parameters-middleware
|
||||||
|
;; content-negotiation
|
||||||
|
muuntaja/format-negotiate-middleware
|
||||||
|
;; encoding response body
|
||||||
|
muuntaja/format-response-middleware
|
||||||
|
;; exception handling
|
||||||
|
exception/exception-middleware
|
||||||
|
;; decoding request body
|
||||||
|
muuntaja/format-request-middleware
|
||||||
|
;; coercing response bodys
|
||||||
|
coercion/coerce-response-middleware
|
||||||
|
;; coercing request parameters
|
||||||
|
coercion/coerce-request-middleware
|
||||||
|
;; multipart
|
||||||
|
multipart/multipart-middleware]}})
|
||||||
|
(ring/routes
|
||||||
|
(swagger-ui/create-swagger-ui-handler
|
||||||
|
{:path "/"
|
||||||
|
:config {:validatorUrl nil
|
||||||
|
:urls [{:name "openapi", :url "openapi.json"}]
|
||||||
|
:urls.primaryName "openapi"
|
||||||
|
:operationsSorter "alpha"}})
|
||||||
|
(ring/create-default-handler))))
|
||||||
|
|
||||||
|
(defn start []
|
||||||
|
(jetty/run-jetty #'app {:port 3000, :join? false})
|
||||||
|
(println "server running in port 3000"))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(start))
|
||||||
11
examples/pedestal-malli-swagger/.gitignore
vendored
Normal file
11
examples/pedestal-malli-swagger/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
/target
|
||||||
|
/classes
|
||||||
|
/checkouts
|
||||||
|
pom.xml
|
||||||
|
pom.xml.asc
|
||||||
|
*.jar
|
||||||
|
*.class
|
||||||
|
/.lein-*
|
||||||
|
/.nrepl-port
|
||||||
|
.hgignore
|
||||||
|
.hg/
|
||||||
9
examples/pedestal-malli-swagger/project.clj
Normal file
9
examples/pedestal-malli-swagger/project.clj
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
(defproject pedestal-malli-swagger-example "0.1.0-SNAPSHOT"
|
||||||
|
:description "Reitit-http with pedestal"
|
||||||
|
: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.10.0"]
|
||||||
|
[metosin/reitit-pedestal "0.10.0"]
|
||||||
|
[metosin/reitit "0.10.0"]]
|
||||||
|
:repl-options {:init-ns server})
|
||||||
164
examples/pedestal-malli-swagger/src/server.clj
Normal file
164
examples/pedestal-malli-swagger/src/server.clj
Normal file
|
|
@ -0,0 +1,164 @@
|
||||||
|
(ns server
|
||||||
|
(:require [clojure.java.io :as io]
|
||||||
|
[io.pedestal.http.route]
|
||||||
|
[reitit.interceptor]
|
||||||
|
[reitit.dev.pretty :as pretty]
|
||||||
|
[reitit.coercion.malli]
|
||||||
|
[io.pedestal.http]
|
||||||
|
[reitit.ring]
|
||||||
|
[reitit.ring.malli]
|
||||||
|
[reitit.http]
|
||||||
|
[reitit.pedestal]
|
||||||
|
[reitit.swagger :as swagger]
|
||||||
|
[reitit.swagger-ui :as swagger-ui]
|
||||||
|
[reitit.http.coercion :as coercion]
|
||||||
|
[reitit.http.interceptors.parameters :as parameters]
|
||||||
|
[reitit.http.interceptors.muuntaja :as muuntaja]
|
||||||
|
[reitit.http.interceptors.multipart :as multipart]
|
||||||
|
[muuntaja.core]
|
||||||
|
[malli.util :as mu]))
|
||||||
|
|
||||||
|
(defn reitit-routes
|
||||||
|
[_config]
|
||||||
|
[["/swagger.json" {:get {:no-doc true
|
||||||
|
:swagger {:info {:title "my-api"
|
||||||
|
:description "with [malli](https://github.com/metosin/malli) and reitit-ring"}
|
||||||
|
:tags [{:name "files",
|
||||||
|
:description "file api"}
|
||||||
|
{:name "math",
|
||||||
|
:description "math api"}]}
|
||||||
|
:handler (swagger/create-swagger-handler)}}]
|
||||||
|
["/files" {:swagger {:tags ["files"]}}
|
||||||
|
["/upload"
|
||||||
|
{:post {:summary "upload a file"
|
||||||
|
:parameters {:multipart [:map [:file reitit.ring.malli/temp-file-part]]}
|
||||||
|
:responses {200 {:body [:map
|
||||||
|
[:name string?]
|
||||||
|
[:size int?]]}}
|
||||||
|
:handler (fn [{{{{:keys [filename
|
||||||
|
size]} :file}
|
||||||
|
:multipart}
|
||||||
|
:parameters}]
|
||||||
|
{:status 200
|
||||||
|
:body {:name filename
|
||||||
|
:size size}})}}]
|
||||||
|
["/download" {:get {:summary "downloads a file"
|
||||||
|
:swagger {:produces ["image/png"]}
|
||||||
|
:handler (fn [_]
|
||||||
|
{:status 200
|
||||||
|
:headers {"Content-Type" "image/png"}
|
||||||
|
:body (-> "reitit.png"
|
||||||
|
(io/resource)
|
||||||
|
(io/input-stream))})}}]]
|
||||||
|
["/math" {:swagger {:tags ["math"]}}
|
||||||
|
["/plus"
|
||||||
|
{:get {:summary "plus with malli query parameters"
|
||||||
|
:parameters {:query [:map
|
||||||
|
[:x
|
||||||
|
{:title "X parameter"
|
||||||
|
:description "Description for X parameter"
|
||||||
|
:json-schema/default 42}
|
||||||
|
int?]
|
||||||
|
[:y int?]]}
|
||||||
|
:responses {200 {:body [:map [:total int?]]}}
|
||||||
|
:handler (fn [{{{:keys [x
|
||||||
|
y]}
|
||||||
|
:query}
|
||||||
|
:parameters}]
|
||||||
|
{:status 200
|
||||||
|
:body {:total (+ x y)}})}
|
||||||
|
:post {:summary "plus with malli body parameters"
|
||||||
|
:parameters {:body [:map
|
||||||
|
[:x
|
||||||
|
{:title "X parameter"
|
||||||
|
:description "Description for X parameter"
|
||||||
|
:json-schema/default 42}
|
||||||
|
int?]
|
||||||
|
[:y int?]]}
|
||||||
|
:responses {200 {:body [:map [:total int?]]}}
|
||||||
|
:handler (fn [{{{:keys [x
|
||||||
|
y]}
|
||||||
|
:body}
|
||||||
|
:parameters}]
|
||||||
|
{:status 200
|
||||||
|
:body {:total (+ x y)}})}}]]])
|
||||||
|
|
||||||
|
(defn reitit-ring-routes
|
||||||
|
[_config]
|
||||||
|
[(swagger-ui/create-swagger-ui-handler
|
||||||
|
{:path "/"
|
||||||
|
:config {:validatorUrl nil
|
||||||
|
:operationsSorter "alpha"}})
|
||||||
|
(reitit.ring/create-resource-handler)
|
||||||
|
(reitit.ring/create-default-handler)])
|
||||||
|
|
||||||
|
|
||||||
|
(defn reitit-router-config
|
||||||
|
[_config]
|
||||||
|
{:exception pretty/exception
|
||||||
|
:data {:coercion (reitit.coercion.malli/create
|
||||||
|
{:error-keys #{:coercion
|
||||||
|
:in
|
||||||
|
:schema
|
||||||
|
:value
|
||||||
|
:errors
|
||||||
|
:humanized}
|
||||||
|
:compile mu/closed-schema
|
||||||
|
:strip-extra-keys true
|
||||||
|
:default-values true
|
||||||
|
:options nil})
|
||||||
|
:muuntaja muuntaja.core/instance
|
||||||
|
:interceptors [swagger/swagger-feature
|
||||||
|
(parameters/parameters-interceptor)
|
||||||
|
(muuntaja/format-negotiate-interceptor)
|
||||||
|
(muuntaja/format-response-interceptor)
|
||||||
|
(muuntaja/format-request-interceptor)
|
||||||
|
(coercion/coerce-response-interceptor)
|
||||||
|
(coercion/coerce-request-interceptor)
|
||||||
|
(multipart/multipart-interceptor)]}})
|
||||||
|
|
||||||
|
(def config
|
||||||
|
{:env :dev
|
||||||
|
:io.pedestal.http/routes []
|
||||||
|
:io.pedestal.http/type :jetty
|
||||||
|
:io.pedestal.http/port 3000
|
||||||
|
:io.pedestal.http/join? false
|
||||||
|
:io.pedestal.http/secure-headers {:content-security-policy-settings
|
||||||
|
{:default-src "'self'"
|
||||||
|
:style-src "'self' 'unsafe-inline'"
|
||||||
|
:script-src "'self' 'unsafe-inline'"}}
|
||||||
|
::reitit-routes reitit-routes
|
||||||
|
::reitit-ring-routes reitit-ring-routes
|
||||||
|
::reitit-router-config reitit-router-config})
|
||||||
|
|
||||||
|
(defn reitit-http-router
|
||||||
|
[{::keys [reitit-routes
|
||||||
|
reitit-ring-routes
|
||||||
|
reitit-router-config]
|
||||||
|
:as config}]
|
||||||
|
(reitit.pedestal/routing-interceptor
|
||||||
|
(reitit.http/router
|
||||||
|
(reitit-routes config)
|
||||||
|
(reitit-router-config config))
|
||||||
|
(->> config
|
||||||
|
reitit-ring-routes
|
||||||
|
(apply reitit.ring/routes))))
|
||||||
|
|
||||||
|
(defonce server (atom nil))
|
||||||
|
|
||||||
|
(defn start
|
||||||
|
[server
|
||||||
|
config]
|
||||||
|
(when @server
|
||||||
|
(io.pedestal.http/stop @server)
|
||||||
|
(println "server stopped"))
|
||||||
|
(-> config
|
||||||
|
io.pedestal.http/default-interceptors
|
||||||
|
(reitit.pedestal/replace-last-interceptor (reitit-http-router config))
|
||||||
|
io.pedestal.http/dev-interceptors
|
||||||
|
io.pedestal.http/create-server
|
||||||
|
io.pedestal.http/start
|
||||||
|
(->> (reset! server)))
|
||||||
|
(println "server running in port 3000"))
|
||||||
|
|
||||||
|
#_(start server config)
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
(defproject ring-example "0.1.0-SNAPSHOT"
|
(defproject ring-example "0.1.0-SNAPSHOT"
|
||||||
:description "Reitit-http with pedestal"
|
:description "Reitit-http with pedestal"
|
||||||
:dependencies [[org.clojure/clojure "1.10.0"]
|
:dependencies [[org.clojure/clojure "1.11.2"]
|
||||||
[io.pedestal/pedestal.service "0.5.5"]
|
[io.pedestal/pedestal.service "0.6.3"]
|
||||||
[io.pedestal/pedestal.jetty "0.5.5"]
|
[io.pedestal/pedestal.jetty "0.6.3"]
|
||||||
[metosin/reitit-pedestal "0.5.0"]
|
[metosin/reitit-pedestal "0.10.0"]
|
||||||
[metosin/reitit "0.5.0"]]
|
[metosin/reitit "0.10.0"]]
|
||||||
:repl-options {:init-ns example.server})
|
:repl-options {:init-ns example.server})
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,6 @@
|
||||||
["/download"
|
["/download"
|
||||||
{:get {:summary "downloads a file"
|
{:get {:summary "downloads a file"
|
||||||
:swagger {:produces ["image/png"]}
|
:swagger {:produces ["image/png"]}
|
||||||
:responses {200 {:description "image"}}
|
|
||||||
:handler (fn [_]
|
:handler (fn [_]
|
||||||
{:status 200
|
{:status 200
|
||||||
:headers {"Content-Type" "image/png"}
|
:headers {"Content-Type" "image/png"}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
(defproject ring-example "0.1.0-SNAPSHOT"
|
(defproject pedestal-example "0.1.0-SNAPSHOT"
|
||||||
:description "Reitit-http with pedestal"
|
:description "Reitit-http with pedestal"
|
||||||
:dependencies [[org.clojure/clojure "1.10.0"]
|
:dependencies [[org.clojure/clojure "1.11.2"]
|
||||||
[io.pedestal/pedestal.service "0.5.5"]
|
[io.pedestal/pedestal.service "0.6.3"]
|
||||||
[io.pedestal/pedestal.jetty "0.5.5"]
|
[io.pedestal/pedestal.jetty "0.6.3"]
|
||||||
[metosin/reitit-pedestal "0.5.0"]
|
[metosin/reitit-pedestal "0.10.0"]
|
||||||
[metosin/reitit "0.5.0"]]
|
[metosin/reitit "0.10.0"]]
|
||||||
:repl-options {:init-ns example.server})
|
:repl-options {:init-ns example.server})
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
(defproject ring-example "0.1.0-SNAPSHOT"
|
(defproject ring-example "0.1.0-SNAPSHOT"
|
||||||
:description "Reitit Ring App"
|
:description "Reitit Ring App"
|
||||||
:dependencies [[org.clojure/clojure "1.10.0"]
|
:dependencies [[org.clojure/clojure "1.11.2"]
|
||||||
[ring/ring-jetty-adapter "1.7.1"]
|
[ring/ring-jetty-adapter "1.12.1"]
|
||||||
[metosin/reitit "0.5.0"]]
|
[metosin/reitit "0.10.0"]]
|
||||||
:repl-options {:init-ns example.server})
|
:repl-options {:init-ns example.server})
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
(defproject ring-integrant-example "0.1.0-SNAPSHOT"
|
(defproject ring-integrant-example "0.1.0-SNAPSHOT"
|
||||||
:description "Reitit Ring App with Integrant"
|
:description "Reitit Ring App with Integrant"
|
||||||
:dependencies [[org.clojure/clojure "1.10.1"]
|
:dependencies [[org.clojure/clojure "1.11.2"]
|
||||||
[ring/ring-jetty-adapter "1.7.1"]
|
[ring/ring-jetty-adapter "1.12.1"]
|
||||||
[metosin/reitit "0.5.0"]
|
[metosin/reitit "0.10.0"]
|
||||||
[integrant "0.7.0"]]
|
[integrant "0.8.1"]]
|
||||||
:main example.server
|
:main example.server
|
||||||
:repl-options {:init-ns user}
|
:repl-options {:init-ns user}
|
||||||
:profiles {:dev {:dependencies [[integrant/repl "0.3.1"]]
|
:profiles {:dev {:dependencies [[integrant/repl "0.3.3"]]
|
||||||
:source-paths ["dev"]}})
|
:source-paths ["dev"]}})
|
||||||
|
|
|
||||||
11
examples/ring-malli-lite-swagger/.gitignore
vendored
Normal file
11
examples/ring-malli-lite-swagger/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
/target
|
||||||
|
/classes
|
||||||
|
/checkouts
|
||||||
|
pom.xml
|
||||||
|
pom.xml.asc
|
||||||
|
*.jar
|
||||||
|
*.class
|
||||||
|
/.lein-*
|
||||||
|
/.nrepl-port
|
||||||
|
.hgignore
|
||||||
|
.hg/
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue