Skip to content

Commit

Permalink
Merge pull request #5 from medly/controller-is-now-api
Browse files Browse the repository at this point in the history
Rename Controller -> Api, Service -> Controller
  • Loading branch information
ashwini-desai authored Jun 10, 2020
2 parents 6f20363 + 4a0b55a commit d7fe0bc
Show file tree
Hide file tree
Showing 8 changed files with 64 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,25 @@ import apifi.parser.models.Path
import com.squareup.kotlinpoet.*
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy

object ControllerBuilder {
object ApiBuilder {

fun build(name: String, paths: List<Path>, securityDependencies: List<SecurityDependency>, basePackageName: String, modelMapping: List<Pair<String, String>>): FileSpec {
val baseName = toTitleCase(name)
val controllerClassName = "${baseName}Controller"
val controllerClassName = "${baseName}Api"

val serviceClass = ServiceBuilder.build(paths, baseName)
val controllerInterfaceClass = ControllerInterfaceBuilder.build(paths, baseName)

val primaryConstructor = FunSpec.constructorBuilder()
.addAnnotation(ClassName("javax.inject", "Inject"))
.addParameter(ParameterSpec.builder("service", ClassName(basePackageName, serviceClass.name!!)).build())
.addParameter(ParameterSpec.builder("controller", ClassName(basePackageName, controllerInterfaceClass.name!!)).build())

val serviceProperty = PropertySpec.builder("service", ClassName(basePackageName, serviceClass.name!!))
.addModifiers(KModifier.PRIVATE).initializer("service").build()
val controllerProperty = PropertySpec.builder("controller", ClassName(basePackageName, controllerInterfaceClass.name!!))
.addModifiers(KModifier.PRIVATE).initializer("controller").build()

val classSpec = TypeSpec.classBuilder(ClassName(basePackageName, controllerClassName))
.addAnnotation(AnnotationSpec.builder(ClassName("io.micronaut.http.annotation", "Controller"))
.build())
.addProperty(serviceProperty)
.addProperty(controllerProperty)
.addFunctions(generateOperationFunctions(paths, modelMapping, securityDependencies))

securityDependencies.forEach { dependency ->
Expand All @@ -41,7 +41,7 @@ object ControllerBuilder {

classSpec.primaryConstructor(primaryConstructor.build())

return FileSpec.builder(basePackageName, "$controllerClassName.kt").addType(classSpec.build()).addType(serviceClass).build()
return FileSpec.builder(basePackageName, "$controllerClassName.kt").addType(classSpec.build()).addType(controllerInterfaceClass).build()
}

private fun generateOperationFunctions(paths: List<Path>, modelMapping: List<Pair<String, String>>, securityDependencies: List<SecurityDependency>): List<FunSpec> {
Expand Down
6 changes: 3 additions & 3 deletions src/main/kotlin/apifi/codegen/CodeGenerator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ object CodeGenerator {
.filter { spec.securityRequirements.contains(it.key.name) }
.map { (def, spec) -> SecurityDependency((spec.members.first() as TypeSpec).name!!, spec.packageName, def.type) }

val controllerGroups = spec.paths.groupBy { it.operations?.firstOrNull { o -> o.tags != null }?.tags?.firstOrNull() }.filter { it.key != null }
val apiGroups = spec.paths.groupBy { it.operations?.firstOrNull { o -> o.tags != null }?.tags?.firstOrNull() }.filter { it.key != null }

val controllerFiles = controllerGroups.map { ControllerBuilder.build(it.key!!, it.value, securityDependencies, basePackageName, modelMapping) }
val apiClassFiles = apiGroups.map { ApiBuilder.build(it.key!!, it.value, securityDependencies, basePackageName, modelMapping) }

return (controllerFiles + modelFiles + responseModelFile)
return (apiClassFiles + modelFiles + responseModelFile)
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import apifi.parser.models.ParamType
import apifi.parser.models.Path
import com.squareup.kotlinpoet.*

object ServiceBuilder {
object ControllerInterfaceBuilder {
fun build(paths: List<Path>, baseName: String): TypeSpec {
val serviceMethods = paths.flatMap { path ->
val controllerMethods = paths.flatMap { path ->
path.operations?.map { operation ->
val queryParams = operation.params?.filter { it.type == ParamType.Query } ?: emptyList()
val pathParams = operation.params?.filter { it.type == ParamType.Path } ?: emptyList()
Expand All @@ -30,7 +30,7 @@ object ServiceBuilder {
} ?: emptyList()
}

return TypeSpec.interfaceBuilder("${baseName}Service")
.addFunctions(serviceMethods).build()
return TypeSpec.interfaceBuilder("${baseName}Controller")
.addFunctions(controllerMethods).build()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import kotlin.Array
import kotlin.String

@Controller
class PetsController @Inject constructor(
private val service: PetsService
class PetsApi @Inject constructor(
private val controller: PetsController
) {
@Post(value = "/pets")
@Consumes("application/json")
Expand All @@ -29,7 +29,7 @@ class PetsController @Inject constructor(
HttpResponse.ok(service.showPetById(petId))
}

interface PetsService {
interface PetsController {
fun createPets(body: Array<Pet>): Error

fun showPetById(petId: String): Pet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ import kotlin.Any
import kotlin.Array

@Controller
class StoreController @Inject constructor(
private val service: StoreService
class StoreApi @Inject constructor(
private val controller: StoreController
) {
@Get(value = "/store/inventory")
fun get(httpRequest: HttpRequest<Any>): HttpResponse<Array<GetStoreInventoryResponse>> =
HttpResponse.ok(service.get())
}

interface StoreService {
interface StoreController {
fun get(): Array<GetStoreInventoryResponse>
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,26 @@ import io.kotlintest.shouldBe
import io.kotlintest.specs.DescribeSpec
import io.swagger.v3.oas.models.PathItem

class ControllerBuilderTest : DescribeSpec({
class ApiBuilderTest : DescribeSpec({

describe("Controller Builder") {
it("generate controller class with controller annotation") {
describe("Api Builder") {
it("generate api class with controller annotation") {
val path = Path("/pets", listOf(Operation(PathItem.HttpMethod.GET, "listPets", emptyList(), null, null, null)))
val controller = ControllerBuilder.build("pets", listOf(path), emptyList(), "apifi.gen", modelMapping())
val controller = ApiBuilder.build("pets", listOf(path), emptyList(), "apifi.gen", modelMapping())
val controllerClass = controller.members[0] as TypeSpec
controllerClass.name shouldBe "PetsController"
controllerClass.name shouldBe "PetsApi"
controllerClass.annotationSpecs[0].toString() shouldBe "@io.micronaut.http.annotation.Controller"
}

it("generate controller method based on spec operation method and url") {
it("generate api method based on spec operation method and url") {
val path1 = Path("/pets", listOf(
Operation(PathItem.HttpMethod.GET, "getOpName", emptyList(), null, null, null, SecurityDefinitionType.BASIC_AUTH),
Operation(PathItem.HttpMethod.POST, "postOpName", emptyList(), null, null, null, SecurityDefinitionType.BASIC_AUTH)
))
val path2 = Path("/pets/{petId}", listOf(
Operation(PathItem.HttpMethod.GET, "getPet", emptyList(), null, null, null, SecurityDefinitionType.BASIC_AUTH)
))
val controller = ControllerBuilder.build("pets", listOf(path1, path2), emptyList(), "apifi.gen", modelMapping())
val controller = ApiBuilder.build("pets", listOf(path1, path2), emptyList(), "apifi.gen", modelMapping())
val controllerClass = controller.members[0] as TypeSpec
controllerClass.funSpecs.size shouldBe 3
controllerClass.funSpecs[0].toString() shouldBe "@io.micronaut.http.annotation.Get(value = \"/pets\")\n" +
Expand All @@ -39,13 +39,13 @@ class ControllerBuilderTest : DescribeSpec({
controllerClass.funSpecs[2].toString() shouldBe "@io.micronaut.http.annotation.Get(value = \"/pets/{petId}\")\n" +
"fun getPet(httpRequest: io.micronaut.http.HttpRequest<kotlin.Any>) = HttpResponse.ok(service.getPet())\n"
}
it("generate controller method with query, path and header params") {
it("generate api method with query, path and header params") {
val queryParam = Param("limit", "kotlin.Int", true, ParamType.Query)
val pathParam = Param("petId", "kotlin.Int", true, ParamType.Path)
val headerParam = Param("x-header", "kotlin.String", true, ParamType.Header)
val operation = Operation(PathItem.HttpMethod.POST, "createPet", emptyList(), listOf(queryParam, pathParam, headerParam), null, null)

val controller = ControllerBuilder.build("pets", listOf(Path("/pets", listOf(operation))), emptyList(), "apifi.gen", modelMapping())
val controller = ApiBuilder.build("pets", listOf(Path("/pets", listOf(operation))), emptyList(), "apifi.gen", modelMapping())

val controllerClass = controller.members[0] as TypeSpec
controllerClass.funSpecs[0].parameters.map { it.toString() } shouldContainExactlyInAnyOrder
Expand All @@ -55,10 +55,10 @@ class ControllerBuilderTest : DescribeSpec({
"httpRequest: io.micronaut.http.HttpRequest<kotlin.Any>")
}

it("generate controller method with request and response") {
it("generate api method with request and response") {
val operation = Operation(PathItem.HttpMethod.POST, "createPet", emptyList(), emptyList(), Request("Pet", listOf("application/json", "text/plain")), listOf("PetResponse"))

val controller = ControllerBuilder.build("pets", listOf(Path("/pets", listOf(operation))), emptyList(), "apifi.gen", modelMapping())
val controller = ApiBuilder.build("pets", listOf(Path("/pets", listOf(operation))), emptyList(), "apifi.gen", modelMapping())

val controllerClass = controller.members[0] as TypeSpec
controllerClass.funSpecs[0].annotations[0].toString() shouldBe "@io.micronaut.http.annotation.Post(value = \"/pets\")"
Expand All @@ -69,55 +69,55 @@ class ControllerBuilderTest : DescribeSpec({
controllerClass.funSpecs[0].returnType.toString() shouldBe "io.micronaut.http.HttpResponse<models.PetResponse>"
}

it("generate controller method block with all blocks when security dependencies are present") {
it("generate api method block with all blocks when security dependencies are present") {
val queryParam = Param("limit", "kotlin.Int", true, ParamType.Query)
val pathParam = Param("petId", "kotlin.Int", true, ParamType.Path)
val headerParam = Param("x-header", "kotlin.String", true, ParamType.Header)
val operation = Operation(PathItem.HttpMethod.POST, "listPets", emptyList(), listOf(queryParam, pathParam, headerParam), Request("Pet", listOf("application/json")), listOf("PetResponse"))

val controller = ControllerBuilder.build("pets", listOf(Path("/pets", listOf(operation))), listOf(SecurityDependency("httpBasic", "security", SecurityDefinitionType.BASIC_AUTH)), "apifi.gen", modelMapping())
val controller = ApiBuilder.build("pets", listOf(Path("/pets", listOf(operation))), listOf(SecurityDependency("httpBasic", "security", SecurityDefinitionType.BASIC_AUTH)), "apifi.gen", modelMapping())

val controllerClass = controller.members[0] as TypeSpec
controllerClass.funSpecs[0].body.toString().trimIndent() shouldBe "return basicauthorizer.authorize(httpRequest.headers.authorization){HttpResponse.ok(service.listPets(limit, petId, body))}"
}

it("generate controller method block with all blocks when security dependencies are not present") {
it("generate api method block with all blocks when security dependencies are not present") {
val queryParam = Param("limit", "kotlin.Int", true, ParamType.Query)
val pathParam = Param("petId", "kotlin.Int", true, ParamType.Path)
val headerParam = Param("x-header", "kotlin.String", true, ParamType.Header)
val operation = Operation(PathItem.HttpMethod.POST, "createPet", emptyList(), listOf(queryParam, pathParam, headerParam), Request("Pet", null), listOf("PetResponse"))

val controller = ControllerBuilder.build("pets", listOf(Path("/pets", listOf(operation))), emptyList(), "apifi.gen", modelMapping())
val controller = ApiBuilder.build("pets", listOf(Path("/pets", listOf(operation))), emptyList(), "apifi.gen", modelMapping())

val controllerClass = controller.members[0] as TypeSpec
controllerClass.funSpecs[0].body.toString().trimIndent() shouldBe "return HttpResponse.ok(service.createPet(limit, petId, body))"
}

it("inject service & security dependencies") {
it("inject controller & security dependencies") {
val operation = Operation(PathItem.HttpMethod.POST, "listPets", listOf(), emptyList(), Request("Pet", null), listOf("PetResponse"))

val controller = ControllerBuilder.build("pets", listOf(Path("/pets", listOf(operation))), listOf(SecurityDependency("BasicAuthorizer", "security", SecurityDefinitionType.BASIC_AUTH)), "apifi.gen", modelMapping())
val controller = ApiBuilder.build("pets", listOf(Path("/pets", listOf(operation))), listOf(SecurityDependency("BasicAuthorizer", "security", SecurityDefinitionType.BASIC_AUTH)), "apifi.gen", modelMapping())

val controllerClass = controller.members[0] as TypeSpec
val serviceClass = controller.members[1] as TypeSpec
controllerClass.name shouldBe "PetsController"
serviceClass.name shouldBe "PetsService"
controllerClass.name shouldBe "PetsApi"
serviceClass.name shouldBe "PetsController"

controllerClass.propertySpecs[0].name shouldBe "service"
controllerClass.propertySpecs[0].type.toString() shouldBe "apifi.gen.PetsService"
controllerClass.propertySpecs[0].name shouldBe "controller"
controllerClass.propertySpecs[0].type.toString() shouldBe "apifi.gen.PetsController"
controllerClass.propertySpecs[0].modifiers shouldContain KModifier.PRIVATE

controllerClass.toString() shouldContain "@io.micronaut.http.annotation.Controller\n" +
"class PetsController @javax.inject.Inject constructor(\n" +
" private val service: apifi.gen.PetsService,\n" +
"class PetsApi @javax.inject.Inject constructor(\n" +
" private val controller: apifi.gen.PetsController,\n" +
" private val basicauthorizer: security.BasicAuthorizer\n" +
")"
}

it("should convert multipart file to java file") {
val operation = Operation(PathItem.HttpMethod.POST, "uploadDocument", emptyList(), emptyList(), Request("io.micronaut.http.multipart.CompleteFileUpload", listOf("multipart/form-data")), null)

val controller = ControllerBuilder.build("pets", listOf(Path("/pets", listOf(operation))), listOf(SecurityDependency("BasicAuthorizer", "security", SecurityDefinitionType.BASIC_AUTH)), "apifi.gen", modelMapping())
val controller = ApiBuilder.build("pets", listOf(Path("/pets", listOf(operation))), listOf(SecurityDependency("BasicAuthorizer", "security", SecurityDefinitionType.BASIC_AUTH)), "apifi.gen", modelMapping())

val controllerClass = controller.members[0] as TypeSpec
controllerClass.funSpecs[0].body.toString().trimIndent() shouldBe "return basicauthorizer.authorize(httpRequest.headers.authorization){HttpResponse.ok(service.uploadDocument(java.io.File.createTempFile(body.filename, \"\").also { it.writeBytes(body.bytes) }))}"
Expand Down
8 changes: 4 additions & 4 deletions src/test/kotlin/apifi/codegen/CodeGeneratorTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ class CodeGeneratorTest : DescribeSpec({
val fileSpecs = CodeGenerator.generate(spec, "com.pets")
fileSpecs.size shouldBe 4

val expectedPetController = FileUtils.getFile("src", "test-res", "codegen", "expected-pet-controller").readText()
val expectedStoreController = FileUtils.getFile("src", "test-res", "codegen", "expected-store-controller").readText()
val expectedPetApi = FileUtils.getFile("src", "test-res", "codegen", "expected-pet-api").readText()
val expectedStoreApi = FileUtils.getFile("src", "test-res", "codegen", "expected-store-api").readText()
val expectedModels = FileUtils.getFile("src", "test-res", "codegen", "expected-models").readText()
val expectedResponseModel = FileUtils.getFile("src", "test-res", "codegen", "expected-response-model").readText()
fileSpecs[0].toString() shouldBe expectedPetController
fileSpecs[1].toString() shouldBe expectedStoreController
fileSpecs[0].toString() shouldBe expectedPetApi
fileSpecs[1].toString() shouldBe expectedStoreApi
fileSpecs[2].toString() shouldBe expectedModels
fileSpecs[3].toString() shouldBe expectedResponseModel
}
Expand Down
Loading

0 comments on commit d7fe0bc

Please sign in to comment.