diff --git a/src/reitit/core.cljc b/src/reitit/core.cljc index 6ee1c63a..3f8db58b 100644 --- a/src/reitit/core.cljc +++ b/src/reitit/core.cljc @@ -60,6 +60,12 @@ (cond->> (->> (walk data opts) (map-meta merge-meta)) coerce (into [] (keep #(coerce % opts))))) +(defn first-conflicting-routes [routes] + (loop [[r & rest] routes] + (if (seq rest) + (or (some #(if (impl/conflicting-routes? r %) [r %]) rest) + (recur rest))))) + (defn name-lookup [[_ {:keys [name]}] opts] (if name #{name})) diff --git a/src/reitit/impl.cljc b/src/reitit/impl.cljc index 0e95c253..1989fa70 100644 --- a/src/reitit/impl.cljc +++ b/src/reitit/impl.cljc @@ -121,6 +121,25 @@ :matcher #(if (= path %) {}) :handler handler}))) +(defn segments [path] + (let [ss (-> (str/split path #"/") rest vec)] + (if (str/ends-with? path "/") + (conj ss "") ss))) + +(defn- catch-all? [segment] + (= \* (first segment))) + +(defn conflicting-routes? [[p1 :as route1] [p2 :as route2]] + (loop [[s1 & ss1] (segments p1) + [s2 & ss2] (segments p2)] + (cond + (= s1 s2 nil) true + (or (nil? s1) (nil? s2)) false + (or (catch-all? s1) (catch-all? s2)) true + (or (wild? s1) (wild? s2)) (recur ss1 ss2) + (not= s1 s2) false + :else (recur ss1 ss2)))) + (defn path-for [^Route route params] (if-let [required (:params route)] (if (every? #(contains? params %) required) diff --git a/test/cljc/reitit/core_test.cljc b/test/cljc/reitit/core_test.cljc index ba8c0dbf..854b02ed 100644 --- a/test/cljc/reitit/core_test.cljc +++ b/test/cljc/reitit/core_test.cljc @@ -1,5 +1,5 @@ (ns reitit.core-test - (:require [clojure.test :refer [deftest testing is]] + (:require [clojure.test :refer [deftest testing is are]] [reitit.core :as reitit #?@(:cljs [:refer [Match]])]) #?(:clj (:import (reitit.core Match) @@ -138,3 +138,37 @@ :path "/api/user/1/2" :params {:id "1", :sub-id "2"}}) (reitit/match-by-path router "/api/user/1/2")))))) + +(deftest first-conflicting-routes-test + (are [conflicting? data] + (let [routes (reitit/resolve-routes data {})] + (= (if conflicting? routes) + (reitit/first-conflicting-routes + (reitit/resolve-routes routes {})))) + + true [["/a"] + ["/a"]] + + true [["/a"] + ["/:b"]] + + true [["/a"] + ["/*b"]] + + true [["/a/1/2"] + ["/*b"]] + + false [["/a"] + ["/a/"]] + + false [["/a"] + ["/a/1"]] + + false [["/a"] + ["/a/:b"]] + + false [["/a"] + ["/a/*b"]] + + true [["/v2/public/messages/dataset/bulk"] + ["/v2/public/messages/dataset/:dataset-id"]]))