ring-swagger

ring-swagger & compojure-api
slipset 2017-10-16T11:58:03.000361Z

@ikitommi, I’m picking up on some of the stuff @psalaberria002 was pestering you with.

slipset 2017-10-16T11:58:09.000393Z

So I’ve got the following:

slipset 2017-10-16T11:58:26.000272Z

(def my-date-time-conforming
  (st/type-conforming
   (assoc conform/string-type-conforming
          :date-time
          (fn [_ value]
            (DateTime. value)))))

(spec/def ::id int?)
(spec/def ::name string?)
(spec/def ::date (st/spec (partial instance? DateTime) {:type :date-time}))

(spec/def ::id-name-date (spec/keys :req-un [::id ::name ::date]))

(context "/spec-test" []
      :coercion :spec
      (GET "/foo" []
        :return ::id-name-date
        (ok {:id 1 :name "foo" :date (DateTime.)}))
      (POST "/foo" []
          :return ::id-name-date
          :body [b ::id-name-date]
          (ok (do (println b) b))))

slipset 2017-10-16T12:01:07.000036Z

The GET request seems to work perfectly, whereas I get an exception on the POST, indicating that somewhere along the line, something doesn’t know how to convert a DateTime to a string:

slipset 2017-10-16T12:01:39.000467Z

Caused by: com.fasterxml.jackson.core.JsonGenerationException: Cannot JSON encode object of class: class java.lang.Class: class org.joda.time.DateTime

slipset 2017-10-16T12:01:55.000082Z

That’s one of my problems.

slipset 2017-10-16T12:02:47.000245Z

The second problem is that swagger understands that I have a field that’s named “date” but it doesn’t understand what kind of field it is, so it presents it as an object:

slipset 2017-10-16T12:02:58.000361Z

{
  "id": 0,
  "name": "string",
  "date": {}
}

slipset 2017-10-16T12:03:56.000404Z

So how do I inform swagger/compojure-api/spec-tools that anything with the type :date-time should have an example value like "2017-10-16T11:59:04.180Z"

slipset 2017-10-16T12:32:05.000204Z

So it seems like there is something wrong with coercion on the way in.

2017-10-16T17:58:17.000150Z

any way to use defroutes with the new :spec coercion?

2017-10-16T17:58:35.000668Z

(GET "/api/company/:id" req :path-params [id :- spec/int?]

2017-10-16T17:58:54.000146Z

returns: No implementation of method: :spec of protocol: #'schema.core/Schema found for class: spec_tools.core.Spec

ikitommi 2017-10-16T18:20:44.000236Z

@slipset hi, compojure-api separates three different stages for coercion: request, response and string. You need to add the conformer also to reponse to make it work.

ikitommi 2017-10-16T18:22:52.000364Z

also, spec conforming is a one-way pipeline, see https://gist.github.com/ikitommi/8a97080357fd7de06882ba99f0df974c.

ikitommi 2017-10-16T18:22:56.000184Z

to get DateTime into a String, you should add a cheshire encoder. (or jsonista encoder if you use that).

ikitommi 2017-10-16T18:23:48.000058Z

I think we need something simpler for the two-way transformations. Good ideas welcome.

ikitommi 2017-10-16T18:24:23.000252Z

@kanwei

(GET "/api/company/:id" req
    :coercion :spec
    :path-params [id :- spec/int?]

ikitommi 2017-10-16T18:25:12.000043Z

:coercion can be set to api, context and all endpoints. And resource too.

2017-10-16T18:28:52.000603Z

hmm

2017-10-16T18:29:02.000393Z

I tried setting :coercion on the defroutes, but that didn't work

ikitommi 2017-10-16T18:29:04.000474Z

and you can explicitely say :coercion compojure.api.coercion.spec/SpecCoercion. All multimethods can break in develop with wild use of tools-refresh.

2017-10-16T18:29:11.000302Z

thanks for the individual route hints

2017-10-16T18:30:07.000456Z

it also works after I changed the whole thing from defroutes to defapi... is there any difference there?

ikitommi 2017-10-16T18:30:16.000740Z

defroutes doesn’t allow that… you could have a empty context?

ikitommi 2017-10-16T18:30:50.000047Z

defapi mounts all the standard middleware, so you shoudn’t use defapi (or api) under a defapi (or api).

2017-10-16T18:31:07.000183Z

hmmmmmmm ok

ikitommi 2017-10-16T18:31:16.000086Z

there is a sample routing app in https://github.com/metosin/c2

ikitommi 2017-10-16T18:31:21.000441Z

(gotta go now)

slipset 2017-10-16T19:17:02.000235Z

@ikitommi it worked!

slipset 2017-10-16T19:17:04.000328Z

I got

slipset 2017-10-16T19:17:08.000158Z

(defn str->date-time [_ value]
  (try 
    (DateTime. value)
    (catch Exception e
      value)))

(def my-date-time-conforming
  (st/type-conforming
   (assoc conform/string-type-conforming
          :date-time
          str->date-time)))

(def custom-coercion
  (-> compojure.api.coercion.spec/default-options
      (assoc-in
        [:body :formats "application/json"]
        (st/type-conforming
          (merge
            conform/json-type-conforming
            {:date-time str->date-time}
            conform/strip-extra-keys-type-conforming)))
      compojure.api.coercion.spec/create-coercion))

(spec/def ::id int?)
(spec/def ::name string?)
(spec/def ::date (st/spec (partial instance? DateTime) {:type :date-time
                                                        :json-schema/default "2017-10-12T05:04:57.585Z"}))

(spec/def ::id-name-date (spec/keys :req-un [::id ::name ::date]))

slipset 2017-10-16T19:17:25.000109Z

and

slipset 2017-10-16T19:17:31.000371Z

(context "/spec-test" []
      :coercion custom-coercion
      (GET "/foo" []
        :return ::id-name-date
        (ok {:id 1 :name "foo" :date (DateTime.)}))
      (POST "/foo" []
        :return ::id-name-date
        :body [b ::id-name-date]
        (ok (do (println b) b #_(assoc b :date "lol")))))

slipset 2017-10-16T19:17:59.000071Z

and swagger shows me

slipset 2017-10-16T19:18:02.000192Z

{
  "id": 0,
  "name": "string",
  "date": "2017-10-12T05:04:57.585Z"
}

slipset 2017-10-16T19:19:00.000392Z

Only problem now is that if I pass an unparsable date, eg 2017-lol-01 either in the request or the response, Muuntaja/jackson becomes upset.

slipset 2017-10-16T19:22:17.000698Z

So it seems like str->date-time needs to return some value (other than an exception) to indicate that it failed to parse the string.

slipset 2017-10-16T19:30:46.000412Z

I would have thought that something like

slipset 2017-10-16T19:30:52.000099Z

(defn str->date-time [_ value]
  (try 
    (DateTime. value)
    (catch Exception e
      ::spec/invalid)))

slipset 2017-10-16T19:31:00.000554Z

would solve it, but alas.