Fork me on GitHub
#reitit
<
2018-06-14
>
roklenarcic09:06:33

hey guys, I wonder if someone has an example of bidirectional routing?

roklenarcic09:06:32

For instance if I have a GET request returning a list of items and I want that list to have href links

ikitommi10:06:23

@roklenarcic something like this?:

ikitommi10:06:28

(require '[reitit.core :as r])
(require '[reitit.ring :as ring])

(def app
  (ring/ring-handler
    (ring/router
      [["/users" {:get (fn [{:keys [::r/router]}]
                         {:status 200
                          :body (for [i (range 10)]
                                  {:uri (:path (r/match-by-name router ::user {:id i}))})})}]
       ["/users/:id" {:name ::user
                      :get (constantly {:status 200, :body "user..."})}]])))

(app {:request-method :get, :uri "/users"})
;{:status 200,
; :body [{:uri "/users/0"}
;        {:uri "/users/1"}
;        {:uri "/users/2"}
;        {:uri "/users/3"}
;        {:uri "/users/4"}
;        {:uri "/users/5"}
;        {:uri "/users/6"}
;        {:uri "/users/7"}
;        {:uri "/users/8"}
;        {:uri "/users/9"}]}

ikitommi10:06:43

e.g. the router (and match) are injected into request and can be used to reverse route to named endpoints

ikitommi10:06:59

::r/router and ::r/match

ikitommi10:06:15

I think needs to be added to the guide…

ikitommi10:06:20

the default coercion (e.g. used in frontend routing) will coerce in future all parameter types, not just path-params, just define :query params for the route and inject them into Match and enjoy:

(def router
  (reitit.core/router
    ["/spec" {:coercion reitit.coercion.spec/coercion}
     ["/:number/:keyword" {:parameters {:path {:number int?
                                               :keyword keyword?}
                                        :query (ds/maybe {:int int?})}}]]
    {:compile reitit.coercion/compile-request-coercers}))

(-> (reitit.core/match-by-path router "/spec/10/kikka")
    (assoc :query-params {:int "10"})
    (reitit.coercion/coerce!))
; {:path {:number 10, :keyword :kikka}
;  :query {:int 10}}

roklenarcic10:06:26

@ikitommi yeah like that but also with query params

roklenarcic10:06:39

not just path params

ikitommi10:06:06

Sure, need to add support for those too.

roklenarcic10:06:37

let's say I request /users?lang=en, then I want all my links to have that too

ikitommi10:06:20

I think we need to add a optional third argument to match-by-name.

ikitommi10:06:46

(match-by-name [this name] [this name path-params]) => (match-by-name [this name] [this name path-params] [this name path-params query-params])

ikitommi10:06:20

could you write an Issue out of that?

roklenarcic10:06:03

there's also a bit of a problem with declaration ordering

ikitommi10:06:29

hmmm.. actually, I think the query-parameters are not part of the route match, so it could be done on top of the router protocol method.

ikitommi10:06:46

what problem?

juhoteperi10:06:14

the frontend routing PR already implements query string logic, but perhaps on the wrong level

roklenarcic10:06:19

so usually my declarations are ordered like this handlers, used by router declaration

roklenarcic10:06:04

but now I would use match-by-name which requires a router in my handlers

roklenarcic10:06:37

handler's emit responses with links, for which they need router, and the router needs to be defined after handlers

ikitommi10:06:54

@juhoteperi the encoding & decoding query strings is needed also here, yes, maybe move functions into impl?

juhoteperi10:06:49

the implementation is different between not only jvm/js but also node/browser so not sure how this should be done

ikitommi10:06:01

@roklenarcic the ring-handler get’s the preconfigured router and emits that into a request. the handler only requires the :name of the route it’s linked to, so there is loose coupling. I guess you also know the routes names ahead-of-time. I’m not sure I understand the problem yet.

ikitommi10:06:43

e.g. you don’t need a hard reference to the router instance, you get it injected in.

roklenarcic11:06:37

ah, under :reitit.core/router

ikitommi11:06:06

@juhoteperi can you find a common base for cljs? I think we only need encode-query-string and decode-query-string in the core. And the first should use the IntoString protocol from the impl.

ikitommi15:06:09

(require '[reitit.core :as r])

(-> (r/router ["/:a/:b" ::route])
    (r/match-by-name! ::route {:a "olipa", :b "kerran"})
    (r/match->path))
; "/olipa/kerran"

(-> (r/router ["/:a/:b" ::route])
    (r/match-by-name! ::route {:a "olipa", :b "kerran"})
    (r/match->path {:iso "pöriläinen"}))
; "/olipa/kerran?iso=p%C3%B6ril%C3%A4inen"

ikitommi15:06:39

fails fast on missing params and allows query-params to be added to paths.

ikitommi15:06:35

and as it’s a separate function on top of match, doesn’t pollute the existing router implementations.

ikitommi15:06:59

… as the query-parameters are not part of any matching.

ikitommi15:06:54

idea: we could optionally coerce the parameters in reverse routing too, as the match contains all the needed info. e.g. if the :a :path-param would be defined as int? defined we could fail as the endpoint would not accept a path with :a as "olipa" string.

ikitommi15:06:06

not sure how useful in real life, but doable.

roklenarcic15:06:16

that's works fine for my purposes

roklenarcic15:06:55

is that url-encoded?

ikitommi15:06:44

I’ll push out a SNAPSHOT, will release after review. just a sec.

ikitommi15:06:18

[metosin/reitit "0.1.3-SNAPSHOT" , all modules updated