-
-
Notifications
You must be signed in to change notification settings - Fork 10
/
spec.clj
58 lines (52 loc) · 3.76 KB
/
spec.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
(ns stub-http.internal.spec
(:require [clojure.string :refer (lower-case)]
[stub-http.internal.functions :refer [substring-before]])
(:import (clojure.lang PersistentArrayMap IPersistentMap IFn Keyword)))
; Make keyword out of string and never mind "case" of keyword (i.e. :GET and :get are treated the same)
(def normalize (comp keyword lower-case name))
(defn- request-spec-matches? [request-spec request]
(letfn [(path-without-query-params [path]
(substring-before path "?"))
(method-matches? [expected-method actual-method]
(let [expected-normalized (normalize (or expected-method actual-method)) ; Assume same as actual if not present
actual-normalized (normalize actual-method)]
(= expected-normalized actual-normalized)))
(path-matches? [expected-path actual-path]
(= (or expected-path actual-path) actual-path))
(query-param-matches? [expected-params actual-params]
(let [expected-params (or expected-params {}) ; Assume empty map if nil
query-params-to-match (select-keys actual-params (keys expected-params))]
(= expected-params query-params-to-match)))
(body-matches? [expected-body actual-body]
(let [expected-body (or expected-body actual-body)] ; Assume match if empty
(= expected-body actual-body)))
(headers-match? [expected-headers actual-headers]
(reduce
(fn [result key] (and result (= (get expected-headers key) (get actual-headers (normalize key)))))
true
(keys expected-headers)))
]
(and (apply path-matches? (map (comp path-without-query-params :path) [request-spec request]))
(query-param-matches? (:query-params request-spec) (:query-params request))
(method-matches? (:method request-spec) (:method request))
(body-matches? (:body request-spec) (get-in request [:body (if (= (:method request) "PUT") "content" "postData")]))
(headers-match? (:headers request-spec) (:headers request)))))
(defn- throw-normalization-exception! [type ^Object val]
(let [class-name (-> val .getClass .getName)
error-message (str "Internal error: Couldn't find " type " conversion strategy for class " (-> val .getClass .getName))]
(throw (ex-info error-message {:class class-name :value val}))))
(defmulti normalize-request-spec class)
(defmethod normalize-request-spec IFn [req-fn] req-fn)
(defmethod normalize-request-spec IPersistentMap [req-spec] (fn [request] (request-spec-matches? req-spec request)))
(defmethod normalize-request-spec PersistentArrayMap [req-spec] (fn [request] (request-spec-matches? req-spec request)))
(defmethod normalize-request-spec String [path] (normalize-request-spec {:path path}))
(defmethod normalize-request-spec Keyword [key] (if (= :default key) ; Allow for default route
(constantly false) ; Default match should never match, it's handled explicitly
(throw (IllegalArgumentException. "Only :default is a valid keyword for the request specification"))))
(defmethod normalize-request-spec :default [value] (throw-normalization-exception! "request" value))
(defmulti normalize-response-spec class)
(defmethod normalize-response-spec IFn [resp-fn] resp-fn)
(defmethod normalize-response-spec IPersistentMap [resp-map] (fn [_] resp-map))
(defmethod normalize-response-spec PersistentArrayMap [resp-map] (fn [_] resp-map))
(defmethod normalize-response-spec String [body] (fn [_] {:status 200 :content-type "text/plain" :headers {:content-type "text/plain"} :body body}))
(defmethod normalize-response-spec :default [value] (throw-normalization-exception! "response" value))