From 7baae391ebe563b0f13d1f47e768264afc0e8b34 Mon Sep 17 00:00:00 2001 From: Tony Tam Date: Tue, 25 Sep 2012 22:30:35 -0700 Subject: [PATCH] added objc files --- .../swagger/codegen/BasicObjcGenerator.scala | 182 ++++++++++++ src/test/scala/ObjcCodegenConfigTest.scala | 277 ++++++++++++++++++ 2 files changed, 459 insertions(+) create mode 100644 src/main/scala/com/wordnik/swagger/codegen/BasicObjcGenerator.scala create mode 100644 src/test/scala/ObjcCodegenConfigTest.scala diff --git a/src/main/scala/com/wordnik/swagger/codegen/BasicObjcGenerator.scala b/src/main/scala/com/wordnik/swagger/codegen/BasicObjcGenerator.scala new file mode 100644 index 00000000000..d3a965135ce --- /dev/null +++ b/src/main/scala/com/wordnik/swagger/codegen/BasicObjcGenerator.scala @@ -0,0 +1,182 @@ +/** + * Copyright 2012 Wordnik, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordnik.swagger.codegen + +import com.wordnik.swagger.core._ + +object BasicObjcGenerator extends BasicObjcGenerator { + def main(args: Array[String]) = generateClient(args) +} + +class BasicObjcGenerator extends BasicGenerator { + override def defaultIncludes = Set( + "bool", + "int", + "NSString", + "NSObject", + "NSArray", + "NSNumber") + + override def languageSpecificPrimitives = Set( + "NSNumber", + "NSString", + "NSObject", + "bool") + + override def reservedWords = Set("void", "char", "short", "int", "void", "char", "short", "int", "long", "float", "double", "signed", "unsigned", "id", "const", "volatile", "in", "out", "inout", "bycopy", "byref", "oneway", "self", "super") + + def foundationClasses = Set( + "NSNumber", + "NSObject", + "NSString") + + override def typeMapping = Map( + "Date" -> "NIKDate", + "boolean" -> "NSNumber", + "string" -> "NSString", + "integer" -> "NSNumber", + "int" -> "NSNumber", + "float" -> "NSNumber", + "long" -> "NSNumber", + "double" -> "NSNumber", + "Array" -> "NSArray", + "object" -> "NSObject") + + override def importMapping = Map( + "Date" -> "NIKDate") + + // naming for the models + override def toModelName(name: String) = { + (typeMapping.values ++ + foundationClasses ++ + importMapping.values ++ + defaultIncludes ++ + languageSpecificPrimitives + ).toSet.contains(name) match { + case true => name.charAt(0).toUpperCase + name.substring(1) + case _ => "NIK" + name.charAt(0).toUpperCase + name.substring(1) + } + } + + // naming for the apis + override def toApiName(name: String) = "NIK" + name.charAt(0).toUpperCase + name.substring(1) + "Api" + + // location of templates + override def templateDir = "src/main/resources/objc" + + // template used for models + modelTemplateFiles += "model-header.mustache" -> ".h" + modelTemplateFiles += "model-body.mustache" -> ".m" + + // template used for apis + apiTemplateFiles += "api-header.mustache" -> ".h" + apiTemplateFiles += "api-body.mustache" -> ".m" + + // package for models + override def invokerPackage = None + + // package for models + override def modelPackage = None + + // package for api classes + override def apiPackage = None + + // response classes + override def processResponseClass(responseClass: String): Option[String] = { + typeMapping.contains(responseClass) match { + case true => Some(typeMapping(responseClass)) + case false => { + responseClass match { + case "void" => None + case e: String => { + responseClass.startsWith("List") match { + case true => Some("NSArray") + case false => Some(toModelName(responseClass)) + } + } + } + } + } + } + + override def processResponseDeclaration(responseClass: String): Option[String] = { + processResponseClass(responseClass) match { + case Some("void") => Some("void") + case Some(e) => Some(e + "*") + case _ => Some(responseClass) + } + } + + override def toDeclaredType(dt: String): String = { + val declaredType = dt.indexOf("[") match { + case -1 => dt + case n: Int => "NSArray" + case _ => dt + } + val t = typeMapping.getOrElse(declaredType, declaredType) + + (languageSpecificPrimitives.contains(t) && !foundationClasses.contains(t)) match { + case true => toModelName(t) + case _ => toModelName(t) + "*" // needs pointer + } + } + + override def toDeclaration(obj: DocumentationSchema) = { + var declaredType = toDeclaredType(obj.getType) + declaredType match { + case "Array" => { + declaredType = "List" + } + case e: String => e + } + + val defaultValue = toDefaultValue(declaredType, obj) + declaredType match { + case "List" => { + val inner = { + if (obj.items.ref != null) obj.items.ref + else obj.items.getType + } + declaredType += "<" + inner + ">" + "NSArray" + } + case _ => + } + (declaredType, defaultValue) + } + + override def escapeReservedWord(word: String) = "_" + word + + // default values + override def toDefaultValue(properCase: String, obj: DocumentationSchema) = { + properCase match { + case "boolean" => "false" + case "int" => "0" + case "long" => "0L" + case "float" => "0.0f" + case "double" => "0.0" + case "List" => { + val inner = { + if (obj.items.ref != null) obj.items.ref + else obj.items.getType + } + "new ArrayList<" + inner + ">" + "()" + } + case _ => "null" + } + } +} diff --git a/src/test/scala/ObjcCodegenConfigTest.scala b/src/test/scala/ObjcCodegenConfigTest.scala new file mode 100644 index 00000000000..fe77673905f --- /dev/null +++ b/src/test/scala/ObjcCodegenConfigTest.scala @@ -0,0 +1,277 @@ +import com.wordnik.swagger.core.util.JsonUtil +import com.wordnik.swagger.core.{Documentation, DocumentationSchema} + +import com.wordnik.swagger.codegen.{BasicObjcGenerator, Codegen} +import com.wordnik.swagger.codegen.util._ +import com.wordnik.swagger.codegen.language._ +import com.wordnik.swagger.codegen.PathUtil + +import org.junit.runner.RunWith +import org.scalatest.junit.JUnitRunner +import org.scalatest.FlatSpec +import org.scalatest.matchers.ShouldMatchers + +import scala.collection.mutable.HashMap +import scala.collection.JavaConverters._ + +@RunWith(classOf[JUnitRunner]) +class BasicObjcGeneratorTest extends FlatSpec with ShouldMatchers { + val json = ScalaJsonUtil.getJsonMapper + + val config = new BasicObjcGenerator + + behavior of "BasicObjcGenerator" + /* + * A response of type "void" will turn into a declaration of None + * for the template generator + */ + it should "process a response declaration" in { + config.processResponseDeclaration("void") should be (Some("void")) + } + + /* + * swagger strings are turned into Objective-C NSString* + */ + it should "process a string response" in { + config.processResponseDeclaration("string") should be (Some("NSString*")) + } + + /* + * swagger int is turned into Objective-c Int + */ + it should "process an unmapped response type" in { + config.processResponseDeclaration("int") should be (Some("NSNumber*")) + } + + /* + * returns the invoker package from the config + */ + it should "get the invoker package" in { + config.invokerPackage should be (None) + } + + /* + * returns the api package + */ + it should "get the api package" in { + config.apiPackage should be (None) + } + + /* + * returns the model package + */ + it should "get the model package" in { + config.modelPackage should be (None) + } + + /* + * types are mapped between swagger types and language-specific + * types + */ + it should "convert to a declared type" in { + config.toDeclaredType("string") should be ("NSString*") + config.toDeclaredType("int") should be ("NSNumber*") + config.toDeclaredType("float") should be ("NSNumber*") + config.toDeclaredType("long") should be ("NSNumber*") + config.toDeclaredType("double") should be ("NSNumber*") + config.toDeclaredType("object") should be ("NSObject*") + config.toDeclaredType("User") should be ("NIKUser*") + } + + /* + * declarations are used in models, and types need to be + * mapped appropriately + */ + it should "convert a string a declaration" in { + val expected = Map("string" -> ("NSString*", "null"), + "int" -> ("NSNumber*", "null"), + "float" -> ("NSNumber*", "null"), + "long" -> ("NSNumber*", "null"), + "double" -> ("NSNumber*", "null"), + "object" -> ("NSObject*", "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 + */ + it should "honor the import mapping" in { + config.importMapping("Date") should be ("NIKDate") + } + + /* + * single tick reserved words + */ + it should "quote a reserved var name" in { + config.toVarName("char") should be ("_char") + } + + /* + * support list declarations with string inner value and the correct default value + */ + it should "create a declaration with a List of strings" in { + val model = new DocumentationSchema + model.name = "arrayOfStrings" + model.setType("Array") + model.items = new DocumentationSchema + model.items.setType("string") + + val m = config.toDeclaration(model) + m._1 should be ("NSArray*") + m._2 should be ("null") + } + + /* + * support list declarations with int inner value and the correct default value + */ + it should "create a declaration with a List of ints" in { + val model = new DocumentationSchema + model.name = "arrayOfInts" + model.setType("Array") + model.items = new DocumentationSchema + model.items.setType("int") + + val m = config.toDeclaration(model) + m._1 should be ("NSArray*") + m._2 should be ("null") + } + + /* + * support list declarations with float inner value and the correct default value + */ + it should "create a declaration with a List of floats" in { + val model = new DocumentationSchema + model.name = "arrayOfFloats" + model.setType("Array") + model.items = new DocumentationSchema + model.items.setType("float") + + val m = config.toDeclaration(model) + m._1 should be ("NSArray*") + m._2 should be ("null") + } + + /* + * support list declarations with double inner value and the correct default value + */ + it should "create a declaration with a List of doubles" in { + val model = new DocumentationSchema + model.name = "arrayOfDoubles" + model.setType("Array") + model.items = new DocumentationSchema + model.items.setType("double") + + val m = config.toDeclaration(model) + m._1 should be ("NSArray*") + m._2 should be ("null") + } + + /* + * support list declarations with complex inner value and the correct default value + */ + it should "create a declaration with a List of complex objects" in { + val model = new DocumentationSchema + model.name = "arrayOfFloats" + model.setType("Array") + model.items = new DocumentationSchema + model.items.setType("User") + + val m = config.toDeclaration(model) + m._1 should be ("NSArray*") + m._2 should be ("null") + } + + it should "verify an api map with path param" in { + val resourceListing = json.readValue(ResourceExtractor.extractListing("src/test/resources/petstore/resources.json", None), classOf[Documentation]) + + val subDocs = ApiExtractor.extractApiDocs("src/test/resources/petstore", resourceListing.getApis.asScala.toList) + val codegen = new Codegen(config) + val petApi = subDocs.filter(doc => doc.getResourcePath == "/pet").head + + val endpoint = petApi.getApis().asScala.filter(api => api.path == "/pet.{format}/{petId}").head + val operation = endpoint.getOperations.asScala.filter(op => op.httpMethod == "GET").head + val m = codegen.apiToMap("http://my.api.com/api", operation) + + m("path") should be ("http://my.api.com/api") + m("bodyParams").asInstanceOf[List[_]].size should be (0) + m("httpMethod") should be ("GET") + // Pet => NIKPet + m("returnBaseType") should be (Some("NIKPet")) + m("returnTypeIsPrimitive") should be (None) + m("pathParams").asInstanceOf[List[_]].size should be (1) + + val idParam = m("pathParams").asInstanceOf[List[_]].head.asInstanceOf[HashMap[String, _]] + idParam("paramName") should be ("petId") + idParam("dataType") should be ("NSString*") + idParam("required") should be ("true") + idParam("swaggerDataType") should be ("string") + idParam("baseName") should be ("petId") + idParam("type") should be ("path") + idParam("allowMultiple") should be ("false") + idParam("defaultValue") should be (None) + } + + it should "verify an api map with query params" in { + val resourceListing = json.readValue(ResourceExtractor.extractListing("src/test/resources/petstore/resources.json", None), classOf[Documentation]) + + val subDocs = ApiExtractor.extractApiDocs("src/test/resources/petstore", resourceListing.getApis.asScala.toList) + val codegen = new Codegen(config) + val petApi = subDocs.filter(doc => doc.getResourcePath == "/pet").head + + val endpoint = petApi.getApis().asScala.filter(api => api.path == "/pet.{format}/findByTags").head + val operation = endpoint.getOperations.asScala.filter(op => op.httpMethod == "GET").head + val m = codegen.apiToMap("http://my.api.com/api", operation) + + m("path") should be ("http://my.api.com/api") + m("bodyParams").asInstanceOf[List[_]].size should be (0) + m("httpMethod") should be ("GET") + + // Pet => NIKPet + m("returnBaseType") should be (Some("NIKPet")) + m("returnType") should be (Some("NSArray*")) + m("returnTypeIsPrimitive") should be (None) + m("pathParams").asInstanceOf[List[_]].size should be (0) + m("returnContainer") should be ("List") + m("requiredParamCount") should be ("1") + + val queryParams = m("queryParams").asInstanceOf[List[_]] + queryParams.size should be (1) + + val queryParam = queryParams.head.asInstanceOf[HashMap[String, _]] + queryParam("type") should be ("query") + queryParam("dataType") should be ("NSString*") + queryParam("required") should be ("true") + queryParam("paramName") should be ("tags") + queryParam("swaggerDataType") should be ("string") + queryParam("allowMultiple") should be ("true") + } + + it should "create an api file" in { + val codegen = new Codegen(config) + val resourceListing = json.readValue(ResourceExtractor.extractListing("src/test/resources/petstore/resources.json", None), classOf[Documentation]) + + val subDocs = ApiExtractor.extractApiDocs("src/test/resources/petstore", resourceListing.getApis.asScala.toList) + val petApi = subDocs.filter(doc => doc.getResourcePath == "/pet").head + + val endpoint = petApi.getApis().asScala.filter(api => api.path == "/pet.{format}/findByTags").head + val operation = endpoint.getOperations.asScala.filter(op => op.httpMethod == "GET").head + val m = codegen.apiToMap("http://my.api.com/api", operation) + + implicit val basePath = "http://localhost:8080/api" + + val allModels = new HashMap[String, DocumentationSchema] + val operations = config.extractOperations(subDocs, allModels) + + val apiMap = config.groupApisToFiles(operations) + val bundle = config.prepareApiBundle(apiMap) + val apiFiles = config.bundleToSource(bundle, config.apiTemplateFiles.toMap) + + apiFiles.size should be (6) + } +}