Fork me on GitHub
#pedestal
<
2022-11-04
>
Shivam A13:11:22

Hi folks, I am trying to integrate reitit with pedestal web backend. For some reason coercion/coerce-exceptions-interceptor is not handling coercion type exceptions. Code: (def router (http/router ["/v1" ["/" {:get {:summary "simple get handler" :parameters {:query {:student-id s/Int}} :handler home-page} :post {:summary "simple post handler" :responses {200 {:body {:total s/Int}}} :parameters {:body {:name s/Str :age s/Int}} :handler home-page}}]] {:exception pretty/exception :data {:muuntaja m/instance :compile rc/compile-request-coercers :coercion reitit.coercion.schema/coercion :interceptors [(parameters/parameters-interceptor) (muuntaja/format-negotiate-interceptor) (muuntaja/format-response-interceptor) (muuntaja/format-request-interceptor) (muuntaja/format-interceptor) (coercion/coerce-response-interceptor) (coercion/coerce-request-interceptor) (coercion/coerce-exceptions-interceptor) (exception/exception-interceptor)]}})) exception: ERROR i.p.http.impl.servlet-interceptor - {:msg "Dev interceptor caught an exception; Forwarding it as the response.", :line 314} clojure.lang.ExceptionInfo: clojure.lang.ExceptionInfo in Interceptor :reitit.http.coercion/coerce-request - Request coercion failed: #reitit.coercion.CoercionError{:schema {:student-id Int, Keyword Any}, :errors {:student-id missing-required-key}} at io.pedestal.interceptor.chain$throwable__GT_ex_info.invokeStatic(chain.clj:35) at io.pedestal.interceptor.chain$throwable__GT_ex_info.invoke(chain.clj:32) at io.pedestal.interceptor.chain$try_f.invokeStatic(chain.clj:57) at io.pedestal.interceptor.chain$try_f.invoke(chain.clj:44) at io.pedestal.interceptor.chain$process_all_with_binding.invokeStatic(chain.clj:171) at io.pedestal.interceptor.chain$process_all_with_binding.invoke(chain.clj:146) at io.pedestal.interceptor.chain$process_all$fn__9181.invoke(chain.clj:188) at clojure.lang.AFn.applyToHelper(AFn.java:152) at clojure.lang.AFn.applyTo(AFn.java:144) at clojure.core$apply.invokeStatic(core.clj:665) at clojure.core$with_bindings_STAR_.invokeStatic(core.clj:1973) at clojure.core$with_bindings_STAR_.doInvoke(core.clj:1973) at clojure.lang.RestFn.invoke(RestFn.java:425) at io.pedestal.interceptor.chain$process_all.invokeStatic(chain.clj:186) at io.pedestal.interceptor.chain$process_all.invoke(chain.clj:182) at io.pedestal.interceptor.chain$enter_all.invokeStatic(chain.clj:235) at io.pedestal.interceptor.chain$enter_all.invoke(chain.clj:229) at io.pedestal.interceptor.chain$execute.invokeStatic(chain.clj:379) at io.pedestal.interceptor.chain$execute.invoke(chain.clj:352) at io.pedestal.interceptor.chain$execute.invokeStatic(chain.clj:389) at io.pedestal.interceptor.chain$execute.invoke(chain.clj:352) at io.pedestal.http.impl.servlet_interceptor$interceptor_service_fn$fn__12805.invoke(servlet_interceptor.clj:351) at io.pedestal.http.servlet.FnServlet.service(servlet.clj:28) at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:799) at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:550) at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:233) at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1434) at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:188) at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:501) at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:186) at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1349) at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141) at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127) at org.eclipse.jetty.server.Server.handle(Server.java:516) at org.eclipse.jetty.server.HttpChannel.lambda$handle$1(HttpChannel.java:400) at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:645) at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:392) at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:277) at .AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:311) at .FillInterest.fillable(FillInterest.java:105) at .ChannelEndPoint$1.run(ChannelEndPoint.java:104) at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:883) at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1034) at java.base/java.lang.Thread.run(Thread.java:833) Caused by: clojure.lang.ExceptionInfo: Request coercion failed: #reitit.coercion.CoercionError{:schema {:student-id Int, Keyword Any}, :errors {:student-id missing-required-key}} at reitit.coercion$request_coercion_failed_BANG_.invokeStatic(coercion.cljc:46) at reitit.coercion$request_coercion_failed_BANG_.invoke(coercion.cljc:44) at reitit.coercion$request_coercer$fn__15954.invoke(coercion.cljc:86) at reitit.coercion$coerce_request$fn__15967.invoke(coercion.cljc:113) at clojure.lang.PersistentArrayMap.kvreduce(PersistentArrayMap.java:377) at clojure.core$fn__8437.invokeStatic(core.clj:6845) at clojure.core$fn__8437.invoke(core.clj:6830) at clojure.core.protocols$fn__8167$G__8162__8176.invoke(protocols.clj:175) at clojure.core$reduce_kv.invokeStatic(core.clj:6856) at clojure.core$reduce_kv.invoke(core.clj:6847) at reitit.coercion$coerce_request.invokeStatic(coercion.cljc:111) at reitit.coercion$coerce_request.invoke(coercion.cljc:110) at reitit.http.coercion$coerce_request_interceptor$fn__310$fn__312.invoke(coercion.cljc:24) at io.pedestal.interceptor.chain$try_f.invokeStatic(chain.clj:54) ... 41 common frames omitted

souenzzo19:11:54

this is more about #C7YF1SBT3

👍 1
agorgl21:11:04

Hello there again! I've read the pedestal 'Your First API' example (http://pedestal.io/guides/your-first-api) and I'm trying to understand why is it a good/better idea to 'inject' the db through interceptors. Couldn't we just use the domain functions to manipulate domain entities / run domain logic? E.g. in the tutorial a simple read operation / api is:

(def list-view
  {:name :list-view
   :enter
   (fn [context]
     (if-let [db-id (get-in context [:request :path-params :list-id])]                  
       (if-let [the-list (find-list-by-id (get-in context [:request :database]) db-id)] 
         (assoc context :result the-list)                                               
         context)
       context))})
Couldn't this just be something like:
(defn list-view [request]
  (if-let [db-id (get-in request [:path-params :list-id])]                  
    (if-let [the-list (find-list-by-id db-id)] 
      the-list)))
Where the find-list-by-id domain method would also hide the implementation detail of the repository / data layer?

👍 1
jmv00:11:22

I typically do it the way you’re suggesting.

hlship17:11:01

I prefer to avoid globals at all cost, since that impacts testing. If the database is in the context, then I can fabricate a context with a mock database for testing purposes, and have confidence in my code. If the database is a global, then tests can have unintended consequences. It's about pushing the side-effecting, non-functional parts of your code into carefully managed corners. Your mileage may vary.

💯 3
vemv02:11:01

I think there are two questions in one in OP: • why use interceptors for domain logic? • why inject the DB? the latter is already answered. For the former, I'd say that not everything has to be an interceptor. Or at least your interceptors can be very thin, invoking independent defns that are agnostic about requests, interceptors and so on. This relates to a couple concepts: • "thin controllers, fat models" which is a Rails mantra (an interceptor is somewhat analog to a controller) • Hexagonal architecture e.g. decouple domain logic from HTTP processing.