Skip to content

PACK-Solutions/ktor-media-type-versioning

Repository files navigation

ktor-media-type-versioning

A Ktor plugin for versioning APIs using the Accept header (Media Type Versioning).

Overview

This plugin allows you to version your API routes based on the Accept header. It supports:

  • Automatic validation of requested vendor media types.
  • Route-based versioning using a clean DSL.
  • Default versions for generic Accept headers (e.g., */*, application/json).
  • Configurable vendor prefixes and generic types.

Installation

The MediaTypeVersioning plugin is route-scoped. You can install it inside your routing block:

routing {
    mediaTypeVersioning(
        versions = listOf(
            Versions.Contract.v1,
            Versions.Contract.v2
        ),
        defaultVersion = Versions.Contract.v2,
        vendorPrefix = "vnd.com.ps" // Optional, default is "vnd.com.ps"
    ) {
        // Versioned routes go here
    }
}

Defining Versions

It's recommended to define your media types in an object for consistency:

object Versions {
    object Contract {
        val v1 = ContentType.parse("application/vnd.com.ps.contracts.v1+json")
        val v2 = ContentType.parse("application/vnd.com.ps.contracts.v2+json")
    }
}

Usage

Use the version selector to define version-specific logic for a route:

route("/contracts/{id}") {
    version(Versions.Contract.v1) {
        get {
            val id = call.parameters["id"] ?: "0"
            call.respond(ContractV1(id, "Contract V1"))
        }
    }

    version(Versions.Contract.v2, isDefault = true) {
        get {
            val id = call.parameters["id"] ?: "0"
            call.respond(ContractV2(id, "Full Contract V2"))
        }
    }
}

Configuration Options

The mediaTypeVersioning block accepts several parameters:

Parameter Type Description
versions List<ContentType> List of supported media types.
defaultVersion ContentType? The version to use when a generic Accept header is provided.
vendorPrefix String? The prefix used to identify vendor-specific media types (default: vnd.com.ps).
genericTypes List<String>? List of types considered "generic" (default: */*, application/json).

Behavior

  1. Specific Version Requested: If the client sends Accept: application/vnd.com.ps.v1+json, the corresponding version(V1) block is executed.
  2. Generic Request: If the client sends Accept: application/json or */*, the block marked with isDefault = true (within version) or the defaultVersion configured in the plugin is used.
  3. Unsupported Version: If the client sends a vendor media type that is not in the versions list, the plugin responds with 406 Not Acceptable.

Default Version Handling

When a client sends a generic Accept header (like */* or application/json), the plugin needs to know which version of the route to execute and what Content-Type to return.

1. Route Selection (isDefault = true)

In your routing tree, you can mark a specific version as the default using isDefault = true:

version(Versions.Contract.v2, isDefault = true) {
    // This block will be executed for generic Accept headers
}

If multiple routes match the path but different versions, the one with isDefault = true will be selected when the Accept header is generic.

2. Response Header (defaultVersion)

While isDefault = true handles routing, the defaultVersion parameter in the mediaTypeVersioning configuration handles the response header:

mediaTypeVersioning(
    versions = listOf(/* ... */),
    defaultVersion = Versions.Contract.v2
) { /* ... */ }

When a generic request is handled, the plugin will automatically add the Content-Type header to the response with the value specified in defaultVersion. This ensures the client knows which version of the resource it actually received.


Features

Here's a list of features included in this project:

Name Description
Routing Provides a structured routing DSL
Content Negotiation Provides automatic content conversion according to Content-Type and Accept headers

Building & Running

To build or run the project, use one of the following tasks:

Task Description
./gradlew test Run the tests
./gradlew build Build everything
./gradlew buildFatJar Build an executable JAR of the server with all dependencies included
./gradlew buildImage Build the docker image to use with the fat JAR
./gradlew publishImageToLocalRegistry Publish the docker image locally
./gradlew run Run the server
./gradlew runDocker Run using the local docker image

If the server starts successfully, you'll see the following output:

2024-12-04 14:32:45.584 [main] INFO  Application - Application started in 0.303 seconds.
2024-12-04 14:32:45.682 [main] INFO  Application - Responding at http://0.0.0.0:8080

About

A Ktor plugin for versioning APIs using the `Accept` header (Media Type Versioning).

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages