Skip to content
Closed
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 @@ -9,6 +9,7 @@
import java.io.File;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;

Expand All @@ -20,6 +21,7 @@ public class ScalaClientCodegen extends AbstractScalaCodegen implements CodegenC
protected String groupId = "io.swagger";
protected String artifactId = "swagger-scala-client";
protected String artifactVersion = "1.0.0";
protected String clientName = "AsyncClient";

public ScalaClientCodegen() {
super();
Expand Down Expand Up @@ -51,10 +53,13 @@ public ScalaClientCodegen() {
additionalProperties.put("asyncHttpClient", asyncHttpClient);
additionalProperties.put("authScheme", authScheme);
additionalProperties.put("authPreemptive", authPreemptive);
additionalProperties.put("clientName", clientName);

supportingFiles.add(new SupportingFile("pom.mustache", "", "pom.xml"));
supportingFiles.add(new SupportingFile("apiInvoker.mustache",
(sourceFolder + File.separator + invokerPackage).replace(".", java.io.File.separator), "ApiInvoker.scala"));
supportingFiles.add(new SupportingFile("client.mustache",
(sourceFolder + File.separator + invokerPackage).replace(".", java.io.File.separator), clientName + ".scala"));
supportingFiles.add(new SupportingFile("git_push.sh.mustache", "", "git_push.sh"));
supportingFiles.add(new SupportingFile("gitignore.mustache", "", ".gitignore"));
// gradle settings
Expand All @@ -75,8 +80,8 @@ public ScalaClientCodegen() {
importMapping.remove("Set");
importMapping.remove("Map");

importMapping.put("DateTime", "org.joda.time.DateTime");
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@clasnake there's a PR by @ePaul to add back the missing dependencies for joda time: #5092

If I remember correctly, we encountered issue with java.util.Date in the Java API client and therefore @cbornet introduced other datetime libraries (e.g. joda) to address the issue.

importMapping.put("ListBuffer", "scala.collection.mutable.ListBuffer");
importMapping.put("Date", "java.util.Date");
importMapping.put("ListBuffer", "scala.collections.mutable.ListBuffer");

typeMapping = new HashMap<String, String>();
typeMapping.put("enum", "NSString");
Expand All @@ -90,14 +95,17 @@ public ScalaClientCodegen() {
typeMapping.put("byte", "Byte");
typeMapping.put("short", "Short");
typeMapping.put("char", "Char");
typeMapping.put("long", "Long");
typeMapping.put("double", "Double");
typeMapping.put("object", "Any");
typeMapping.put("file", "File");
//TODO binary should be mapped to byte array
// mapped to String as a workaround
typeMapping.put("binary", "String");
typeMapping.put("ByteArray", "String");
typeMapping.put("date-time", "Date");
// typeMapping.put("date", "Date");
// typeMapping.put("Date", "Date");
typeMapping.put("DateTime", "Date");

instantiationTypes.put("array", "ListBuffer");
instantiationTypes.put("map", "HashMap");
Expand All @@ -108,7 +116,6 @@ public ScalaClientCodegen() {
@Override
public void processOpts() {
super.processOpts();

if (additionalProperties.containsKey(CodegenConstants.MODEL_PROPERTY_NAMING)) {
setModelPropertyNaming((String) additionalProperties.get(CodegenConstants.MODEL_PROPERTY_NAMING));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ class {{clientName}}(config: SwaggerConfig) extends Closeable {
client.close()
}
}

162 changes: 88 additions & 74 deletions modules/swagger-codegen/src/main/resources/scala/api.mustache
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
{{>licenseInfo}}
package {{package}}

import java.text.SimpleDateFormat

{{#imports}}import {{import}}
{{/imports}}
import {{invokerPackage}}.ApiInvoker
import {{invokerPackage}}.ApiException
import {{invokerPackage}}.{ApiInvoker, ApiException}

import com.sun.jersey.multipart.FormDataMultiPart
import com.sun.jersey.multipart.file.FileDataBodyPart
Expand All @@ -16,13 +17,40 @@ import java.util.Date

import scala.collection.mutable.HashMap

import com.wordnik.swagger.client._
import scala.concurrent.Future
import collection.mutable

import java.net.URI

import com.wordnik.swagger.client.ClientResponseReaders.Json4sFormatsReader._
import com.wordnik.swagger.client.RequestWriters.Json4sFormatsWriter._

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent._
import scala.concurrent.duration._
import scala.util.{Failure, Success, Try}

{{#operations}}
class {{classname}}(val defBasePath: String = "{{basePath}}",
defApiInvoker: ApiInvoker = ApiInvoker) {

implicit val formats = new org.json4s.DefaultFormats {
override def dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS+0000")
}
implicit val stringReader = ClientResponseReaders.StringReader
implicit val jsonReader = JsonFormatsReader
implicit val stringWriter = RequestWriters.StringWriter
implicit val jsonWriter = JsonFormatsWriter

var basePath = defBasePath
var apiInvoker = defApiInvoker

def addHeader(key: String, value: String) = apiInvoker.defaultHeaders += key -> value
def addHeader(key: String, value: String) = apiInvoker.defaultHeaders += key -> value

val config = SwaggerConfig.forUrl(new URI(defBasePath))
val client = new RestClient(config)
val helper = new {{classname}}AsyncHelper(client, config)

{{#operation}}
/**
Expand All @@ -32,97 +60,83 @@ class {{classname}}(val defBasePath: String = "{{basePath}}",
{{/allParams}} * @return {{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}
*/
def {{operationId}}({{#allParams}}{{paramName}}: {{#required}}{{dataType}}{{#defaultValue}} /* = {{{defaultValue}}}*/{{/defaultValue}}{{/required}}{{^required}}Option[{{dataType}}]{{#defaultValue}} /* = {{{defaultValue}}}*/{{/defaultValue}}{{^defaultValue}} = None{{/defaultValue}}{{/required}}{{#hasMore}}, {{/hasMore}}{{/allParams}}){{#returnType}}: Option[{{returnType}}]{{/returnType}} = {
// create path and map variables
val path = "{{path}}".replaceAll("\\{format\\}", "json"){{#pathParams}}.replaceAll("\\{" + "{{baseName}}" + "\\}",apiInvoker.escape({{paramName}})){{/pathParams}}
val await = Try(Await.result({{operationId}}Async({{#allParams}}{{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}}), Duration.Inf))
await match {
case Success(i) => Some(await.get)
case Failure(t) => None
}

val contentTypes = List({{#consumes}}"{{{mediaType}}}"{{#hasMore}}, {{/hasMore}}{{/consumes}}{{^consumes}}"application/json"{{/consumes}})
val contentType = contentTypes(0)
}

val queryParams = new HashMap[String, String]
val headerParams = new HashMap[String, String]
val formParams = new HashMap[String, String]
/**
* {{summary}} asynchronously
* {{notes}}
{{#allParams}} * @param {{paramName}} {{description}} {{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}
{{/allParams}} * @return Future({{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}})
*/
def {{operationId}}Async({{#allParams}}{{paramName}}: {{#required}}{{dataType}}{{#defaultValue}} /* = {{{defaultValue}}}*/{{/defaultValue}}{{/required}}{{^required}}Option[{{dataType}}]{{#defaultValue}} /* = {{{defaultValue}}}*/{{/defaultValue}}{{^defaultValue}} = None{{/defaultValue}}{{/required}}{{#hasMore}}, {{/hasMore}}{{/allParams}}){{#returnType}}: Future[{{returnType}}]{{/returnType}} = {
helper.{{operationId}}({{#allParams}}{{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}})
}

{{#allParams}}
{{#required}}
{{^isPrimitiveType}}
if ({{paramName}} == null) throw new Exception("Missing required parameter '{{paramName}}' when calling {{classname}}->{{operationId}}")

{{/isPrimitiveType}}
{{#isString}}
if ({{paramName}} == null) throw new Exception("Missing required parameter '{{paramName}}' when calling {{classname}}->{{operationId}}")
{{/operation}}
}

{{/isString}}
{{/required}}
{{/allParams}}
{{#queryParams}}
{{#required}}
queryParams += "{{baseName}}" -> {{paramName}}.toString
{{/required}}
{{^required}}
{{paramName}}.map(paramVal => queryParams += "{{baseName}}" -> paramVal.toString)
{{/required}}
{{/queryParams}}

{{#headerParams}}
{{#required}}
headerParams += "{{baseName}}" -> {{paramName}}
{{/required}}
{{^required}}
{{paramName}}.map(paramVal => headerParams += "{{baseName}}" -> paramVal)
{{/required}}
{{/headerParams}}
class {{classname}}AsyncHelper(client: TransportClient, config: SwaggerConfig) extends ApiClient(client, config) {

{{#operation}}
def {{operationId}}({{#allParams}}{{^required}}{{paramName}}: Option[{{dataType}}] = {{#defaultValue}}Some({{defaultValue}}){{/defaultValue}}{{^defaultValue}}None{{/defaultValue}}{{#hasMore}},{{/hasMore}}
{{/required}}{{#required}}{{paramName}}: {{dataType}}{{#defaultValue}} = {{{defaultValue}}}{{/defaultValue}}{{#hasMore}},
{{/hasMore}}{{/required}}{{/allParams}})(implicit reader: ClientResponseReader[{{#returnType}}{{returnType}}{{/returnType}}{{^returnType}}Unit{{/returnType}}]{{#bodyParams}}, writer: RequestWriter[{{dataType}}]{{/bodyParams}}){{#returnType}}: Future[{{returnType}}]{{/returnType}}{{^returnType}}: Future[Unit]{{/returnType}} = {
// create path and map variables
val path = (addFmt("{{path}}"){{#pathParams}}
replaceAll ("\\{" + "{{baseName}}" + "\\}",{{paramName}}.toString){{/pathParams}})

var postBody: AnyRef = {{#bodyParam}}{{#required}}{{paramName}}{{/required}}{{^required}}{{paramName}}.map(paramVal => paramVal){{/required}}{{/bodyParam}}{{^bodyParam}}null{{/bodyParam}}
// query params
val queryParams = new mutable.HashMap[String, String]
val headerParams = new mutable.HashMap[String, String]

if (contentType.startsWith("multipart/form-data")) {
val mp = new FormDataMultiPart
{{#formParams}}
{{#notFile}}
{{#allParams}}
{{#required}}
mp.field("{{baseName}}", {{paramName}}.toString, MediaType.MULTIPART_FORM_DATA_TYPE)
{{^isPrimitiveType}}
if ({{paramName}} == null) throw new Exception("Missing required parameter '{{paramName}}' when calling {{classname}}->{{operationId}}")
{{/isPrimitiveType}}
{{#isString}}
if ({{paramName}} == null) throw new Exception("Missing required parameter '{{paramName}}' when calling {{classname}}->{{operationId}}")

{{/isString}}
{{/required}}
{{/allParams}}
{{#queryParams}}
{{^required}}
{{paramName}}.map(paramVal => mp.field("{{baseName}}", paramVal.toString, MediaType.MULTIPART_FORM_DATA_TYPE))
{{paramName}} match {
case Some(param) => queryParams += "{{baseName}}" -> param.toString
case _ => queryParams
}
{{/required}}
{{/notFile}}
{{#isFile}}
{{#required}}
mp.field("{{baseName}}", file.getName)
mp.bodyPart(new FileDataBodyPart("{{baseName}}", {{paramName}}, MediaType.MULTIPART_FORM_DATA_TYPE))
queryParams += "{{baseName}}" -> {{paramName}}.toString
{{/required}}
{{/queryParams}}
{{#headerParams}}
{{^required}}
file.map(fileVal => mp.field("{{baseName}}", fileVal.getName))
{{paramName}}.map(paramVal => mp.bodyPart(new FileDataBodyPart("{{baseName}}", paramVal, MediaType.MULTIPART_FORM_DATA_TYPE)))
{{paramName}} match {
case Some(param) => headerParams += "{{baseName}}" -> param.toString
case _ => headerParams
}
{{/required}}
{{/isFile}}
{{/formParams}}
postBody = mp
} else {
{{#formParams}}
{{#notFile}}
{{#required}}
formParams += "{{baseName}}" -> {{paramName}}.toString
headerParams += "{{baseName}}" -> {{paramName}}.toString
{{/required}}
{{^required}}
{{paramName}}.map(paramVal => formParams += "{{baseName}}" -> paramVal.toString)
{{/required}}
{{/notFile}}
{{/formParams}}
}
{{/headerParams}}

try {
apiInvoker.invokeApi(basePath, path, "{{httpMethod}}", queryParams.toMap, formParams.toMap, postBody, headerParams.toMap, contentType) match {
case s: String =>
{{#returnType}} Some(apiInvoker.deserialize(s, "{{returnContainer}}", classOf[{{returnBaseType}}]).asInstanceOf[{{returnType}}])
{{/returnType}}
case _ => None
}
} catch {
case ex: ApiException if ex.code == 404 => None
case ex: ApiException => throw ex
val resFuture = client.submit("{{httpMethod}}", path, queryParams.toMap, headerParams.toMap, {{#bodyParam}}writer.write({{paramName}}){{/bodyParam}}{{^bodyParam}}"{{emptyBodyParam}}"{{/bodyParam}})
resFuture flatMap { resp =>
process(reader.read(resp))
}
}

{{/operation}}
{{/operation}}

}
{{/operations}}
67 changes: 34 additions & 33 deletions modules/swagger-codegen/src/main/resources/scala/build.sbt.mustache
Original file line number Diff line number Diff line change
@@ -1,33 +1,34 @@
lazy val root = (project in file(".")).
settings(
version := "{{artifactVersion}}",
name := "{{artifactId}}",
organization := "{{groupId}}",
scalaVersion := "2.11.8",

libraryDependencies ++= Seq(
"com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.4.2",
"com.sun.jersey" % "jersey-core" % "1.19",
"com.sun.jersey" % "jersey-client" % "1.19",
"com.sun.jersey.contribs" % "jersey-multipart" % "1.19",
"org.jfarcand" % "jersey-ahc-client" % "1.0.5",
"io.swagger" % "swagger-core" % "1.5.8",
"joda-time" % "joda-time" % "2.2",
"org.joda" % "joda-convert" % "1.2",
"org.scalatest" %% "scalatest" % "2.2.4" % "test",
"junit" % "junit" % "4.8.1" % "test"
),

resolvers ++= Seq(
Resolver.jcenterRepo,
Resolver.mavenLocal
),

scalacOptions := Seq(
"-unchecked",
"-deprecation",
"-feature"
),

publishArtifact in (Compile, packageDoc) := false
)
version := "{{artifactVersion}}"

name := "{{artifactId}}"

organization := "{{groupId}}"

scalaVersion := "2.11.8"

libraryDependencies ++= Seq(
"com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.4.2",
"com.sun.jersey" % "jersey-core" % "1.19",
"com.sun.jersey" % "jersey-client" % "1.19",
"com.sun.jersey.contribs" % "jersey-multipart" % "1.19",
"org.jfarcand" % "jersey-ahc-client" % "1.0.5",
"io.swagger" % "swagger-core" % "1.5.8",
"joda-time" % "joda-time" % "2.2",
"org.joda" % "joda-convert" % "1.2",
"org.scalatest" %% "scalatest" % "2.2.4" % "test",
"junit" % "junit" % "4.8.1" % "test",
"com.wordnik.swagger" %% "swagger-async-httpclient" % "0.3.5"
)

resolvers ++= Seq(
Resolver.mavenLocal
)

scalacOptions := Seq(
"-unchecked",
"-deprecation",
"-feature"
)

publishArtifact in (Compile, packageDoc) := false

22 changes: 22 additions & 0 deletions modules/swagger-codegen/src/main/resources/scala/client.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package {{invokerPackage}}

{{#imports}}import {{import}}
{{/imports}}
import {{apiPackage}}._

import com.wordnik.swagger.client._

import java.io.Closeable

class {{clientName}}(config: SwaggerConfig) extends Closeable {
val locator = config.locator
val name = config.name

private[this] val client = transportClient

protected def transportClient: TransportClient = new RestClient(config)

def close() {
client.close()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,10 @@ public void simpleModelTest() {
Assert.assertEquals(property3.baseName, "createdAt");
Assert.assertEquals(property3.getter, "getCreatedAt");
Assert.assertEquals(property3.setter, "setCreatedAt");
Assert.assertEquals(property3.datatype, "DateTime");
Assert.assertEquals(property3.datatype, "Date");
Assert.assertEquals(property3.name, "createdAt");
Assert.assertEquals(property3.defaultValue, "null");
Assert.assertEquals(property3.baseType, "DateTime");
Assert.assertEquals(property3.baseType, "Date");
Assert.assertFalse(property3.hasMore);
Assert.assertFalse(property3.required);
Assert.assertTrue(property3.isNotContainer);
Expand Down
Loading