code-reviews

hindol 2020-02-16T18:26:50.027200Z

Hi, I have the following code,

(defn error->status
  "Converts a JSON-RPC error code into the appropriate HTTP status."
  [code]
  (cond
    (= -32600 code)                400
    (= -32601 code)                404
    (#{-32602 -32603 -32700} code) 500
    (<= -32099 code -32000)        500))

(defn infer-status
  "Infer the HTTP status code from the JSON-RPC response.
   See: <https://www.jsonrpc.org/historical/json-rpc-over-http.html#errors>"
  [body]
  (if-let [error (:error body)]
    (if-let [code (:code error)]
      (if (int? code)
        (if-let [status (error-&gt;status code)]
          status
          (throw (ex-info "Error code invalid!"
                          {:response body})))
        (throw (ex-info "Error code is not a number!"
                        {:response body})))
      (throw (ex-info "Error code missing from error response!"
                      {:response body})))
    200))
What would be a more sane way to write the infer-status part? The nested ifs are getting out of hand.

seancorfield 2020-02-16T18:43:25.028300Z

@hindol.adhya I guess I'd ask if you really need the different exceptions? Are you surfacing that message somewhere?

hindol 2020-02-16T18:45:21.029600Z

Yes, it is getting logged elsewhere. The different messages are good to have.

seancorfield 2020-02-16T18:45:56.030300Z

Given that you're "only" throwing one exception type and ex-data is going to be identical in each case, I'd probably collapse those down into one error -- you can figure out the specifics based on ex-data after the fact.

seancorfield 2020-02-16T18:46:41.031100Z

I'd also probably use Spec to validate the response, and just log the explain-str.

hindol 2020-02-16T18:49:07.033700Z

The thought of using spec did occur to me. I have never used it before, so just procrastinating I guess.

seancorfield 2020-02-16T18:49:51.034500Z

(if (s/valid? ::response-code body)
  (if-let [status (some-&gt; body :error :code error-&gt;status)]
    status
    (throw (ex-info "Invalid/missing error code" {:response body :failure (s/explain-str ::response-code body)})
  200)

hindol 2020-02-16T18:50:12.034600Z

If it's not too much, some sample code for this approach will be really helpful.

hindol 2020-02-16T18:50:24.034800Z

Like how this approach is being used in real projects.

seancorfield 2020-02-16T18:50:41.035Z

See the code I pasted it the main channel.

👍 1
seancorfield 2020-02-16T18:51:33.036100Z

The ::response-code spec would have error as an optional key, and it would be specified to have a require code field that is int?.

seancorfield 2020-02-16T18:52:47.037500Z

(s/def ::code int?)
(s/def ::error (s/keys :req-un [::code]))
(s/def ::response-code (s/keys :opt-un [::error]))
off the top of my head.

hindol 2020-02-16T18:53:53.038Z

Thank you so much for this! I have to try this out. Looks much cleaner to me.

hindol 2020-02-16T18:55:03.038700Z

A very tangential question, why do we :req-un and yet give a fully qualified keyword in the vector?

seancorfield 2020-02-16T18:59:18.039200Z

Your structure has unqualified keywords but specs are always qualified keywords.

seancorfield 2020-02-16T19:00:02.040100Z

The namespace part of the keyword is to make the spec unique (in your case so they don't collide with any other :error and :code mappings in your codebase).

hindol 2020-02-16T19:02:09.040200Z

in your case so they don't collide with any other :error and :code mappings in your codebase - don't think I understand but that might just be because I am very inexperienced with spec.

seancorfield 2020-02-16T19:04:48.040900Z

I just updated the spec above -- I wasn't writing what I intended!

seancorfield 2020-02-16T19:06:56.042800Z

Another option would be:

(s/def :error/code int?)
(s/def  :response/error (s/keys :req-un [:error/code]))
(s/def ::response-code (s/keys :opt-un [:response/error]))
Is that clearer?

seancorfield 2020-02-16T19:07:54.043600Z

You could have :error/code and :success/code and other :code semantics in different places in your code.

seancorfield 2020-02-16T19:09:25.044400Z

Spec uses qualified keywords, to describe both qualified keywords and unqualified keywords 🙂

👍 1
hindol 2020-02-16T19:11:09.044800Z

This does look clearer. Thanks!