Skip to content
This repository was archived by the owner on Jan 30, 2019. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,23 +51,23 @@ in the base URL:

```clojure
=> (url "https://api.twitter.com/")
#cemerick.url.URL{:protocol "https", :username nil, :password nil,
:host "api.twitter.com", :port -1, :path "/", :query nil,
:anchor nil}
#cemerick.url.URL{:scheme :https, :username nil, :password nil,
:server-name "api.twitter.com", :server-port -1, :uri "/", :query-params nil,
:fragment nil}
=> (url "https://api.twitter.com/" "1" "users" "profile_image" "cemerick")
#cemerick.url.URL{:protocol "https", :username nil, :password nil,
:host "api.twitter.com", :port -1,
:path "/1/users/profile_image/cemerick", :query nil, :anchor nil}
#cemerick.url.URL{:scheme :https, :username nil, :password nil,
:server-name "api.twitter.com", :server-port -1,
:uri "/1/users/profile_image/cemerick", :query-params nil, :fragment nil}
=> (str *1)
"https://api.twitter.com/1/users/profile_image/cemerick"
=> (str (url "https://api.twitter.com/1/users/profile_image/cemerick" "../../lookup.json"))
"https://api.twitter.com/1/users/lookup.json"
```

The `:query` slot can be a string or a map of params:
The `:query-params` slot can be a string or a map of params:

```clojure
=> (str (assoc *3 :query {:a 5 :b 6}))
=> (str (assoc *3 :query-params {:a 5 :b 6}))
"https://api.twitter.com/1/users/profile_image/cemerick?a=5&b=6"
```

Expand Down
48 changes: 28 additions & 20 deletions src/cemerick/url.cljx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@
[clojure.string :as string]
#+cljs [goog.Uri :as uri]))

(def ^:dynamic *ports*
{:amqp 5672
:amqps 5671
:http 80
:https 443
:mysql 3306
:postgresql 5432
:ssh 22})

#+clj
(defn url-encode
[string]
Expand Down Expand Up @@ -55,11 +64,10 @@
(apply hash-map))))

(defn- port-str
[protocol port]
[scheme port]
(when (and (not= nil port)
(not= -1 port)
(not (and (== port 80) (= protocol "http")))
(not (and (== port 443) (= protocol "https"))))
(not (= port (get *ports* (keyword scheme)))))
(str ":" port)))

(defn- url-creds
Expand All @@ -68,34 +76,35 @@
(str username ":" password)))

(defrecord URL
[protocol username password host port path query anchor]
[scheme username password server-name server-port uri query-params fragment]
Object
(toString [this]
(let [creds (url-creds username password)]
(str protocol "://"
(str (name scheme) "://"
creds
(when creds \@)
host
(port-str protocol port)
path
(when (seq query) (str \? (if (string? query)
query
(map->query query))))
(when anchor (str \# anchor))))))
server-name
(port-str scheme server-port)
uri
(when (seq query-params)
(str \? (if (string? query-params)
query-params
(map->query query-params))))
(when fragment (str \# fragment))))))

#+clj
(defn- url*
[url]
(let [url (java.net.URL. url)
(let [url (java.net.URI. url)
[user pass] (string/split (or (.getUserInfo url) "") #":" 2)]
(URL. (.toLowerCase (.getProtocol url))
(URL. (keyword (.toLowerCase (.getScheme url)))
(and (seq user) user)
(and (seq pass) pass)
(.getHost url)
(.getPort url)
(pathetic/normalize (.getPath url))
(query->map (.getQuery url))
(.getRef url))))
(.getFragment url))))

#+cljs
(defn translate-default
Expand All @@ -109,7 +118,7 @@
[url]
(let [url (goog.Uri. url)
[user pass] (string/split (or (.getUserInfo url) "") #":" 2)]
(URL. (.getScheme url)
(URL. (keyword (.getScheme url))
(and (seq user) user)
(and (seq pass) pass)
(.getDomain url)
Expand All @@ -125,7 +134,7 @@
a pre-existing URL record instance that will serve as the basis for the new
URL. Any additional arguments must be strings, which are interpreted as
relative paths that are successively resolved against the base url's path
to construct the final :path in the returned URL record.
to construct the final :uri in the returned URL record.

This function does not perform any url-encoding. Use `url-encode` to encode
URL path segments as desired before passing them into this fn."
Expand All @@ -135,7 +144,6 @@
(url* url)))
([base-url & path-segments]
(let [base-url (if (instance? URL base-url) base-url (url base-url))]
(assoc base-url :path (pathetic/normalize (reduce pathetic/resolve
(:path base-url)
(assoc base-url :uri (pathetic/normalize (reduce pathetic/resolve
(:uri base-url)
path-segments))))))

24 changes: 12 additions & 12 deletions test/cemerick/test_url.cljx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
(ns cemerick.test-url
#+clj (:import java.net.URL)
#+clj (:import java.net.URI)
#+clj (:use cemerick.url
clojure.test)
#+cljs (:require-macros [cemerick.cljs.test :refer (are is deftest with-test run-tests testing)])
Expand All @@ -18,25 +18,26 @@
(deftest url-roundtripping
(let [aurl (url "https://username:password@some.host.com/database?query=string")]
(is (= "https://username:password@some.host.com/database?query=string" (str aurl)))
(is (== -1 (:port aurl)))
(is (== -1 (:server-port aurl)))
(is (= "username" (:username aurl)))
(is (= "password" (:password aurl)))
(is (= "https://username:password@some.host.com" (str (assoc aurl :path nil :query nil))))))
(is (= "https://username:password@some.host.com" (str (assoc aurl :uri nil :query-params nil))))))

(deftest url-segments
(is (= "http://localhost:5984/a/b" (url-str "http://localhost:5984" "a" "b")))
(is (= "http://localhost:5984/a/b/c" (url-str "http://localhost:5984" "a" "b" "c")))
(is (= "http://localhost:5984/a/b/c" (url-str (url "http://localhost:5984" "a") "b" "c"))))

(deftest port-normalization
#+clj (is (== -1 (-> "https://foo" url-str URL. .getPort)))
#+clj (is (== -1 (-> "https://foo" url-str URI. .getPort)))
(is (= "http://localhost" (url-str "http://localhost")))
(is (= "http://localhost" (url-str "http://localhost:80")))
(is (= "http://localhost:8080" (url-str "http://localhost:8080")))
(is (= "https://localhost" (url-str "https://localhost")))
(is (= "https://localhost" (url-str "https://localhost:443")))
(is (= "https://localhost:8443" (url-str "https://localhost:8443")))
(is (= "http://localhost" (str (map->URL {:host "localhost" :protocol "http"})))))
(is (= "http://localhost" (str (map->URL {:server-name "localhost" :scheme :http}))))
(is (= "mysql://localhost" (str (map->URL {:server-name "localhost" :scheme :mysql })))))

(deftest query-params
(are [query map] (is (= map (query->map query)))
Expand All @@ -53,24 +54,23 @@
["a" nil] "http://a:@foo"
["a" "b:c"] "http://a:b:c@foo"))

(deftest path-normalization
(deftest uri-normalization
(is (= "http://a/" (url-str "http://a/b/c/../..")))

(is (= "http://a/b/c" (url-str "http://a/b/" "c")))
(is (= "http://a/b/c" (url-str "http://a/b/.." "b" "c")))
(is (= "http://a/b/c" (str (url "http://a/b/..////./" "b" "c" "../././.." "b" "c"))))
(is (= "http://a/" (str (url "http://a/b/..////./" "b" "c" "../././.." "b" "c" "/"))))

(is (= "http://a/x" (str (url "http://a/b/c" "/x"))))
(is (= "http://a/" (str (url "http://a/b/c" "/"))))
(is (= "http://a/" (str (url "http://a/b/c" "../.."))))
(is (= "http://a/x" (str (url "http://a/b/c" "../.." "." "./x")))))

(deftest anchors
(deftest fragments
(is (= "http://a#x" (url-str "http://a#x")))
(is (= "http://a?b=c#x" (url-str "http://a?b=c#x")))
(is (= "http://a?b=c#x" (-> "http://a#x" url (assoc :query {:b "c"}) str))))
(is (= "http://a?b=c#x" (-> "http://a#x" url (assoc :query-params {:b "c"}) str))))

(deftest no-bare-?
(is (= "http://a" (-> "http://a?b=c" url (update-in [:query] dissoc "b") str))))

(is (= "http://a" (-> "http://a?b=c" url (update-in [:query-params] dissoc "b") str))))