summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAsko Nõmm <ano@ano.ee>2022-10-05 22:29:03 +0300
committerAsko Nõmm <ano@ano.ee>2022-10-05 22:29:03 +0300
commit9e8abbf26d4422ba133bba7186363bee5b1f38d7 (patch)
tree152702f0d75313eb26a6b2d79b7f208ac039e399
parent78659212f95cac827efc816dfbdab8181c25fc3d (diff)
1.3.0
-rw-r--r--README.md9
-rw-r--r--project.clj10
-rw-r--r--src/ruuter/core.cljc99
-rw-r--r--test/ruuter/core_test.cljc14
4 files changed, 82 insertions, 50 deletions
diff --git a/README.md b/README.md
index 21e70ba..5384ea8 100644
--- a/README.md
+++ b/README.md
@@ -122,6 +122,10 @@ To create parameters from the path, prepend a colon (:) in front of a path slice
Additionally, you may want to use an optional parameter, in which case you'd want to add a question mark to the end of it, like `/hi/:name?`, which will match the `\/hi\/?.*?` regex, meaning that the previous forward slash is optional, and what comes after that is also optional.
+##### Wildcard matching
+
+The above-mentioned `:name` and `:name?` only match in its own sequence, e.g inside a space of two slashes. They cannot, by design, match the whole URL path. If you need wildcard matching, instead use `:name*`, which will match everything, including forward slashes.
+
#### `:method`
The HTTP method to listen for when matching the given path. This can be whatever the HTTP server uses. For example, if you're using http-kit for the HTTP server then the accepted values are:
@@ -157,6 +161,11 @@ What the actual map can contain that you return depends again on the HTTP server
## Changelog
+### 1.3.0
+
+- Fixed an issue with optional parameters not matching correctly when there were multiple optional paremeters in use.
+- Implemented wildcard parameters in the form of `:name*`, which will match everything including forward slashes.
+
### 1.2.2
- Fixed an issue where CLJS compilation would fail because of the `(:gen-class)` that is JVM-only.
diff --git a/project.clj b/project.clj
index 4d1d18d..29f048e 100644
--- a/project.clj
+++ b/project.clj
@@ -1,11 +1,13 @@
-(defproject org.clojars.askonomm/ruuter "1.2.2"
+(defproject org.clojars.askonomm/ruuter "1.3.0"
:description "A tiny HTTP router"
:url "https://github.com/askonomm/ruuter"
:license {:name "MIT"
:url "https://raw.githubusercontent.com/askonomm/ruuter/master/LICENSE.txt"}
- :dependencies [[org.clojure/clojure "1.10.3"]
- [org.clojure/clojurescript "1.10.879"]]
- :plugins [[lein-cloverage "1.2.2"]]
+ :dependencies [[org.clojure/clojure "1.11.1"]
+ [org.clojure/clojurescript "1.11.60"]]
+ :deploy-repositories [["releases" :clojars]
+ ["snapshots" :clojars]]
+ :plugins [[lein-cloverage "1.2.3"]]
:main ruuter.core
:min-lein-version "2.0.0"
:aot [ruuter.core]
diff --git a/src/ruuter/core.cljc b/src/ruuter/core.cljc
index 444788f..6eaa912 100644
--- a/src/ruuter/core.cljc
+++ b/src/ruuter/core.cljc
@@ -1,57 +1,74 @@
(ns ruuter.core
- (:require [clojure.string :as string])
+ (:require
+ [clojure.string :as string])
#?(:clj (:gen-class)))
-
(defn- path->regex-path
"Takes in a raw route `path` and turns it into a regex pattern to
match against the request URI."
[path]
- (if (= "/" path)
- "\\/"
- (->> (string/split path #"/")
- (map #(cond
- ; matches anything, and must be present
- ; for example `:name`
- (and (string/starts-with? % ":")
- (not (string/ends-with? % "?")))
- ".*"
- ; matches anything, but is optional
- ; for example `:name?`
- (and (string/starts-with? % ":")
- (string/ends-with? % "?"))
- "?.*?"
- :else
- ; what comes around, goes around
- %))
- (string/join "\\/"))))
+ (cond (= "/" path)
+ "\\/"
+
+ (re-find #"\*" path)
+ (-> (string/replace path #"\:.*?\*" ".*?")
+ (string/replace #"/" "\\/"))
+ :else
+ (->> (string/split path #"/")
+ (map #(cond
+ ; matches anything, and must be present
+ ; for example `:name`
+ (and (string/starts-with? % ":")
+ (not (string/ends-with? % "?")))
+ ".*"
+ ; matches anything, but is optional
+ ; for example `:name?`
+ (and (string/starts-with? % ":")
+ (string/ends-with? % "?"))
+ "?.*?"
+ :else
+ ; what comes around, goes around
+ %))
+ (string/join "\\/"))))
(defn- path+uri->path-params
"Takes a raw route `path` and the actual request `uri`, which it then
turns into a map of k:v, if any parameters were used in the `path`."
[path uri]
- (if (= "/" path)
- {}
- (let [split-path (string/split path #"/")
- split-uri (string/split uri #"/")]
- (into {} (map-indexed
- (fn [idx item]
- (cond
- ; required parameter
- (and (string/starts-with? item ":")
- (not (string/ends-with? item "?")))
- {(keyword (subs item 1)) (get split-uri idx)}
- ; optional parameter
- (and (string/starts-with? item ":")
- (string/ends-with? item "?")
- (get split-uri idx))
- {(keyword (-> item
- (subs 0 (- (count item) 1))
- (subs 1)))
- (get split-uri idx)}))
- split-path)))))
+ (cond (= "/" path)
+ {}
+
+ (re-find #"\*" path)
+ (let [index-of-k-start (string/index-of path ":")
+ k (-> (subs path (+ 1 index-of-k-start))
+ keyword)
+ v (subs uri (+ 1 index-of-k-start))]
+ {k v})
+ :else
+ (let [split-path (->> (string/split path #"/")
+ (remove empty?)
+ vec)
+ split-uri (->> (string/split uri #"/")
+ (remove empty?)
+ vec)]
+ (into {} (map-indexed
+ (fn [idx item]
+ (cond
+ ; required parameter
+ (and (string/starts-with? item ":")
+ (not (string/ends-with? item "?")))
+ {(keyword (subs item 1)) (get split-uri idx)}
+ ; optional parameter
+ (and (string/starts-with? item ":")
+ (string/ends-with? item "?")
+ (get split-uri idx))
+ {(keyword (-> item
+ (subs 0 (- (count item) 1))
+ (subs 1)))
+ (get split-uri idx)}))
+ split-path)))))
(defn- match-route
"For a collection of `route`, will attempt to find one that matches
@@ -67,7 +84,6 @@
(when route
(dissoc route :regex-path))))
-
(defn- route+req->response
"Given the current route and the current HTTP request, it will
attempt to return a response, either directly if it's a map or
@@ -96,7 +112,6 @@
{:status 404
:body "Not found."}))
-
(defn route
"For a given collection of `routes` and the current HTTP request as
`req`, will attempt to match a route with the HTTP request, which it
diff --git a/test/ruuter/core_test.cljc b/test/ruuter/core_test.cljc
index 9f1df23..75a71a0 100644
--- a/test/ruuter/core_test.cljc
+++ b/test/ruuter/core_test.cljc
@@ -4,7 +4,6 @@
#?(:cljs (:require [cljs.test :refer-macros [deftest testing is]]
[ruuter.core :as ruuter])))
-
(deftest path+uri->path-params-test
(let [testfn #'ruuter/path+uri->path-params]
(testing "No params returns an empty map"
@@ -22,8 +21,16 @@
(testfn "/hello/:who/:why?" "/hello/world")))
(is (= {:who "world"
:why "because"}
- (testfn "/hello/:who/:why?" "/hello/world/because"))))))
-
+ (testfn "/hello/:who/:why?" "/hello/world/because"))))
+ (testing "Multiple params, but all are optional"
+ (is (= {:who "world"
+ :why "because"}
+ (testfn "hello/:who?/:why?" "/hello/world/because")))
+ (is (= {:who "world"}
+ (testfn "hello/:who?/:why?" "/hello/world"))))
+ (testing "Wildcard param"
+ (is (= {:everything* "this/means/literally/everything"}
+ (testfn "hello/:everything*" "/hello/this/means/literally/everything"))))))
(deftest match-route-test
(let [testfn #'ruuter/match-route]
@@ -40,7 +47,6 @@
(is (= nil
(testfn [] "/hello" :get))))))
-
(deftest route+req->response-test
(let [testfn #'ruuter/route+req->response]
(testing "Returning a map when the response is a direct map"