diff --git a/src/main/resources/scala/model.mustache b/src/main/resources/scala/model.mustache index 2bd35641744..9558b44320e 100644 --- a/src/main/resources/scala/model.mustache +++ b/src/main/resources/scala/model.mustache @@ -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}} \ No newline at end of file diff --git a/src/main/scala/com/wordnik/swagger/codegen/BasicGenerator.scala b/src/main/scala/com/wordnik/swagger/codegen/BasicGenerator.scala index 5f25f989f24..a7fe1010b5b 100644 --- a/src/main/scala/com/wordnik/swagger/codegen/BasicGenerator.scala +++ b/src/main/scala/com/wordnik/swagger/codegen/BasicGenerator.scala @@ -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) = { diff --git a/src/main/scala/com/wordnik/swagger/codegen/BasicJavaGenerator.scala b/src/main/scala/com/wordnik/swagger/codegen/BasicJavaGenerator.scala index 7c2b6ecb865..3a8b730404a 100644 --- a/src/main/scala/com/wordnik/swagger/codegen/BasicJavaGenerator.scala +++ b/src/main/scala/com/wordnik/swagger/codegen/BasicJavaGenerator.scala @@ -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 diff --git a/src/main/scala/com/wordnik/swagger/codegen/language/CodegenConfig.scala b/src/main/scala/com/wordnik/swagger/codegen/language/CodegenConfig.scala index 8b977e77db1..aa81d3e848b 100644 --- a/src/main/scala/com/wordnik/swagger/codegen/language/CodegenConfig.scala +++ b/src/main/scala/com/wordnik/swagger/codegen/language/CodegenConfig.scala @@ -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) diff --git a/src/test/scala/BasicGeneratorTest.scala b/src/test/scala/BasicGeneratorTest.scala index 51b1357d26b..562b5b5633b 100644 --- a/src/test/scala/BasicGeneratorTest.scala +++ b/src/test/scala/BasicGeneratorTest.scala @@ -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 + } +} \ No newline at end of file diff --git a/src/test/scala/JavaCodegenConfigTest.scala b/src/test/scala/JavaCodegenConfigTest.scala index f5ccf2dbd05..85ffcf46fbb 100644 --- a/src/test/scala/JavaCodegenConfigTest.scala +++ b/src/test/scala/JavaCodegenConfigTest.scala @@ -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 diff --git a/src/test/scala/ScalaCodegenConfigTest.scala b/src/test/scala/ScalaCodegenConfigTest.scala index 40b5b9bbef6..a6cc0093baa 100644 --- a/src/test/scala/ScalaCodegenConfigTest.scala +++ b/src/test/scala/ScalaCodegenConfigTest.scala @@ -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