[WIP][Scala] Finch generator (#3905)

* Feature/objc tasks 2.3.0 (#3522)

* change api and ApiClient to return cancellable NSURLSessionTasks instead of NSNumber

* define a configuration protocol for custom configurations, which can be passed to api clients instead of a global configuration, provide a default implementation with a singleton option

* integrate a workaround for a current JSONModel concurrency bug

* update to new ISO8601 pod

* add missing call to super

* integrate new templates into codegen

* updates documentation templates

* updates petstore objc generated code

* fixes objc client tests

* [ObjC] Add version define and share default headers of each client

* add finch generator and resource

* update license, add errros

* Fix problem with multitheard api client

* fix some errors for finch

* [finch] Remove license header

* [finch] Remove finatra stuff, fix a few issues

* WIP: Finch server generator

* [finch] WIP: server generator impl

This puts parameters (input/output) in the right format. Currently, this
is done in the generator class using vendorExtensions, but should be
refactored to imported templates to clean up.

Previous commits of the server generator output to appropriate
models/api directories. I've made no changes to this logic, but code
currently generates to the root scala package directory. This will need
to be fixed.

There's also an issue with circe's and Option[Date] in the Order type.
This issue will need to be resolved. As well, there's some unused
imports to clean up.

Initial implementation lacks support for custom imports, type mappings,
etc.

* [finch] Update api/model package and imports

* [finch] Explicit import/type mappings

* [finch] Regenerate example
This commit is contained in:
wing328
2017-01-29 12:15:39 +08:00
committed by GitHub
parent 6890ef9755
commit ae8a123484
140 changed files with 5262 additions and 2421 deletions

View File

@@ -0,0 +1,133 @@
package io.swagger.petstore
// TODO: properly handle custom imports
import java.io._
import java.util.Date
import io.swagger.petstore.models._
trait DataAccessor {
// TODO: apiInfo -> apis -> operations = ???
// NOTE: ??? throws a not implemented exception
/**
*
* @return A Unit
*/
def Pet_addPet(body: Pet): Unit = ???
/**
*
* @return A Unit
*/
def Pet_deletePet(petId: Long, apiKey: String): Unit = ???
/**
*
* @return A Seq[Pet]
*/
def Pet_findPetsByStatus(status: Seq[String]): Seq[Pet] = ???
/**
*
* @return A Seq[Pet]
*/
def Pet_findPetsByTags(tags: Seq[String]): Seq[Pet] = ???
/**
*
* @return A Pet
*/
def Pet_getPetById(petId: Long): Pet = ???
/**
*
* @return A Unit
*/
def Pet_updatePet(body: Pet): Unit = ???
/**
*
* @return A Unit
*/
def Pet_updatePetWithForm(petId: Long, name: String, status: String): Unit = ???
/**
*
* @return A ApiResponse
*/
def Pet_uploadFile(petId: Long, additionalMetadata: String, file: File): ApiResponse = ???
/**
*
* @return A Unit
*/
def Store_deleteOrder(orderId: String): Unit = ???
/**
*
* @return A Map[String, Int]
*/
def Store_getInventory(): Map[String, Int] = ???
/**
*
* @return A Order
*/
def Store_getOrderById(orderId: Long): Order = ???
/**
*
* @return A Order
*/
def Store_placeOrder(body: Order): Order = ???
/**
*
* @return A Unit
*/
def User_createUser(body: User): Unit = ???
/**
*
* @return A Unit
*/
def User_createUsersWithArrayInput(body: Seq[User]): Unit = ???
/**
*
* @return A Unit
*/
def User_createUsersWithListInput(body: Seq[User]): Unit = ???
/**
*
* @return A Unit
*/
def User_deleteUser(username: String): Unit = ???
/**
*
* @return A User
*/
def User_getUserByName(username: String): User = ???
/**
*
* @return A String
*/
def User_loginUser(username: String, password: String): String = ???
/**
*
* @return A Unit
*/
def User_logoutUser(): Unit = ???
/**
*
* @return A Unit
*/
def User_updateUser(username: String, body: User): Unit = ???
}

View File

@@ -0,0 +1,36 @@
package io.swagger.petstore
import io.finch._
import io.finch.circe._
import io.circe.{ Decoder, ObjectEncoder }
import io.circe.generic.auto._
import io.circe.generic.semiauto
import io.circe.generic.semiauto._
import io.circe.java8.time._
import com.twitter.finagle.Http
import com.twitter.finagle.util.LoadService
import com.twitter.util.{ Await, Future }
class Server {
// Loads implementation defined in resources/META-INF/services/io.swagger.petstore.DataAccessor
val db = LoadService[DataAccessor]() match {
case accessor :: _ => accessor
case _ => new DataAccessor {}
}
val service = endpoint.makeService(db)
val server = Http.serve(":8080", service) //creates service
def close(): Future[Unit] = {
Await.ready(server.close())
}
}
/**
* Launches the PetstoreAPI service when the system is ready.
*/
object Server extends Server with App {
Await.ready(server)
}

View File

@@ -0,0 +1,48 @@
package io.swagger.petstore
import com.twitter.finagle.Service
import com.twitter.finagle.http.{ Request, Response }
import com.twitter.finagle.http.exp.Multipart.FileUpload
import com.twitter.util.Future
import io.finch._, items._
import io.circe.{ Encoder, Json }
import io.finch.circe._
import io.circe.generic.semiauto._
import io.swagger.petstore.apis._
/**
* Provides the paths and endpoints for all the API's public service methods.
*/
object endpoint {
def errorToJson(e: Exception): Json = e match {
case Error.NotPresent(_) =>
Json.obj("error" -> Json.fromString("something_not_present"))
case Error.NotParsed(_, _, _) =>
Json.obj("error" -> Json.fromString("something_not_parsed"))
case Error.NotValid(_, _) =>
Json.obj("error" -> Json.fromString("something_not_valid"))
case error: PetstoreError =>
Json.obj("error" -> Json.fromString(error.message))
}
implicit val ee: Encoder[Exception] = Encoder.instance {
case e: Error => errorToJson(e)
case Errors(nel) => Json.arr(nel.toList.map(errorToJson): _*)
}
/**
* Compiles together all the endpoints relating to public service methods.
*
* @return A service that contains all provided endpoints of the API.
*/
def makeService(da: DataAccessor): Service[Request, Response] = (
PetApi.endpoints(da) :+:
StoreApi.endpoints(da) :+:
UserApi.endpoints(da)
).handle({
case e: PetstoreError => NotFound(e)
}).toService
}

View File

@@ -0,0 +1,27 @@
package io.swagger.petstore
/**
* The parent error from which most PetstoreAPI errors extend. Thrown whenever something in the api goes wrong.
*/
abstract class PetstoreError(msg: String) extends Exception(msg) {
def message: String
}
/**
* Thrown when the object given is invalid
* @param message An error message
*/
case class InvalidInput(message: String) extends PetstoreError(message)
/**
* Thrown when the given object is missing a unique ID.
* @param message An error message
*/
case class MissingIdentifier(message: String) extends PetstoreError(message)
/**
* Thrown when the given record does not exist in the database.
* @param message An error message
*/
case class RecordNotFound(message: String) extends PetstoreError(message)

View File

@@ -0,0 +1,148 @@
package io.swagger.petstore.apis
import java.io._
import java.util.Date
import io.swagger.petstore._
import io.swagger.petstore.models._
import io.swagger.petstore.models.Pet
import java.io.File
import io.swagger.petstore.models.ApiResponse
import io.finch.circe._
import io.circe.generic.semiauto._
import com.twitter.concurrent.AsyncStream
import com.twitter.finagle.Service
import com.twitter.finagle.Http
import com.twitter.finagle.http.{ Request, Response }
import com.twitter.finagle.http.exp.Multipart.{ FileUpload, InMemoryFileUpload, OnDiskFileUpload }
import com.twitter.util.Future
import com.twitter.io.Buf
import io.finch._, items._
import java.io.File
object PetApi {
/**
* Compiles all service endpoints.
* @return Bundled compilation of all service endpoints.
*/
def endpoints(da: DataAccessor) =
addPet(da) :+:
deletePet(da) :+:
findPetsByStatus(da) :+:
findPetsByTags(da) :+:
getPetById(da) :+:
updatePet(da) :+:
updatePetWithForm(da) :+:
uploadFile(da)
/**
*
* @return And endpoint representing a Unit
*/
private def addPet(da: DataAccessor): Endpoint[Unit] =
post("pet" :: jsonBody[Pet]) { (body: Pet) =>
da.Pet_addPet(body)
NoContent[Unit]
} handle {
case e: Exception => BadRequest(e)
}
/**
*
* @return And endpoint representing a Unit
*/
private def deletePet(da: DataAccessor): Endpoint[Unit] =
delete("pet" :: long :: string) { (petId: Long, apiKey: String) =>
da.Pet_deletePet(petId, apiKey)
NoContent[Unit]
} handle {
case e: Exception => BadRequest(e)
}
/**
*
* @return And endpoint representing a Seq[Pet]
*/
private def findPetsByStatus(da: DataAccessor): Endpoint[Seq[Pet]] =
get("pet" :: "findByStatus" :: params("status")) { (status: Seq[String]) =>
Ok(da.Pet_findPetsByStatus(status))
} handle {
case e: Exception => BadRequest(e)
}
/**
*
* @return And endpoint representing a Seq[Pet]
*/
private def findPetsByTags(da: DataAccessor): Endpoint[Seq[Pet]] =
get("pet" :: "findByTags" :: params("tags")) { (tags: Seq[String]) =>
Ok(da.Pet_findPetsByTags(tags))
} handle {
case e: Exception => BadRequest(e)
}
/**
*
* @return And endpoint representing a Pet
*/
private def getPetById(da: DataAccessor): Endpoint[Pet] =
get("pet" :: long) { (petId: Long) =>
Ok(da.Pet_getPetById(petId))
} handle {
case e: Exception => BadRequest(e)
}
/**
*
* @return And endpoint representing a Unit
*/
private def updatePet(da: DataAccessor): Endpoint[Unit] =
put("pet" :: jsonBody[Pet]) { (body: Pet) =>
da.Pet_updatePet(body)
NoContent[Unit]
} handle {
case e: Exception => BadRequest(e)
}
/**
*
* @return And endpoint representing a Unit
*/
private def updatePetWithForm(da: DataAccessor): Endpoint[Unit] =
post("pet" :: long :: string :: string) { (petId: Long, name: String, status: String) =>
da.Pet_updatePetWithForm(petId, name, status)
NoContent[Unit]
} handle {
case e: Exception => BadRequest(e)
}
/**
*
* @return And endpoint representing a ApiResponse
*/
private def uploadFile(da: DataAccessor): Endpoint[ApiResponse] =
post("pet" :: long :: "uploadImage" :: string :: fileUpload("file")) { (petId: Long, additionalMetadata: String, file: FileUpload) =>
Ok(da.Pet_uploadFile(petId, additionalMetadata, file))
} handle {
case e: Exception => BadRequest(e)
}
implicit private def fileUploadToFile(fileUpload: FileUpload): File = {
fileUpload match {
case upload: InMemoryFileUpload =>
bytesToFile(Buf.ByteArray.Owned.extract(upload.content))
case upload: OnDiskFileUpload =>
upload.content
case _ => null
}
}
private def bytesToFile(input: Array[Byte]): java.io.File = {
val file = File.createTempFile("tmpPetApi", null)
val output = new FileOutputStream(file)
output.write(input)
file
}
// This assists in params(string) application (which must be Seq[A] in parameter list) when the param is used as a List[A] elsewhere.
implicit def seqList[A](input: Seq[A]): List[A] = input.toList
}

View File

@@ -0,0 +1,95 @@
package io.swagger.petstore.apis
import java.io._
import java.util.Date
import io.swagger.petstore._
import io.swagger.petstore.models._
import io.swagger.petstore.models.Order
import io.finch.circe._
import io.circe.generic.semiauto._
import com.twitter.concurrent.AsyncStream
import com.twitter.finagle.Service
import com.twitter.finagle.Http
import com.twitter.finagle.http.{ Request, Response }
import com.twitter.finagle.http.exp.Multipart.{ FileUpload, InMemoryFileUpload, OnDiskFileUpload }
import com.twitter.util.Future
import com.twitter.io.Buf
import io.finch._, items._
import java.io.File
object StoreApi {
/**
* Compiles all service endpoints.
* @return Bundled compilation of all service endpoints.
*/
def endpoints(da: DataAccessor) =
deleteOrder(da) :+:
getInventory(da) :+:
getOrderById(da) :+:
placeOrder(da)
/**
*
* @return And endpoint representing a Unit
*/
private def deleteOrder(da: DataAccessor): Endpoint[Unit] =
delete("store" :: "order" :: string) { (orderId: String) =>
da.Store_deleteOrder(orderId)
NoContent[Unit]
} handle {
case e: Exception => BadRequest(e)
}
/**
*
* @return And endpoint representing a Map[String, Int]
*/
private def getInventory(da: DataAccessor): Endpoint[Map[String, Int]] =
get("store" :: "inventory") {
Ok(da.Store_getInventory())
} handle {
case e: Exception => BadRequest(e)
}
/**
*
* @return And endpoint representing a Order
*/
private def getOrderById(da: DataAccessor): Endpoint[Order] =
get("store" :: "order" :: long) { (orderId: Long) =>
Ok(da.Store_getOrderById(orderId))
} handle {
case e: Exception => BadRequest(e)
}
/**
*
* @return And endpoint representing a Order
*/
private def placeOrder(da: DataAccessor): Endpoint[Order] =
post("store" :: "order" :: jsonBody[Order]) { (body: Order) =>
Ok(da.Store_placeOrder(body))
} handle {
case e: Exception => BadRequest(e)
}
implicit private def fileUploadToFile(fileUpload: FileUpload): File = {
fileUpload match {
case upload: InMemoryFileUpload =>
bytesToFile(Buf.ByteArray.Owned.extract(upload.content))
case upload: OnDiskFileUpload =>
upload.content
case _ => null
}
}
private def bytesToFile(input: Array[Byte]): java.io.File = {
val file = File.createTempFile("tmpStoreApi", null)
val output = new FileOutputStream(file)
output.write(input)
file
}
// This assists in params(string) application (which must be Seq[A] in parameter list) when the param is used as a List[A] elsewhere.
implicit def seqList[A](input: Seq[A]): List[A] = input.toList
}

View File

@@ -0,0 +1,149 @@
package io.swagger.petstore.apis
import java.io._
import java.util.Date
import io.swagger.petstore._
import io.swagger.petstore.models._
import io.swagger.petstore.models.User
import scala.collection.immutable.Seq
import io.finch.circe._
import io.circe.generic.semiauto._
import com.twitter.concurrent.AsyncStream
import com.twitter.finagle.Service
import com.twitter.finagle.Http
import com.twitter.finagle.http.{ Request, Response }
import com.twitter.finagle.http.exp.Multipart.{ FileUpload, InMemoryFileUpload, OnDiskFileUpload }
import com.twitter.util.Future
import com.twitter.io.Buf
import io.finch._, items._
import java.io.File
object UserApi {
/**
* Compiles all service endpoints.
* @return Bundled compilation of all service endpoints.
*/
def endpoints(da: DataAccessor) =
createUser(da) :+:
createUsersWithArrayInput(da) :+:
createUsersWithListInput(da) :+:
deleteUser(da) :+:
getUserByName(da) :+:
loginUser(da) :+:
logoutUser(da) :+:
updateUser(da)
/**
*
* @return And endpoint representing a Unit
*/
private def createUser(da: DataAccessor): Endpoint[Unit] =
post("user" :: jsonBody[User]) { (body: User) =>
da.User_createUser(body)
NoContent[Unit]
} handle {
case e: Exception => BadRequest(e)
}
/**
*
* @return And endpoint representing a Unit
*/
private def createUsersWithArrayInput(da: DataAccessor): Endpoint[Unit] =
post("user" :: "createWithArray" :: jsonBody[Seq[User]]) { (body: Seq[User]) =>
da.User_createUsersWithArrayInput(body)
NoContent[Unit]
} handle {
case e: Exception => BadRequest(e)
}
/**
*
* @return And endpoint representing a Unit
*/
private def createUsersWithListInput(da: DataAccessor): Endpoint[Unit] =
post("user" :: "createWithList" :: jsonBody[Seq[User]]) { (body: Seq[User]) =>
da.User_createUsersWithListInput(body)
NoContent[Unit]
} handle {
case e: Exception => BadRequest(e)
}
/**
*
* @return And endpoint representing a Unit
*/
private def deleteUser(da: DataAccessor): Endpoint[Unit] =
delete("user" :: string) { (username: String) =>
da.User_deleteUser(username)
NoContent[Unit]
} handle {
case e: Exception => BadRequest(e)
}
/**
*
* @return And endpoint representing a User
*/
private def getUserByName(da: DataAccessor): Endpoint[User] =
get("user" :: string) { (username: String) =>
Ok(da.User_getUserByName(username))
} handle {
case e: Exception => BadRequest(e)
}
/**
*
* @return And endpoint representing a String
*/
private def loginUser(da: DataAccessor): Endpoint[String] =
get("user" :: "login" :: string :: string) { (username: String, password: String) =>
Ok(da.User_loginUser(username, password))
} handle {
case e: Exception => BadRequest(e)
}
/**
*
* @return And endpoint representing a Unit
*/
private def logoutUser(da: DataAccessor): Endpoint[Unit] =
get("user" :: "logout") {
da.User_logoutUser()
NoContent[Unit]
} handle {
case e: Exception => BadRequest(e)
}
/**
*
* @return And endpoint representing a Unit
*/
private def updateUser(da: DataAccessor): Endpoint[Unit] =
put("user" :: string :: jsonBody[User]) { (username: String, body: User) =>
da.User_updateUser(username, body)
NoContent[Unit]
} handle {
case e: Exception => BadRequest(e)
}
implicit private def fileUploadToFile(fileUpload: FileUpload): File = {
fileUpload match {
case upload: InMemoryFileUpload =>
bytesToFile(Buf.ByteArray.Owned.extract(upload.content))
case upload: OnDiskFileUpload =>
upload.content
case _ => null
}
}
private def bytesToFile(input: Array[Byte]): java.io.File = {
val file = File.createTempFile("tmpUserApi", null)
val output = new FileOutputStream(file)
output.write(input)
file
}
// This assists in params(string) application (which must be Seq[A] in parameter list) when the param is used as a List[A] elsewhere.
implicit def seqList[A](input: Seq[A]): List[A] = input.toList
}

View File

@@ -0,0 +1,27 @@
package io.swagger.petstore.models
import io.circe._
import io.finch.circe._
import io.circe.generic.semiauto._
import io.circe.java8.time._
import io.swagger.petstore._
/**
* Describes the result of uploading an image resource
* @param code
* @param _type
* @param message
*/
case class ApiResponse(
code: Option[Int],
_type: Option[String],
message: Option[String]
)
object ApiResponse {
/**
* Creates the codec for converting ApiResponse from and to JSON.
*/
implicit val decoder: Decoder[ApiResponse] = deriveDecoder
implicit val encoder: ObjectEncoder[ApiResponse] = deriveEncoder
}

View File

@@ -0,0 +1,25 @@
package io.swagger.petstore.models
import io.circe._
import io.finch.circe._
import io.circe.generic.semiauto._
import io.circe.java8.time._
import io.swagger.petstore._
/**
* A category for a pet
* @param id
* @param name
*/
case class Category(
id: Option[Long],
name: Option[String]
)
object Category {
/**
* Creates the codec for converting Category from and to JSON.
*/
implicit val decoder: Decoder[Category] = deriveDecoder
implicit val encoder: ObjectEncoder[Category] = deriveEncoder
}

View File

@@ -0,0 +1,34 @@
package io.swagger.petstore.models
import io.circe._
import io.finch.circe._
import io.circe.generic.semiauto._
import io.circe.java8.time._
import io.swagger.petstore._
import java.time.LocalDateTime
/**
* An order for a pets from the pet store
* @param id
* @param petId
* @param quantity
* @param shipDate
* @param status Order Status
* @param complete
*/
case class Order(
id: Option[Long],
petId: Option[Long],
quantity: Option[Int],
shipDate: Option[LocalDateTime],
status: Option[String],
complete: Option[Boolean]
)
object Order {
/**
* Creates the codec for converting Order from and to JSON.
*/
implicit val decoder: Decoder[Order] = deriveDecoder
implicit val encoder: ObjectEncoder[Order] = deriveEncoder
}

View File

@@ -0,0 +1,36 @@
package io.swagger.petstore.models
import io.circe._
import io.finch.circe._
import io.circe.generic.semiauto._
import io.circe.java8.time._
import io.swagger.petstore._
import io.swagger.petstore.models.Category
import io.swagger.petstore.models.Tag
import scala.collection.immutable.Seq
/**
* A pet for sale in the pet store
* @param id
* @param category
* @param name
* @param photoUrls
* @param tags
* @param status pet status in the store
*/
case class Pet(
id: Option[Long],
category: Option[Category],
name: String,
photoUrls: Seq[String],
tags: Option[Seq[Tag]],
status: Option[String]
)
object Pet {
/**
* Creates the codec for converting Pet from and to JSON.
*/
implicit val decoder: Decoder[Pet] = deriveDecoder
implicit val encoder: ObjectEncoder[Pet] = deriveEncoder
}

View File

@@ -0,0 +1,25 @@
package io.swagger.petstore.models
import io.circe._
import io.finch.circe._
import io.circe.generic.semiauto._
import io.circe.java8.time._
import io.swagger.petstore._
/**
* A tag for a pet
* @param id
* @param name
*/
case class Tag(
id: Option[Long],
name: Option[String]
)
object Tag {
/**
* Creates the codec for converting Tag from and to JSON.
*/
implicit val decoder: Decoder[Tag] = deriveDecoder
implicit val encoder: ObjectEncoder[Tag] = deriveEncoder
}

View File

@@ -0,0 +1,37 @@
package io.swagger.petstore.models
import io.circe._
import io.finch.circe._
import io.circe.generic.semiauto._
import io.circe.java8.time._
import io.swagger.petstore._
/**
* A User who is purchasing from the pet store
* @param id
* @param username
* @param firstName
* @param lastName
* @param email
* @param password
* @param phone
* @param userStatus User Status
*/
case class User(
id: Option[Long],
username: Option[String],
firstName: Option[String],
lastName: Option[String],
email: Option[String],
password: Option[String],
phone: Option[String],
userStatus: Option[Int]
)
object User {
/**
* Creates the codec for converting User from and to JSON.
*/
implicit val decoder: Decoder[User] = deriveDecoder
implicit val encoder: ObjectEncoder[User] = deriveEncoder
}