Can I have some advice please on my ongoing effort to move to reitit from compojure? Regarding laying out routes, i'm not clear what the intent is behind supporting crud or not. A 'route conflict' seems inevitable when you have a :get /resource-name/create, to show a form, and a :get /resource-name/:slug to fetch that entity. Traditionally, in rails like frameworks, this is fine, because it's not a problem having a 'conflict' that you can't have a slug with the name 'create'. Yet, to do this in reitit, it seems the only way is to say :conflicts nil? The other issue i'm having is that it seems to avoid conflicts being reported one must group first by url, i.e. always nest :get and :post for the same url within one definition, because a route conflict is reported when you have the same path stated again, even if there is no overlap with the http methods within. (viz., one has only :post, the other has only :get)
recent discussion about this: https://clojurians.slack.com/archives/C7YF1SBT3/p1723221176588629?thread_ts=1722955344.445439&cid=C7YF1SBT3
reitit is not super user-friendly as it wants to educate “good routing trees” with having route-conflicts not resolved by default (e.g. exception thrown). Also, merging route paths could be smarter, e.g. grouping by path.. and optionally auto-organizing routes that the most spesific is matched first.
good thing is, that one can do all of those helpers on top of the current abstractions, but it’s extra work (and thus, not super friendly for new people coming in).
has been long in my todo to create an optional “just works” layer on top of core reitit.
Thanks, imo right now, it's not a problem, because if I want REST-like resource paths, the practical answer is to just turn off conflict detection; so i'll consider that feature as an optional extra, not as a default. But also, because reitit-core doesn't assume http methods; those being added only in retit-ring, it's understandable that matching is on url first, and not on 'both simultaneously', which is what compojure essentially achieves(?). But as mentioned, we can easily use reitit in the compojure way, just by setting {:conflicting nil}. IMO trying to make reitit match on both url and http method the same time might not be needed?... it's kind of against the initial choice it makes and what it delivers by being applicable in cases where there's no http method (e.g., as a front end router). (i haven't actually used it as that yet personally, so my goal now is do that a.s.a.p., to fill in a blank space on the map in my understanding). So, no issue here i think; question really has been about understanding the design, so I can make the choices to use it in a sensible way for my case. (Adding the necessary {:conflicting true} statements to each route to have what I wanted pass while keeping the conflict detection on, didn't work prettily; so the best option in my case it seems to me is just router option conflicts off; which actually works fine... :) )
Interesting to know Tommi that in your view standard REST routing != "good routing tree". (because of the masking going on) ? Do i represent your view correctly?
By good I mean conflict-free. For conflict-free route trees, highly optimized radix-tree can be created for matching, which is order(s) of magnitude faster than the (compojure-style) linear router.
If one need something else, me or reitit doesn't want to be in the way. Do you have a link to a modern REST guide which points that you need a route structure that requires a conflicting (in reitit terms) route tree? Long time since I've bumped into conflicts, so actually curious
Sorry I wasn't meaning to hold and wave a flag for REST. The example in front of me right now is GET /a-resource/create, coming before GET /a-resource/:slug. That's the only one in the standard seven that actually conflicts if we also match on the http method.
OK so now trying route names. I find - 1. while you could do: ["/threads/create" threads.create] (but this has no handler, so i don't see the purpose of it) 2. you cannot do: ["/threads/create" threads.create {:get (fn [req] nil)}] (name is not recognised/printed out by r/route-names) 3. so you must do: ["/threads/create" {:name threads.create :get (fn [req] nil)}] not clear in the docs page for route-syntax? (it wasn't to me. just had to discover by experiment.) (i had assumed 2. would work but it didn't).
reitit-pedestal still https://github.com/metosin/reitit/blob/02d4f797cacd1475e08c21ce6b0a5949903ec115/project.clj#L58 [io.pedestal/pedestal.service "0.6.4"] Is it ok to use reitit with pedestal version 0.7.0? There is promising support for the new Jetty running on Java 21 virtual threads in 0.7.0.
reitit still supports Java8, Pedestal went Java11, tests didn't pass on Java8, didn't have time to invest isolating Pedestal tests to go Java11+
should work with new pedestal, at least test passed ok on java11
Is there a way to get a proper output of coercion problems? Can I use lingo for that or is expound able to return a string? Someone must have that problem or am I missing something?
Anyone can spot where I’m going wrong with my openapi / reitit named examples?
(def entry
{:name ::hello
:get {:parameters {:query [:map
{:openapi/examples
{:Bob1
{:summary "Bob1 is good"
:value {:name "Bob1"}}}}
[:name string?]]}
:responses {200 {:body [:map
[:greeting string?]]
:openapi/examples {:Bob1 {:value {:greeting "Hi, Bob1"}}}}}
:handler hello-get})so for the :responses it should be just :examples
that's reitit data, not malli metadata (where you need to use the :openapi/ prefix)
let me check the code, I'm not sure named examples work with :responses :body, you might need to use :responses :content instead
yeah, you need to use the new :content style of coercion, so that should be something like:
:responses {200 {:content {:default {:schema [:map [:greeting string?]]
:examples {:Bob1 {:value {:greeting "Hi, Bob1"}}}}}}}Now for the query parameter, I'm afraid the problem is that you need to specify the examples per parameter, not for the whole parameter map. You see, when the query parameter map gets converted into the openapi spec, each key-value pair results in a separate OAS3.1 Parameter object.
So you'll need to do something like
:query [:map [:name {:openapi/examples {:Bob1 {:value "Bob1"}}} string?]]But actually now that I'm reading the code it looks like we don't really support openapi examples for parameters, just json-schema examples
so no named examples
:query [:map [:name {:json-schema/example "Bob1} string?]]https://github.com/metosin/reitit/blob/master/examples/openapi/src/example/server.clj#L96-L101
(We do support named examples for request bodies, but that's a different codepath due to historical reasons)
Does that help? What kind of docs would you want for this stuff?
I finally had time to improve the docs a bit, which also let me find a minor bug in the impl. Here are the doc changes: https://github.com/metosin/reitit/pull/702/files#diff-9f681dd6517443ca22265f7f257274442db4421541843707b32f6b51732f3950 Comments welcome!
Aw nice, thanks!!
Oh man, this is going to be harder than I thought!
What I’d had in mind was examples that are request / response pairs. I want them to show up nicely in redoc like this.
https://redocly.com/docs/openapi-visual-reference/example
But it looks like in OpenAPI, request examples only work for request bodies. For query params like you say, each one is their own thing.
So for now I’ll focus on the response examples. I guess my question is what is the extra nesting for :content :default?
In terms of docs it would be good to have a fleshed out example with examples - but also maybe an explanation for the reason things are arranged the way they are - specifically, what is it that means that you need the extra nesting if you only need examples, not per content-type coercions (we have a json only api)
Thoughts?
Thanks for writing out the examples though, super helpful 🙂
the extra nesting is so that you can specify per-content-type schemas
it mimics the structure openapi has
some docs here: https://github.com/metosin/reitit/blob/master/doc/ring/openapi.md#per-content-type-coercions
the openapi example tries to showcase all of the options, but yeah, the rationale for why things are the way they are is missing
https://github.com/metosin/reitit/tree/master/examples/openapi
maybe the docs could have a separate section of snippets for all the different ways you can set openapi examples 🤔
Yeah, section of snippets would be great!
You can only put examples iff you use per-content-type schemas - even if you don’t need that, is that right?
yeah, the per-content-type is the new syntax that supports various openapi things. the :body syntax is still around for backwards compatibility
Ok, so potentially if I only have :body everywhere and just need I could assoc in the examples under the per-content-type?
What happens if the example is under the openapi key, but the schema is under :body?
I'm sorry it won't merge them. You need to use :content :default for your schemas as well.
merging the different syntaxes would cause even more misunderstandings I think
Ah ok, got it. But I could for example pre-process the schema myself.
So I copy the :body key to :content :default :schema and add an :examples key in there at the same time.
Basically trying to minimize churn in our codebase, but also add named examples.
hmm
you might be able to merge it in via the :openapi key in route data, let me try it out
I have a step where I do other similar preprocessing using the :compile option on the router, I think it should fit there, hopefully 🙂
so this should work for the response examples:
(def entry
{:name ::hello
:get {:parameters {:query [:map
[:name string?]]}
:responses {200 {:body [:map
[:greeting string?]]}}
:openapi {:responses {200 {:content {"application/json" {:examples {:Bob1 {:value {:greeting "Hi, Bob1"}}}}}}}}
:handler hello-get})anything under :openapi in the route data is deep-merged into the openapi doc for that endpoint
so as long as you know the structure of the openapi data you want to produce, you can do things like this
ah cool, that can work 🙂
and that structure mirrors the openapi json spec?
yeah
nice
I think for our use case with the existing code that will be the best bet, thanks!!
great, this is kinda the reason we have the :openapi override
when you know exactly what you want in the doc and don't need reitit getting in the way 😛
🙂 thanks so much
Related - kinda - do you know where these other content-types are coming from
We just return application/json - I tried removing the other formats from the muuntaja options, but they are still coming through. Any ideas
Fixed, this worked actually 🙂
Doesn’t seem to be coming through
I’m trying to use multiple named examples, with matching keys so the request and response examples match up.
I’ve tried using the key json-schema/examples also
Any ideas?