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

PR for ability to easily debug newly created codegen classes. #2388

Merged
merged 4 commits into from
Mar 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ public void run() {
ImmutableList.of(
new SupportingFile("pom.mustache", "", "pom.xml"),
new SupportingFile("generatorClass.mustache", on(File.separator).join("src/main/java", asPath(targetPackage)), mainClass.concat(".java")),
new SupportingFile("debugGeneratorTest.mustache", on(File.separator).join("src/test/java", asPath("org.openapitools.codegen.debug")), "DebugCodegenLauncher.java"),
new SupportingFile("README.mustache", "", "README.md"),
new SupportingFile("api.template", "src/main/resources" + File.separator + name,"api.mustache"),
new SupportingFile("model.template", "src/main/resources" + File.separator + name,"model.mustache"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ The `{{generatorClass}}.java` has comments in it--lots of comments. There is no
for reading the code more, though. See how the `{{generatorClass}}` implements `CodegenConfig`.
That class has the signature of all values that can be overridden.

You can also step through {{generatorClass}}.java in a debugger. Just debug the JUnit
test in DebugCodegenLauncher. That runs the command line tool and lets you inspect what the code is doing.

For the templates themselves, you have a number of values available to you for generation.
You can execute the `java` command from above while passing different debug flags to show
the object you have available during client generation:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.openapitools.codegen.debug;

import org.junit.Test;
import org.openapitools.codegen.OpenAPIGenerator;

/***
* This test allows you to easily launch your code generation software under a debugger.
* Then run this test under debug mode. You will be able to step through your java code
* and then see the results in the out directory.
*
* To experiment with debugging your code generator:
* 1) Set a break point in {{generatorClass}}.java in the postProcessOperationsWithModels() method.
* 2) To launch this test in Eclipse: right-click | Debug As | JUnit Test
*
*/
public class DebugCodegenLauncher
{
@Test
public void launchCodeGeneratorInDebugMode()
{
// use this test to launch you code generator in the debugger.
// this allows you to easily set break points in {{generatorClass}}.
String commandLineParams =
"generate "+
"-i https://raw.githubusercontent.com/openapitools/openapi-generator/master/modules/openapi-generator/src/test/resources/2_0/petstore.yaml "+ // sample swagger
"-t ./src/main/resources/{{name}} "+ // template directory
"-o out/{{name}} "+ // output directory
"-g {{name}} "; // use this codegen library

try{
OpenAPIGenerator.main( commandLineParams.split(" ") );
}
catch(Exception ex) {
System.err.println(ex.toString());
}
catch(Error er) {
System.err.println(er.toString());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,31 @@ public class {{generatorClass}} extends DefaultCodegen implements CodegenConfig
return "{{name}}";
}

/**
* Provides an opportunity to inspect and modify operation data before the code is generated.
*/
@SuppressWarnings("unchecked")
@Override
public Map<String, Object> postProcessOperationsWithModels(Map<String, Object> objs, List<Object> allModels) {

// to try debugging your code generator:
// set a break point on the next line.
// then debug the JUnit test called LaunchGeneratorInDebugger

Map<String, Object> results = super.postProcessOperationsWithModels(objs, allModels);

Map<String, Object> ops = (Map<String, Object>)results.get("operations");
ArrayList<CodegenOperation> opList = (ArrayList<CodegenOperation>)ops.get("operation");

// iterate over the operation and perhaps modify something
for(CodegenOperation co : opList){
// example:
// co.httpMethod = co.httpMethod.toLowerCase();
}

return results;
}

/**
* Returns human-friendly help for the generator. Provide the consumer with help
* tips, parameters here
Expand Down
10 changes: 10 additions & 0 deletions modules/openapi-generator/src/main/resources/codegen/pom.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,16 @@
<version>${openapi-generator-version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-cli</artifactId>
<version>${openapi-generator-version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit-version}</version>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
/**
* Swagger Petstore *_/ ' \" =end -- \\r\\n \\n \\r
* This spec is mainly for testing Petstore server and contains fake endpoints, models. Please do not use this for any other purpose. Special characters: \" \\ *_/ ' \" =end --
*
* OpenAPI spec version: 1.0.0 *_/ ' \" =end -- \\r\\n \\n \\r
* Contact: [email protected] *_/ ' \" =end -- \\r\\n \\n \\r
*
* NOTE: This class is auto generated by the swagger code generator program.
* https://github.com/swagger-api/swagger-codegen.git
* Do not edit the class manually.
*/

package io.swagger.client

import com.sun.jersey.api.client.Client
import com.sun.jersey.api.client.ClientResponse
import com.sun.jersey.api.client.config.ClientConfig
import com.sun.jersey.api.client.config.DefaultClientConfig
import com.sun.jersey.api.client.filter.LoggingFilter

import com.sun.jersey.core.util.MultivaluedMapImpl
import com.sun.jersey.multipart.FormDataMultiPart
import com.sun.jersey.multipart.file.FileDataBodyPart

import java.io.File
import java.net.URLEncoder
import java.util.UUID
import javax.ws.rs.core.MediaType

import scala.collection.JavaConverters._
import scala.collection.mutable

import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.fasterxml.jackson.datatype.joda.JodaModule
import com.fasterxml.jackson.core.JsonGenerator.Feature
import com.fasterxml.jackson.databind._
import com.fasterxml.jackson.annotation._
import com.fasterxml.jackson.databind.annotation.JsonSerialize

object ScalaJsonUtil {
def getJsonMapper: ObjectMapper = {
val mapper = new ObjectMapper()
mapper.registerModule(new DefaultScalaModule())
mapper.registerModule(new JodaModule())
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL)
mapper.setSerializationInclusion(JsonInclude.Include.NON_DEFAULT)
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY)
mapper
}
}

class ApiInvoker(val mapper: ObjectMapper = ScalaJsonUtil.getJsonMapper,
httpHeaders: mutable.HashMap[String, String] = mutable.HashMap(),
hostMap: mutable.HashMap[String, Client] = mutable.HashMap(),
asyncHttpClient: Boolean = false,
authScheme: String = "",
authPreemptive: Boolean = false
) {

var defaultHeaders: mutable.HashMap[String, String] = httpHeaders

def escape(value: String): String = {
URLEncoder.encode(value, "utf-8").replaceAll("\\+", "%20")
}
def escape(values: List[String]): String = {
values.map(escape).mkString(",")
}

def escape(value: Long): String = value.toString
def escape(value: Double): String = value.toString
def escape(value: Float): String = value.toString
def escape(value: UUID): String = value.toString

def deserialize(json: String, containerType: String, cls: Class[_]) = {
if (cls == classOf[String]) {
json match {
case s: String =>
if (s.startsWith("\"") && s.endsWith("\"") && s.length > 1) {
s.substring(1, s.length - 1)
} else {
s
}
case _ => null
}
} else {
containerType.toLowerCase match {
case "array" =>
val typeInfo = mapper.getTypeFactory.constructCollectionType(classOf[java.util.List[_]], cls)
val response = mapper.readValue(json, typeInfo).asInstanceOf[java.util.List[_]]
response.asScala.toList
case "list" =>
val typeInfo = mapper.getTypeFactory.constructCollectionType(classOf[java.util.List[_]], cls)
val response = mapper.readValue(json, typeInfo).asInstanceOf[java.util.List[_]]
response.asScala.toList
case _ =>
json match {
case e: String if "\"\"" == e => null
case _ => mapper.readValue(json, cls)
}
}
}
}

def serialize(obj: AnyRef): String = {
if (obj != null) {
obj match {
case e: List[_] => mapper.writeValueAsString(obj.asInstanceOf[List[_]].asJava)
case _ => mapper.writeValueAsString(obj)
}
} else {
null
}
}

def invokeApi(
host: String,
path: String,
method: String,
queryParams: Map[String, String],
formParams: Map[String, String],
body: AnyRef,
headerParams: Map[String, String],
contentType: String
): String = {
val client = getClient(host)

val querystring = queryParams.filter(k => k._2 != null).map(k => escape(k._1) + "=" + escape(k._2)).mkString("?", "&", "")
val builder = client.resource(host + path + querystring).accept(contentType)
headerParams.map(p => builder.header(p._1, p._2))
defaultHeaders.foreach(p => {
if (!headerParams.contains(p._1) && p._2 != null) {
builder.header(p._1, p._2)
}
})
var formData: MultivaluedMapImpl = null
if (contentType == "application/x-www-form-urlencoded") {
formData = new MultivaluedMapImpl()
formParams.foreach(p => formData.add(p._1, p._2))
}

val response: ClientResponse = method match {
case "GET" => builder.get(classOf[ClientResponse])
case "POST" =>
if (formData != null && formData.size() > 0) {
builder.post(classOf[ClientResponse], formData)
} else if (body != null && body.isInstanceOf[File]) {
val file = body.asInstanceOf[File]
val form = new FormDataMultiPart()
form.field("filename", file.getName)
form.bodyPart(new FileDataBodyPart("file", file, MediaType.MULTIPART_FORM_DATA_TYPE))
builder.post(classOf[ClientResponse], form)
} else {
if (body == null) {
builder.post(classOf[ClientResponse], serialize(body))
} else {
builder.`type`(contentType).post(classOf[ClientResponse], serialize(body))
}
}
case "PUT" =>
if (formData != null) {
builder.post(classOf[ClientResponse], formData)
} else if (body == null) {
builder.put(classOf[ClientResponse], null)
} else {
builder.`type`(contentType).put(classOf[ClientResponse], serialize(body))
}
case "DELETE" => builder.delete(classOf[ClientResponse])
case "PATCH" =>
if(formData != null) {
builder.header("X-HTTP-Method-Override", "PATCH").post(classOf[ClientResponse], formData)
} else if(body == null) {
builder.header("X-HTTP-Method-Override", "PATCH").post(classOf[ClientResponse], null)
} else {
builder.header("X-HTTP-Method-Override", "PATCH").`type`(contentType).post(classOf[ClientResponse], serialize(body))
}
case _ => null
}
response.getStatusInfo.getStatusCode match {
case 204 => ""
case code: Int if Range(200, 299).contains(code) =>
if (response.hasEntity) {
response.getEntity(classOf[String])
} else {
""
}
case _ =>
val entity = if (response.hasEntity) {
response.getEntity(classOf[String])
} else {
"no data"
}
throw new ApiException(response.getStatusInfo.getStatusCode, entity)
}
}

def getClient(host: String): Client = {
if (hostMap.contains(host)) {
hostMap(host)
} else {
val client = newClient(host)
// client.addFilter(new LoggingFilter())
hostMap += host -> client
client
}
}

def newClient(host: String): Client = if (asyncHttpClient) {
import com.ning.http.client.Realm
import org.sonatype.spice.jersey.client.ahc.AhcHttpClient
import org.sonatype.spice.jersey.client.ahc.config.DefaultAhcConfig

val config: DefaultAhcConfig = new DefaultAhcConfig()
if (!authScheme.isEmpty) {
val authSchemeEnum = Realm.AuthScheme.valueOf(authScheme)
config
.getAsyncHttpClientConfigBuilder
.setRealm(new Realm.RealmBuilder().setScheme(authSchemeEnum)
.setUsePreemptiveAuth(authPreemptive).build)
}
AhcHttpClient.create(config)
} else {
Client.create()
}
}

object ApiInvoker extends ApiInvoker(
mapper = ScalaJsonUtil.getJsonMapper,
httpHeaders = mutable.HashMap(),
hostMap = mutable.HashMap(),
asyncHttpClient = false,
authScheme = "",
authPreemptive = false
)

class ApiException(val code: Int, msg: String) extends RuntimeException(msg)
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.swagger.client

import io.swagger.client.api._

import com.wordnik.swagger.client._

import java.io.Closeable

class AsyncClient(config: SwaggerConfig) extends Closeable {
lazy val locator: ServiceLocator = config.locator
lazy val name: String = config.name

private[this] val client = transportClient

protected def transportClient: TransportClient = new RestClient(config)

def close() {
client.close()
}
}
Loading