diff options
| author | Asko Nõmm <asko@bien.ee> | 2021-10-03 12:50:37 -0300 |
|---|---|---|
| committer | Asko Nõmm <asko@bien.ee> | 2021-10-03 12:50:37 -0300 |
| commit | e1b8f0969a4bd5f1e1e390364a55ffc6aa141d65 (patch) | |
| tree | 625dbd89675daa807c375a5df66449b79a72c855 /src/ruuter/core.cljc | |
| parent | bf91cdfd50daffaa4814e2271a2eba82674fba76 (diff) | |
Optional path parameters, ClojureScript support and Babashka example in README
Diffstat (limited to 'src/ruuter/core.cljc')
| -rw-r--r-- | src/ruuter/core.cljc | 118 |
1 files changed, 118 insertions, 0 deletions
diff --git a/src/ruuter/core.cljc b/src/ruuter/core.cljc new file mode 100644 index 0000000..0da46b6 --- /dev/null +++ b/src/ruuter/core.cljc @@ -0,0 +1,118 @@ +(ns ruuter.core + (:require [clojure.string :as string]) + (: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 "\\/") + (re-pattern)))) + + +(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))))) + + +(defn- match-route + "For a collection of `route`, will attempt to find one that matches + the given `uri` and `request-method`. If none is matched, `nil` will + be returned instead." + [routes uri request-method] + (let [route (->> routes + (filter #(not (= :not-found (:path %)))) + (map #(merge % {:regex-path (path->regex-path (:path %))})) + (filter #(and (re-matches (:regex-path %) uri) + (= (:method %) request-method))) + first)] + (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 + indirectly if it's a function. In case of a function, it will also + pass along the request map with added-in params that were parsed + from the route path. + + If the response is invalid, or does not exist, a error message with + status code 404 will be returned instead." + [{:keys [path response]} {:keys [uri] :as req}] + (cond + ; responses are maps, so there's no reason they can't be + ; direct maps. + (map? response) + response + ; responses can also be functions that return maps, and + ; when using a function, you get the whole `req` and params + ; with it as well. + (fn? response) + (response (->> {:params (path+uri->path-params path uri)} + (merge req))) + ; if by whatever reason we make it here it must mean the + ; route is invalid, or doesn't exist, in which case we return + ; an error message. + :else + {: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 + will then try to return a response for. The only requirement for `req` + is to contain both a `uri` and `request-method` key. First should match + the request path (like the paths defined in routes) and the second + should match the request method used by the HTTP server you pass this fn to. + + If no route matched for a given HTTP request it will try to find a + route with `:not-found` as its `:path` instead, and return the response + for that, and if that route was also not found, will return a built-in + 404 response instead." + [routes {:keys [uri request-method] :as req}] + (if-let [route (match-route routes uri request-method)] + (route+req->response route req) + (route+req->response (->> routes + (filter #(= :not-found (:path %))) + first) req))) |
