reitit

https://cljdoc.org/d/metosin/reitit/ https://github.com/metosin/reitit/
jumar 2021-01-18T05:39:58.039500Z

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
...

ikitommi 2021-01-18T09:41:30.040800Z

@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.

ikitommi 2021-01-18T09:45:44.044500Z

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.

jumar 2021-01-18T11:20:28.045Z

With var I get this error:

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

jumar 2021-01-18T11:21:45.045200Z

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 πŸ™‚.

ikitommi 2021-01-18T11:21:59.045400Z

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

ikitommi 2021-01-18T11:22:14.045600Z

does that help?

jumar 2021-01-18T11:23:50.045800Z

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

ikitommi 2021-01-18T11:24:20.046Z

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

πŸ‘ 1
2021-01-18T15:11:46.047800Z

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

2021-01-18T15:12:01.048200Z

Did anyone go through the same trouble?

2021-01-18T15:13:19.048500Z

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

;; you can use babashka for this example 

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

;; =&gt; {"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"]}

2021-01-18T15:15:19.048900Z

The funny thing is this works fine

2021-01-18T15:15:45.049300Z

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

dharrigan 2021-01-18T15:29:36.049900Z

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

dharrigan 2021-01-18T15:29:40.050100Z

might that have anything to do with it?

2021-01-18T15:31:00.050600Z

Let me double check

2021-01-18T15:31:38.050900Z

nope, it it is not that πŸ˜•

2021-01-18T15:32:08.051100Z

It drives me crazy haha

2021-01-18T15:32:51.051700Z

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.

dharrigan 2021-01-18T15:34:01.052Z

can you share your schema please?

dharrigan 2021-01-18T15:34:16.052300Z

and your router setup please

2021-01-18T15:36:10.052600Z

(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]
   [<http://clojure.java.io|clojure.java.io> :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](<https://github.com/metosin/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 &amp; 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 (-&gt; {:env :dev
                         ::server/type :jetty
                         ::server/port (or port 3000)
                         ::server/join? false
                         ;; no pedestal routes
                         ::server/routes []
                         ;; allow serving the swagger-ui styles &amp; 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))

dharrigan 2021-01-18T15:48:33.052800Z

This works

dharrigan 2021-01-18T15:48:35.053Z

(client/post
  "<http://localhost:8080/math/plus>"
  {:headers {"Content-Type" "application/json"}
   :body (j/encode {:x 1 :y 20})})

dharrigan 2021-01-18T15:48:46.053300Z

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

dharrigan 2021-01-18T15:49:07.053500Z

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

οŒƒ  ~                                                                                                                       
❯ bb foo.clj 
{"total":21}

2021-01-18T15:50:44.053800Z

Thanks!

2021-01-18T15:50:54.054100Z

I am sorry for the trouble...

dharrigan 2021-01-18T15:51:10.054600Z

not a problemo

Steven Deobald 2021-01-18T16:35:04.055200Z

@gupta.harsh96 Small world. πŸ™‚ If you're evaluating reitit, fwiw, I fully endorse.

πŸ˜€ 1
Harsh Gupta 2021-01-18T17:44:30.055400Z

Thanks! reitit, is super cool πŸ˜„