reitit

miwal 2024-09-09T09:06:23.522389Z

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)

ikitommi 2024-09-09T10:14:30.120079Z

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.

ikitommi 2024-09-09T10:15:31.893169Z

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).

ikitommi 2024-09-09T10:16:29.343619Z

has been long in my todo to create an optional “just works” layer on top of core reitit.

miwal 2024-09-09T13:28:50.293879Z

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... :) )

👍 1
miwal 2024-09-09T13:31:03.713169Z

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?

ikitommi 2024-09-09T14:09:50.732209Z

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.

ikitommi 2024-09-09T14:12:51.093879Z

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

miwal 2024-09-10T07:26:35.267799Z

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.

miwal 2024-09-10T09:07:56.687269Z

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).

Leo E 2024-09-09T10:36:14.693909Z

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.

ikitommi 2024-09-09T16:26:12.622839Z

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+

👍 1
ikitommi 2024-09-09T16:27:31.926529Z

should work with new pedestal, at least test passed ok on java11

timo 2024-09-09T10:54:51.634159Z

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?

danielneal 2024-09-09T16:16:05.053289Z

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})

opqdonut 2024-09-10T06:40:11.236409Z

so for the :responses it should be just :examples

opqdonut 2024-09-10T06:40:37.391969Z

that's reitit data, not malli metadata (where you need to use the :openapi/ prefix)

opqdonut 2024-09-10T06:40:58.254419Z

let me check the code, I'm not sure named examples work with :responses :body, you might need to use :responses :content instead

opqdonut 2024-09-10T06:43:29.531519Z

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"}}}}}}}

opqdonut 2024-09-10T06:48:23.181349Z

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.

opqdonut 2024-09-10T06:49:14.528369Z

So you'll need to do something like

:query [:map [:name {:openapi/examples {:Bob1 {:value "Bob1"}}} string?]]

opqdonut 2024-09-10T06:56:26.923969Z

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

opqdonut 2024-09-10T06:56:35.215419Z

so no named examples

opqdonut 2024-09-10T06:57:13.444099Z

:query [:map [:name {:json-schema/example "Bob1} string?]]

opqdonut 2024-09-10T06:57:58.444109Z

(We do support named examples for request bodies, but that's a different codepath due to historical reasons)

opqdonut 2024-09-10T06:58:08.658299Z

Does that help? What kind of docs would you want for this stuff?

opqdonut 2024-09-16T09:51:22.179069Z

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!

danielneal 2024-09-16T10:15:54.192439Z

Aw nice, thanks!!

danielneal 2024-09-10T09:36:34.270619Z

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?

danielneal 2024-09-10T09:59:40.860359Z

Thanks for writing out the examples though, super helpful 🙂

opqdonut 2024-09-10T10:07:27.729979Z

the extra nesting is so that you can specify per-content-type schemas

opqdonut 2024-09-10T10:07:32.569359Z

it mimics the structure openapi has

opqdonut 2024-09-10T10:08:49.338279Z

the openapi example tries to showcase all of the options, but yeah, the rationale for why things are the way they are is missing

opqdonut 2024-09-10T10:11:06.821359Z

maybe the docs could have a separate section of snippets for all the different ways you can set openapi examples 🤔

danielneal 2024-09-10T10:17:38.684289Z

Yeah, section of snippets would be great!

danielneal 2024-09-10T10:18:12.691609Z

You can only put examples iff you use per-content-type schemas - even if you don’t need that, is that right?

opqdonut 2024-09-10T10:42:31.077509Z

yeah, the per-content-type is the new syntax that supports various openapi things. the :body syntax is still around for backwards compatibility

danielneal 2024-09-10T11:50:27.231209Z

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?

opqdonut 2024-09-10T12:01:11.162819Z

I'm sorry it won't merge them. You need to use :content :default for your schemas as well.

opqdonut 2024-09-10T12:01:31.842189Z

merging the different syntaxes would cause even more misunderstandings I think

danielneal 2024-09-10T12:01:39.401819Z

Ah ok, got it. But I could for example pre-process the schema myself.

danielneal 2024-09-10T12:02:11.796589Z

So I copy the :body key to :content :default :schema and add an :examples key in there at the same time.

danielneal 2024-09-10T12:02:29.044989Z

Basically trying to minimize churn in our codebase, but also add named examples.

opqdonut 2024-09-10T12:04:35.102159Z

hmm

opqdonut 2024-09-10T12:06:29.323379Z

you might be able to merge it in via the :openapi key in route data, let me try it out

danielneal 2024-09-10T12:07:42.335339Z

I have a step where I do other similar preprocessing using the :compile option on the router, I think it should fit there, hopefully 🙂

opqdonut 2024-09-10T12:10:23.317349Z

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})

opqdonut 2024-09-10T12:10:47.281269Z

anything under :openapi in the route data is deep-merged into the openapi doc for that endpoint

opqdonut 2024-09-10T12:11:10.770459Z

so as long as you know the structure of the openapi data you want to produce, you can do things like this

danielneal 2024-09-10T12:11:43.715699Z

ah cool, that can work 🙂

danielneal 2024-09-10T12:12:08.229229Z

and that structure mirrors the openapi json spec?

opqdonut 2024-09-10T12:12:12.531059Z

yeah

danielneal 2024-09-10T12:12:15.026299Z

nice

danielneal 2024-09-10T12:12:30.323949Z

I think for our use case with the existing code that will be the best bet, thanks!!

opqdonut 2024-09-10T12:12:42.937759Z

great, this is kinda the reason we have the :openapi override

opqdonut 2024-09-10T12:12:55.736669Z

when you know exactly what you want in the doc and don't need reitit getting in the way 😛

danielneal 2024-09-10T12:18:45.693909Z

🙂 thanks so much

danielneal 2024-09-10T13:03:03.748039Z

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 🙂

danielneal 2024-09-09T16:16:41.710219Z

Doesn’t seem to be coming through

danielneal 2024-09-09T16:23:42.811839Z

I’m trying to use multiple named examples, with matching keys so the request and response examples match up.

danielneal 2024-09-09T16:29:51.978169Z

I’ve tried using the key json-schema/examples also

danielneal 2024-09-09T16:33:53.848659Z

Any ideas?