Fork me on GitHub
#reitit
<
2018-08-27
>
billh10:08:46

Hello, please forgive me as I'm a bit of a noob with swagger and spec but I'm having a little trouble with the swagger api with spec coercion. Basically I have some maps where the fields have quite complicated specs and test generators, here is an example contract map with a couple of specific decimal fields:

(defn non-negative? [n] (>= n 0))
(defn pos-scaled-decimal?
  [scale]
  (s/spec
    (fn [x]
      (and (non-negative? x) (decimal? x) (>= scale (.scale x))))
    :gen #(gen/fmap (fn [x] (bigdec (/ (Math/abs x)
                                      (Math/pow 10 (+ 1 (rand-int 6))))))
            (gen/large-integer))))

(s/def ::pos-decimal-2dp (pos-scaled-decimal? 2))
(s/def ::urate1 ::pos-decimal-2dp)
(s/def ::urate2 ::pos-decimal-2dp)
(s/def ::contract (s/keys :req-un [::urate1 ::urate2]))
I would like to use generated maps as example input data on the swagger UI, on a vanilla swagger install to fill out the example post body with generated data I think that would look something like this:
:swagger {:paths {"/contract" {:post {:requestBody {:content {:application/json {:schema {:type "object" :example (gen/generate (s/gen ::contract))}}}}}}}
Is there a way to do this with reitit? I tried to use the ::contract spec with parameter coercion:
["/contract"
        {:post {:handler    (fn [_]
                              {:status 200
                               :body   "?"})
                :parameters {:body ::contract}}}]
But example data on swagger ui gets turned into {:urate1 {}, :urate2 {}} not (for example) {:urate1 0.01M, :urate2 0.07M} Thanks

ikitommi12:08:57

@billh you can wrap the spec into spec-tools.core/Spec, which can have extra metadata, including the :example, something like:

(st/spec {:spec ::contract
          :swagger/example (-> (s/exercise ::contract 1) first second)})
, should be picked up by the swagger transformer

ikitommi12:08:28

also, both the coercion and the swagger transformer need to know the type of the leaf predicates to work properly. In your example the pos-scaled-decimal? should return a spec-tools.core/Spec too, instead of just functions / reified clojure.spec protocols.

ikitommi12:08:40

something like this could work:

(defn pos-scaled-decimal?
  [scale]
  (st/spec
    {:type :double
     :spec (s/spec
             (fn [x]
               (and (non-negative? x) (decimal? x) (>= scale (.scale x))))
             :gen #(gen/fmap (fn [x] (bigdec (/ (Math/abs x)
                                                (Math/pow 10 (+ 1 (rand-int 6))))))
                             (gen/large-integer)))}))

ikitommi12:08:27

or use s/and and have the type as first argument:

(s/def ::urate1 (s/and integer? ::pos-decimal-2dp))

ikitommi12:08:32

types are inferred from most/all core predicates.

billh14:08:07

Working now, thanks Tommi. Thanks for the great work you're doing on reitit as well!

4