4

I am adding Swagger annotations to JaxRs annotated services.

I have the following:

(^{
 GET true
 Path "/{who}"
 ApiOperation {:value "Get a hello" :notes "simple clojure GET"}
 Produces ["text/plain; charset=UTF-8"]
 ApiResponses {:value [(ApiResponse {:code 200 :message "yay!"})]}

}

If I decompile the produced class the annotations look like this:

@ApiResponses({@com.wordnik.swagger.annotations.ApiResponse(code=200L, message="yay!")})
@Produces({"text/plain; charset=UTF-8"})
@ApiOperation(value="Get a hello", notes="simple clojure GET")
@Path("/{who}")
@GET(true)

notes that in the first annotation code = 200L

During runtime, this value must be an int, and I cannot figure out how to make this happen

if I try

ApiResponses {:value [(ApiResponse {:code (int 200) :message "yay!"})]}

I get a compilation error (using the maven swagger plugin)

Exception in thread "main" java.lang.ClassCastException: clojure.lang.Var cannot be cast to java.lang.Class, compiling:(pocclj/resourceclj.clj:14)

I have tried

(def success (int 200))
 ...
ApiResponses {:value [(ApiResponse {:code success :message "yay!"})]}

Which produces this compilation error:

Exception in thread "main" java.lang.IllegalArgumentException: Unsupported annotation value: success of class class java.lang.Integer, compiling:(pocclj/resourceclj.clj:14)

I have tried a bunch of other stuff (deref etc) but cant find the secret sauce.

I am fairly new to clojure and desperate for some help on this.

Thanks in advance

Martin

mbedoian
  • 41
  • 2

2 Answers2

0

You are setting the type of ':code' correctly. Which can be tested independently:

user> (def something ^{:ApiResponses {:code (int 200) :message "yay!"}} {:some :data :goes :here})
#'user/something

user> (meta something)
{:ApiResponses {:code 200, :message "yay!"}}

user> (-> (meta something) :ApiResponses :code type)
java.lang.Integer

And without the cast the metadata contains the wrong type:

user> (def something-wrong ^{:ApiResponses {:code 200 :message "yay!"}} {:some :data :goes :here})
#'user/something-wrong

user> (meta something)
{:ApiResponses {:code 200, :message "yay!"}}

user> (-> (meta something-wrong) :ApiResponses :code type)
java.lang.Long

From the exception it looks like perhaps the call to ApiResponse is crashing. If ApiResponse is a macro that expects a number and not an s-expression, then I could see it not handling this properly. If it is a function you would need to look into why it is crashing.

If I provide a stub implementation for ApiResponse then It works for me:

user> (definterface Fooi (Something []))
user.Fooi

user> (def ApiResponse identity)
#'user/ApiResponse

user> (deftype Foo []
        Fooi
        (Something
          ^{GET true
            Path "/{who}"
            ApiOperation {:value "Get a hello" :notes "simple clojure GET"}
            Produces ["text/plain; charset=UTF-8"]
            ApiResponses {:value [(ApiResponse {:code (int 200) :message "yay!"})]}}
          [this] (identity this)))
user.Foo
Arthur Ulfeldt
  • 90,827
  • 27
  • 201
  • 284
0

I don't know about ApiResponse, or much about annotations really, but: it looks like some macro (deftype?) is producing annotations for you, and you need it to see 200 as an int. Clojure doesn't have int literals, so the only way to hand an Integer object directly to a macro is through some other macro that calls it. It's not really possible to do this in a nice way; as far as I know you have to either use eval, or be very narrow by aiming specifically at int literals. Here's a sketch of a solution:

user> (use 'clojure.walk)
user> (defmacro with-int-literals [named-ints & body]
        (prewalk-replace (into {}
                               (for [[k v] (partition 2 named-ints)]
                                 [k (int v)]))
                         `(do ~@body)))

user> (map class (second (macroexpand-1 '(with-int-literals [x 100, y 200] [x y]))))
(java.lang.Integer java.lang.Integer)

So if you wrap your entire deftype (or whatever macro is generating these annotations) with a with-int-literals form, you can produce integers for it instead of longs. I don't actually know that this will work; perhaps there's something in the annotation processor that's fundamentally unable to handle ints for some reason. But at least this way you can offer it ints and hope for the best.

Edit

Since you actually need int literals in metadata, rather than in "ordinary" code, prewalk won't actually look at the data you care about. You'll have to write a version of walk that treats metadata in a sensible way, and then use it instead of prewalk here.

amalloy
  • 89,153
  • 8
  • 140
  • 205
  • 1
    >> Clojure doesn't have int literals, so the only way to hand an Integer object directly to a macro is through some other macro that calls it. Kind of what I was afraid...I was hoping there was something analagous to 200M (ie 200I, etc). Thanks for the clarification – mbedoian Aug 18 '13 at 17:46