AnyEndpoint

sealed interface AnyEndpoint(source)

A specific HTTP method in a resource.

All instances of this interface must be immutable.

Example

Endpoints are declared in the body of a resource, by calling one of the HTTP method names.

object User : DynamicResource<Users>("user", parent = Users) {

// GET …/{user}
val get by get()
.response<UserDto>()

// POST …/{user}
val create by post()
.request<UserCreationDto>()
.response<UserDto>()

// PATCH …/{user}
val edit by patch()
.request<UserEditionDto>()
.response<UserDto>()

// DELETE …/{user}
val delete by delete()

}

Configuration options on an endpoint during its creation are listed in AnyEndpoint.Builder.

The trick

To represent endpoints in a type-safe manner, they must declare their information as type parameters. For example, the requestType and responseType must appear in type parameters.

However, adding a type parameter to a class in Kotlin is a source-incompatible change: all usages of a type must explicitly say which type parameters are used.

Because endpoints are the core of this library, and we expect to add more information to endpoints in the future, we know that we will want to add new type parameters, which would break source compatibility for all users!

To avoid this, the only part of the public API is AnyEndpoint, which does not declare the type parameters at all. AnyEndpoint is therefore safe to use in user code. However, it lacks typing information (though the exact types are still available as reflection entities in requestType and responseType). AnyEndpoint is a sealed interface with a single implementation that is hidden in the library, Endpoint.

When we declare an endpoint, we do not specify the type explicitly:

val list by get()
.response<List<UserDto>>()

Because we did not declare a type, Kotlin infers it to the real type returned by the function, even though it is hidden. In fact, it infers the type to be:

val list: Endpoint<Unit, UserDto, Parameters.Empty> by get()
.response<List<UserDto>>()

As you can see, all type parameters are indeed declared. You can see this by enabling inlay hints in IntelliJ.

However, if you try to write the type yourself, you will see that it will not compile, because Endpoint cannot be accessed. This is an intended protection: you can create a value of type Endpoint, but you cannot write the type Endpoint in your code, because the type Endpoint will change in source-incompatible ways in the future. Writing a value of type Endpoint without the type appearance in your code is safe, so it is allowed.

The type you are allowed to use is AnyEndpoint, this interface:

val list: AnyEndpoint by get()
.response<List<UserDto>>()

However, this interface doesn't have type parameters, so this removes all type safety. As a consequence, if you do this, none of the other functions of this library will compile for this endpoint, as it cannot be used safely.

This interface is still useful because you may want to create operations that act on any endpoint without caring about the type of a specific one. For example, you may create a function that accepts an endpoint and prints information about it:

fun AnyEndpoint.print() {
println("$method $fullSlug")
println(" - Input: $requestType")
println(" - Output: $responseType")
}

As a rule of thumb:

  • Endpoints declaration should not have an explicit type declaration, and should instead rely on the inferred type.

  • If you want to process endpoints, use AnyEndpoint.

  • If you really absolutely must use type parameters, you can force access to Endpoint via a suppression. Note, however, that this guarantees that your code will stop compiling in future versions of this library.

Types

Link copied to clipboard
sealed interface Builder

The super-type for the endpoint declaration syntax.

Properties

Link copied to clipboard

Constructor for query parameters.

Link copied to clipboard

The complete URL for this endpoint, starting at its RootResource.

Link copied to clipboard
abstract val method: HttpMethod

The HttpMethod used when invoking this Endpoint.

Link copied to clipboard
abstract val path: Path.Segment?

Optional path extension from the Resource this endpoint is a part of.

Link copied to clipboard
abstract val requestType: KClass<*>

The type of the body went the client sends data to the server.

Link copied to clipboard
abstract val resource: Resource
Link copied to clipboard
abstract val responseType: KClass<*>

The type of the body when the server responds to a client.