(ns reitit.coercion-test
  (:require [clojure.test :refer [deftest is testing]]
            [malli.experimental.lite :as l]
            [reitit.coercion :as coercion]
            [reitit.coercion.malli]
            [reitit.coercion.schema]
            [reitit.coercion.spec]
            [reitit.core :as r]
            [schema.core :as s]
            [spec-tools.data-spec :as ds])
  #?(:clj
     (:import (clojure.lang ExceptionInfo))))

(deftest coercion-test
  (let [r (r/router
           [["/schema" {:coercion reitit.coercion.schema/coercion}
             ["/:number/:keyword" {:parameters {:path {:number s/Int
                                                       :keyword s/Keyword}
                                                :query (s/maybe {:int s/Int, :ints [s/Int], :map {s/Int s/Int}})}}]]
            ["/malli" {:coercion reitit.coercion.malli/coercion}
             ["/:number/:keyword" {:parameters {:path [:map [:number int?] [:keyword keyword?]]
                                                :query [:maybe [:map [:int int?]
                                                                [:ints [:vector int?]]
                                                                [:map [:map-of int? int?]]]]}}]]
            ["/malli-lite" {:coercion reitit.coercion.malli/coercion}
             ["/:number/:keyword" {:parameters {:path {:number int?
                                                       :keyword keyword?}
                                                :query (l/maybe {:int int?
                                                                 :ints (l/vector int?)
                                                                 :map (l/map-of int? int?)})}}]]
            ["/spec" {:coercion reitit.coercion.spec/coercion}
             ["/:number/:keyword" {:parameters {:path {:number int?
                                                       :keyword keyword?}
                                                :query (ds/maybe {:int int?, :ints [int?], :map {int? int?}})}}]]
            ["/none"
             ["/:number/:keyword" {:parameters {:path {:number int?
                                                       :keyword keyword?}}}]]]
           {:compile coercion/compile-request-coercers})]

    (testing "schema-coercion"
      (testing "succeeds"
        (let [m (r/match-by-path r "/schema/1/abba")]
          (is (= {:path {:keyword :abba, :number 1}, :query nil}
                 (coercion/coerce! m))))
        (let [m (r/match-by-path r "/schema/1/abba")]
          (is (= {:path {:keyword :abba, :number 1}, :query {:int 10, :ints [1, 2, 3], :map {1 1, 2 2}}}
                 (coercion/coerce! (assoc m :query-params {"int" "10", "ints" ["1" "2" "3"], "map" {:1 "1", "2" "2"}}))))))
      (testing "throws with invalid input"
        (let [m (r/match-by-path r "/schema/kikka/abba")]
          (is (thrown? ExceptionInfo (coercion/coerce! m))))))

    (testing "malli-coercion"
      (testing "succeeds"
        (let [m (r/match-by-path r "/malli/1/abba")]
          (is (= {:path {:keyword :abba, :number 1}, :query nil}
                 (coercion/coerce! m))))
        (let [m (r/match-by-path r "/malli/1/abba")]
          (is (= {:path {:keyword :abba, :number 1}, :query {:int 10, :ints [1, 2, 3], :map {1 1, 2 2}}}
                 (coercion/coerce! (assoc m :query-params {"int" "10", "ints" ["1" "2" "3"], "map" {:1 "1", "2" "2"}}))))))
      (testing "throws with invalid input"
        (let [m (r/match-by-path r "/malli/kikka/abba")]
          (is (thrown? ExceptionInfo (coercion/coerce! m))))))

    (testing "malli-lite coercion"
      (testing "succeeds"
        (let [m (r/match-by-path r "/malli-lite/1/abba")]
          (is (= {:path {:keyword :abba, :number 1}, :query nil}
                 (coercion/coerce! m))))
        (let [m (r/match-by-path r "/malli-lite/1/abba")]
          (is (= {:path {:keyword :abba, :number 1}, :query {:int 10, :ints [1, 2, 3], :map {1 1, 2 2}}}
                 (coercion/coerce! (assoc m :query-params {"int" "10", "ints" ["1" "2" "3"], "map" {:1 "1", "2" "2"}}))))))
      (testing "throws with invalid input"
        (let [m (r/match-by-path r "/malli-lite/kikka/abba")]
          (is (thrown? ExceptionInfo (coercion/coerce! m))))))

    ;; TODO: :map-of fails with string-keys
    (testing "spec-coercion"
      (testing "succeeds"
        (let [m (r/match-by-path r "/spec/1/abba")]
          (is (= {:path {:keyword :abba, :number 1}, :query nil}
                 (coercion/coerce! m))))
        (let [m (r/match-by-path r "/schema/1/abba")]
          (is (= {:path {:keyword :abba, :number 1}, :query {:int 10, :ints [1, 2, 3], :map {1 1, #_#_2 2}}}
                 (coercion/coerce! (assoc m :query-params {"int" "10", "ints" ["1" "2" "3"], "map" {:1 "1"}, #_#_"2" "2"}))))))
      (testing "throws with invalid input"
        (let [m (r/match-by-path r "/spec/kikka/abba")]
          (is (thrown? ExceptionInfo (coercion/coerce! m))))))

    (testing "no coercion defined"
      (testing "doesn't coerce"
        (let [m (r/match-by-path r "/none/1/abba")]
          (is (= nil (coercion/coerce! m))))
        (let [m (r/match-by-path r "/none/kikka/abba")]
          (is (= nil (coercion/coerce! m))))))))

(defn match-by-path-and-coerce! [router path]
  (if-let [match (r/match-by-path router path)]
    (assoc match :parameters (coercion/coerce! match))))

(deftest data-spec-example-test
  (let [router (r/router
                ["/:company/users/:user-id" {:name ::user-view
                                             :coercion reitit.coercion.spec/coercion
                                             :parameters {:path {:company string?
                                                                 :user-id int?}}}]
                {:compile coercion/compile-request-coercers})]
    (is (= {:path {:user-id 123, :company "metosin"}}
           (:parameters (match-by-path-and-coerce! router "/metosin/users/123"))))))

 TODO: :map-of fails with string-keys
(ns reitit.coercion-test
  (:require [clojure.test :refer [deftest is testing]]
            [malli.experimental.lite :as l]
            [reitit.coercion :as coercion]
            [reitit.coercion.malli]
            [reitit.coercion.schema]
            [reitit.coercion.spec]
            [reitit.core :as r]
            [schema.core :as s]
            [spec-tools.data-spec :as ds])
  #?(:clj
     (:import (clojure.lang ExceptionInfo))))

(deftest coercion-test
  (let [r (r/router
           [["/schema" {:coercion reitit.coercion.schema/coercion}
             ["/:number/:keyword" {:parameters {:path {:number s/Int
                                                       :keyword s/Keyword}
                                                :query (s/maybe {:int s/Int, :ints [s/Int], :map {s/Int s/Int}})}}]]
            ["/malli" {:coercion reitit.coercion.malli/coercion}
             ["/:number/:keyword" {:parameters {:path [:map [:number int?] [:keyword keyword?]]
                                                :query [:maybe [:map [:int int?]
                                                                [:ints [:vector int?]]
                                                                [:map [:map-of int? int?]]]]}}]]
            ["/malli-lite" {:coercion reitit.coercion.malli/coercion}
             ["/:number/:keyword" {:parameters {:path {:number int?
                                                       :keyword keyword?}
                                                :query (l/maybe {:int int?
                                                                 :ints (l/vector int?)
                                                                 :map (l/map-of int? int?)})}}]]
            ["/spec" {:coercion reitit.coercion.spec/coercion}
             ["/:number/:keyword" {:parameters {:path {:number int?
                                                       :keyword keyword?}
                                                :query (ds/maybe {:int int?, :ints [int?], :map {int? int?}})}}]]
            ["/none"
             ["/:number/:keyword" {:parameters {:path {:number int?
                                                       :keyword keyword?}}}]]]
           {:compile coercion/compile-request-coercers})]

    (testing "schema-coercion"
      (testing "succeeds"
        (let [m (r/match-by-path r "/schema/1/abba")]
          (is (= {:path {:keyword :abba, :number 1}, :query nil}
                 (coercion/coerce! m))))
        (let [m (r/match-by-path r "/schema/1/abba")]
          (is (= {:path {:keyword :abba, :number 1}, :query {:int 10, :ints [1, 2, 3], :map {1 1, 2 2}}}
                 (coercion/coerce! (assoc m :query-params {"int" "10", "ints" ["1" "2" "3"], "map" {:1 "1", "2" "2"}}))))))
      (testing "throws with invalid input"
        (let [m (r/match-by-path r "/schema/kikka/abba")]
          (is (thrown? ExceptionInfo (coercion/coerce! m))))))

    (testing "malli-coercion"
      (testing "succeeds"
        (let [m (r/match-by-path r "/malli/1/abba")]
          (is (= {:path {:keyword :abba, :number 1}, :query nil}
                 (coercion/coerce! m))))
        (let [m (r/match-by-path r "/malli/1/abba")]
          (is (= {:path {:keyword :abba, :number 1}, :query {:int 10, :ints [1, 2, 3], :map {1 1, 2 2}}}
                 (coercion/coerce! (assoc m :query-params {"int" "10", "ints" ["1" "2" "3"], "map" {:1 "1", "2" "2"}}))))))
      (testing "throws with invalid input"
        (let [m (r/match-by-path r "/malli/kikka/abba")]
          (is (thrown? ExceptionInfo (coercion/coerce! m))))))

    (testing "malli-lite coercion"
      (testing "succeeds"
        (let [m (r/match-by-path r "/malli-lite/1/abba")]
          (is (= {:path {:keyword :abba, :number 1}, :query nil}
                 (coercion/coerce! m))))
        (let [m (r/match-by-path r "/malli-lite/1/abba")]
          (is (= {:path {:keyword :abba, :number 1}, :query {:int 10, :ints [1, 2, 3], :map {1 1, 2 2}}}
                 (coercion/coerce! (assoc m :query-params {"int" "10", "ints" ["1" "2" "3"], "map" {:1 "1", "2" "2"}}))))))
      (testing "throws with invalid input"
        (let [m (r/match-by-path r "/malli-lite/kikka/abba")]
          (is (thrown? ExceptionInfo (coercion/coerce! m))))))

    (testing "spec-coercion"
      (testing "succeeds"
        (let [m (r/match-by-path r "/spec/1/abba")]
          (is (= {:path {:keyword :abba, :number 1}, :query nil}
                 (coercion/coerce! m))))
        (let [m (r/match-by-path r "/schema/1/abba")]
          (is (= {:path {:keyword :abba, :number 1}, :query {:int 10, :ints [1, 2, 3], :map {1 1, #_#_2 2}}}
                 (coercion/coerce! (assoc m :query-params {"int" "10", "ints" ["1" "2" "3"], "map" {:1 "1"}, #_#_"2" "2"}))))))
      (testing "throws with invalid input"
        (let [m (r/match-by-path r "/spec/kikka/abba")]
          (is (thrown? ExceptionInfo (coercion/coerce! m))))))

    (testing "no coercion defined"
      (testing "doesn't coerce"
        (let [m (r/match-by-path r "/none/1/abba")]
          (is (= nil (coercion/coerce! m))))
        (let [m (r/match-by-path r "/none/kikka/abba")]
          (is (= nil (coercion/coerce! m))))))))

(defn match-by-path-and-coerce! [router path]
  (if-let [match (r/match-by-path router path)]
    (assoc match :parameters (coercion/coerce! match))))

(deftest data-spec-example-test
  (let [router (r/router
                ["/:company/users/:user-id" {:name ::user-view
                                             :coercion reitit.coercion.spec/coercion
                                             :parameters {:path {:company string?
                                                                 :user-id int?}}}]
                {:compile coercion/compile-request-coercers})]
    (is (= {:path {:user-id 123, :company "metosin"}}
           (:parameters (match-by-path-and-coerce! router "/metosin/users/123"))))))
