Fork me on GitHub
#yada
<
2016-09-22
>
grav08:09:24

What’s the idiomatic way of adding a custom error handler to all resources? Just wrapping yada/resource in a function that adds :responses {500 (fn [ctx] …}?

grav08:09:23

Like this?

(defn- resource [m]
  (-> (assoc m :responses {500 {:produces "text/plain"
                                :response (fn [ctx]
                                            (pr-str ctx))}})
      yada/resource))

chromaticgliss18:09:28

not sure if this is specifically a yada issue, but I'm running into a problem when I try to use the result of a function that queries for datomic data as a response in a yada resource. almost anything I return gives...

com.fasterxml.jackson.core.JsonGenerationException: Cannot JSON encode object of class: class java.lang.NullPointerException: java.lang.NullPointerException\n\tat cheshire.generate$generate.invokeStatic(generate.clj:154)\n\tat cheshire.generate$generate.invoke(generate.clj:116)\n\tat cheshire.generate$generate.invokeStatic(generate.clj:124)\n\tat cheshire.generate$generate.invoke(generate.clj:116)\n\tat cheshire.core$generate_string.invokeStatic(core.clj:73)\n\tat cheshire.core$generate_string.invoke(core.clj:48)\n\tat yada.body$fn__19868.invokeStatic(body.clj:166)\n\tat yada.body$fn__19868.invoke(body.clj:163)\n\tat clojure.lang.MultiFn.invoke(MultiFn.java:233)\n\tat yada.body$fn__19856.invokeStatic(body.clj:89)\n\tat yada.body$fn__19856.invoke(body.clj:57)\n\tat yada.body$fn__19755$G__19748__19762.invoke(body.clj:34)\n\tat yada.handler$standard_error.invokeStatic(handler.clj:73)\n\tat yada.handler$standard_error.invoke(handler.clj:72)\n\tat yada.handler$handle_request_with_maybe_subresources$fn__21738.invoke(handler.clj:150)\n\tat manifold.deferred$catch_SINGLEQUOTE_$fn__1531.invoke(deferred.clj:962)\n\tat manifold.deferred.Listener.onError(deferred.clj:220)\n\tat manifold.deferred.Deferred$fn__1311$fn__1312.invoke(deferred.clj:400)\n\tat clojure.lang.AFn.run(AFn.java:22)\n\tat io.aleph.dirigiste.Executor$Worker$1.run(Executor.java:62)\n\tat manifold.executor$thread_factory$reify__812$f__813.invoke(executor.clj:36)\n\tat clojure.lang.AFn.run(AFn.java:22)\n\tat java.lang.Thread.run(Thread.java:745)\n

chromaticgliss18:09:30

where the NullPointer is happening is beyond me, since put the result of that function is just a string

malcolmsparks19:09:23

@chromaticgliss never seen that before, could you paste your resource map?

chromaticgliss19:09:54

(defn get-sales-resource
  [sales]
  (yada/resource
   {:methods
    {:post
     {:consumes "application/edn"
      :produces "application/edn"
      :parameters {:body {:store/pks clojure.lang.PersistentVector}}
                          ;; (schema/optional-key :discount/name) String
                          ;; (schema/optional-key :product/pk) String
                          ;; (schema/optional-key :discount/date) java.util.Date}}
      :response #(get-sales sales %)}}}))

chromaticgliss19:09:21

when I was returning a string my :produces was text/plain

chromaticgliss19:09:37

similar issues though

chromaticgliss19:09:53

the only way I could get a valid response was when my string was "Hello"

chromaticgliss19:09:21

posted the wrong resource, initally, woops

chromaticgliss19:09:30

if i call my (get-sales ...) directly I get a sensible value returned though. it's currently reduced to a statically defined datomic query

chromaticgliss19:09:08

user> (def ctx {:parameters {:body {:store/pks [50420]}}})
#'user/ctx
user> (def sales (:sales-api system))
#'user/sales
user> (s/get-sales sales ctx)
#{[285873023224729]}
user> 

chromaticgliss19:09:27

My function as it currently stands is just this:

(defn get-sales
  "Endpoint to get sales."
  [sales ctx]
  (d/q '[:find ?e :where [?e :discount/name _] [?e :store/store [:store/pk 50420]]] (d/db (-> sales :stores-db :conn))))

malcolmsparks20:09:43

Strange that cheshire is involved, given you're return edn. Lots of Clojure things, like Datomic entites, will need help to encode to json.

malcolmsparks20:09:56

Try returning a map not a string. E.g. {:message result}

chromaticgliss20:09:44

progress maybe! it yields a slightly more useful json exception

chromaticgliss20:09:15

but yeah I'm not sure why cheshire is involved at all

chromaticgliss20:09:23

'com.fasterxml.jackson.core.JsonGenerationException: Cannot JSON encode object of class: class java.lang.Exception: java.lang.Exception: processing rule: (q__12392 ?version-id ?tx), message: processing clause: [?publish :publish/tx ?tx], message: :db.error/not-an-entity Unable to resolve entity: :publish/tx\n\tat cheshire.generate$generate.invokeStatic(generate.clj:154)\n\tat cheshire.generate$generate.invoke(generate.clj:116)\n\tat cheshire.generate$generate.invokeStatic(generate.clj:124)\n\tat cheshire.generate$generate.invoke(generate.clj:116)\n\tat cheshire.core$generate_string.invokeStatic(core.clj:73)\n\tat cheshire.core$generate_string.invoke(core.clj:48)\n\tat yada.body$fn__19868.invokeStatic(body.clj:166)\n\tat yada.body$fn__19868.invoke(body.clj:163)\n\tat clojure.lang.MultiFn.invoke(MultiFn.java:233)\n\tat yada.body$fn__19856.invokeStatic(body.clj:89)\n\tat yada.body$fn__19856.invoke(body.clj:57)\n\tat yada.body$fn__19755$G__19748__19762.invoke(body.clj:34)\n\tat yada.handler$standard_error.invokeStatic(handler.clj:73)\n\tat yada.handler$standard_error.invoke(handler.clj:72)\n\tat yada.handler$handle_request_with_maybe_subresources$fn__21738.invoke(handler.clj:150)\n\tat manifold.deferred$catch_SINGLEQUOTE_$fn__1531.invoke(deferred.clj:962)\n\tat manifold.deferred.Listener.onError(deferred.clj:220)\n\tat manifold.deferred.Deferred$fn__1311$fn__1312.invoke(deferred.clj:400)\n\tat clojure.lang.AFn.run(AFn.java:22)\n\tat io.aleph.dirigiste.Executor$Worker$1.run(Executor.java:62)\n\tat manifold.executor$thread_factory$reify__812$f__813.invoke(executor.clj:36)\n\tat clojure.lang.AFn.run(AFn.java:22)\n\tat java.lang.Thread.run(Thread.java:745)\n'

chromaticgliss20:09:14

when calling directly the function yields

user> (s/get-sales sales ctx)
{:message #{[285873023224729]}}

malcolmsparks20:09:19

Do you declare you produce application/json anywhere?

chromaticgliss20:09:08

not that I know of... we've got a couple other endpoints but they're all doing things in edn

malcolmsparks20:09:41

something is hitting yada.body line 166, and that's a defmethod for "application/json"

malcolmsparks20:09:01

ah, I see, there's an error that's thrown in the Datomic query - then yada tries to provide the error in application/json

malcolmsparks20:09:10

(def error-representations (ys/representation-seq (ys/representation-set-coercer [{:media-type #{"application/json" "application/json;pretty=true;q=0.96" "text/plain;q=0.9" "text/html;q=0.8" "application/edn;q=0.6" "application/edn;pretty=true;q=0.5"} :charset charset/platform-charsets}])))

malcolmsparks20:09:36

relevant code is line 61 onwards in yada.handler

malcolmsparks20:09:46

then the error itself can't be converted to json

malcolmsparks20:09:59

so the thing to do is wrap your query in a try/catch

chromaticgliss20:09:09

ah... have to figure out what Datomic is throwing

malcolmsparks20:09:49

at least in your catch block you can return some map that says 'datomic error', perhaps pr-str that datomic error so it marshalls in json ok

malcolmsparks20:09:59

yada should have a mechanism to override the error types that are produces, but for now you can workaround this by ensuring all datomic access is properly wrapped in try/catch, that the errors are logged, and that a simplified error is passed back to the user-agent

malcolmsparks20:09:28

respond with a 500 (if it's reached the datomic level, it's bypassed all request defences, so it's a 500 I think)

malcolmsparks20:09:37

but if you log the datomic error on the server you'll be able to debug

malcolmsparks20:09:08

thanks for chatting this on slack, it's revealed a potential improvement in the way yada does errors

chromaticgliss20:09:20

excellent, will do. thanks so much!