Skip to content

Commit 488aa98

Browse files
Taylor Brownwing328
authored andcommitted
Adding a new Scala client codegen (#6572)
* 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
1 parent 1050aa9 commit 488aa98

26 files changed

+1690
-0
lines changed

bin/scalaz-petstore.sh

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/bin/sh
2+
3+
SCRIPT="$0"
4+
5+
while [ -h "$SCRIPT" ] ; do
6+
ls=`ls -ld "$SCRIPT"`
7+
link=`expr "$ls" : '.*-> \(.*\)$'`
8+
if expr "$link" : '/.*' > /dev/null; then
9+
SCRIPT="$link"
10+
else
11+
SCRIPT=`dirname "$SCRIPT"`/"$link"
12+
fi
13+
done
14+
15+
if [ ! -d "${APP_DIR}" ]; then
16+
APP_DIR=`dirname "$SCRIPT"`/..
17+
APP_DIR=`cd "${APP_DIR}"; pwd`
18+
fi
19+
20+
executable="./modules/swagger-codegen-cli/target/swagger-codegen-cli.jar"
21+
22+
if [ ! -f "$executable" ]
23+
then
24+
mvn clean package
25+
fi
26+
27+
# if you've executed sbt assembly previously it will use that instead.
28+
export JAVA_OPTS="${JAVA_OPTS} -XX:MaxPermSize=256M -Xmx1024M -DloggerPath=conf/log4j.properties"
29+
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"
30+
31+
java $JAVA_OPTS -jar $executable $ags
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
package io.swagger.codegen.languages;
2+
3+
import com.google.common.base.CaseFormat;
4+
import com.samskivert.mustache.Mustache;
5+
import com.samskivert.mustache.Template;
6+
7+
import io.swagger.codegen.*;
8+
9+
import io.swagger.models.auth.SecuritySchemeDefinition;
10+
import io.swagger.models.properties.ArrayProperty;
11+
import io.swagger.models.properties.BooleanProperty;
12+
import io.swagger.models.properties.DateProperty;
13+
import io.swagger.models.properties.DateTimeProperty;
14+
import io.swagger.models.properties.DoubleProperty;
15+
import io.swagger.models.properties.FloatProperty;
16+
import io.swagger.models.properties.IntegerProperty;
17+
import io.swagger.models.properties.LongProperty;
18+
import io.swagger.models.properties.MapProperty;
19+
import io.swagger.models.properties.Property;
20+
import io.swagger.models.properties.StringProperty;
21+
22+
import java.io.File;
23+
import java.io.IOException;
24+
import java.io.StringWriter;
25+
import java.io.Writer;
26+
import java.util.*;
27+
28+
import org.apache.commons.lang3.StringUtils;
29+
30+
import org.slf4j.Logger;
31+
import org.slf4j.LoggerFactory;
32+
33+
public class ScalazClientCodegen extends AbstractScalaCodegen implements CodegenConfig {
34+
35+
public ScalazClientCodegen() {
36+
super();
37+
outputFolder = "generated-code/scalaz";
38+
embeddedTemplateDir = templateDir = "scalaz";
39+
apiPackage = "io.swagger.client.api";
40+
modelPackage = "io.swagger.client.api";
41+
42+
modelTemplateFiles.put("model.mustache", ".scala");
43+
apiTemplateFiles.put("api.mustache", ".scala");
44+
45+
setReservedWordsLowerCase(
46+
Arrays.asList(
47+
// local variable names used in API methods (endpoints)
48+
"path", "contentTypes", "contentType", "queryParams", "headerParams",
49+
"formParams", "postBody", "mp", "basePath", "apiInvoker",
50+
51+
// scala reserved words
52+
"abstract", "case", "catch", "class", "def", "do", "else", "extends",
53+
"false", "final", "finally", "for", "forSome", "if", "implicit",
54+
"import", "lazy", "match", "new", "null", "object", "override", "package",
55+
"private", "protected", "return", "sealed", "super", "this", "throw",
56+
"trait", "try", "true", "type", "val", "var", "while", "with", "yield")
57+
);
58+
59+
additionalProperties.put("apiPackage", apiPackage);
60+
61+
supportingFiles.add(new SupportingFile("build.sbt.mustache", "", "build.sbt"));
62+
supportingFiles.add(new SupportingFile("dateTimeCodecs.mustache", (sourceFolder + File.separator + apiPackage).replace(".", File.separator), "DateTimeCodecs.scala"));
63+
supportingFiles.add(new SupportingFile("HelperCodecs.mustache", (sourceFolder + File.separator + apiPackage).replace(".", File.separator), "HelperCodecs.scala"));
64+
supportingFiles.add(new SupportingFile("QueryParamTypeclass.mustache", (sourceFolder + File.separator + apiPackage).replace(".", File.separator), "QueryParamTypeclass.scala"));
65+
66+
importMapping.remove("List");
67+
importMapping.remove("Set");
68+
importMapping.remove("Map");
69+
70+
importMapping.put("Date", "java.util.Date");
71+
importMapping.put("ListBuffer", "scala.collection.mutable.ListBuffer");
72+
73+
typeMapping = new HashMap<String, String>();
74+
typeMapping.put("enum", "NSString");
75+
typeMapping.put("array", "List");
76+
typeMapping.put("set", "Set");
77+
typeMapping.put("boolean", "Boolean");
78+
typeMapping.put("string", "String");
79+
typeMapping.put("int", "Int");
80+
typeMapping.put("long", "Long");
81+
typeMapping.put("float", "Float");
82+
typeMapping.put("byte", "Byte");
83+
typeMapping.put("short", "Short");
84+
typeMapping.put("char", "Char");
85+
typeMapping.put("double", "Double");
86+
typeMapping.put("object", "Any");
87+
typeMapping.put("file", "File");
88+
typeMapping.put("number", "BigDecimal");
89+
typeMapping.put("date-time", "DateTime");
90+
typeMapping.put("date", "DateTime");
91+
92+
93+
//instantiationTypes.put("array", "ListBuffer");
94+
instantiationTypes.put("array", "ListBuffer");
95+
instantiationTypes.put("map", "HashMap");
96+
97+
additionalProperties.put("fnEnumEntry", new EnumEntryLambda());
98+
99+
cliOptions.add(new CliOption(CodegenConstants.MODEL_PROPERTY_NAMING, CodegenConstants.MODEL_PROPERTY_NAMING_DESC).defaultValue("camelCase"));
100+
}
101+
102+
@Override
103+
public void processOpts() {
104+
super.processOpts();
105+
if (additionalProperties.containsKey(CodegenConstants.MODEL_PROPERTY_NAMING)) {
106+
setModelPropertyNaming((String) additionalProperties.get(CodegenConstants.MODEL_PROPERTY_NAMING));
107+
}
108+
}
109+
110+
public void setModelPropertyNaming(String naming) {
111+
if ("original".equals(naming) || "camelCase".equals(naming) ||
112+
"PascalCase".equals(naming) || "snake_case".equals(naming)) {
113+
this.modelPropertyNaming = naming;
114+
} else {
115+
throw new IllegalArgumentException("Invalid model property naming '" +
116+
naming + "'. Must be 'original', 'camelCase', " +
117+
"'PascalCase' or 'snake_case'");
118+
}
119+
}
120+
121+
public String getModelPropertyNaming() {
122+
return this.modelPropertyNaming;
123+
}
124+
@Override
125+
public String toVarName(String name) {
126+
// sanitize name
127+
name = sanitizeName(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'.
128+
129+
if("_".equals(name)) {
130+
name = "_u";
131+
}
132+
133+
// if it's all uppper case, do nothing
134+
if (name.matches("^[A-Z_]*$")) {
135+
return name;
136+
}
137+
138+
name = getNameUsingModelPropertyNaming(name);
139+
140+
// for reserved word or word starting with number, append _
141+
if (isReservedWord(name) || name.matches("^\\d.*")) {
142+
name = escapeReservedWord(name);
143+
}
144+
145+
return name;
146+
}
147+
148+
@Override
149+
public String toParamName(String name) {
150+
// should be the same as variable name
151+
return toVarName(name);
152+
}
153+
154+
@Override
155+
public String toEnumName(CodegenProperty property) {
156+
return formatIdentifier(property.baseName, true);
157+
}
158+
159+
public String getNameUsingModelPropertyNaming(String name) {
160+
switch (CodegenConstants.MODEL_PROPERTY_NAMING_TYPE.valueOf(getModelPropertyNaming())) {
161+
case original: return name;
162+
case camelCase: return camelize(name, true);
163+
case PascalCase: return camelize(name);
164+
case snake_case: return underscore(name);
165+
default: throw new IllegalArgumentException("Invalid model property naming '" +
166+
name + "'. Must be 'original', 'camelCase', " +
167+
"'PascalCase' or 'snake_case'");
168+
}
169+
170+
}
171+
172+
@Override
173+
public CodegenType getTag() {
174+
return CodegenType.CLIENT;
175+
}
176+
177+
@Override
178+
public String getName() {
179+
return "scalaz";
180+
}
181+
182+
@Override
183+
public String getHelp() {
184+
return "Generates a Scalaz client library that uses http4s";
185+
}
186+
187+
@Override
188+
public String toOperationId(String operationId) {
189+
// throw exception if method name is empty
190+
if (StringUtils.isEmpty(operationId)) {
191+
throw new RuntimeException("Empty method name (operationId) not allowed");
192+
}
193+
194+
// method name cannot use reserved keyword, e.g. return
195+
if (isReservedWord(operationId)) {
196+
throw new RuntimeException(operationId + " (reserved word) cannot be used as method name");
197+
}
198+
199+
return camelize(operationId, true);
200+
}
201+
202+
@Override
203+
public String toModelName(final String name) {
204+
final String sanitizedName = sanitizeName(modelNamePrefix + this.stripPackageName(name) + modelNameSuffix);
205+
206+
// camelize the model name
207+
// phone_number => PhoneNumber
208+
final String camelizedName = camelize(sanitizedName);
209+
210+
// model name cannot use reserved keyword, e.g. return
211+
if (isReservedWord(camelizedName)) {
212+
final String modelName = "Model" + camelizedName;
213+
LOGGER.warn(camelizedName + " (reserved word) cannot be used as model name. Renamed to " + modelName);
214+
return modelName;
215+
}
216+
217+
// model name starts with number
218+
if (name.matches("^\\d.*")) {
219+
final String modelName = "Model" + camelizedName; // e.g. 200Response => Model200Response (after camelize)
220+
LOGGER.warn(name + " (model name starts with number) cannot be used as model name. Renamed to " + modelName);
221+
return modelName;
222+
}
223+
224+
return camelizedName;
225+
}
226+
227+
private static abstract class CustomLambda implements Mustache.Lambda {
228+
@Override
229+
public void execute(Template.Fragment frag, Writer out) throws IOException {
230+
final StringWriter tempWriter = new StringWriter();
231+
frag.execute(tempWriter);
232+
out.write(formatFragment(tempWriter.toString()));
233+
}
234+
235+
public abstract String formatFragment(String fragment);
236+
}
237+
238+
@Override
239+
public String escapeQuotationMark(String input) {
240+
// remove " to avoid code injection
241+
return input.replace("\"", "");
242+
}
243+
244+
private class EnumEntryLambda extends CustomLambda {
245+
@Override
246+
public String formatFragment(String fragment) {
247+
return formatIdentifier(fragment, true);
248+
}
249+
}
250+
}

modules/swagger-codegen/src/main/resources/META-INF/services/io.swagger.codegen.CodegenConfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ io.swagger.codegen.languages.RubyClientCodegen
5858
io.swagger.codegen.languages.RustClientCodegen
5959
io.swagger.codegen.languages.ScalaClientCodegen
6060
io.swagger.codegen.languages.ScalatraServerCodegen
61+
io.swagger.codegen.languages.ScalazClientCodegen
6162
io.swagger.codegen.languages.SilexServerCodegen
6263
io.swagger.codegen.languages.SinatraServerCodegen
6364
io.swagger.codegen.languages.SlimFrameworkServerCodegen
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package {{apiPackage}}
2+
3+
import argonaut._
4+
import argonaut.EncodeJson._
5+
import argonaut.DecodeJson._
6+
7+
import org.http4s._
8+
import org.http4s.{EntityDecoder, EntityEncoder}
9+
import org.http4s.argonaut._
10+
11+
import org.joda.time.DateTime
12+
13+
object HelperCodecs {
14+
implicit def returnTypeDecoder[A: EncodeJson]: EntityEncoder[List[A]] = jsonEncoderOf[List[A]]
15+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package {{apiPackage}}
2+
3+
trait QueryParam[A] {
4+
def toParamString(a: A): String
5+
}
6+
7+
object QueryParam {
8+
implicit def strQueryParam: QueryParam[String] = new QueryParam[String] {
9+
def toParamString(s: String): String = s
10+
}
11+
12+
implicit def listStrQueryParam: QueryParam[List[String]] = new QueryParam[List[String]] {
13+
def toParamString(s: List[String]): String = s.mkString(",")
14+
}
15+
}

0 commit comments

Comments
 (0)