Fork me on GitHub
#reitit
<
2021-01-18
>
jumar05:01:58

Do I have to re-eval the router definition every time I change my handler?

(def app
  (ring/ring-handler
   (ring/router
    [["/" main-page] 
     ["/config" {:post config/save}]]) ; config/save cannot be used as var here and thus you need to re-eval `app` every time
...

ikitommi09:01:30

@jumar it doesn’t work with Var? It could, and there is a multimethod to handle how things are expanded, Var could be added as default mapping to it.

jumar11:01:28

With var I get this error:

No implementation of method: :expand of protocol: #'reitit.core/Expand found for class: clojure.lang.Var

ikitommi11:01:59

(extend-protocol reitit.core/Expand
  clojure.lang.Var
  (expand [this _] {:handler this}))

ikitommi11:01:14

does that help?

jumar11:01:50

Yes! You are super-helpful, thanks! Is it worth filing an issue or should I just keep it in my code?

ikitommi11:01:20

I think it could do that out-of-the-box, so PR welcome

👍 1
ikitommi09:01:44

you could also re-create the top-level ring handler for all requests in dev. quite slow, but always up-to-date, e.g. (defn app [request] ((ring/…) request)). Personally using integrant-repl and have keyboard mapping of shift + Cmd + ö to run reset. takes few millis so, just that.

jumar11:01:45

I try to avoid using tools.namespace refresh (if that's the thing integrant uses) - at least when changes are small. Reloading the app (I do that now) is easy when it's in the same namespace but since config/save is another ns it's more annoying 🙂.

David Pham15:01:46

I know it might not be the best place to ask this, but I am trying to run the example from Reitit+Malli, and I can create a post request with httpie, but I failed to make the same request with http-kit

David Pham15:01:01

Did anyone go through the same trouble?

David Pham15:01:19

(ns test.server
  (:require
   [org.httpkit.client :as client]
   [cheshire.core :as j]))

;; you can use babashka for this example 

(->>
 (client/post
  ""
  {:accept :json
   :body (j/encode {:x 1 :y 20}) :as :text})
 deref
 :body
 println)

;; => {"schema":"[:map [:x any?] [:y any?]]","errors":[{"in":[],"value":null,"message":"invalid type","schema":"[:map [:x any?] [:y any?]]","path":[],"type":"malli.core/invalid-type"}],"value":null,"type":"reitit.coercion/request-coercion","coercion":"malli","in":["request","body-params"],"humanized":["invalid type"]}

David Pham15:01:19

The funny thing is this works fine

David Pham15:01:45

http POST :3000/math/plus x:=1 y:=20

dharrigan15:01:36

The content type isn't being set on the http-kit client, whereas in httpie, it's set automatically as application/json

dharrigan15:01:40

might that have anything to do with it?

David Pham15:01:00

Let me double check

David Pham15:01:38

nope, it it is not that 😕

David Pham15:01:08

It drives me crazy haha

David Pham15:01:51

I am fairly certain it has to play with the coercion, because when I just make an echo of the request, I do see a json body.

dharrigan15:01:01

can you share your schema please?

dharrigan15:01:16

and your router setup please

David Pham15:01:10

(ns ch.my.server
  (:require
   ;; Uncomment to use
   ;; [reitit.http.interceptors.dev :as dev]
   ;; [spec-tools.spell :as spell]
   [reitit.swagger :as swagger]
   [clojure.core.async :as a]
   [ :as io]
   [clojure.pprint :as pprint]
   [integrant.core :as ig]
   [io.pedestal.http :as server]
   [muuntaja.core :as m]
   [muuntaja.interceptor]
   [malli.util :as mu]
   [reitit.coercion.malli]
   [reitit.dev.pretty :as pretty]
   [reitit.http :as http]
   [reitit.http.coercion :as coercion]
   [reitit.http.interceptors.exception :as exception]
   [reitit.http.interceptors.multipart :as multipart]
   [reitit.http.interceptors.muuntaja :as muuntaja]
   [reitit.http.interceptors.parameters :as parameters]
   [reitit.pedestal :as pedestal]
   [reitit.ring :as ring]
   [reitit.ring.malli]
   [reitit.swagger-ui :as swagger-ui]))

(defn interceptor [x]
  {:enter (fn [ctx] (update-in ctx [:request :via] (fnil conj []) {:enter x}))
   :leave (fn [ctx] (update-in ctx [:response :body] conj {:leave x}))})

(def routes
  [["/echo"
    {:get {:handler
           (fn [context] (pprint/pprint context)
             {:status 200
              :body (slurp (:body context))})}
     :post {:handler
            (fn [context] (pprint/pprint context)
              {:status 200
               :body (slurp (:body context))})}}]

   ["/swagger.json"
    {:get {:no-doc true
           :swagger
           {:info {:title "my-api"
                   :description "with [malli]() and reitit-ring"}
            :tags [{:name "files", :description "file api"}
                   {:name "math", :description "math api"}]}
           :handler (swagger/create-swagger-handler)}}]

   ["/math"
    {:swagger {:tags ["math"]}
     #_#_:interceptors [(interceptor :api)]}

    ["/plus"
     {:get {:summary "plus with malli query parameters"
            :parameters {:query [:map [:x int?] [:y int?]]}
            :responses {200 {:body [:map [:total int?]]}}
            :handler (fn [{{{:keys [x y]} :query} :parameters}]
                       {:status 200
                        :body {:total (+ x y)}})}

      :post {:summary "plus with malli body parameters"
             :parameters {:body [:map [:x int?] [:y int?]]}
             ;; :responses {200 {:body [:map [:total int?]]}}
             :handler (fn [{{{:keys [x y]} :body} :parameters :as context}]
                        (pprint/pprint context)
                        {:status 200
                         :body {:total (+ x y)}})}}]]])

(def router-args
  {;:reitit.interceptor/transform dev/print-context-diffs ;; pretty context diffs
   ;;:validate spec/validate ;; enable spec validation for route data
   ;;:reitit.spec/wrap spell/closed ;; strict top-level validation
   :exception pretty/exception
   :data
   {:coercion (reitit.coercion.malli/create {:compile mu/open-schema})
    :muuntaja m/instance
    :interceptors
    [;; swagger feature
     ;; swagger/swagger-feature
     ;; ;; format
     (muuntaja/format-interceptor)
     ;; query-params & form-params
     (parameters/parameters-interceptor)
     ;; content-negotiation
     (muuntaja/format-negotiate-interceptor)
     ;; decoding request body
     (muuntaja/format-request-interceptor)
     ;; encoding response body
     (muuntaja/format-response-interceptor)
     ;; exception handling
     (exception/exception-interceptor)
     ;; coercing response bodys
     (coercion/coerce-response-interceptor)
     ;; coercing request parameters
     (coercion/coerce-request-interceptor)
     ;; multipart
     #_(multipart/multipart-interceptor)]}})

(defn router []
  (pedestal/routing-interceptor
   (http/router routes router-args)
   ;; optional default ring handler (if no routes have matched)
   (ring/routes
    (swagger-ui/create-swagger-ui-handler
     {:path "/"
      :config {:validatorUrl nil
               :operationsSorter "alpha"}})
    (ring/create-resource-handler)
    (ring/create-default-handler))))

(defn start [{:keys [port router]}]
  (let [service-map (-> {:env :dev
                         ::server/type :jetty
                         ::server/port (or port 3000)
                         ::server/join? false
                         ;; no pedestal routes
                         ::server/routes []
                         ;; allow serving the swagger-ui styles & scripts from self
                         ::server/secure-headers
                         {:content-security-policy-settings
                          {:default-src "'self'"
                           :style-src "'self' 'unsafe-inline'"
                           :script-src "'self' 'unsafe-inline'"}}}
                        (server/default-interceptors)
                        ;; use the reitit router
                        (pedestal/replace-last-interceptor router)
                        (server/dev-interceptors)
                        (server/create-server))]
    (server/start service-map)
    service-map))

(def system-config
  {::jetty   {:port    3000
              :join?   false
              :router (ig/ref ::router)}
   ::router {}})

(defmethod ig/init-key ::jetty [_ {:keys [port join? router]}]
  (println "server running in port" port)
  (start {:port port :join? join? :router router}))

(defmethod ig/halt-key! ::jetty [_ service-map]
  (println "Stopping" service-map)
  (server/stop service-map))

(defmethod ig/init-key ::router [_ _]
  (router))

(defn -main []
  (ig/init system-config))

dharrigan15:01:35

(client/post
  ""
  {:headers {"Content-Type" "application/json"}
   :body (j/encode {:x 1 :y 20})})

dharrigan15:01:46

For some reason, it doesn't support setting the :content-type directly

dharrigan15:01:07

❯ cat foo.clj                                              
(ns foo
  (:require
   [org.httpkit.client :as client]
   [cheshire.core :as j]))
;; you can use babashka for this example
(->>
 (client/post
  ""
  {:headers {"Content-Type" "application/json"}
   :body (j/encode {:x 1 :y 20})})
 deref
 :body
 println)

  ~                                                                                                                       
❯ bb foo.clj 
{"total":21}

David Pham15:01:54

I am sorry for the trouble...

dharrigan15:01:10

not a problemo

Steven Deobald16:01:04

@gupta.harsh96 Small world. 🙂 If you're evaluating reitit, fwiw, I fully endorse.

😀 1
Harsh Gupta17:01:30

Thanks! reitit, is super cool 😄