Skip to content

Routing with regex based constraints #722

@danielcompton

Description

@danielcompton

I have an existing routing structure I'm trying to migrate from Bidi. It uses regular expressions quite heavily. Bidi supports these quite happily: https://github.com/juxt/bidi#regular-expressions. However, I'm not sure how to translate these into Reitit routes.

Here's one of our simpler examples:

(let [rtr (r/router ["/"
                     ["" ::home]
                     ["inbox" ::inbox]
                     ["teams" ::teams]
                     ["teams/:team-id-b58/members" {:name ::->members
                                                    :parameters {:path {:team-id-b58 (partial re-matches #"[a-z]")}}}]])]
  [(r/match-by-path rtr "/")
   (r/match-by-path rtr "/inbox")
   (r/match-by-path rtr "/teams/123/members")
   (r/match-by-path rtr "/teams/abcdefg/members")])
output
[#reitit.core.Match{:template "/", :data {:name :user/home}, :result nil, :path-params {}, :path "/"}
 #reitit.core.Match{:template "/inbox", :data {:name :user/inbox}, :result nil, :path-params {}, :path "/inbox"}
 #reitit.core.Match{:template "/teams/:team-id-b58/members",
                    :data {:name :user/->members,
                           :parameters {:path [{:team-id-b58 #object[clojure.core$partial$fn__5927
                                                                     0x4bedf1bc
                                                                     "clojure.core$partial$fn__5927@4bedf1bc"]}]}},
                    :result nil,
                    :path-params {:team-id-b58 "123"},
                    :path "/teams/123/members"}
 #reitit.core.Match{:template "/teams/:team-id-b58/members",
                    :data {:name :user/->members,
                           :parameters {:path [{:team-id-b58 #object[clojure.core$partial$fn__5927
                                                                     0x4bedf1bc
                                                                     "clojure.core$partial$fn__5927@4bedf1bc"]}]}},
                    :result nil,
                    :path-params {:team-id-b58 "abcdefg"},
                    :path "/teams/abcdefg/members"}]

Here there are no conflicts, but Reitit is matching both of the routes. If I try to load /teams/123/members in our frontend app, it crashes with an exception once request coercion runs:

:app.boot/init-router event error: Request coercion failed 
:type :reitit.coercion/request-coercion]
[:coercion ]
[:value {:team-id-b58 "123"}]
[:in [:request :path-params]]

Ideally I'd like those /teams/123/members to not be matched at all so I can serve a default "not found" route.

Then we have a more challenging example:

(let [rtr (r/router ["/"
                     ["" ::home]
                     [":item-id" {:name ::item
                                  :parameters {:path {:item-id (partial re-matches #"[a-z]{16,20}")}}}]
                     ["inbox" ::inbox {:conflicting true}]
                     ["teams" ::teams {:conflicting true}]
                     ["teams/:team-id-b58/members" {:name ::->members
                                                    :parameters {:path {:team-id-b58 (partial re-matches #"[a-z]")}}}]
                     ])]
  [(r/match-by-path rtr "/")
   (r/match-by-path rtr "/inbox")
   (r/match-by-path rtr "/itemwithlongname")
   (r/match-by-path rtr "/teams/123/members")
   (r/match-by-path rtr "/teams/abcdefg/members")])
output
[#reitit.core.Match{:template "/", :data {:name :user/home}, :result nil, :path-params {}, :path "/"}
 #reitit.core.Match{:template "/:item-id",
                    :data {:name :user/item,
                           :parameters {:path [{:item-id #object[clojure.core$partial$fn__5927
                                                                 0x4f1cba3b
                                                                 "clojure.core$partial$fn__5927@4f1cba3b"]}]}},
                    :result nil,
                    :path-params {:item-id "inbox"},
                    :path "/inbox"}
 #reitit.core.Match{:template "/:item-id",
                    :data {:name :user/item,
                           :parameters {:path [{:item-id #object[clojure.core$partial$fn__5927
                                                                 0x4f1cba3b
                                                                 "clojure.core$partial$fn__5927@4f1cba3b"]}]}},
                    :result nil,
                    :path-params {:item-id "itemwithlongname"},
                    :path "/itemwithlongname"}
 #reitit.core.Match{:template "/teams/:team-id-b58/members",
                    :data {:name :user/->members,
                           :parameters {:path [{:team-id-b58 #object[clojure.core$partial$fn__5927
                                                                     0x5a45ffe6
                                                                     "clojure.core$partial$fn__5927@5a45ffe6"]}]}},
                    :result nil,
                    :path-params {:team-id-b58 "123"},
                    :path "/teams/123/members"}
 #reitit.core.Match{:template "/teams/:team-id-b58/members",
                    :data {:name :user/->members,
                           :parameters {:path [{:team-id-b58 #object[clojure.core$partial$fn__5927
                                                                     0x5a45ffe6
                                                                     "clojure.core$partial$fn__5927@5a45ffe6"]}]}},
                    :result nil,
                    :path-params {:team-id-b58 "abcdefg"},
                    :path "/teams/abcdefg/members"}]

We have a root "item-id" route which is matched by a regular expression. This lives at the root of the app, so we can't prefix it with /item/:item-id and put it under another route prefix. Is there a way to achieve this route with Reitit? I understand it might be a bit slower because it would need to evaluate a regex or similar on the fly, but it is the routing structure we need to live with.

Similar to, but different from #721.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions