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->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 if
s are getting out of hand.@hindol.adhya I guess I'd ask if you really need the different exceptions? Are you surfacing that message somewhere?
Yes, it is getting logged elsewhere. The different messages are good to have.
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.
I'd also probably use Spec to validate the response, and just log the explain-str
.
The thought of using spec
did occur to me. I have never used it before, so just procrastinating I guess.
(if (s/valid? ::response-code body)
(if-let [status (some-> body :error :code error->status)]
status
(throw (ex-info "Invalid/missing error code" {:response body :failure (s/explain-str ::response-code body)})
200)
If it's not too much, some sample code for this approach will be really helpful.
Like how this approach is being used in real projects.
See the code I pasted it the main channel.
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?
.
(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.Thank you so much for this! I have to try this out. Looks much cleaner to me.
A very tangential question, why do we :req-un
and yet give a fully qualified keyword in the vector?
Your structure has unqualified keywords but specs are always qualified keywords.
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).
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.
I just updated the spec above -- I wasn't writing what I intended!
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?You could have :error/code
and :success/code
and other :code
semantics in different places in your code.
Spec uses qualified keywords, to describe both qualified keywords and unqualified keywords 🙂
This does look clearer. Thanks!