Is there a way to make fn spec that allows fn to throw exceptions? I want to express a contract "this function returns any value and can even throw"
IIRC, no.
This has encouraged me to spec out some of my functions to return values that indicate success or failure. Often they’re :cognitect.anomalies/anomaly
values. I’m pretty happy with this approach. It makes the functions more testable, I think. And by speccing out the error values you get the property testing to extend to error cases as well.
Yeah, but I want to document that I allow user-provided callback to throw an exception. It’s like a promise “it’s okay to fail”…
Yeah, makes sense, reasonable. I just think that’s out of scope for spec.
If I were to speculate the thinking behind that call, I’d guess that it’s an example of the position that exceptions should be used in truly exceptional cases — cases that are hard to predict and handle — and can thus be thrown anywhere, at any time. (That’s a reductive summary missing much nuance but I hope potentially slightly useful.) Just speculation though, I could easily be mistaken.
This might be a little silly, but in some cases I’ve written functions that catch Exceptions and then return them as values! e.g.
(s/fdef check-render-result
:args (s/cat :result (s/or :success ::r/success-result
:failure ::r/failure-result)
:path ::fs/file-path-str)
:ret (s/or :success nil?
:failure (partial instance? Exception)))
function specs document non-exceptional use
so they don't include any way to talk about exceptions
understood
I am trying to spec the requests and responses to an API, but am running into issues with namespace collisions.
(ns my-app.validate.endpoint-a)
(s/def :ok-response/status #{200})
(s/def :ok-response/body ...)
(s/def ::ok-response (s/keys :req-un [:ok-response/status
:ok-response/body]))
(s/def :created-response/status #{201})
(s/def :created-response/body ...)
(s/def ::created-response (s/keys :req-un [:created-response/status
:created-response/body]))
(s/def ::response (s/or :ok ::ok-response
:created ::created-response))
The problem comes when I add validation for endpoint-b
which will also have an :ok-response/body
which will be different than that for endpoint-a
.
I am already encoding the type of response (`ok-response` vs. created-response
) in the kw namespace due to s/keys
mandating that the kw name matches the key in the map being spec'd.
I can continue to encode further dimensions (endpoint, method) into the namespace to avoid these collisions, but it quickly gets unwieldy:
(s/def :get-endpoint-a-ok-response/body ...)
Am I overlooking an idiomatic way of handling this problem?
I'd love to see something like
(s/def ::ok-response-body ...)
(s/def ::ok-response (s/named-keys :req {:status #{200}
:body ::ok-response-body}))
Note: s/named-keys
above could also address support for string keys https://clojure.atlassian.net/browse/CLJ-2196
Spec 2 will provide that functionality @jmromrell if I’m understanding correctly what you are asking.
But it sounds like you might also want to look at multi-spec
?
That's great. The inflexibility of s/keys
has been a pain point for me multiple times.
I'll look into it. Thank you!