diff --git a/project.clj b/project.clj index 4991158..0f0b197 100644 --- a/project.clj +++ b/project.clj @@ -8,5 +8,7 @@ :profiles {:dev {:plugins [[lein-cloverage "1.0.10"] [lein-kibit "0.1.6"] - [jonase/eastwood "0.2.5"]] - :dependencies [[org.clojure/clojure "1.9.0"]]}}) + [jonase/eastwood "0.2.5"] + [lein-eftest "0.5.2"]] + :dependencies [[org.clojure/clojure "1.9.0"] + [funcool/struct "1.3.0"]]}}) diff --git a/src/rop/core.clj b/src/rop/core.clj index 2eb2242..da1610f 100644 --- a/src/rop/core.clj +++ b/src/rop/core.clj @@ -83,3 +83,30 @@ :status (get-in extracted-result [:response :status] 200) :headers (get-in extracted-result [:response :headers] {})} extracted-result))) + + +(defn =validate-request= + "A railway function that validates a request by a given scheme. + If data are valid it updates them in the request (with coerced data), otherwise returns Bad Requests within errors. + + Parameters: + - `validate` a function that takes an input and a validation scheme, it should return a tuple of errors + and validated input + - `scheme` a validation scheme + - `default` default values as a `hash-map`, it will be merged into a validated input + - `request-key a key in a `request` that holds the input data` + - `input` a ROP input" + [validate scheme defaults request-key {:keys [request] :as input}] + (let [[errors validated-input] (validate (select-keys (get request request-key) (keys scheme)) scheme)] + (if (nil? errors) + (succeed (assoc-in input [:request request-key] (merge (zipmap (keys scheme) (repeat nil)) + defaults + validated-input))) + (fail {:status 400, :body {:errors errors}})))) + + +(defn =merge-params= + "A railway function that merges a given `source` key into a `target` key in a request. + It's useful when route params and body params are validated together." + [source target {:keys [request] :as input}] + (succeed (update-in input [:request target] merge (get request source)))) diff --git a/test/rop/core_test.clj b/test/rop/core_test.clj index 5a755fc..5a6f8d9 100644 --- a/test/rop/core_test.clj +++ b/test/rop/core_test.clj @@ -2,6 +2,8 @@ (:require [clojure.string :refer [blank? lower-case]] [clojure.test :refer [deftest is testing]] + [cats.core :as m] + [struct.core :as st] [rop.core :as rop])) @@ -83,3 +85,28 @@ validate-email (rop/switch #(assoc % :new-user {:email (:email %), :id 1})) (rop/dead send-email!)))))) + + +(deftest validate-request-test + (testing "should assoc a validated input" + (is (= {:request {:params {:id 1}}} + (m/extract (rop/=validate-request= st/validate + {:id [st/number-str]} + {} + :params + {:request {:params {:id "1"}}}))))) + + (testing "should assoc errors" + (is (= {:status 400, :body {:errors {:id "must be a number"}}} + (m/extract (rop/=validate-request= st/validate + {:id [st/number-str]} + {} + :params + {:request {:params {:id ""}}})))))) + + +(deftest merge-params-test + (is (= {:request {:params {:id 1, :foo :bar}, :body-params {:foo :bar}}} + (m/extract (rop/=merge-params= :body-params + :params + {:request {:params {:id 1}, :body-params {:foo :bar}}})))))