Fork me on GitHub
#ring-swagger
<
2017-10-16
>
slipset11:10:03

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

slipset11:10:09

So I’ve got the following:

slipset11:10:26

(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))))

slipset12:10:07

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:

slipset12:10:39

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

slipset12:10:55

That’s one of my problems.

slipset12:10:47

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:

slipset12:10:58

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

slipset12:10:56

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"

slipset12:10:05

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

kanwei17:10:17

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

kanwei17:10:35

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

kanwei17:10:54

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

ikitommi18:10:44

@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.

ikitommi18:10:56

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

ikitommi18:10:48

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

ikitommi18:10:23

@kanwei

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

ikitommi18:10:12

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

kanwei18:10:02

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

ikitommi18:10:04

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

kanwei18:10:11

thanks for the individual route hints

kanwei18:10:07

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

ikitommi18:10:16

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

ikitommi18:10:50

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

kanwei18:10:07

hmmmmmmm ok

ikitommi18:10:16

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

ikitommi18:10:21

(gotta go now)

slipset19:10:08

(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]))

slipset19:10:31

(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")))))

slipset19:10:59

and swagger shows me

slipset19:10:02

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

slipset19:10:00

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.

slipset19:10:17

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.

slipset19:10:46

I would have thought that something like

slipset19:10:52

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

slipset19:10:00

would solve it, but alas.