refactored to make more testable

This commit is contained in:
Tony Tam 2012-09-23 12:18:22 -07:00
parent d6cf7fc9aa
commit cec3fa5810
7 changed files with 192 additions and 43 deletions

View File

@ -7,25 +7,14 @@ package {{package}}
import scala.reflect.BeanProperty
{{#model}}
class {{classname}} {
case class {{classname}} (
{{#vars}}
{{#notes}}/* {{notes}} */
{{/notes}}
{{#description}}/* {{description}} */
{{/description}}
@BeanProperty var {{name}}: {{datatype}} = {{defaultValue}}
{{/vars}}
override def toString: String = {
val sb = new StringBuilder
sb.append("class {{classname}} {\n")
{{#vars}}
sb.append(" {{name}}: ").append({{name}}).append("\n")
{{/vars}}
sb.append("}\n")
sb.toString
}
}
{{name}}: {{datatype}}{{#hasMore}},{{newline}} {{/hasMore}}
{{/vars}})
{{/model}}
{{/models}}

View File

@ -92,15 +92,36 @@ abstract class BasicGenerator extends CodegenConfig with PathUtil {
val operations = extractOperations(subDocs, allModels)
val apiMap = groupApisToFiles(operations)
processApiMap(apiMap)
processModelMap(allModels)
val modelBundle = prepareModelBundle(allModels.toMap)
val modelFiles = bundleToSource(modelBundle, modelTemplateFiles.toMap)
modelFiles.map(m => {
val filename = m._1
val fw = new FileWriter(filename, false)
fw.write(m._2 + "\n")
fw.close()
println("wrote model " + filename)
})
val apiBundle = prepareApiBundle(apiMap.toMap)
val apiFiles = bundleToSource(apiBundle, apiTemplateFiles.toMap)
apiFiles.map(m => {
val filename = m._1
val fw = new FileWriter(filename, false)
fw.write(m._2 + "\n")
fw.close()
println("wrote api " + filename)
})
codegen.writeSupportingClasses(apiMap.toMap, allModels.toMap)
}
def processModelMap(models: HashMap[String, DocumentationSchema]) = {
val modelBundleList = new ListBuffer[Map[String, AnyRef]]
for ((name, schema) <- models) {
/**
* creates a map of models and properties needed to write source
*/
def prepareModelBundle(models: Map[String, DocumentationSchema]): List[Map[String, AnyRef]] = {
(for ((name, schema) <- models) yield {
if (!defaultIncludes.contains(name)) {
val m = new HashMap[String, AnyRef]
m += "name" -> name
@ -111,23 +132,21 @@ abstract class BasicGenerator extends CodegenConfig with PathUtil {
m += "invokerPackage" -> invokerPackage
m += "outputDirectory" -> (destinationDir + File.separator + modelPackage.getOrElse("").replaceAll("\\.", File.separator))
m += "newline" -> "\n"
modelBundleList += m.toMap
for ((file, suffix) <- modelTemplateFiles) {
m += "filename" -> (name + suffix)
generateAndWrite(m.toMap, file)
}
Some(m.toMap)
}
}
else None
}).flatten.toList
}
def processApiMap(apiMap: Map[(String, String), List[(String, DocumentationOperation)]] ) = {
for ((identifier, operationList) <- apiMap) {
def prepareApiBundle(apiMap: Map[(String, String), List[(String, DocumentationOperation)]] ): List[Map[String, AnyRef]] = {
(for ((identifier, operationList) <- apiMap) yield {
val basePath = identifier._1
val name = identifier._2
val className = toApiName(name)
val m = new HashMap[String, AnyRef]
m += "name" -> name
m += "name" -> toApiName(name)
m += "className" -> className
m += "basePath" -> basePath
m += "package" -> apiPackage
@ -137,11 +156,21 @@ abstract class BasicGenerator extends CodegenConfig with PathUtil {
m += "outputDirectory" -> (destinationDir + File.separator + apiPackage.getOrElse("").replaceAll("\\.", File.separator))
m += "newline" -> "\n"
for ((file, suffix) <- apiTemplateFiles) {
m += "filename" -> (className + suffix)
generateAndWrite(m.toMap, file)
Some(m.toMap)
}).flatten.toList
}
/**
* turns a bundle into source files with output file name
*/
def bundleToSource(bundle:List[Map[String, AnyRef]], templates: Map[String, String]): List[(String, String)] = {
val output = new ListBuffer[(String, String)]
bundle.foreach(m => {
for ((file, suffix) <- templates) {
output += Tuple2(m("outputDirectory").toString + File.separator + m("name").toString + suffix, codegen.generateSource(m, file))
}
}
})
output.toList
}
def generateAndWrite(bundle: Map[String, AnyRef], templateFile: String) = {

View File

@ -31,6 +31,13 @@ class BasicJavaGenerator extends BasicGenerator {
"String",
"boolean")
/**
* We are using java objects instead of primitives to avoid showing default
* primitive values when the API returns missing data. For instance, having a
* {"count":0} != count is unknown. You can change this to use primitives if you
* desire, but update the default values as well or they'll be set to null in
* variable declarations.
*/
override def typeMapping = Map(
"string" -> "String",
"int" -> "Integer",
@ -94,9 +101,8 @@ class BasicJavaGenerator extends BasicGenerator {
val declaredType = dt.indexOf("[") match {
case -1 => dt
case n: Int => {
if (dt.substring(0, n) == "Array") {
if (dt.substring(0, n) == "Array")
"List" + dt.substring(n).replaceAll("\\[", "<").replaceAll("\\]", ">")
}
else dt.replaceAll("\\[", "<").replaceAll("\\]", ">")
}
case _ => dt
@ -128,14 +134,17 @@ class BasicJavaGenerator extends BasicGenerator {
(declaredType, defaultValue)
}
// default values
/**
* we are defaulting to null values since the codegen uses java objects instead of primitives
* If you change to primitives, you can put in the appropriate values (0.0f, etc).
*/
override def toDefaultValue(dataType: String, obj: DocumentationSchema) = {
dataType match {
case "boolean" => "false"
case "int" => "0"
case "long" => "0L"
case "float" => "0.0f"
case "double" => "0.0"
case "Boolean" => "null"
case "Integer" => "null"
case "Long" => "null"
case "Float" => "null"
case "Double" => "null"
case "List" => {
val inner = {
if (obj.items.ref != null) obj.items.ref

View File

@ -106,7 +106,6 @@ abstract class CodegenConfig {
}
def toDefaultValue(datatype: String, defaultValue: String): Option[String] = {
if (defaultValue != "" && defaultValue != null) {
toDeclaredType(datatype) match {
case "int" => Some(defaultValue)

View File

@ -16,7 +16,16 @@ import scala.reflect.BeanProperty
class BasicGeneratorTest extends FlatSpec with ShouldMatchers {
val json = ScalaJsonUtil.getJsonMapper
class SampleGenerator extends BasicGenerator
class SampleGenerator extends BasicGenerator {
modelTemplateFiles += "model.mustache" -> ".test"
override def typeMapping = Map(
"string" -> "String",
"int" -> "Int",
"float" -> "Float",
"long" -> "Long",
"double" -> "Double",
"object" -> "Any")
}
behavior of "BasicGenerator"
@ -73,4 +82,79 @@ class BasicGeneratorTest extends FlatSpec with ShouldMatchers {
(orderOperations.map(m => m.httpMethod).toSet & Set("GET", "DELETE")).size should be (2)
(orderOperations.map(m => m.nickname).toSet & Set("getOrderById", "deleteOrder")).size should be (2)
}
}
it should "create a model map" in {
implicit val basePath = "http://localhost:8080/api"
val generator = new SampleGenerator
val model = getSampleModel
val bundle = generator.prepareModelBundle(Map(model.id -> model)).head
// inspect properties
bundle("name") should be ("SampleObject")
bundle("className") should be ("SampleObject")
bundle("invokerPackage") should be (Some("com.wordnik.client.common"))
bundle("package") should be (Some("com.wordnik.client.model"))
// inspect models
val modelList = bundle("models").asInstanceOf[List[(String, DocumentationSchema)]]
modelList.size should be (1)
modelList.head._1 should be ("SampleObject")
modelList.head._2.getClass should be (classOf[DocumentationSchema])
}
it should "create a model file" in {
implicit val basePath = "http://localhost:8080/api"
val generator = new SampleGenerator
val model = getSampleModel
val bundle = generator.prepareModelBundle(Map(model.id -> model))
val modelFile = generator.bundleToSource(bundle, generator.modelTemplateFiles.toMap).head
// modelFile._1 should be ("SampleObject.test")
val fileContents = modelFile._2
fileContents.indexOf("case class SampleObject") should not be (-1)
fileContents.indexOf("longValue: Long") should not be (-1)
fileContents.indexOf("intValue: Int") should not be (-1)
fileContents.indexOf("doubleValue: Double") should not be (-1)
fileContents.indexOf("stringValue: String") should not be (-1)
fileContents.indexOf("floatValue: Float") should not be (-1)
}
def getSampleModel = {
val model = new DocumentationSchema
model.id = "SampleObject"
model.name = "SampleObject"
model.properties = {
val list = new HashMap[String, DocumentationSchema]
val stringProperty = new DocumentationSchema
stringProperty.name = "stringValue"
stringProperty.setType("string")
list += "stringValue" -> stringProperty
val intProperty = new DocumentationSchema
intProperty.name = "intValue"
intProperty.setType("int")
list += "intValue" -> intProperty
val longProperty = new DocumentationSchema
longProperty.name = "longValue"
longProperty.setType("long")
list += "longValue" -> longProperty
val floatProperty = new DocumentationSchema
floatProperty.name = "floatValue"
floatProperty.setType("float")
list += "floatValue" -> floatProperty
val doubleProperty = new DocumentationSchema
doubleProperty.name = "doubleValue"
doubleProperty.setType("double")
list += "doubleValue" -> doubleProperty
list.asJava
}
model
}
}

View File

@ -93,6 +93,26 @@ class BasicJavaGeneratorTest extends FlatSpec with ShouldMatchers {
config.toDeclaredType("object") should be ("Object")
}
/*
* declarations are used in models, and types need to be
* mapped appropriately
*/
it should "convert a string a declaration" in {
val expected = Map(
"string" -> ("String", "null"),
"int" -> ("Integer", "null"),
"float" -> ("Float", "null"),
"long" -> ("Long", "null"),
"double" -> ("Double", "null"),
"object" -> ("Object", "null"))
expected.map(e => {
val model = new DocumentationSchema
model.name = "simple_" + e._1
model.setType(e._1)
config.toDeclaration(model) should be (e._2)
})
}
/*
* codegen should honor special imports to avoid generating
* classes

View File

@ -93,6 +93,25 @@ class BasicScalaGeneratorTest extends FlatSpec with ShouldMatchers {
config.toDeclaredType("object") should be ("Any")
}
/*
* declarations are used in models, and types need to be
* mapped appropriately
*/
it should "convert a string a declaration" in {
val expected = Map("string" -> ("String", "_"),
"int" -> ("Int", "0"),
"float" -> ("Float", "0f"),
"long" -> ("Long", "0L"),
"double" -> ("Double", "0.0"),
"object" -> ("Any", "_"))
expected.map(e => {
val model = new DocumentationSchema
model.name = "simple_" + e._1
model.setType(e._1)
config.toDeclaration(model) should be (e._2)
})
}
/*
* codegen should honor special imports to avoid generating
* classes