Generate a Server

The server generator targets Spring Web MVC.

Use it when you want Spring controller interfaces to stay aligned with the same tapik contract that also drives clients and documentation.

Use dev.akif.tapik:spring-webmvc to enable it.

The spring-webmvc module carries the shared Spring HTTP mapping helpers from dev.akif.tapik:common-spring transitively, so typical application builds only need the spring-webmvc dependency.

Enable the generator

dependencies {
    implementation("dev.akif.tapik:spring-webmvc:<version>")
}

tapik {
    springWebMvc { }
}

If you want a different generated suffix:

tapik {
    springWebMvc {
        serverSuffix("Api")
    }
}

What gets generated

The generator contributes Kotlin source files under:

  • build/generated/sources/tapik/main/kotlin/<package>/…​

For each source file of endpoint declarations, tapik generates:

  • an aggregate server interface such as CatalogApiServer,

  • one nested server contract per endpoint,

  • one generated response model per endpoint.

import dev.akif.tapik.spring.toResponseEntity

interface CatalogApiServer : CatalogApiEndpoints.GetProduct.Server

interface CatalogApiEndpoints {
    interface GetProduct {
        sealed interface Response : TapikResponse {
            data class Ok(
                val body: ProductView
            ) : Response

            data class NotFound(
                val body: ProblemDetails
            ) : Response
        }

        interface Server {
            @org.springframework.web.bind.annotation.GetMapping(path = ["/api/v1/products/{productId}"])
            fun getProduct(
                @org.springframework.web.bind.annotation.PathVariable(name = "productId") productId: String,
                @org.springframework.web.bind.annotation.RequestHeader(name = "X-Request-Id") xRequestId: java.util.UUID
            ): Response
        }
    }
}

fun CatalogApiEndpoints.GetProduct.Response.toResponseEntity(): org.springframework.http.ResponseEntity<kotlin.Any> =
    when (this) {
        is CatalogApiEndpoints.GetProduct.Response.Ok ->
            toResponseEntity(
                status = dev.akif.tapik.Status.Ok,
                headers = kotlin.collections.emptyMap(),
                mediaType = CatalogApi.getProduct.outputs.item1.body.mediaType,
                body = CatalogApi.getProduct.outputs.item1.body.bytes(body)
            )

        is CatalogApiEndpoints.GetProduct.Response.NotFound ->
            toResponseEntity(
                status = dev.akif.tapik.Status.NotFound,
                headers = kotlin.collections.emptyMap(),
                mediaType = CatalogApi.getProduct.outputs.item2.body.mediaType,
                body = CatalogApi.getProduct.outputs.item2.body.bytes(body)
            )
    }

@org.springframework.web.bind.annotation.ControllerAdvice
class CatalogApiServerResponseAdvice : dev.akif.tapik.spring.TapikResponseBodyAdvice() {
    override fun supportsTapikResponseType(responseType: Class<*>): kotlin.Boolean =
        CatalogApiEndpoints.GetProduct.Response::class.java.isAssignableFrom(responseType)

    override fun toResponseEntity(
        body: dev.akif.tapik.TapikResponse
    ): org.springframework.http.ResponseEntity<kotlin.Any> =
        when (body) {
            is CatalogApiEndpoints.GetProduct.Response -> body.toResponseEntity()
            else -> kotlin.error("Unsupported TapikResponse type: " + body::class.qualifiedName)
        }
}

The exact generated code varies with the contract, but the behavior is consistent:

  • method and path come from the endpoint definition,

  • path variables, query parameters, required headers, and request bodies map to the matching Spring annotations,

  • input headers declared with fixed HeaderValues(…​) stay in the contract and docs but are not exposed as server method parameters,

  • multi-output endpoints keep distinct response variants,

  • generated response advice turns those variants into Spring responses by using Tapik codecs and endpoint metadata.

How Spring mapping is derived

The generator uses the endpoint contract to choose the mapping form:

  • standard verbs such as GET and POST use the corresponding Spring mapping annotations,

  • nonstandard verbs such as HEAD fall back to @RequestMapping with an explicit request method,

  • media types come from the declared input and output bodies,

  • fixed response headers and typed bodies are reflected in the generated response model.

Implement the generated contract

Your application still owns the controller implementation:

@org.springframework.web.bind.annotation.RestController
class CatalogController : CatalogApiServer {
    override fun getProduct(
        productId: String,
        xRequestId: java.util.UUID
    ): CatalogApiEndpoints.GetProduct.Response = TODO()
}

This keeps the transport contract generated while leaving your business logic, persistence, and orchestration code handwritten.

Boundaries

The server generator does not:

  • register controllers for you,

  • implement endpoint logic,

  • turn tapik into a full Spring application framework.

It generates the transport-facing interface and response model. Your application provides the actual behavior.