Fork me on GitHub
#ring-swagger
<
2018-05-15
>
jumar14:05:45

I have this compojure-api definition:

(context "/api" []
  :coercion :spec
  ,,,
  (POST "/api/check" []
    :body-params [user :- ::specs/user
                  k :- ::specs/k
                  address :- (s/nilable string?)]
    (do-sth user k address version)))
Now I'd like to add a new optional attribute version (and also fixing existing address to be truly optional). What's the easiest way to do it?
(context "/api" []
  :coercion :spec
  ,,,
  (POST "/api/check" []
    :body-params [user :- ::specs/user
                  k :- ::specs/k
;; s/nilable isn't enough - 400 bad request is returned if address and version are not present in request params
                  address :- (s/nilable string?)
                  version :- (s/nilable string?)]
    (do-sth user k address version)))

jumar14:05:31

So far I've found only this solution (which seems to work):

(context "/api" []
  ,,,
  (context "/check" []
    (resource
     {:coercion :spec
      :post {:parameters {:body-params (s/keys :req-un [::specs/user ::specs/k]
                                               :opt-un [::address ::version])}
             :handler (fn [{{:keys [user k address version]} :body-params}]
                        (do-sth user k address version))}})))

ikitommi14:05:56

@jumar all the -params use the Plumatic fn syntax (https://github.com/plumatic/plumbing#bring-on-defnk). With it, the optional is maked with curly braces. This should work:

(def app
  (context "/api" []
    :coercion :spec
    (POST "/check" []
      :body-params [{address :- (s/nilable string?) nil}
                    {version :- (s/nilable string?) nil}]
      (ok [address version]))))

(app {:request-method :post, :uri "/api/check", :body-params {}})
; {:status 200, :headers {}, :body [nil nil]}

(app {:request-method :post, :uri "/api/check", :body-params {:address "Hämeenkatu"}})
; {:status 200, :headers {}, :body ["Hämeenkatu" nil]}

ikitommi14:05:03

I think the resource version is cleaner here.

jumar14:05:34

@ikitommi great, thanks!

ikitommi15:05:41

@plins Threads should be managed somehow. I would recommend using some lifecycle management solution like Integrant, Mount or Component to start (and stop) those. There is a example of Mount in https://github.com/yogthos/memory-hole

carocad19:05:12

Hey guys sorry for the ignorant question but may I ask why is spec tools build around a separate model instead of using Clojure spec directly? I see this project https://github.com/wilkerlucio/spec-coerce and wonder if there is something that I'm not understanding since I thought that it was not possible to do it based on the current Clojure directly implementation.

ikitommi20:05:20

@carocad good question! spec-tools actually builds on top of clojure.spec. if the patch in CLJ-2116 would have been accepted, spec-tools would work 100% with normal specs too and st/spec wrapping could be made optional. There is even an issue about spec meta-data, so we could remove the wrapping completely. For now, there are two ways to do this: 1) lean on s/conform* which is implemented for all specs (e.g. works with all regexps too) and wrap the specs to enable overriding it at runtime, e.g. "spec-tools way" or b) copy the spec walking code (the s/conform* impls) into the coercion library. I think spec-coerce has done this, for most common specs, but not all like regexps. Also, parsing spec forms at each invocation is slow.

ikitommi20:05:55

I hope the next version of spec has something better for 3rd party libaries. I think the api in spec-tools is ok (the new encode & decode and separate transformer like Schema has), but the implementation behind will change a lot when/if spec evolves.

ikitommi20:05:39

CLJ-2251 would solve this for good.

ikitommi20:05:33

But, I'll recheck spec-coerce, I think it's a great lib too.