Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parsing exception with swagger interface generated with grape-swagger gem #27

Closed
bfreeman123 opened this issue Jan 24, 2013 · 7 comments

Comments

@bfreeman123
Copy link

I have a Ruby project that defines a REST API using the grape gem. I am generating the swagger definition with the grape-swagger gem. The swagger definition grape-swagger generates works fine with swagger-ui. It's able to parse the display the API with no errors.

However, when I try to run the same swagger definition through the swagger-codegen validate.sh script, it throws the following exception:

[brian@master swagger-codegen]$ ./bin/validate.sh http://localhost:3000/swagger_doc.json
calling: http://localhost:3000/swagger_doc/users.json
org.json4s.MappingException: Did not find value which can be converted into java.lang.String
    at org.json4s.Extraction$.convert(Extraction.scala:419)
    at org.json4s.Extraction$.build$1(Extraction.scala:325)
    at org.json4s.Extraction$.org$json4s$Extraction$$extract0(Extraction.scala:372)
    at org.json4s.Extraction$.org$json4s$Extraction$$extract0(Extraction.scala:212)
    at org.json4s.Extraction$.extract(Extraction.scala:47)
    at org.json4s.JsonAST$JValue.extract(JsonAST.scala:384)
    at com.wordnik.swagger.model.SwaggerSerializers$OperationSerializer$$anonfun$$init$$6$$anonfun$apply$27.apply(SwaggerModelSerializer.scala:139)
    at com.wordnik.swagger.model.SwaggerSerializers$OperationSerializer$$anonfun$$init$$6$$anonfun$apply$27.apply(SwaggerModelSerializer.scala:132)
    at org.json4s.CustomSerializer$$anonfun$deserialize$2.apply(Formats.scala:273)
    at org.json4s.CustomSerializer$$anonfun$deserialize$2.apply(Formats.scala:271)
    at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:46)
    at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:45)
    at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:45)
    at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:45)
    at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:45)
    at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:45)
    at org.json4s.Extraction$.newInstance$1(Extraction.scala:293)
    at org.json4s.Extraction$.build$1(Extraction.scala:326)
    at org.json4s.Extraction$$anonfun$19.apply(Extraction.scala:316)
    at org.json4s.Extraction$$anonfun$19.apply(Extraction.scala:316)
    at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:194)
    at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:194)
    at scala.collection.LinearSeqOptimized$class.foreach(LinearSeqOptimized.scala:59)
    at scala.collection.immutable.List.foreach(List.scala:45)
    at scala.collection.TraversableLike$class.map(TraversableLike.scala:194)
    at scala.collection.immutable.List.map(List.scala:45)
    at org.json4s.Extraction$.newCollection$1(Extraction.scala:316)
    at org.json4s.Extraction$.build$1(Extraction.scala:333)
    at org.json4s.Extraction$.org$json4s$Extraction$$extract0(Extraction.scala:372)
    at org.json4s.Extraction$.org$json4s$Extraction$$extract0(Extraction.scala:212)
    at org.json4s.Extraction$.extract(Extraction.scala:47)
    at org.json4s.JsonAST$JValue.extract(JsonAST.scala:384)
    at com.wordnik.swagger.model.SwaggerSerializers$ApiDescriptionSerializer$$anonfun$$init$$4$$anonfun$apply$18.apply(SwaggerModelSerializer.scala:101)
    at com.wordnik.swagger.model.SwaggerSerializers$ApiDescriptionSerializer$$anonfun$$init$$4$$anonfun$apply$18.apply(SwaggerModelSerializer.scala:95)
    at org.json4s.CustomSerializer$$anonfun$deserialize$2.apply(Formats.scala:273)
    at org.json4s.CustomSerializer$$anonfun$deserialize$2.apply(Formats.scala:271)
    at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:46)
    at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:45)
    at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:45)
    at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:45)
    at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:45)
    at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:45)
    at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:45)
    at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:45)
    at org.json4s.Extraction$.newInstance$1(Extraction.scala:293)
    at org.json4s.Extraction$.build$1(Extraction.scala:326)
    at org.json4s.Extraction$$anonfun$19.apply(Extraction.scala:316)
    at org.json4s.Extraction$$anonfun$19.apply(Extraction.scala:316)
    at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:194)
    at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:194)
    at scala.collection.LinearSeqOptimized$class.foreach(LinearSeqOptimized.scala:59)
    at scala.collection.immutable.List.foreach(List.scala:45)
    at scala.collection.TraversableLike$class.map(TraversableLike.scala:194)
    at scala.collection.immutable.List.map(List.scala:45)
    at org.json4s.Extraction$.newCollection$1(Extraction.scala:316)
    at org.json4s.Extraction$.build$1(Extraction.scala:333)
    at org.json4s.Extraction$.org$json4s$Extraction$$extract0(Extraction.scala:372)
    at org.json4s.Extraction$.org$json4s$Extraction$$extract0(Extraction.scala:212)
    at org.json4s.Extraction$.extract(Extraction.scala:47)
    at org.json4s.JsonAST$JValue.extract(JsonAST.scala:384)
    at com.wordnik.swagger.model.SwaggerSerializers$ApiListingSerializer$$anonfun$$init$$1$$anonfun$apply$2.apply(SwaggerModelSerializer.scala:32)
    at com.wordnik.swagger.model.SwaggerSerializers$ApiListingSerializer$$anonfun$$init$$1$$anonfun$apply$2.apply(SwaggerModelSerializer.scala:24)
    at org.json4s.CustomSerializer$$anonfun$deserialize$2.apply(Formats.scala:273)
    at org.json4s.CustomSerializer$$anonfun$deserialize$2.apply(Formats.scala:271)
    at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:46)
    at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:45)
    at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:45)
    at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:45)
    at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:45)
    at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:45)
    at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:45)
    at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:45)
    at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:45)
    at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:45)
    at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:45)
    at org.json4s.Extraction$.newInstance$1(Extraction.scala:293)
    at org.json4s.Extraction$.build$1(Extraction.scala:326)
    at org.json4s.Extraction$.org$json4s$Extraction$$extract0(Extraction.scala:372)
    at org.json4s.Extraction$.org$json4s$Extraction$$extract0(Extraction.scala:212)
    at org.json4s.Extraction$.extract(Extraction.scala:47)
    at org.json4s.JsonAST$JValue.extract(JsonAST.scala:384)
    at com.wordnik.swagger.codegen.util.ApiExtractor$$anonfun$fetchApiListings$1.apply(ApiExtractor.scala:42)
    at com.wordnik.swagger.codegen.util.ApiExtractor$$anonfun$fetchApiListings$1.apply(ApiExtractor.scala:34)
    at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:194)
    at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:194)
    at scala.collection.LinearSeqOptimized$class.foreach(LinearSeqOptimized.scala:59)
    at scala.collection.immutable.List.foreach(List.scala:45)
    at scala.collection.TraversableLike$class.map(TraversableLike.scala:194)
    at scala.collection.immutable.List.map(List.scala:45)
    at com.wordnik.swagger.codegen.util.ApiExtractor$.fetchApiListings(ApiExtractor.scala:34)
    at com.wordnik.swagger.codegen.spec.Validator$.main(Validator.scala:48)
    at com.wordnik.swagger.codegen.spec.Validator.main(Validator.scala)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at scala.tools.nsc.util.ScalaClassLoader$$anonfun$run$1.apply(ScalaClassLoader.scala:78)
    at scala.tools.nsc.util.ScalaClassLoader$class.asContext(ScalaClassLoader.scala:24)
    at scala.tools.nsc.util.ScalaClassLoader$URLClassLoader.asContext(ScalaClassLoader.scala:88)
    at scala.tools.nsc.util.ScalaClassLoader$class.run(ScalaClassLoader.scala:78)
    at scala.tools.nsc.util.ScalaClassLoader$URLClassLoader.run(ScalaClassLoader.scala:101)
    at scala.tools.nsc.ObjectRunner$.run(ObjectRunner.scala:33)
    at scala.tools.nsc.ObjectRunner$.runAndCatch(ObjectRunner.scala:40)
    at scala.tools.nsc.MainGenericRunner.runTarget$1(MainGenericRunner.scala:56)
    at scala.tools.nsc.MainGenericRunner.process(MainGenericRunner.scala:80)
    at scala.tools.nsc.MainGenericRunner$.main(MainGenericRunner.scala:89)
    at scala.tools.nsc.MainGenericRunner.main(MainGenericRunner.scala)

Tracing through the scala code, it appears that the OperationSerializer is expecting a list of keys to be in the json document it's parsing. Specifically, the stack trace seems to be pointing to the missing "responseClass" key in the json document.

Unfortunately, the grape-swagger gem doesn't generate this key. When I curl the api that swagger-codegen is trying to parse (http://localhost:3000/swagger_doc/users.json), it only contains the following keys (notes, summary, nickname, httpMethod, parameters). See below:

[brian@master swagger-codegen]$ curl http://localhost:3000/swagger_doc/users.json
{
    "apiVersion": "v1",
    "swaggerVersion": "1.1",
    "basePath": "http://localhost:3000",
    "resourcePath": "",
    "apis": [
        {
            "path": "/v1/users/lock.{format}",
            "operations": [
                {
                    "notes": null,
                    "summary": "Lock User to ensure no more activity can resume with this users credentials",
                    "nickname": "POST--version-users-lock---format-",
                    "httpMethod": "POST",
                    "parameters": [
                        {
                            "paramType": "body",
                            "name": "auth_token",
                            "description": "Login Token",
                            "dataType": "String",
                            "required": true
                        }
                    ]
                }
            ]
        }
    ]
}

Since I am new to scala, it's difficult for me to understand exactly what is going on in the code. So it's possible I'm mis-reading this stack-trace. It's also possible the grape-swagger gem isn't fully implementing the swagger definition, in which case this is an issue for grape-swagger and not swagger-codegen (however that seems odd since swagger-ui can still parse it).

@fehguy
Copy link
Contributor

fehguy commented Jan 24, 2013

Looks like you are missing the responseClass. The error reporting is not good right now but the trace points here:

https://github.com/wordnik/swagger-codegen/blob/master/src/main/scala/com/wordnik/swagger/model/SwaggerModelSerializer.scala#L139

@bfreeman123
Copy link
Author

Thanks for the response. That's what I figured. So I guess my question is as follows. Is there a reason why certain fields are required in swagger-codegen but are not required in swagger-ui? Again, I am simply feeding the output from grape-swagger into both, and it works in swagger-ui, but not in swagger-codegen.

@fehguy
Copy link
Contributor

fehguy commented Jan 24, 2013

Yes, the code generator is more strict with structure that the ui. Valid swagger specs are best, of course.

@bfreeman123
Copy link
Author

Thanks again for the explanation. I was able to get this to work by monkey-patching the swagger-grape gem to add the responseClass and errorResponses keys to Operations, and added the models key to the ApiListing (all of which are missing in the most recent version of the gem). I'll submit a pull request to the grape-swagger project that will implement these missing keys.

@fehguy
Copy link
Contributor

fehguy commented Jan 25, 2013

Thank you. And we will improve the validator messages as well as make a javascript version that's bundled with Swagger-ui.

@fehguy fehguy closed this as completed Feb 6, 2013
@berlincount
Copy link

bfreeman123, did you ever submit that pull request? I'm running into the same issues ..

@bfreeman123
Copy link
Author

No, I did not. While my patch got the code generator over that hurdle, it merely spit out un-usable code (at least for objective-c). As I recall, it wasn't mapping attributes correctly. I have since moved onto another project and abandoned the swagger codegen, but if you'd like to give it a try, you can simply add the responseClass key to the Operations hash on the grape-swagger gem.

If you look at https://github.com/tim-vandecasteele/grape-swagger/blob/master/lib/grape-swagger.rb starting on line 91, you essentially want to change:

operations = {
  :notes => notes,
  :summary => route.route_description || '',
  :nickname   => route.route_method + route.route_path.gsub(/[\/:\(\)\.]/,'-'),
  :httpMethod => route.route_method,
  :parameters => parse_header_params(route.route_headers) +
  parse_params(route.route_params, route.route_path, route.route_method)
}

to

operations = {
  :notes => notes,
  :summary => route.route_description || '',
  :nickname   => route.route_method + route.route_path.gsub(/[\/:\(\)\.]/,'-'),
  :httpMethod => route.route_method,
  :parameters => parse_header_params(route.route_headers) +
  parse_params(route.route_params, route.route_path, route.route_method),
  :responseClass => @@class_name
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants