Repository: cjbooms/fabrikt
Pull request #17 (2020-11-23 22:12:05)

Fix controllers post with response body
Fix controllers post with response body


Patch:
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/controller/SpringControllerCodeBlocks.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/controller/SpringControllerCodeBlocks.kt
index 94ede31..8ff9813 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/generators/controller/SpringControllerCodeBlocks.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/controller/SpringControllerCodeBlocks.kt
@@ -13,7 +13,7 @@ object ControllerCodeBlocks {
     fun postWithResponsebody(serviceFuncName: String, parameters: List<IncomingParameter>): SimpleCodeBlock =
         SimpleCodeBlock(
             """return ResponseEntity
-                .ok(${serviceCallLine(serviceFuncName, parameters)})
+                .ok(${serviceCallLine(serviceFuncName, parameters)}.second!!)
             """
         )
 


Test patch:
diff --git a/src/test/kotlin/com/cjbooms/fabrikt/generators/ModelGeneratorTest.kt b/src/test/kotlin/com/cjbooms/fabrikt/generators/ModelGeneratorTest.kt
index ce4adb6..3ddd49d 100644
--- a/src/test/kotlin/com/cjbooms/fabrikt/generators/ModelGeneratorTest.kt
+++ b/src/test/kotlin/com/cjbooms/fabrikt/generators/ModelGeneratorTest.kt
@@ -48,7 +48,8 @@ class ModelGeneratorTest {
         "arrayOfArrays",
         "defaultValues",
         "enumPolymorphicDiscriminator",
-        "externalReferences"
+        "externalReferences",
+        "githubApi"
     )
 
     @ParameterizedTest
diff --git a/src/test/kotlin/com/cjbooms/fabrikt/generators/SpringControllerGeneratorTest.kt b/src/test/kotlin/com/cjbooms/fabrikt/generators/SpringControllerGeneratorTest.kt
index 94d8ebb..3eec5fb 100644
--- a/src/test/kotlin/com/cjbooms/fabrikt/generators/SpringControllerGeneratorTest.kt
+++ b/src/test/kotlin/com/cjbooms/fabrikt/generators/SpringControllerGeneratorTest.kt
@@ -27,7 +27,7 @@ class SpringControllerGeneratorTest {
     private fun testCases(): Stream<String> = Stream.of(
         "simpleRequestBody",
         "putApi",
-        "customerExampleApi"
+        "githubApi"
     )
 
     private fun setupGithubApiTestEnv() {
@@ -40,7 +40,7 @@ class SpringControllerGeneratorTest {
     @Test
     fun `should contain correct number of controller classes`() {
         setupGithubApiTestEnv()
-        assertThat(generated.size).isEqualTo(5)
+        assertThat(generated.size).isEqualTo(6)
     }
 
     @Test
@@ -48,6 +48,7 @@ class SpringControllerGeneratorTest {
         setupGithubApiTestEnv()
         assertThat(generated.map { it.name })
             .containsOnly(
+                "InternalEventsController",
                 "ContributorsController",
                 "OrganisationsController",
                 "OrganisationsContributorsController",
@@ -84,6 +85,7 @@ class SpringControllerGeneratorTest {
             generated.flatMap { it.members.flatMap { (it as TypeSpec).propertySpecs.map { it.type.toString() } } }
         val servicePackage = servicesPackage(basePackage)
         assertThat(controllerServices).containsOnly(
+            "$servicePackage.InternalEventsService",
             "$servicePackage.ContributorsService",
             "$servicePackage.OrganisationsService",
             "$servicePackage.OrganisationsContributorsService",
@@ -92,29 +94,6 @@ class SpringControllerGeneratorTest {
         )
     }
 
-    @Test
-    fun `ensure controllers have all functions`() {
-        setupGithubApiTestEnv()
-        val manyToMany = listOf("OrganisationsContributorsController")
-        val manyToOne = listOf(
-            "ContributorsController",
-            "OrganisationsController",
-            "RepositoriesController",
-            "RepositoriesPullRequestsController"
-        )
-
-        assertThat(generated.map { it.name }).containsExactlyInAnyOrderElementsOf(manyToMany.union(manyToOne))
-
-        generated.forEach { gen ->
-            if (manyToMany.contains(gen.name))
-                assertThat(gen.members.flatMap { mem -> (mem as TypeSpec).funSpecs.map { it.name } })
-                    .containsExactlyInAnyOrder("get", "deleteById", "getById", "putById")
-            else
-                assertThat(gen.members.flatMap { mem -> (mem as TypeSpec).funSpecs.map { it.name } })
-                    .containsExactlyInAnyOrder("get", "post", "getById", "putById")
-        }
-    }
-
     @Test
     fun `ensure that subresource specific controllers are created`() {
         val api = SourceApi(javaClass.getResource("/examples/githubApi/api.yaml").readText())
@@ -122,7 +101,7 @@ class SpringControllerGeneratorTest {
         val services = SpringServiceInterfaceGenerator(Packages(basePackage), api, models).generate().services
         val controllers = SpringControllerGenerator(Packages(basePackage), api, services, models).generate()
 
-        assertThat(controllers.files).size().isEqualTo(5)
+        assertThat(controllers.files).size().isEqualTo(6)
         assertThat(controllers.files.map { it.name }).containsAll(
             listOf(
                 "ContributorsController",
diff --git a/src/test/kotlin/com/cjbooms/fabrikt/generators/SpringServiceInterfaceGeneratorTest.kt b/src/test/kotlin/com/cjbooms/fabrikt/generators/SpringServiceInterfaceGeneratorTest.kt
index 1b24c4b..7775be2 100644
--- a/src/test/kotlin/com/cjbooms/fabrikt/generators/SpringServiceInterfaceGeneratorTest.kt
+++ b/src/test/kotlin/com/cjbooms/fabrikt/generators/SpringServiceInterfaceGeneratorTest.kt
@@ -26,7 +26,7 @@ class SpringServiceInterfaceGeneratorTest {
     private fun testCases(): Stream<String> = Stream.of(
         "putApi",
         "simpleRequestBody",
-        "customerExampleApi"
+        "githubApi"
     )
 
     private val basePackage = "testdata"
@@ -39,6 +39,7 @@ class SpringServiceInterfaceGeneratorTest {
 
         assertThat(generated.map { it.name })
             .containsOnly(
+                "InternalEventsService",
                 "ContributorsService",
                 "OrganisationsService",
                 "OrganisationsContributorsService",
diff --git a/src/test/resources/examples/customerExampleApi/api.yaml b/src/test/resources/examples/customerExampleApi/api.yaml
deleted file mode 100644
index 4f2a54d..0000000
--- a/src/test/resources/examples/customerExampleApi/api.yaml
+++ /dev/null
@@ -1,466 +0,0 @@
-openapi: 3.0.0
-info:
-  description: Eample API
-  version: 1.0.0
-  title: example-api
-  contact:
-    name: cjbooms
-    email: example@cjbooms.com
-    url: http://example.docs.cjbooms.com/
-externalDocs:
-  description: Example API Docs
-  url: https://example.docs.cjbooms.com/
-servers:
-  - url: https://example.docs.cjbooms.com/api
-    description: API service url
-
-paths:
-  /customers/{customer_number}:
-    get:
-      summary: Information about a customer.
-      parameters:
-        - $ref: '#/components/parameters/FlowId'
-        - $ref: '#/components/parameters/CustomerNumberPathParameter'
-      responses:
-        200:
-          description: successful operation
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/Customer'
-        429:
-          description: User requests are being throttled
-          headers:
-            Retry-After:
-              $ref: '#/components/headers/RetryAfter'
-            X-Rate-Limit:
-              $ref: '#/components/headers/RateLimit'
-
-  /delete-requests:
-    get:
-      summary: Page through all the DeleteRequest resources matching the query filters
-      parameters:
-        - $ref: '#/components/parameters/FlowId'
-        - $ref: '#/components/parameters/CustomerNumberQueryParam'
-        - $ref: '#/components/parameters/Limit'
-        - $ref: '#/components/parameters/Cursor'
-      responses:
-        200:
-          description: successful operation
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/DeleteRequestQueryResult'
-        429:
-          description: User requests are being throttled
-          headers:
-            Retry-After:
-              $ref: '#/components/headers/RetryAfter'
-            X-Rate-Limit:
-              $ref: '#/components/headers/RateLimit'
-
-    post:
-      summary: Create a new DeleteRequest
-      parameters:
-        - $ref: '#/components/parameters/FlowId'
-      requestBody:
-        $ref: '#/components/requestBodies/DeleteRequestBody'
-      responses:
-        201:
-          headers:
-            Location:
-              $ref: '#/components/headers/Location'
-          description: DeleteRequest successfully created
-        429:
-          description: User requests are being throttled
-          headers:
-            Retry-After:
-              $ref: '#/components/headers/RetryAfter'
-            X-Rate-Limit:
-              $ref: '#/components/headers/RateLimit'
-
-  /delete-requests/{id}:
-    get:
-      summary: Get a DeleteRequest by ID
-      tags:
-        - deleterequest
-      parameters:
-        - $ref: '#/components/parameters/FlowId'
-        - $ref: '#/components/parameters/IdPathParam'
-      responses:
-        200:
-          description: successful operation
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/DeleteRequest'
-        429:
-          description: User requests are being throttled
-          headers:
-            Retry-After:
-              $ref: '#/components/headers/RetryAfter'
-            X-Rate-Limit:
-              $ref: '#/components/headers/RateLimit'
-
-    put:
-      summary: Update an existing DeleteRequest
-      tags:
-        - deleterequest
-      parameters:
-        - $ref: '#/components/parameters/FlowId'
-        - $ref: '#/components/parameters/IdPathParam'
-      requestBody:
-        $ref: '#/components/requestBodies/DeleteRequestBody'
-      responses:
-        204:
-          description: DeleteRequest successfully updated
-        429:
-          description: User requests are being throttled
-          headers:
-            Retry-After:
-              $ref: '#/components/headers/RetryAfter'
-            X-Rate-Limit:
-              $ref: '#/components/headers/RateLimit'
-
-  /data-access-requests:
-    get:
-      summary: Page through all the DataAccessRequest resources matching the query filters
-      parameters:
-        - $ref: '#/components/parameters/FlowId'
-        - $ref: '#/components/parameters/CustomerNumberQueryParam'
-        - $ref: '#/components/parameters/Limit'
-        - $ref: '#/components/parameters/Cursor'
-      responses:
-        200:
-          description: successful operation
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/DataAccessRequestQueryResult'
-        429:
-          description: User requests are being throttled
-          headers:
-            Retry-After:
-              $ref: '#/components/headers/RetryAfter'
-            X-Rate-Limit:
-              $ref: '#/components/headers/RateLimit'
-
-    post:
-      summary: Create a new DataAccessRequest
-      parameters:
-        - $ref: '#/components/parameters/FlowId'
-      requestBody:
-        $ref: '#/components/requestBodies/DataAccessRequestBody'
-      responses:
-        201:
-          headers:
-            Location:
-              $ref: '#/components/headers/Location'
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/DataAccessRequest'
-          description: DataAccessRequest successfully created
-        429:
-          description: User requests are being throttled
-          headers:
-            Retry-After:
-              $ref: '#/components/headers/RetryAfter'
-            X-Rate-Limit:
-              $ref: '#/components/headers/RateLimit'
-
-  /data-access-requests/{id}:
-    get:
-      summary: Get a DataAccessRequest by ID
-      tags:
-        - dataaccessrequest
-      parameters:
-        - $ref: '#/components/parameters/FlowId'
-        - $ref: '#/components/parameters/IdPathParam'
-      responses:
-        200:
-          description: successful operation
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/DataAccessRequest'
-        429:
-          description: User requests are being throttled
-          headers:
-            Retry-After:
-              $ref: '#/components/headers/RetryAfter'
-            X-Rate-Limit:
-              $ref: '#/components/headers/RateLimit'
-
-    put:
-      summary: Update an existing DataAccessRequest
-      parameters:
-        - $ref: '#/components/parameters/FlowId'
-        - $ref: '#/components/parameters/IdPathParam'
-      requestBody:
-        $ref: '#/components/requestBodies/DataAccessRequestBody'
-      responses:
-        204:
-          description: DataAccessRequest successfully updated
-        429:
-          description: User requests are being throttled
-          headers:
-            Retry-After:
-              $ref: '#/components/headers/RetryAfter'
-            X-Rate-Limit:
-              $ref: '#/components/headers/RateLimit'
-
-components:
-  parameters:
-    IdPathParam:
-      name: id
-      description: The unique id for the resource
-      in: path
-      required: true
-      schema:
-        type: string
-    CustomerNumberPathParameter:
-      name: customer_number
-      description: The unique customer number
-      in: path
-      required: true
-      schema:
-        type: string
-    FlowId:
-      name: X-Flow-Id
-      description: |
-        A custom header that will be passed onto any further requests and can be used for diagnosing.
-      in: header
-      required: false
-      schema:
-        type: string
-    Limit:
-      name: limit
-      description: |
-        Upper bound for number of results to be returned from query api. A default limit will be applied if this is not present
-      in: query
-      required: false
-      schema:
-        type: integer
-        minimum: 1
-        maximum: 100
-        default: 10
-    Cursor:
-      name: cursor
-      description: |
-        An encoded value which represents a point in the data from where pagination will commence. The pagination direction (either forward or backward) will also be encoded within the cursor value. The cursor value will always be generated server side
-      in: query
-      required: false
-      schema:
-        type: string
-    CustomerNumberQueryParam:
-      name: customer_number
-      description: |
-        Filters search results to those matching the customer number
-      in: query
-      schema:
-        type: string
-
-  headers:
-    Location:
-      description: The Location header indicates the URL of a newly created resource
-      schema:
-        type: string
-      example: /eligibility-rule-statuses/{id}
-    RetryAfter:
-      description: |
-        This header will be retrieved in tandem with a '429' HTTP response code. The value of the header will be the number of seconds that a client should wait before attempting a new request.
-      schema:
-        type: integer
-    RateLimit:
-      description: |
-        This header will be retrieved in tandem with a '429' HTTP response code. The value of the header indicates how many requests per hour the client is allowed to make. The actual rate limiting will typically be applied per minute though. So if your hourly limit is 1800, you will be allowed to make (1800 / 60 = 30) requests per minute before being rate limited for the remainder of that minute window.
-      schema:
-        type: integer
-
-  requestBodies:
-    DeleteRequestBody:
-      required: true
-      content:
-        application/json:
-          schema:
-            $ref: '#/components/schemas/DeleteRequest'
-
-    DataAccessRequestBody:
-      required: true
-      content:
-        application/json:
-          schema:
-            $ref: '#/components/schemas/DataAccessRequest'
-  schemas:
-    PaginationLink:
-      type: string
-      format: uri
-      description: The hyperlink to a page of data
-      readOnly: true
-      example: https://example.docs.cjbooms.com/api/{resource}?cursor=NDI%3D&limit=5
-
-    DeleteRequestQueryResult:
-      type: object
-      required:
-        - items
-      properties:
-        prev:
-          $ref: '#/components/schemas/PaginationLink'
-        next:
-          $ref: '#/components/schemas/PaginationLink'
-        items:
-          type: array
-          minItems: 0
-          items:
-            $ref: '#/components/schemas/DeleteRequest'
-
-    DataAccessRequestQueryResult:
-      type: object
-      required:
-        - items
-      properties:
-        prev:
-          $ref: '#/components/schemas/PaginationLink'
-        next:
-          $ref: '#/components/schemas/PaginationLink'
-        items:
-          type: array
-          minItems: 0
-          items:
-            $ref: '#/components/schemas/DataAccessRequest'
-
-    Customer:
-      type: object
-      description: Customer data in the context of data deletion
-      properties:
-        is_eligible:
-          type: boolean
-        eligibility_results:
-          type: array
-          description: List of all rules checked and their statuses that make a customer
-            eligible or not for deletion.
-          items:
-            $ref: '#/components/schemas/EligibilityRule'
-
-    DeleteRequest:
-      type: object
-      description: Deletion request object.
-      required:
-        - id
-        - created
-        - created_by_uid
-        - modified
-        - modified_by_uid
-      properties:
-        id:
-          description: This unique server-generated security token will always be present
-            in the response.
-          type: string
-          readOnly: true
-        customer_number:
-          type: integer
-          format: int32
-        status:
-          $ref: '#/components/schemas/RequestStatus'
-        expiry_date:
-          type: string
-          format: date-time
-        created:
-          description: Indicates the timestamp that this resource was initially created
-            at
-          type: string
-          format: date-time
-          readOnly: true
-        modified:
-          description: Indicates the timestamp that this resource was last modified
-            at
-          type: string
-          format: date-time
-          readOnly: true
-        created_by_uid:
-          description: Represents the uid from the auth token that first created this
-            resource
-          type: string
-          readOnly: true
-        modified_by_uid:
-          description: Represents the uid from the auth token that last modified this
-            resource
-          type: string
-          readOnly: true
-
-    DataAccessRequest:
-      type: object
-      description: Data access request object.
-      required:
-        - id
-        - created
-        - created_by_uid
-        - modified
-        - modified_by_uid
-      properties:
-        id:
-          description: This unique server-generated security token identifier will always be present
-            in the response.
-          type: string
-          readOnly: true
-        customer_number:
-          type: integer
-          format: int32
-        status:
-          $ref: '#/components/schemas/RequestStatus'
-        expiry_date:
-          type: string
-          format: date-time
-        created:
-          description: Indicates the timestamp that this resource was initially created at
-          type: string
-          format: date-time
-          readOnly: true
-        modified:
-          description: Indicates the timestamp that this resource was last modified
-            at
-          type: string
-          format: date-time
-          readOnly: true
-        created_by_uid:
-          description: Represents the uid from the auth token that first created this
-            resource
-          type: string
-          readOnly: true
-        modified_by_uid:
-          description: Represents the uid from the auth token that last modified this
-            resource
-          type: string
-          readOnly: true
-
-    EligibilityRule:
-      type: object
-      description: Deletion eligibility rule
-      properties:
-        code:
-          type: string
-          description: Eligibility rule code
-          example: 444
-        message:
-          type: string
-          description: Definition of the error code
-          example: Customer is blacklisted and cannot receive emails
-        status:
-          $ref: '#/components/schemas/EligibilityRuleStatus'
-
-    EligibilityRuleStatus:
-      type: string
-      description: Status of the rule
-      x-extensible-enum:
-        - fail
-        - pass
-    RequestStatus:
-      type: string
-      x-extensible-enum:
-        - OPEN
-        - IN_PROGRESS
-        - DONE
-        - CANCELLED
-        - ERROR
-
diff --git a/src/test/resources/examples/customerExampleApi/controllers/Controllers.kt b/src/test/resources/examples/customerExampleApi/controllers/Controllers.kt
deleted file mode 100644
index 71b22c8..0000000
--- a/src/test/resources/examples/customerExampleApi/controllers/Controllers.kt
+++ /dev/null
@@ -1,316 +0,0 @@
-package examples.customerExampleApi.controllers
-
-import examples.customerExampleApi.models.Customer
-import examples.customerExampleApi.models.DataAccessRequest
-import examples.customerExampleApi.models.DataAccessRequestQueryResult
-import examples.customerExampleApi.models.DeleteRequest
-import examples.customerExampleApi.models.DeleteRequestQueryResult
-import examples.customerExampleApi.service.CustomersService
-import examples.customerExampleApi.service.DataAccessRequestsService
-import examples.customerExampleApi.service.DeleteRequestsService
-import javax.validation.Valid
-import javax.validation.constraints.Max
-import javax.validation.constraints.Min
-import kotlin.Int
-import kotlin.String
-import kotlin.Unit
-import org.springframework.http.ResponseEntity
-import org.springframework.stereotype.Controller
-import org.springframework.validation.annotation.Validated
-import org.springframework.web.bind.annotation.PathVariable
-import org.springframework.web.bind.annotation.RequestBody
-import org.springframework.web.bind.annotation.RequestHeader
-import org.springframework.web.bind.annotation.RequestMapping
-import org.springframework.web.bind.annotation.RequestMethod
-import org.springframework.web.bind.annotation.RequestParam
-
-@Controller
-@Validated
-@RequestMapping("/api")
-class CustomersController(
-    val service: CustomersService
-) {
-    /**
-     * Information about a customer.
-     *
-     * @param xFlowId A custom header that will be passed onto any further requests and can be used
-     * for diagnosing.
-     *
-     * @param customerNumber The unique customer number
-     */
-    @RequestMapping(
-        value = ["/customers/{customer_number}"],
-        produces = ["application/json"],
-        method = [RequestMethod.GET]
-    )
-    fun getById(
-        @PathVariable(value = "customer_number", required = true)
-        customerNumber: String,
-        @RequestHeader(value = "X-Flow-Id", required = false)
-        xFlowId: String?
-    ):
-    ResponseEntity<Customer> {
-        val svcResp = service.read(customerNumber, xFlowId)
-        return ResponseEntity
-            .ok()
-            .body(svcResp)
-    }
-}
-
-@Controller
-@Validated
-@RequestMapping("/api")
-class DeleteRequestsController(
-    val service: DeleteRequestsService
-) {
-    /**
-     * Page through all the DeleteRequest resources matching the query filters
-     *
-     * @param xFlowId A custom header that will be passed onto any further requests and can be used
-     * for diagnosing.
-     *
-     * @param customerNumber Filters search results to those matching the customer number
-     *
-     * @param limit Upper bound for number of results to be returned from query api. A default limit
-     * will be applied if this is not present
-     *
-     * @param cursor An encoded value which represents a point in the data from where pagination will
-     * commence. The pagination direction (either forward or backward) will also be encoded within the
-     * cursor value. The cursor value will always be generated server side
-     *
-     */
-    @RequestMapping(
-        value = ["/delete-requests"],
-        produces = ["application/json"],
-        method = [RequestMethod.GET]
-    )
-    fun get(
-        @Min(1)
-        @Max(100)
-        @RequestParam(value = "limit", required = false, defaultValue = "10")
-        limit: Int,
-        @RequestHeader(value = "X-Flow-Id", required = false)
-        xFlowId: String?,
-        @RequestParam(value = "customer_number", required = false)
-        customerNumber: String?,
-        @RequestParam(value = "cursor", required = false)
-        cursor: String?
-    ): ResponseEntity<DeleteRequestQueryResult> {
-        val svcResp = service.query(limit, xFlowId, customerNumber, cursor)
-        return ResponseEntity
-            .ok()
-            .body(svcResp)
-    }
-
-    /**
-     * Create a new DeleteRequest
-     *
-     * @param xFlowId A custom header that will be passed onto any further requests and can be used
-     * for diagnosing.
-     *
-     */
-    @RequestMapping(
-        value = ["/delete-requests"],
-        produces = [],
-        method = [RequestMethod.POST],
-        consumes = ["application/json"]
-    )
-    fun post(
-        @RequestBody @Valid
-        deleteRequest: DeleteRequest,
-        @RequestHeader(
-            value = "X-Flow-Id",
-            required = false
-        )
-        xFlowId: String?
-    ): ResponseEntity<Unit> {
-        val svcResp = service.create(deleteRequest, xFlowId)
-        val response = ResponseEntity.created(svcResp.first)
-        return response.build()
-    }
-
-    /**
-     * Get a DeleteRequest by ID
-     *
-     * @param xFlowId A custom header that will be passed onto any further requests and can be used
-     * for diagnosing.
-     *
-     * @param id The unique id for the resource
-     */
-    @RequestMapping(
-        value = ["/delete-requests/{id}"],
-        produces = ["application/json"],
-        method = [RequestMethod.GET]
-    )
-    fun getById(
-        @PathVariable(value = "id", required = true)
-        id: String,
-        @RequestHeader(
-            value =
-                "X-Flow-Id",
-            required = false
-        )
-        xFlowId: String?
-    ): ResponseEntity<DeleteRequest> {
-        val svcResp = service.read(id, xFlowId)
-        return ResponseEntity
-            .ok()
-            .body(svcResp)
-    }
-
-    /**
-     * Update an existing DeleteRequest
-     *
-     * @param xFlowId A custom header that will be passed onto any further requests and can be used
-     * for diagnosing.
-     *
-     * @param id The unique id for the resource
-     */
-    @RequestMapping(
-        value = ["/delete-requests/{id}"],
-        produces = [],
-        method = [RequestMethod.PUT],
-        consumes = ["application/json"]
-    )
-    fun putById(
-        @RequestBody @Valid
-        deleteRequest: DeleteRequest,
-        @PathVariable(value = "id", required = true)
-        id: String,
-        @RequestHeader(value = "X-Flow-Id", required = false)
-        xFlowId: String?
-    ): ResponseEntity<Unit> {
-        service.update(deleteRequest, id, xFlowId)
-        return ResponseEntity.noContent().build()
-    }
-}
-
-@Controller
-@Validated
-@RequestMapping("/api")
-class DataAccessRequestsController(
-    val service: DataAccessRequestsService
-) {
-    /**
-     * Page through all the DataAccessRequest resources matching the query filters
-     *
-     * @param xFlowId A custom header that will be passed onto any further requests and can be used
-     * for diagnosing.
-     *
-     * @param customerNumber Filters search results to those matching the customer number
-     *
-     * @param limit Upper bound for number of results to be returned from query api. A default limit
-     * will be applied if this is not present
-     *
-     * @param cursor An encoded value which represents a point in the data from where pagination will
-     * commence. The pagination direction (either forward or backward) will also be encoded within the
-     * cursor value. The cursor value will always be generated server side
-     *
-     */
-    @RequestMapping(
-        value = ["/data-access-requests"],
-        produces = ["application/json"],
-        method = [RequestMethod.GET]
-    )
-    fun get(
-        @Min(1)
-        @Max(100)
-        @RequestParam(value = "limit", required = false, defaultValue = "10")
-        limit: Int,
-        @RequestHeader(value = "X-Flow-Id", required = false)
-        xFlowId: String?,
-        @RequestParam(value = "customer_number", required = false)
-        customerNumber: String?,
-        @RequestParam(value = "cursor", required = false)
-        cursor: String?
-    ): ResponseEntity<DataAccessRequestQueryResult> {
-        val svcResp = service.query(limit, xFlowId, customerNumber, cursor)
-        return ResponseEntity
-            .ok()
-            .body(svcResp)
-    }
-
-    /**
-     * Create a new DataAccessRequest
-     *
-     * @param xFlowId A custom header that will be passed onto any further requests and can be used
-     * for diagnosing.
-     *
-     */
-    @RequestMapping(
-        value = ["/data-access-requests"],
-        produces = ["application/json"],
-        method = [RequestMethod.POST],
-        consumes = ["application/json"]
-    )
-    fun post(
-        @RequestBody @Valid
-        dataAccessRequest: DataAccessRequest,
-        @RequestHeader(
-            value =
-                "X-Flow-Id",
-            required = false
-        )
-        xFlowId: String?
-    ): ResponseEntity<DataAccessRequest> {
-        val svcResp = service.create(dataAccessRequest, xFlowId)
-        val response = ResponseEntity.created(svcResp.first)
-        if (svcResp.second != null) response.body(svcResp.second)
-        return response.build()
-    }
-
-    /**
-     * Get a DataAccessRequest by ID
-     *
-     * @param xFlowId A custom header that will be passed onto any further requests and can be used
-     * for diagnosing.
-     *
-     * @param id The unique id for the resource
-     */
-    @RequestMapping(
-        value = ["/data-access-requests/{id}"],
-        produces = ["application/json"],
-        method = [RequestMethod.GET]
-    )
-    fun getById(
-        @PathVariable(value = "id", required = true)
-        id: String,
-        @RequestHeader(
-            value =
-                "X-Flow-Id",
-            required = false
-        )
-        xFlowId: String?
-    ): ResponseEntity<DataAccessRequest> {
-        val svcResp = service.read(id, xFlowId)
-        return ResponseEntity
-            .ok()
-            .body(svcResp)
-    }
-
-    /**
-     * Update an existing DataAccessRequest
-     *
-     * @param xFlowId A custom header that will be passed onto any further requests and can be used
-     * for diagnosing.
-     *
-     * @param id The unique id for the resource
-     */
-    @RequestMapping(
-        value = ["/data-access-requests/{id}"],
-        produces = [],
-        method = [RequestMethod.PUT],
-        consumes = ["application/json"]
-    )
-    fun putById(
-        @RequestBody @Valid
-        dataAccessRequest: DataAccessRequest,
-        @PathVariable(value = "id", required = true)
-        id: String,
-        @RequestHeader(value = "X-Flow-Id", required = false)
-        xFlowId: String?
-    ): ResponseEntity<Unit> {
-        service.update(dataAccessRequest, id, xFlowId)
-        return ResponseEntity.noContent().build()
-    }
-}
diff --git a/src/test/resources/examples/customerExampleApi/models/Models.kt b/src/test/resources/examples/customerExampleApi/models/Models.kt
deleted file mode 100644
index fddcddc..0000000
--- a/src/test/resources/examples/customerExampleApi/models/Models.kt
+++ /dev/null
@@ -1,142 +0,0 @@
-package examples.customerExampleApi.models
-
-import com.fasterxml.jackson.annotation.JsonProperty
-import com.fasterxml.jackson.annotation.JsonValue
-import java.time.OffsetDateTime
-import javax.validation.Valid
-import javax.validation.constraints.NotNull
-import javax.validation.constraints.Size
-import kotlin.Boolean
-import kotlin.Int
-import kotlin.String
-import kotlin.collections.List
-
-data class DeleteRequestQueryResult(
-    @param:JsonProperty("prev")
-    @get:JsonProperty("prev")
-    val prev: String? = null,
-    @param:JsonProperty("next")
-    @get:JsonProperty("next")
-    val next: String? = null,
-    @param:JsonProperty("items")
-    @get:JsonProperty("items")
-    @get:NotNull
-    @get:Size(min = 0)
-    @get:Valid
-    val items: List<DeleteRequest>
-)
-
-data class DataAccessRequestQueryResult(
-    @param:JsonProperty("prev")
-    @get:JsonProperty("prev")
-    val prev: String? = null,
-    @param:JsonProperty("next")
-    @get:JsonProperty("next")
-    val next: String? = null,
-    @param:JsonProperty("items")
-    @get:JsonProperty("items")
-    @get:NotNull
-    @get:Size(min = 0)
-    @get:Valid
-    val items: List<DataAccessRequest>
-)
-
-data class Customer(
-    @param:JsonProperty("is_eligible")
-    @get:JsonProperty("is_eligible")
-    val isEligible: Boolean? = null,
-    @param:JsonProperty("eligibility_results")
-    @get:JsonProperty("eligibility_results")
-    @get:Valid
-    val eligibilityResults: List<EligibilityRule>? = null
-)
-
-data class DeleteRequest(
-    @param:JsonProperty("id")
-    @get:JsonProperty("id")
-    val id: String? = null,
-    @param:JsonProperty("customer_number")
-    @get:JsonProperty("customer_number")
-    val customerNumber: Int? = null,
-    @param:JsonProperty("status")
-    @get:JsonProperty("status")
-    val status: RequestStatus? = null,
-    @param:JsonProperty("expiry_date")
-    @get:JsonProperty("expiry_date")
-    val expiryDate: OffsetDateTime? = null,
-    @param:JsonProperty("created")
-    @get:JsonProperty("created")
-    val created: OffsetDateTime? = null,
-    @param:JsonProperty("modified")
-    @get:JsonProperty("modified")
-    val modified: OffsetDateTime? = null,
-    @param:JsonProperty("created_by_uid")
-    @get:JsonProperty("created_by_uid")
-    val createdByUid: String? = null,
-    @param:JsonProperty("modified_by_uid")
-    @get:JsonProperty("modified_by_uid")
-    val modifiedByUid: String? = null
-)
-
-enum class RequestStatus(
-    @JsonValue
-    val value: String
-) {
-    OPEN("OPEN"),
-
-    IN_PROGRESS("IN_PROGRESS"),
-
-    DONE("DONE"),
-
-    CANCELLED("CANCELLED"),
-
-    ERROR("ERROR");
-}
-
-data class DataAccessRequest(
-    @param:JsonProperty("id")
-    @get:JsonProperty("id")
-    val id: String? = null,
-    @param:JsonProperty("customer_number")
-    @get:JsonProperty("customer_number")
-    val customerNumber: Int? = null,
-    @param:JsonProperty("status")
-    @get:JsonProperty("status")
-    val status: RequestStatus? = null,
-    @param:JsonProperty("expiry_date")
-    @get:JsonProperty("expiry_date")
-    val expiryDate: OffsetDateTime? = null,
-    @param:JsonProperty("created")
-    @get:JsonProperty("created")
-    val created: OffsetDateTime? = null,
-    @param:JsonProperty("modified")
-    @get:JsonProperty("modified")
-    val modified: OffsetDateTime? = null,
-    @param:JsonProperty("created_by_uid")
-    @get:JsonProperty("created_by_uid")
-    val createdByUid: String? = null,
-    @param:JsonProperty("modified_by_uid")
-    @get:JsonProperty("modified_by_uid")
-    val modifiedByUid: String? = null
-)
-
-data class EligibilityRule(
-    @param:JsonProperty("code")
-    @get:JsonProperty("code")
-    val code: String? = null,
-    @param:JsonProperty("message")
-    @get:JsonProperty("message")
-    val message: String? = null,
-    @param:JsonProperty("status")
-    @get:JsonProperty("status")
-    val status: EligibilityRuleStatus? = null
-)
-
-enum class EligibilityRuleStatus(
-    @JsonValue
-    val value: String
-) {
-    FAIL("fail"),
-
-    PASS("pass");
-}
diff --git a/src/test/resources/examples/customerExampleApi/service/Services.kt b/src/test/resources/examples/customerExampleApi/service/Services.kt
deleted file mode 100644
index 8c448f0..0000000
--- a/src/test/resources/examples/customerExampleApi/service/Services.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-package examples.customerExampleApi.service
-
-import examples.customerExampleApi.models.Customer
-import examples.customerExampleApi.models.DataAccessRequest
-import examples.customerExampleApi.models.DataAccessRequestQueryResult
-import examples.customerExampleApi.models.DeleteRequest
-import examples.customerExampleApi.models.DeleteRequestQueryResult
-import java.net.URI
-import kotlin.Int
-import kotlin.Pair
-import kotlin.String
-
-interface CustomersService {
-    fun read(customerNumber: String, xFlowId: String?): Customer
-}
-
-interface DeleteRequestsService {
-    fun query(
-        limit: Int,
-        xFlowId: String?,
-        customerNumber: String?,
-        cursor: String?
-    ): DeleteRequestQueryResult
-
-    fun create(deleteRequest: DeleteRequest, xFlowId: String?): Pair<URI, DeleteRequest?>
-
-    fun read(id: String, xFlowId: String?): DeleteRequest
-
-    fun update(
-        deleteRequest: DeleteRequest,
-        id: String,
-        xFlowId: String?
-    ): DeleteRequest
-}
-
-interface DataAccessRequestsService {
-    fun query(
-        limit: Int,
-        xFlowId: String?,
-        customerNumber: String?,
-        cursor: String?
-    ): DataAccessRequestQueryResult
-
-    fun create(dataAccessRequest: DataAccessRequest, xFlowId: String?): Pair<URI, DataAccessRequest?>
-
-    fun read(id: String, xFlowId: String?): DataAccessRequest
-
-    fun update(
-        dataAccessRequest: DataAccessRequest,
-        id: String,
-        xFlowId: String?
-    ): DataAccessRequest
-}
diff --git a/src/test/resources/examples/githubApi/api.yaml b/src/test/resources/examples/githubApi/api.yaml
index 6e9702c..82775d0 100644
--- a/src/test/resources/examples/githubApi/api.yaml
+++ b/src/test/resources/examples/githubApi/api.yaml
@@ -11,6 +11,31 @@ externalDocs:
   url: http://find.them.here
 
 paths:
+  /internal/events:
+    post:
+      tags:
+        - events
+      summary: Generate change events for a list of entities
+      requestBody:
+        $ref: '#/components/requestBodies/BulkEventGenerationBody'
+      responses:
+        '200':
+          description: successful operation
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/EventResults'
+        default:
+          description: >-
+            error occurred - see status code and problem object for more information.
+          content:
+            application/problem+json:
+              schema:
+                $ref: 'https://opensource.zalando.com/problem/schema.yaml#/Problem'
+      security:
+        - oauth2:
+            - uid
+            - fabric-event-scheduler.read  
   /contributors:
     get:
       summary: Page through all the Contributor resources matching the query filters
@@ -797,6 +822,12 @@ components:
         type: integer
 
   requestBodies:
+    BulkEventGenerationBody:
+      required: true
+      content:
+        application/json:
+          schema:
+            $ref: '#/components/schemas/BulkEntityDetails'    
     ContributorBody:
       required: true
       content:
@@ -822,6 +853,56 @@ components:
           schema:
             $ref: '#/components/schemas/PullRequest'
   schemas:
+    BulkEntityDetails:
+      type: object
+      additionalProperties: true
+      required:
+        - entities
+      properties:
+        entities:
+          type: array
+          items:
+            $ref: '#/components/schemas/EntityDetails'
+
+    EntityDetails:
+      type: object
+      additionalProperties: true
+      description: |
+        Describes the SQL entity we are requesting events for.
+      required:
+        - id
+      properties:
+        id:
+          description: |
+            The value from the table:column which is being monitored by ferit.
+          type: string
+
+    EventResults:
+      type: object
+      additionalProperties: true
+      required:
+        - change_events
+      properties:
+        change_events:
+          type: array
+          minItems: 0
+          items:
+            $ref: '#/components/schemas/Event'
+
+    Event:
+      type: object
+      additionalProperties: true
+      required:
+        - entity_id
+        - data
+      properties:
+        entity_id:
+          type: string
+          description: The id of the entity this event belongs to
+        data:
+          type: object
+          description: This unstructured event content must conform to the Nakadi Event Type's schema
+
     PaginationLink:
       type: string
       format: uri
diff --git a/src/test/resources/examples/githubApi/controllers/Controllers.kt b/src/test/resources/examples/githubApi/controllers/Controllers.kt
new file mode 100644
index 0000000..b76ddcb
--- /dev/null
+++ b/src/test/resources/examples/githubApi/controllers/Controllers.kt
@@ -0,0 +1,861 @@
+package examples.githubApi.controllers
+
+import examples.githubApi.models.BulkEntityDetails
+import examples.githubApi.models.Contributor
+import examples.githubApi.models.ContributorQueryResult
+import examples.githubApi.models.EventResults
+import examples.githubApi.models.Organisation
+import examples.githubApi.models.OrganisationQueryResult
+import examples.githubApi.models.PullRequest
+import examples.githubApi.models.PullRequestQueryResult
+import examples.githubApi.models.Repository
+import examples.githubApi.models.RepositoryQueryResult
+import examples.githubApi.models.StatusQueryParam
+import examples.githubApi.service.ContributorsService
+import examples.githubApi.service.InternalEventsService
+import examples.githubApi.service.OrganisationsContributorsService
+import examples.githubApi.service.OrganisationsService
+import examples.githubApi.service.RepositoriesPullRequestsService
+import examples.githubApi.service.RepositoriesService
+import javax.validation.Valid
+import javax.validation.constraints.Max
+import javax.validation.constraints.Min
+import kotlin.Boolean
+import kotlin.Int
+import kotlin.String
+import kotlin.Unit
+import kotlin.collections.List
+import org.springframework.http.ResponseEntity
+import org.springframework.stereotype.Controller
+import org.springframework.validation.annotation.Validated
+import org.springframework.web.bind.annotation.PathVariable
+import org.springframework.web.bind.annotation.RequestBody
+import org.springframework.web.bind.annotation.RequestHeader
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RequestMethod
+import org.springframework.web.bind.annotation.RequestParam
+
+@Controller
+@Validated
+@RequestMapping("")
+class InternalEventsController(
+    val service: InternalEventsService
+) {
+    /**
+     * Generate change events for a list of entities
+     */
+    @RequestMapping(
+        value = ["/internal/events"],
+        produces = ["application/json", "application/problem+json"],
+        method = [RequestMethod.POST],
+        consumes = ["application/json"]
+    )
+    fun post(
+        @RequestBody @Valid
+        bulkEntityDetails: BulkEntityDetails
+    ): ResponseEntity<EventResults> =
+        ResponseEntity
+            .ok(service.create(bulkEntityDetails).second!!)
+}
+
+@Controller
+@Validated
+@RequestMapping("")
+class ContributorsController(
+    val service: ContributorsService
+) {
+    /**
+     * Page through all the Contributor resources matching the query filters
+     *
+     * @param xFlowId A custom header that will be passed onto any further requests and can be used
+     * for diagnosing.
+     *
+     * @param includeInactive A query parameter to request both active and inactive entities
+     * @param limit Upper bound for number of results to be returned from query api. A default limit
+     * will be applied if this is not present
+     *
+     * @param cursor An encoded value which represents a point in the data from where pagination will
+     * commence. The pagination direction (either forward or backward) will also be encoded within the
+     * cursor value. The cursor value will always be generated server side
+     *
+     */
+    @RequestMapping(
+        value = ["/contributors"],
+        produces = ["application/json"],
+        method = [RequestMethod.GET]
+    )
+    fun get(
+        @Min(1)
+        @Max(100)
+        @RequestParam(value = "limit", required = false, defaultValue = "10")
+        limit: Int,
+        @RequestHeader(value = "X-Flow-Id", required = false)
+        xFlowId: String?,
+        @RequestParam(value = "include_inactive", required = false)
+        includeInactive: Boolean?,
+        @RequestParam(value = "cursor", required = false)
+        cursor: String?
+    ): ResponseEntity<ContributorQueryResult> {
+        val svcResp = service.query(limit, xFlowId, includeInactive, cursor)
+        return ResponseEntity
+            .ok()
+            .body(svcResp)
+    }
+
+    /**
+     * Create a new Contributor
+     *
+     * @param xFlowId A custom header that will be passed onto any further requests and can be used
+     * for diagnosing.
+     *
+     * @param idempotencyKey This unique identifier can be used to allow for clients to safely retry
+     * requests without accidentally performing the same operation twice. This is useful in cases such as
+     * network failures. This Id should remain the same for a given operation, so that the server can use
+     * it to recognise subsequent retries of the same request. Clients should be careful in using this
+     * key as subsequent requests with the same key return the same result. How the key is generated is
+     * up to the client, but it is suggested to use UUID (v4), or any other random string with enough
+     * entropy to avoid collisions.
+     *
+     */
+    @RequestMapping(
+        value = ["/contributors"],
+        produces = [],
+        method = [RequestMethod.POST],
+        consumes = ["application/json"]
+    )
+    fun post(
+        @RequestBody @Valid
+        contributor: Contributor,
+        @RequestHeader(value = "X-Flow-Id", required = false)
+        xFlowId: String?,
+        @RequestHeader(value = "Idempotency-Key", required = false)
+        idempotencyKey: String?
+    ): ResponseEntity<Unit> {
+        val svcResp = service.create(contributor, xFlowId, idempotencyKey)
+        val response = ResponseEntity.created(svcResp.first)
+        return response.build()
+    }
+
+    /**
+     * Get a Contributor by ID
+     *
+     * @param xFlowId A custom header that will be passed onto any further requests and can be used
+     * for diagnosing.
+     *
+     * @param id The unique id for the resource
+     * @param ifNoneMatch The RFC7232 If-None-Match header field in a request requires the server to
+     * only operate on the resource if it does not match any of the provided entity-tags. If the provided
+     * entity-tag is `*`, it is required that the resource does not exist at all.
+     *
+     * @param status Changes the behavior of GET | HEAD based on the value of the status property.
+     * Will return a 404 if the status property does not match the query parameter."
+     *
+     */
+    @RequestMapping(
+        value = ["/contributors/{id}"],
+        produces = ["application/json"],
+        method = [RequestMethod.GET]
+    )
+    fun getById(
+        @PathVariable(value = "id", required = true)
+        id: String,
+        @RequestParam(value = "status", required = false, defaultValue = "all")
+        status: StatusQueryParam,
+        @RequestHeader(value = "X-Flow-Id", required = false)
+        xFlowId: String?,
+        @RequestHeader(value = "If-None-Match", required = false)
+        ifNoneMatch: String?
+    ): ResponseEntity<Contributor> {
+        val svcResp = service.read(id, status, xFlowId, ifNoneMatch)
+        return ResponseEntity
+            .ok()
+            .body(svcResp)
+    }
+
+    /**
+     * Update an existing Contributor
+     *
+     * @param xFlowId A custom header that will be passed onto any further requests and can be used
+     * for diagnosing.
+     *
+     * @param id The unique id for the resource
+     * @param ifMatch The RFC7232 If-Match header field in a request requires the server to only
+     * operate on the resource that matches at least one of the provided entity-tags. This allows clients
+     * express a precondition that prevent the method from being applied if there have been any changes
+     * to the resource.
+     *
+     * @param idempotencyKey This unique identifier can be used to allow for clients to safely retry
+     * requests without accidentally performing the same operation twice. This is useful in cases such as
+     * network failures. This Id should remain the same for a given operation, so that the server can use
+     * it to recognise subsequent retries of the same request. Clients should be careful in using this
+     * key as subsequent requests with the same key return the same result. How the key is generated is
+     * up to the client, but it is suggested to use UUID (v4), or any other random string with enough
+     * entropy to avoid collisions.
+     *
+     */
+    @RequestMapping(
+        value = ["/contributors/{id}"],
+        produces = [],
+        method = [RequestMethod.PUT],
+        consumes = ["application/json"]
+    )
+    fun putById(
+        @RequestBody @Valid
+        contributor: Contributor,
+        @PathVariable(value = "id", required = true)
+        id: String,
+        @RequestHeader(value = "If-Match", required = true)
+        ifMatch: String,
+        @RequestHeader(value = "X-Flow-Id", required = false)
+        xFlowId: String?,
+        @RequestHeader(value = "Idempotency-Key", required = false)
+        idempotencyKey: String?
+    ): ResponseEntity<Unit> {
+        service.update(contributor, id, ifMatch, xFlowId, idempotencyKey)
+        return ResponseEntity.noContent().build()
+    }
+}
+
+@Controller
+@Validated
+@RequestMapping("")
+class OrganisationsController(
+    val service: OrganisationsService
+) {
+    /**
+     * Page through all the Organisation resources matching the query filters
+     *
+     * @param xFlowId A custom header that will be passed onto any further requests and can be used
+     * for diagnosing.
+     *
+     * @param includeInactive A query parameter to request both active and inactive entities
+     * @param limit Upper bound for number of results to be returned from query api. A default limit
+     * will be applied if this is not present
+     *
+     * @param cursor An encoded value which represents a point in the data from where pagination will
+     * commence. The pagination direction (either forward or backward) will also be encoded within the
+     * cursor value. The cursor value will always be generated server side
+     *
+     */
+    @RequestMapping(
+        value = ["/organisations"],
+        produces = ["application/json"],
+        method = [RequestMethod.GET]
+    )
+    fun get(
+        @Min(1)
+        @Max(100)
+        @RequestParam(value = "limit", required = false, defaultValue = "10")
+        limit: Int,
+        @RequestHeader(value = "X-Flow-Id", required = false)
+        xFlowId: String?,
+        @RequestParam(value = "include_inactive", required = false)
+        includeInactive: Boolean?,
+        @RequestParam(value = "cursor", required = false)
+        cursor: String?
+    ): ResponseEntity<OrganisationQueryResult> {
+        val svcResp = service.query(limit, xFlowId, includeInactive, cursor)
+        return ResponseEntity
+            .ok()
+            .body(svcResp)
+    }
+
+    /**
+     * Create a new Organisation
+     *
+     * @param xFlowId A custom header that will be passed onto any further requests and can be used
+     * for diagnosing.
+     *
+     * @param idempotencyKey This unique identifier can be used to allow for clients to safely retry
+     * requests without accidentally performing the same operation twice. This is useful in cases such as
+     * network failures. This Id should remain the same for a given operation, so that the server can use
+     * it to recognise subsequent retries of the same request. Clients should be careful in using this
+     * key as subsequent requests with the same key return the same result. How the key is generated is
+     * up to the client, but it is suggested to use UUID (v4), or any other random string with enough
+     * entropy to avoid collisions.
+     *
+     */
+    @RequestMapping(
+        value = ["/organisations"],
+        produces = [],
+        method = [RequestMethod.POST],
+        consumes = ["application/json"]
+    )
+    fun post(
+        @RequestBody @Valid
+        organisation: Organisation,
+        @RequestHeader(value = "X-Flow-Id", required = false)
+        xFlowId: String?,
+        @RequestHeader(value = "Idempotency-Key", required = false)
+        idempotencyKey: String?
+    ): ResponseEntity<Unit> {
+        val svcResp = service.create(organisation, xFlowId, idempotencyKey)
+        val response = ResponseEntity.created(svcResp.first)
+        return response.build()
+    }
+
+    /**
+     * Get a Organisation by ID
+     *
+     * @param xFlowId A custom header that will be passed onto any further requests and can be used
+     * for diagnosing.
+     *
+     * @param id The unique id for the resource
+     * @param ifNoneMatch The RFC7232 If-None-Match header field in a request requires the server to
+     * only operate on the resource if it does not match any of the provided entity-tags. If the provided
+     * entity-tag is `*`, it is required that the resource does not exist at all.
+     *
+     * @param status Changes the behavior of GET | HEAD based on the value of the status property.
+     * Will return a 404 if the status property does not match the query parameter."
+     *
+     */
+    @RequestMapping(
+        value = ["/organisations/{id}"],
+        produces = ["application/json"],
+        method = [RequestMethod.GET]
+    )
+    fun getById(
+        @PathVariable(value = "id", required = true)
+        id: String,
+        @RequestParam(value = "status", required = false, defaultValue = "all")
+        status: StatusQueryParam,
+        @RequestHeader(value = "X-Flow-Id", required = false)
+        xFlowId: String?,
+        @RequestHeader(value = "If-None-Match", required = false)
+        ifNoneMatch: String?
+    ): ResponseEntity<Organisation> {
+        val svcResp = service.read(id, status, xFlowId, ifNoneMatch)
+        return ResponseEntity
+            .ok()
+            .body(svcResp)
+    }
+
+    /**
+     * Update an existing Organisation
+     *
+     * @param xFlowId A custom header that will be passed onto any further requests and can be used
+     * for diagnosing.
+     *
+     * @param id The unique id for the resource
+     * @param ifMatch The RFC7232 If-Match header field in a request requires the server to only
+     * operate on the resource that matches at least one of the provided entity-tags. This allows clients
+     * express a precondition that prevent the method from being applied if there have been any changes
+     * to the resource.
+     *
+     * @param idempotencyKey This unique identifier can be used to allow for clients to safely retry
+     * requests without accidentally performing the same operation twice. This is useful in cases such as
+     * network failures. This Id should remain the same for a given operation, so that the server can use
+     * it to recognise subsequent retries of the same request. Clients should be careful in using this
+     * key as subsequent requests with the same key return the same result. How the key is generated is
+     * up to the client, but it is suggested to use UUID (v4), or any other random string with enough
+     * entropy to avoid collisions.
+     *
+     */
+    @RequestMapping(
+        value = ["/organisations/{id}"],
+        produces = [],
+        method = [RequestMethod.PUT],
+        consumes = ["application/json"]
+    )
+    fun putById(
+        @RequestBody @Valid
+        organisation: Organisation,
+        @PathVariable(value = "id", required = true)
+        id: String,
+        @RequestHeader(value = "If-Match", required = true)
+        ifMatch: String,
+        @RequestHeader(value = "X-Flow-Id", required = false)
+        xFlowId: String?,
+        @RequestHeader(value = "Idempotency-Key", required = false)
+        idempotencyKey: String?
+    ): ResponseEntity<Unit> {
+        service.update(organisation, id, ifMatch, xFlowId, idempotencyKey)
+        return ResponseEntity.noContent().build()
+    }
+}
+
+@Controller
+@Validated
+@RequestMapping("")
+class OrganisationsContributorsController(
+    val service: OrganisationsContributorsService
+) {
+    /**
+     * Page through all the Contributor resources for this parent Organisation matching the query
+     * filters
+     *
+     * @param xFlowId A custom header that will be passed onto any further requests and can be used
+     * for diagnosing.
+     *
+     * @param parentId The unique id for the parent resource
+     * @param includeInactive A query parameter to request both active and inactive entities
+     * @param limit Upper bound for number of results to be returned from query api. A default limit
+     * will be applied if this is not present
+     *
+     * @param cursor An encoded value which represents a point in the data from where pagination will
+     * commence. The pagination direction (either forward or backward) will also be encoded within the
+     * cursor value. The cursor value will always be generated server side
+     *
+     */
+    @RequestMapping(
+        value = ["/organisations/{parent-id}/contributors"],
+        produces = ["application/json"],
+        method = [RequestMethod.GET]
+    )
+    fun get(
+        @PathVariable(value = "parent-id", required = true)
+        parentId: String,
+        @Min(1)
+        @Max(100)
+        @RequestParam(value = "limit", required = false, defaultValue = "10")
+        limit: Int,
+        @RequestHeader(value = "X-Flow-Id", required = false)
+        xFlowId: String?,
+        @RequestParam(value = "include_inactive", required = false)
+        includeInactive: Boolean?,
+        @RequestParam(value = "cursor", required = false)
+        cursor: String?
+    ): ResponseEntity<ContributorQueryResult> {
+        val svcResp = service.query(parentId, limit, xFlowId, includeInactive, cursor)
+        return ResponseEntity
+            .ok()
+            .body(svcResp)
+    }
+
+    /**
+     * Get a Contributor for this Organisation by ID
+     *
+     * @param parentId The unique id for the parent resource
+     * @param xFlowId A custom header that will be passed onto any further requests and can be used
+     * for diagnosing.
+     *
+     * @param id The unique id for the resource
+     * @param ifNoneMatch The RFC7232 If-None-Match header field in a request requires the server to
+     * only operate on the resource if it does not match any of the provided entity-tags. If the provided
+     * entity-tag is `*`, it is required that the resource does not exist at all.
+     *
+     */
+    @RequestMapping(
+        value = ["/organisations/{parent-id}/contributors/{id}"],
+        produces = ["application/json"],
+        method = [RequestMethod.GET]
+    )
+    fun getById(
+        @PathVariable(value = "parent-id", required = true)
+        parentId: String,
+        @PathVariable(value = "id", required = true)
+        id: String,
+        @RequestHeader(value = "X-Flow-Id", required = false)
+        xFlowId: String?,
+        @RequestHeader(value = "If-None-Match", required = false)
+        ifNoneMatch: String?
+    ): ResponseEntity<Contributor> {
+        val svcResp = service.read(parentId, id, xFlowId, ifNoneMatch)
+        return ResponseEntity
+            .ok()
+            .body(svcResp)
+    }
+
+    /**
+     * Add an existing Contributor to this Organisation
+     *
+     * @param parentId The unique id for the parent resource
+     * @param xFlowId A custom header that will be passed onto any further requests and can be used
+     * for diagnosing.
+     *
+     * @param id The unique id for the resource
+     * @param ifMatch The RFC7232 If-Match header field in a request requires the server to only
+     * operate on the resource that matches at least one of the provided entity-tags. This allows clients
+     * express a precondition that prevent the method from being applied if there have been any changes
+     * to the resource.
+     *
+     * @param idempotencyKey This unique identifier can be used to allow for clients to safely retry
+     * requests without accidentally performing the same operation twice. This is useful in cases such as
+     * network failures. This Id should remain the same for a given operation, so that the server can use
+     * it to recognise subsequent retries of the same request. Clients should be careful in using this
+     * key as subsequent requests with the same key return the same result. How the key is generated is
+     * up to the client, but it is suggested to use UUID (v4), or any other random string with enough
+     * entropy to avoid collisions.
+     *
+     */
+    @RequestMapping(
+        value = ["/organisations/{parent-id}/contributors/{id}"],
+        produces = [],
+        method = [RequestMethod.PUT]
+    )
+    fun putById(
+        @PathVariable(value = "parent-id", required = true)
+        parentId: String,
+        @PathVariable(value = "id", required = true)
+        id: String,
+        @RequestHeader(value = "If-Match", required = true)
+        ifMatch: String,
+        @RequestHeader(value = "X-Flow-Id", required = false)
+        xFlowId: String?,
+        @RequestHeader(value = "Idempotency-Key", required = false)
+        idempotencyKey: String?
+    ): ResponseEntity<Unit> {
+        service.addSubresource(parentId, id, ifMatch, xFlowId, idempotencyKey)
+        return ResponseEntity.noContent().build()
+    }
+
+    /**
+     * Remove Contributor from this Organisation. Does not delete the underlying Contributor.
+     *
+     * @param parentId The unique id for the parent resource
+     * @param xFlowId A custom header that will be passed onto any further requests and can be used
+     * for diagnosing.
+     *
+     * @param id The unique id for the resource
+     */
+    @RequestMapping(
+        value = ["/organisations/{parent-id}/contributors/{id}"],
+        produces = [],
+        method = [RequestMethod.DELETE]
+    )
+    fun deleteById(
+        @PathVariable(value = "parent-id", required = true)
+        parentId: String,
+        @PathVariable(value = "id", required = true)
+        id: String,
+        @RequestHeader(value = "X-Flow-Id", required = false)
+        xFlowId: String?
+    ): ResponseEntity<Unit> {
+        service.removeSubresource(parentId, id, xFlowId)
+        return ResponseEntity.noContent().build()
+    }
+}
+
+@Controller
+@Validated
+@RequestMapping("")
+class RepositoriesController(
+    val service: RepositoriesService
+) {
+    /**
+     * Page through all the Repository resources matching the query filters
+     *
+     * @param xFlowId A custom header that will be passed onto any further requests and can be used
+     * for diagnosing.
+     *
+     * @param slug Filters resources by the [slug] property. Accepts comma-delimited values
+     *
+     * @param name Filters resources by the [name] property. Accepts comma-delimited values
+     *
+     * @param includeInactive A query parameter to request both active and inactive entities
+     * @param limit Upper bound for number of results to be returned from query api. A default limit
+     * will be applied if this is not present
+     *
+     * @param cursor An encoded value which represents a point in the data from where pagination will
+     * commence. The pagination direction (either forward or backward) will also be encoded within the
+     * cursor value. The cursor value will always be generated server side
+     *
+     */
+    @RequestMapping(
+        value = ["/repositories"],
+        produces = ["application/json"],
+        method = [RequestMethod.GET]
+    )
+    fun get(
+        @Min(1)
+        @Max(100)
+        @RequestParam(value = "limit", required = false, defaultValue = "10")
+        limit: Int,
+        @RequestHeader(value = "X-Flow-Id", required = false)
+        xFlowId: String?,
+        @Valid
+        @RequestParam(value = "slug", required = false)
+        slug: List<String>?,
+        @Valid
+        @RequestParam(value = "name", required = false)
+        name: List<String>?,
+        @RequestParam(value = "include_inactive", required = false)
+        includeInactive: Boolean?,
+        @RequestParam(value = "cursor", required = false)
+        cursor: String?
+    ): ResponseEntity<RepositoryQueryResult> {
+        val svcResp = service.query(limit, xFlowId, slug, name, includeInactive, cursor)
+        return ResponseEntity
+            .ok()
+            .body(svcResp)
+    }
+
+    /**
+     * Create a new Repository
+     *
+     * @param xFlowId A custom header that will be passed onto any further requests and can be used
+     * for diagnosing.
+     *
+     * @param idempotencyKey This unique identifier can be used to allow for clients to safely retry
+     * requests without accidentally performing the same operation twice. This is useful in cases such as
+     * network failures. This Id should remain the same for a given operation, so that the server can use
+     * it to recognise subsequent retries of the same request. Clients should be careful in using this
+     * key as subsequent requests with the same key return the same result. How the key is generated is
+     * up to the client, but it is suggested to use UUID (v4), or any other random string with enough
+     * entropy to avoid collisions.
+     *
+     */
+    @RequestMapping(
+        value = ["/repositories"],
+        produces = [],
+        method = [RequestMethod.POST],
+        consumes = ["application/json"]
+    )
+    fun post(
+        @RequestBody @Valid
+        repository: Repository,
+        @RequestHeader(value = "X-Flow-Id", required = false)
+        xFlowId: String?,
+        @RequestHeader(value = "Idempotency-Key", required = false)
+        idempotencyKey: String?
+    ): ResponseEntity<Unit> {
+        val svcResp = service.create(repository, xFlowId, idempotencyKey)
+        val response = ResponseEntity.created(svcResp.first)
+        return response.build()
+    }
+
+    /**
+     * Get a Repository by ID
+     *
+     * @param xFlowId A custom header that will be passed onto any further requests and can be used
+     * for diagnosing.
+     *
+     * @param id The unique id for the resource
+     * @param ifNoneMatch The RFC7232 If-None-Match header field in a request requires the server to
+     * only operate on the resource if it does not match any of the provided entity-tags. If the provided
+     * entity-tag is `*`, it is required that the resource does not exist at all.
+     *
+     * @param status Changes the behavior of GET | HEAD based on the value of the status property.
+     * Will return a 404 if the status property does not match the query parameter."
+     *
+     */
+    @RequestMapping(
+        value = ["/repositories/{id}"],
+        produces = ["application/json"],
+        method = [RequestMethod.GET]
+    )
+    fun getById(
+        @PathVariable(value = "id", required = true)
+        id: String,
+        @RequestParam(value = "status", required = false, defaultValue = "all")
+        status: StatusQueryParam,
+        @RequestHeader(value = "X-Flow-Id", required = false)
+        xFlowId: String?,
+        @RequestHeader(value = "If-None-Match", required = false)
+        ifNoneMatch: String?
+    ): ResponseEntity<Repository> {
+        val svcResp = service.read(id, status, xFlowId, ifNoneMatch)
+        return ResponseEntity
+            .ok()
+            .body(svcResp)
+    }
+
+    /**
+     * Update an existing Repository
+     *
+     * @param xFlowId A custom header that will be passed onto any further requests and can be used
+     * for diagnosing.
+     *
+     * @param id The unique id for the resource
+     * @param ifMatch The RFC7232 If-Match header field in a request requires the server to only
+     * operate on the resource that matches at least one of the provided entity-tags. This allows clients
+     * express a precondition that prevent the method from being applied if there have been any changes
+     * to the resource.
+     *
+     * @param idempotencyKey This unique identifier can be used to allow for clients to safely retry
+     * requests without accidentally performing the same operation twice. This is useful in cases such as
+     * network failures. This Id should remain the same for a given operation, so that the server can use
+     * it to recognise subsequent retries of the same request. Clients should be careful in using this
+     * key as subsequent requests with the same key return the same result. How the key is generated is
+     * up to the client, but it is suggested to use UUID (v4), or any other random string with enough
+     * entropy to avoid collisions.
+     *
+     */
+    @RequestMapping(
+        value = ["/repositories/{id}"],
+        produces = [],
+        method = [RequestMethod.PUT],
+        consumes = ["application/json"]
+    )
+    fun putById(
+        @RequestBody @Valid
+        repository: Repository,
+        @PathVariable(value = "id", required = true)
+        id: String,
+        @RequestHeader(value = "If-Match", required = true)
+        ifMatch: String,
+        @RequestHeader(value = "X-Flow-Id", required = false)
+        xFlowId: String?,
+        @RequestHeader(value = "Idempotency-Key", required = false)
+        idempotencyKey: String?
+    ): ResponseEntity<Unit> {
+        service.update(repository, id, ifMatch, xFlowId, idempotencyKey)
+        return ResponseEntity.noContent().build()
+    }
+}
+
+@Controller
+@Validated
+@RequestMapping("")
+class RepositoriesPullRequestsController(
+    val service: RepositoriesPullRequestsService
+) {
+    /**
+     * Page through all the PullRequest resources for this parent Repository matching the query
+     * filters
+     *
+     * @param xFlowId A custom header that will be passed onto any further requests and can be used
+     * for diagnosing.
+     *
+     * @param parentId The unique id for the parent resource
+     * @param includeInactive A query parameter to request both active and inactive entities
+     * @param limit Upper bound for number of results to be returned from query api. A default limit
+     * will be applied if this is not present
+     *
+     * @param cursor An encoded value which represents a point in the data from where pagination will
+     * commence. The pagination direction (either forward or backward) will also be encoded within the
+     * cursor value. The cursor value will always be generated server side
+     *
+     */
+    @RequestMapping(
+        value = ["/repositories/{parent-id}/pull-requests"],
+        produces = ["application/json"],
+        method = [RequestMethod.GET]
+    )
+    fun get(
+        @PathVariable(value = "parent-id", required = true)
+        parentId: String,
+        @Min(1)
+        @Max(100)
+        @RequestParam(value = "limit", required = false, defaultValue = "10")
+        limit: Int,
+        @RequestHeader(value = "X-Flow-Id", required = false)
+        xFlowId: String?,
+        @RequestParam(value = "include_inactive", required = false)
+        includeInactive: Boolean?,
+        @RequestParam(value = "cursor", required = false)
+        cursor: String?
+    ): ResponseEntity<PullRequestQueryResult> {
+        val svcResp = service.query(parentId, limit, xFlowId, includeInactive, cursor)
+        return ResponseEntity
+            .ok()
+            .body(svcResp)
+    }
+
+    /**
+     * Create a new PullRequest for this parent Repository
+     *
+     * @param parentId The unique id for the parent resource
+     * @param xFlowId A custom header that will be passed onto any further requests and can be used
+     * for diagnosing.
+     *
+     * @param idempotencyKey This unique identifier can be used to allow for clients to safely retry
+     * requests without accidentally performing the same operation twice. This is useful in cases such as
+     * network failures. This Id should remain the same for a given operation, so that the server can use
+     * it to recognise subsequent retries of the same request. Clients should be careful in using this
+     * key as subsequent requests with the same key return the same result. How the key is generated is
+     * up to the client, but it is suggested to use UUID (v4), or any other random string with enough
+     * entropy to avoid collisions.
+     *
+     */
+    @RequestMapping(
+        value = ["/repositories/{parent-id}/pull-requests"],
+        produces = [],
+        method = [RequestMethod.POST],
+        consumes = ["application/json"]
+    )
+    fun post(
+        @RequestBody @Valid
+        pullRequest: PullRequest,
+        @PathVariable(value = "parent-id", required = true)
+        parentId: String,
+        @RequestHeader(value = "X-Flow-Id", required = false)
+        xFlowId: String?,
+        @RequestHeader(value = "Idempotency-Key", required = false)
+        idempotencyKey: String?
+    ): ResponseEntity<Unit> {
+        val svcResp = service.create(pullRequest, parentId, xFlowId, idempotencyKey)
+        val response = ResponseEntity.created(svcResp.first)
+        return response.build()
+    }
+
+    /**
+     * Get a PullRequest for this Repository by ID
+     *
+     * @param parentId The unique id for the parent resource
+     * @param xFlowId A custom header that will be passed onto any further requests and can be used
+     * for diagnosing.
+     *
+     * @param id The unique id for the resource
+     * @param ifNoneMatch The RFC7232 If-None-Match header field in a request requires the server to
+     * only operate on the resource if it does not match any of the provided entity-tags. If the provided
+     * entity-tag is `*`, it is required that the resource does not exist at all.
+     *
+     */
+    @RequestMapping(
+        value = ["/repositories/{parent-id}/pull-requests/{id}"],
+        produces = ["application/json"],
+        method = [RequestMethod.GET]
+    )
+    fun getById(
+        @PathVariable(value = "parent-id", required = true)
+        parentId: String,
+        @PathVariable(value = "id", required = true)
+        id: String,
+        @RequestHeader(value = "X-Flow-Id", required = false)
+        xFlowId: String?,
+        @RequestHeader(value = "If-None-Match", required = false)
+        ifNoneMatch: String?
+    ): ResponseEntity<PullRequest> {
+        val svcResp = service.read(parentId, id, xFlowId, ifNoneMatch)
+        return ResponseEntity
+            .ok()
+            .body(svcResp)
+    }
+
+    /**
+     * Update the PullRequest owned by this Repository
+     *
+     * @param parentId The unique id for the parent resource
+     * @param xFlowId A custom header that will be passed onto any further requests and can be used
+     * for diagnosing.
+     *
+     * @param id The unique id for the resource
+     * @param ifMatch The RFC7232 If-Match header field in a request requires the server to only
+     * operate on the resource that matches at least one of the provided entity-tags. This allows clients
+     * express a precondition that prevent the method from being applied if there have been any changes
+     * to the resource.
+     *
+     * @param idempotencyKey This unique identifier can be used to allow for clients to safely retry
+     * requests without accidentally performing the same operation twice. This is useful in cases such as
+     * network failures. This Id should remain the same for a given operation, so that the server can use
+     * it to recognise subsequent retries of the same request. Clients should be careful in using this
+     * key as subsequent requests with the same key return the same result. How the key is generated is
+     * up to the client, but it is suggested to use UUID (v4), or any other random string with enough
+     * entropy to avoid collisions.
+     *
+     */
+    @RequestMapping(
+        value = ["/repositories/{parent-id}/pull-requests/{id}"],
+        produces = [],
+        method = [RequestMethod.PUT],
+        consumes = ["application/json"]
+    )
+    fun putById(
+        @RequestBody @Valid
+        pullRequest: PullRequest,
+        @PathVariable(value = "parent-id", required = true)
+        parentId: String,
+        @PathVariable(value = "id", required = true)
+        id: String,
+        @RequestHeader(value = "If-Match", required = true)
+        ifMatch: String,
+        @RequestHeader(value = "X-Flow-Id", required = false)
+        xFlowId: String?,
+        @RequestHeader(value = "Idempotency-Key", required = false)
+        idempotencyKey: String?
+    ): ResponseEntity<Unit> {
+        service.update(pullRequest, parentId, id, ifMatch, xFlowId, idempotencyKey)
+        return ResponseEntity.noContent().build()
+    }
+}
diff --git a/src/test/resources/examples/githubApi/models/Models.kt b/src/test/resources/examples/githubApi/models/Models.kt
new file mode 100644
index 0000000..a96ebe7
--- /dev/null
+++ b/src/test/resources/examples/githubApi/models/Models.kt
@@ -0,0 +1,364 @@
+package examples.githubApi.models
+
+import com.fasterxml.jackson.annotation.JsonAnySetter
+import com.fasterxml.jackson.annotation.JsonProperty
+import com.fasterxml.jackson.annotation.JsonValue
+import java.time.OffsetDateTime
+import javax.validation.Valid
+import javax.validation.constraints.NotNull
+import javax.validation.constraints.Size
+import kotlin.Any
+import kotlin.String
+import kotlin.collections.List
+import kotlin.collections.Map
+import kotlin.collections.MutableMap
+
+data class BulkEntityDetails(
+    @param:JsonProperty("entities")
+    @get:JsonProperty("entities")
+    @get:NotNull
+    @get:Valid
+    val entities: List<EntityDetails>
+) {
+    val properties: MutableMap<String, Any> = mutableMapOf()
+
+    @JsonAnySetter
+    fun set(name: String, value: Any) {
+        properties[name] = value
+    }
+}
+
+data class EntityDetails(
+    @param:JsonProperty("id")
+    @get:JsonProperty("id")
+    @get:NotNull
+    val id: String
+) {
+    val properties: MutableMap<String, Any> = mutableMapOf()
+
+    @JsonAnySetter
+    fun set(name: String, value: Any) {
+        properties[name] = value
+    }
+}
+
+data class EventResults(
+    @param:JsonProperty("change_events")
+    @get:JsonProperty("change_events")
+    @get:NotNull
+    @get:Size(min = 0)
+    @get:Valid
+    val changeEvents: List<Event>
+) {
+    val properties: MutableMap<String, Any> = mutableMapOf()
+
+    @JsonAnySetter
+    fun set(name: String, value: Any) {
+        properties[name] = value
+    }
+}
+
+data class Event(
+    @param:JsonProperty("entity_id")
+    @get:JsonProperty("entity_id")
+    @get:NotNull
+    val entityId: String,
+    @param:JsonProperty("data")
+    @get:JsonProperty("data")
+    @get:NotNull
+    val data: Map<String, Any>
+) {
+    val properties: MutableMap<String, Any> = mutableMapOf()
+
+    @JsonAnySetter
+    fun set(name: String, value: Any) {
+        properties[name] = value
+    }
+}
+
+data class ContributorQueryResult(
+    @param:JsonProperty("prev")
+    @get:JsonProperty("prev")
+    val prev: String? = null,
+    @param:JsonProperty("next")
+    @get:JsonProperty("next")
+    val next: String? = null,
+    @param:JsonProperty("items")
+    @get:JsonProperty("items")
+    @get:NotNull
+    @get:Size(min = 0)
+    @get:Valid
+    val items: List<Contributor>
+)
+
+data class OrganisationQueryResult(
+    @param:JsonProperty("prev")
+    @get:JsonProperty("prev")
+    val prev: String? = null,
+    @param:JsonProperty("next")
+    @get:JsonProperty("next")
+    val next: String? = null,
+    @param:JsonProperty("items")
+    @get:JsonProperty("items")
+    @get:NotNull
+    @get:Size(min = 0)
+    @get:Valid
+    val items: List<Organisation>
+)
+
+data class RepositoryQueryResult(
+    @param:JsonProperty("prev")
+    @get:JsonProperty("prev")
+    val prev: String? = null,
+    @param:JsonProperty("next")
+    @get:JsonProperty("next")
+    val next: String? = null,
+    @param:JsonProperty("items")
+    @get:JsonProperty("items")
+    @get:NotNull
+    @get:Size(min = 0)
+    @get:Valid
+    val items: List<Repository>
+)
+
+data class PullRequestQueryResult(
+    @param:JsonProperty("prev")
+    @get:JsonProperty("prev")
+    val prev: String? = null,
+    @param:JsonProperty("next")
+    @get:JsonProperty("next")
+    val next: String? = null,
+    @param:JsonProperty("items")
+    @get:JsonProperty("items")
+    @get:NotNull
+    @get:Size(min = 0)
+    @get:Valid
+    val items: List<PullRequest>
+)
+
+data class Contributor(
+    @param:JsonProperty("id")
+    @get:JsonProperty("id")
+    val id: String? = null,
+    @param:JsonProperty("audit_actor")
+    @get:JsonProperty("audit_actor")
+    val auditActor: String? = null,
+    @param:JsonProperty("created")
+    @get:JsonProperty("created")
+    val created: OffsetDateTime? = null,
+    @param:JsonProperty("created_by")
+    @get:JsonProperty("created_by")
+    val createdBy: String? = null,
+    @param:JsonProperty("created_by_uid")
+    @get:JsonProperty("created_by_uid")
+    val createdByUid: String? = null,
+    @param:JsonProperty("modified")
+    @get:JsonProperty("modified")
+    val modified: OffsetDateTime? = null,
+    @param:JsonProperty("modified_by")
+    @get:JsonProperty("modified_by")
+    val modifiedBy: String? = null,
+    @param:JsonProperty("modified_by_uid")
+    @get:JsonProperty("modified_by_uid")
+    val modifiedByUid: String? = null,
+    @param:JsonProperty("status")
+    @get:JsonProperty("status")
+    @get:NotNull
+    val status: Status,
+    @param:JsonProperty("etag")
+    @get:JsonProperty("etag")
+    val etag: String? = null,
+    @param:JsonProperty("username")
+    @get:JsonProperty("username")
+    @get:NotNull
+    val username: String,
+    @param:JsonProperty("name")
+    @get:JsonProperty("name")
+    val name: String? = null
+)
+
+enum class Status(
+    @JsonValue
+    val value: String
+) {
+    ACTIVE("active"),
+
+    INACTIVE("inactive");
+}
+
+data class Organisation(
+    @param:JsonProperty("id")
+    @get:JsonProperty("id")
+    val id: String? = null,
+    @param:JsonProperty("audit_actor")
+    @get:JsonProperty("audit_actor")
+    val auditActor: String? = null,
+    @param:JsonProperty("created")
+    @get:JsonProperty("created")
+    val created: OffsetDateTime? = null,
+    @param:JsonProperty("created_by")
+    @get:JsonProperty("created_by")
+    val createdBy: String? = null,
+    @param:JsonProperty("created_by_uid")
+    @get:JsonProperty("created_by_uid")
+    val createdByUid: String? = null,
+    @param:JsonProperty("modified")
+    @get:JsonProperty("modified")
+    val modified: OffsetDateTime? = null,
+    @param:JsonProperty("modified_by")
+    @get:JsonProperty("modified_by")
+    val modifiedBy: String? = null,
+    @param:JsonProperty("modified_by_uid")
+    @get:JsonProperty("modified_by_uid")
+    val modifiedByUid: String? = null,
+    @param:JsonProperty("status")
+    @get:JsonProperty("status")
+    @get:NotNull
+    val status: Status,
+    @param:JsonProperty("etag")
+    @get:JsonProperty("etag")
+    val etag: String? = null,
+    @param:JsonProperty("name")
+    @get:JsonProperty("name")
+    @get:NotNull
+    val name: String,
+    @param:JsonProperty("icon")
+    @get:JsonProperty("icon")
+    val icon: String? = null,
+    @param:JsonProperty("hooks")
+    @get:JsonProperty("hooks")
+    @get:Valid
+    val hooks: List<Webhook>? = null
+)
+
+data class Repository(
+    @param:JsonProperty("id")
+    @get:JsonProperty("id")
+    val id: String? = null,
+    @param:JsonProperty("audit_actor")
+    @get:JsonProperty("audit_actor")
+    val auditActor: String? = null,
+    @param:JsonProperty("created")
+    @get:JsonProperty("created")
+    val created: OffsetDateTime? = null,
+    @param:JsonProperty("created_by")
+    @get:JsonProperty("created_by")
+    val createdBy: String? = null,
+    @param:JsonProperty("created_by_uid")
+    @get:JsonProperty("created_by_uid")
+    val createdByUid: String? = null,
+    @param:JsonProperty("modified")
+    @get:JsonProperty("modified")
+    val modified: OffsetDateTime? = null,
+    @param:JsonProperty("modified_by")
+    @get:JsonProperty("modified_by")
+    val modifiedBy: String? = null,
+    @param:JsonProperty("modified_by_uid")
+    @get:JsonProperty("modified_by_uid")
+    val modifiedByUid: String? = null,
+    @param:JsonProperty("status")
+    @get:JsonProperty("status")
+    @get:NotNull
+    val status: Status,
+    @param:JsonProperty("etag")
+    @get:JsonProperty("etag")
+    val etag: String? = null,
+    @param:JsonProperty("slug")
+    @get:JsonProperty("slug")
+    @get:NotNull
+    val slug: String,
+    @param:JsonProperty("name")
+    @get:JsonProperty("name")
+    @get:NotNull
+    val name: String,
+    @param:JsonProperty("visibility")
+    @get:JsonProperty("visibility")
+    val visibility: Visibility? = null,
+    @param:JsonProperty("tags")
+    @get:JsonProperty("tags")
+    val tags: List<String>? = null
+)
+
+enum class Visibility(
+    @JsonValue
+    val value: String
+) {
+    PRIVATE("Private"),
+
+    PUBLIC("Public");
+}
+
+data class PullRequest(
+    @param:JsonProperty("id")
+    @get:JsonProperty("id")
+    val id: String? = null,
+    @param:JsonProperty("audit_actor")
+    @get:JsonProperty("audit_actor")
+    val auditActor: String? = null,
+    @param:JsonProperty("created")
+    @get:JsonProperty("created")
+    val created: OffsetDateTime? = null,
+    @param:JsonProperty("created_by")
+    @get:JsonProperty("created_by")
+    val createdBy: String? = null,
+    @param:JsonProperty("created_by_uid")
+    @get:JsonProperty("created_by_uid")
+    val createdByUid: String? = null,
+    @param:JsonProperty("modified")
+    @get:JsonProperty("modified")
+    val modified: OffsetDateTime? = null,
+    @param:JsonProperty("modified_by")
+    @get:JsonProperty("modified_by")
+    val modifiedBy: String? = null,
+    @param:JsonProperty("modified_by_uid")
+    @get:JsonProperty("modified_by_uid")
+    val modifiedByUid: String? = null,
+    @param:JsonProperty("status")
+    @get:JsonProperty("status")
+    @get:NotNull
+    val status: Status,
+    @param:JsonProperty("etag")
+    @get:JsonProperty("etag")
+    val etag: String? = null,
+    @param:JsonProperty("title")
+    @get:JsonProperty("title")
+    @get:NotNull
+    val title: String,
+    @param:JsonProperty("description")
+    @get:JsonProperty("description")
+    val description: String? = null,
+    @param:JsonProperty("author")
+    @get:JsonProperty("author")
+    @get:Valid
+    val author: Author? = null
+)
+
+data class Author(
+    @param:JsonProperty("name")
+    @get:JsonProperty("name")
+    val name: String? = null,
+    @param:JsonProperty("email")
+    @get:JsonProperty("email")
+    val email: String? = null
+)
+
+data class Webhook(
+    @param:JsonProperty("url")
+    @get:JsonProperty("url")
+    @get:NotNull
+    val url: String,
+    @param:JsonProperty("name")
+    @get:JsonProperty("name")
+    val name: String? = null
+)
+
+enum class StatusQueryParam(
+    @JsonValue
+    val value: String
+) {
+    ACTIVE("active"),
+
+    INACTIVE("inactive"),
+
+    ALL("all");
+}
diff --git a/src/test/resources/examples/githubApi/service/Services.kt b/src/test/resources/examples/githubApi/service/Services.kt
new file mode 100644
index 0000000..c2e045c
--- /dev/null
+++ b/src/test/resources/examples/githubApi/service/Services.kt
@@ -0,0 +1,179 @@
+package examples.githubApi.service
+
+import examples.githubApi.models.BulkEntityDetails
+import examples.githubApi.models.Contributor
+import examples.githubApi.models.ContributorQueryResult
+import examples.githubApi.models.EventResults
+import examples.githubApi.models.Organisation
+import examples.githubApi.models.OrganisationQueryResult
+import examples.githubApi.models.PullRequest
+import examples.githubApi.models.PullRequestQueryResult
+import examples.githubApi.models.Repository
+import examples.githubApi.models.RepositoryQueryResult
+import examples.githubApi.models.StatusQueryParam
+import java.net.URI
+import kotlin.Boolean
+import kotlin.Int
+import kotlin.Pair
+import kotlin.String
+import kotlin.collections.List
+
+interface InternalEventsService {
+    fun create(bulkEntityDetails: BulkEntityDetails): Pair<URI, EventResults?>
+}
+
+interface ContributorsService {
+    fun query(
+        limit: Int,
+        xFlowId: String?,
+        includeInactive: Boolean?,
+        cursor: String?
+    ): ContributorQueryResult
+
+    fun create(
+        contributor: Contributor,
+        xFlowId: String?,
+        idempotencyKey: String?
+    ): Pair<URI, Contributor?>
+
+    fun read(
+        id: String,
+        status: StatusQueryParam,
+        xFlowId: String?,
+        ifNoneMatch: String?
+    ): Contributor
+
+    fun update(
+        contributor: Contributor,
+        id: String,
+        ifMatch: String,
+        xFlowId: String?,
+        idempotencyKey: String?
+    ): Contributor
+}
+
+interface OrganisationsService {
+    fun query(
+        limit: Int,
+        xFlowId: String?,
+        includeInactive: Boolean?,
+        cursor: String?
+    ): OrganisationQueryResult
+
+    fun create(
+        organisation: Organisation,
+        xFlowId: String?,
+        idempotencyKey: String?
+    ): Pair<URI, Organisation?>
+
+    fun read(
+        id: String,
+        status: StatusQueryParam,
+        xFlowId: String?,
+        ifNoneMatch: String?
+    ): Organisation
+
+    fun update(
+        organisation: Organisation,
+        id: String,
+        ifMatch: String,
+        xFlowId: String?,
+        idempotencyKey: String?
+    ): Organisation
+}
+
+interface OrganisationsContributorsService {
+    fun query(
+        parentId: String,
+        limit: Int,
+        xFlowId: String?,
+        includeInactive: Boolean?,
+        cursor: String?
+    ): ContributorQueryResult
+
+    fun read(
+        parentId: String,
+        id: String,
+        xFlowId: String?,
+        ifNoneMatch: String?
+    ): Contributor
+
+    fun addSubresource(
+        parentId: String,
+        id: String,
+        ifMatch: String,
+        xFlowId: String?,
+        idempotencyKey: String?
+    )
+
+    fun removeSubresource(
+        parentId: String,
+        id: String,
+        xFlowId: String?
+    )
+}
+
+interface RepositoriesService {
+    fun query(
+        limit: Int,
+        xFlowId: String?,
+        slug: List<String>?,
+        name: List<String>?,
+        includeInactive: Boolean?,
+        cursor: String?
+    ): RepositoryQueryResult
+
+    fun create(
+        repository: Repository,
+        xFlowId: String?,
+        idempotencyKey: String?
+    ): Pair<URI, Repository?>
+
+    fun read(
+        id: String,
+        status: StatusQueryParam,
+        xFlowId: String?,
+        ifNoneMatch: String?
+    ): Repository
+
+    fun update(
+        repository: Repository,
+        id: String,
+        ifMatch: String,
+        xFlowId: String?,
+        idempotencyKey: String?
+    ): Repository
+}
+
+interface RepositoriesPullRequestsService {
+    fun query(
+        parentId: String,
+        limit: Int,
+        xFlowId: String?,
+        includeInactive: Boolean?,
+        cursor: String?
+    ): PullRequestQueryResult
+
+    fun create(
+        pullRequest: PullRequest,
+        parentId: String,
+        xFlowId: String?,
+        idempotencyKey: String?
+    ): Pair<URI, PullRequest?>
+
+    fun read(
+        parentId: String,
+        id: String,
+        xFlowId: String?,
+        ifNoneMatch: String?
+    ): PullRequest
+
+    fun update(
+        pullRequest: PullRequest,
+        parentId: String,
+        id: String,
+        ifMatch: String,
+        xFlowId: String?,
+        idempotencyKey: String?
+    ): PullRequest
+}
diff --git a/src/test/resources/examples/simpleRequestBody/controllers/Controllers.kt b/src/test/resources/examples/simpleRequestBody/controllers/Controllers.kt
index 5ff790f..b5d44c5 100644
--- a/src/test/resources/examples/simpleRequestBody/controllers/Controllers.kt
+++ b/src/test/resources/examples/simpleRequestBody/controllers/Controllers.kt
@@ -29,5 +29,5 @@ class UploadController(
         @RequestBody @Valid
         oASDocument: String
     ): ResponseEntity<String> = ResponseEntity
-        .ok(service.create(oASDocument))
+        .ok(service.create(oASDocument).second!!)
 }