Skip to content

Commit

Permalink
Adding a new Scala client codegen (#6572)
Browse files Browse the repository at this point in the history
* Adding a Scalaz codegen client

* Fixing imports and removing commented code

* Adding the bash file and updating the Pet store samples for Scalaz.

* Finalizing Scalaz generation so that it works for the Petstore.yaml

* Removing some unnecessary files and comments

* Removing some files that were accidentally generated for the wrong Scala
  • Loading branch information
Taylor Brown authored and wing328 committed Oct 16, 2017
1 parent 1050aa9 commit 488aa98
Show file tree
Hide file tree
Showing 26 changed files with 1,690 additions and 0 deletions.
31 changes: 31 additions & 0 deletions bin/scalaz-petstore.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/bin/sh

SCRIPT="$0"

while [ -h "$SCRIPT" ] ; do
ls=`ls -ld "$SCRIPT"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
SCRIPT="$link"
else
SCRIPT=`dirname "$SCRIPT"`/"$link"
fi
done

if [ ! -d "${APP_DIR}" ]; then
APP_DIR=`dirname "$SCRIPT"`/..
APP_DIR=`cd "${APP_DIR}"; pwd`
fi

executable="./modules/swagger-codegen-cli/target/swagger-codegen-cli.jar"

if [ ! -f "$executable" ]
then
mvn clean package
fi

# if you've executed sbt assembly previously it will use that instead.
export JAVA_OPTS="${JAVA_OPTS} -XX:MaxPermSize=256M -Xmx1024M -DloggerPath=conf/log4j.properties"
ags="$@ generate -t modules/swagger-codegen/src/main/resources/scalaz -i modules/swagger-codegen/src/test/resources/2_0/petstore.yaml -l scalaz -o samples/client/petstore/scalaz"

java $JAVA_OPTS -jar $executable $ags
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
package io.swagger.codegen.languages;

import com.google.common.base.CaseFormat;
import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Template;

import io.swagger.codegen.*;

import io.swagger.models.auth.SecuritySchemeDefinition;
import io.swagger.models.properties.ArrayProperty;
import io.swagger.models.properties.BooleanProperty;
import io.swagger.models.properties.DateProperty;
import io.swagger.models.properties.DateTimeProperty;
import io.swagger.models.properties.DoubleProperty;
import io.swagger.models.properties.FloatProperty;
import io.swagger.models.properties.IntegerProperty;
import io.swagger.models.properties.LongProperty;
import io.swagger.models.properties.MapProperty;
import io.swagger.models.properties.Property;
import io.swagger.models.properties.StringProperty;

import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.*;

import org.apache.commons.lang3.StringUtils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ScalazClientCodegen extends AbstractScalaCodegen implements CodegenConfig {

public ScalazClientCodegen() {
super();
outputFolder = "generated-code/scalaz";
embeddedTemplateDir = templateDir = "scalaz";
apiPackage = "io.swagger.client.api";
modelPackage = "io.swagger.client.api";

modelTemplateFiles.put("model.mustache", ".scala");
apiTemplateFiles.put("api.mustache", ".scala");

setReservedWordsLowerCase(
Arrays.asList(
// local variable names used in API methods (endpoints)
"path", "contentTypes", "contentType", "queryParams", "headerParams",
"formParams", "postBody", "mp", "basePath", "apiInvoker",

// scala reserved words
"abstract", "case", "catch", "class", "def", "do", "else", "extends",
"false", "final", "finally", "for", "forSome", "if", "implicit",
"import", "lazy", "match", "new", "null", "object", "override", "package",
"private", "protected", "return", "sealed", "super", "this", "throw",
"trait", "try", "true", "type", "val", "var", "while", "with", "yield")
);

additionalProperties.put("apiPackage", apiPackage);

supportingFiles.add(new SupportingFile("build.sbt.mustache", "", "build.sbt"));
supportingFiles.add(new SupportingFile("dateTimeCodecs.mustache", (sourceFolder + File.separator + apiPackage).replace(".", File.separator), "DateTimeCodecs.scala"));
supportingFiles.add(new SupportingFile("HelperCodecs.mustache", (sourceFolder + File.separator + apiPackage).replace(".", File.separator), "HelperCodecs.scala"));
supportingFiles.add(new SupportingFile("QueryParamTypeclass.mustache", (sourceFolder + File.separator + apiPackage).replace(".", File.separator), "QueryParamTypeclass.scala"));

importMapping.remove("List");
importMapping.remove("Set");
importMapping.remove("Map");

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

typeMapping = new HashMap<String, String>();
typeMapping.put("enum", "NSString");
typeMapping.put("array", "List");
typeMapping.put("set", "Set");
typeMapping.put("boolean", "Boolean");
typeMapping.put("string", "String");
typeMapping.put("int", "Int");
typeMapping.put("long", "Long");
typeMapping.put("float", "Float");
typeMapping.put("byte", "Byte");
typeMapping.put("short", "Short");
typeMapping.put("char", "Char");
typeMapping.put("double", "Double");
typeMapping.put("object", "Any");
typeMapping.put("file", "File");
typeMapping.put("number", "BigDecimal");
typeMapping.put("date-time", "DateTime");
typeMapping.put("date", "DateTime");


//instantiationTypes.put("array", "ListBuffer");
instantiationTypes.put("array", "ListBuffer");
instantiationTypes.put("map", "HashMap");

additionalProperties.put("fnEnumEntry", new EnumEntryLambda());

cliOptions.add(new CliOption(CodegenConstants.MODEL_PROPERTY_NAMING, CodegenConstants.MODEL_PROPERTY_NAMING_DESC).defaultValue("camelCase"));
}

@Override
public void processOpts() {
super.processOpts();
if (additionalProperties.containsKey(CodegenConstants.MODEL_PROPERTY_NAMING)) {
setModelPropertyNaming((String) additionalProperties.get(CodegenConstants.MODEL_PROPERTY_NAMING));
}
}

public void setModelPropertyNaming(String naming) {
if ("original".equals(naming) || "camelCase".equals(naming) ||
"PascalCase".equals(naming) || "snake_case".equals(naming)) {
this.modelPropertyNaming = naming;
} else {
throw new IllegalArgumentException("Invalid model property naming '" +
naming + "'. Must be 'original', 'camelCase', " +
"'PascalCase' or 'snake_case'");
}
}

public String getModelPropertyNaming() {
return this.modelPropertyNaming;
}
@Override
public String toVarName(String name) {
// sanitize name
name = sanitizeName(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'.

if("_".equals(name)) {
name = "_u";
}

// if it's all uppper case, do nothing
if (name.matches("^[A-Z_]*$")) {
return name;
}

name = getNameUsingModelPropertyNaming(name);

// for reserved word or word starting with number, append _
if (isReservedWord(name) || name.matches("^\\d.*")) {
name = escapeReservedWord(name);
}

return name;
}

@Override
public String toParamName(String name) {
// should be the same as variable name
return toVarName(name);
}

@Override
public String toEnumName(CodegenProperty property) {
return formatIdentifier(property.baseName, true);
}

public String getNameUsingModelPropertyNaming(String name) {
switch (CodegenConstants.MODEL_PROPERTY_NAMING_TYPE.valueOf(getModelPropertyNaming())) {
case original: return name;
case camelCase: return camelize(name, true);
case PascalCase: return camelize(name);
case snake_case: return underscore(name);
default: throw new IllegalArgumentException("Invalid model property naming '" +
name + "'. Must be 'original', 'camelCase', " +
"'PascalCase' or 'snake_case'");
}

}

@Override
public CodegenType getTag() {
return CodegenType.CLIENT;
}

@Override
public String getName() {
return "scalaz";
}

@Override
public String getHelp() {
return "Generates a Scalaz client library that uses http4s";
}

@Override
public String toOperationId(String operationId) {
// throw exception if method name is empty
if (StringUtils.isEmpty(operationId)) {
throw new RuntimeException("Empty method name (operationId) not allowed");
}

// method name cannot use reserved keyword, e.g. return
if (isReservedWord(operationId)) {
throw new RuntimeException(operationId + " (reserved word) cannot be used as method name");
}

return camelize(operationId, true);
}

@Override
public String toModelName(final String name) {
final String sanitizedName = sanitizeName(modelNamePrefix + this.stripPackageName(name) + modelNameSuffix);

// camelize the model name
// phone_number => PhoneNumber
final String camelizedName = camelize(sanitizedName);

// model name cannot use reserved keyword, e.g. return
if (isReservedWord(camelizedName)) {
final String modelName = "Model" + camelizedName;
LOGGER.warn(camelizedName + " (reserved word) cannot be used as model name. Renamed to " + modelName);
return modelName;
}

// model name starts with number
if (name.matches("^\\d.*")) {
final String modelName = "Model" + camelizedName; // e.g. 200Response => Model200Response (after camelize)
LOGGER.warn(name + " (model name starts with number) cannot be used as model name. Renamed to " + modelName);
return modelName;
}

return camelizedName;
}

private static abstract class CustomLambda implements Mustache.Lambda {
@Override
public void execute(Template.Fragment frag, Writer out) throws IOException {
final StringWriter tempWriter = new StringWriter();
frag.execute(tempWriter);
out.write(formatFragment(tempWriter.toString()));
}

public abstract String formatFragment(String fragment);
}

@Override
public String escapeQuotationMark(String input) {
// remove " to avoid code injection
return input.replace("\"", "");
}

private class EnumEntryLambda extends CustomLambda {
@Override
public String formatFragment(String fragment) {
return formatIdentifier(fragment, true);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ io.swagger.codegen.languages.RubyClientCodegen
io.swagger.codegen.languages.RustClientCodegen
io.swagger.codegen.languages.ScalaClientCodegen
io.swagger.codegen.languages.ScalatraServerCodegen
io.swagger.codegen.languages.ScalazClientCodegen
io.swagger.codegen.languages.SilexServerCodegen
io.swagger.codegen.languages.SinatraServerCodegen
io.swagger.codegen.languages.SlimFrameworkServerCodegen
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package {{apiPackage}}

import argonaut._
import argonaut.EncodeJson._
import argonaut.DecodeJson._

import org.http4s._
import org.http4s.{EntityDecoder, EntityEncoder}
import org.http4s.argonaut._

import org.joda.time.DateTime

object HelperCodecs {
implicit def returnTypeDecoder[A: EncodeJson]: EntityEncoder[List[A]] = jsonEncoderOf[List[A]]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package {{apiPackage}}

trait QueryParam[A] {
def toParamString(a: A): String
}

object QueryParam {
implicit def strQueryParam: QueryParam[String] = new QueryParam[String] {
def toParamString(s: String): String = s
}

implicit def listStrQueryParam: QueryParam[List[String]] = new QueryParam[List[String]] {
def toParamString(s: List[String]): String = s.mkString(",")
}
}
Loading

0 comments on commit 488aa98

Please sign in to comment.