Update example application

This commit is contained in:
Michael Bull 2018-11-01 12:13:15 +00:00
parent b6bb3aafaa
commit 97d0567489
8 changed files with 49 additions and 41 deletions

View File

@ -7,7 +7,7 @@ plugins {
}
application {
mainClassName = "io.ktor.server.netty.DevelopmentEngine"
mainClassName = "io.ktor.server.netty.EngineMain"
}
repositories {

View File

@ -1,2 +1,2 @@
ktorVersion=0.9.0
ktorVersion=1.0.0-beta-3
logbackVersion=1.2.3

View File

@ -27,6 +27,7 @@ import com.github.michaelbull.result.mapBoth
import com.github.michaelbull.result.mapError
import com.github.michaelbull.result.toResultOr
import io.ktor.application.Application
import io.ktor.application.ApplicationCall
import io.ktor.application.call
import io.ktor.application.install
import io.ktor.features.CallLogging
@ -35,12 +36,12 @@ import io.ktor.features.ContentNegotiation
import io.ktor.features.DefaultHeaders
import io.ktor.gson.gson
import io.ktor.http.HttpStatusCode
import io.ktor.http.Parameters
import io.ktor.request.receive
import io.ktor.response.respond
import io.ktor.routing.get
import io.ktor.routing.post
import io.ktor.routing.routing
import io.ktor.util.ValuesMap
fun Application.main() {
install(DefaultHeaders)
@ -52,30 +53,35 @@ fun Application.main() {
}
}
routing {
get("/customers/{id}") {
readId(call.parameters)
call.parameters.readId()
.andThen(CustomerId.Companion::create)
.andThen(CustomerService::getById)
.mapError(::messageToResponse)
.mapBoth(
{ call.respond(HttpStatusCode.OK, CustomerDto.from(it)) },
{ call.respond(it.first, it.second) }
success = { customer ->
call.respond(HttpStatusCode.OK, CustomerDto.from(customer))
},
failure = { (status, message) ->
call.respond(status, message)
}
)
}
post("/customers/{id}") {
readId(call.parameters)
.andThen {
call.parameters.readId()
.andThen { id ->
val dto = call.receive<CustomerDto>()
dto.id = it
dto.id = id
Ok(dto)
}
.andThen(Customer.Companion::from)
.andThen(CustomerService::upsert)
.mapError(::messageToResponse)
.mapBoth(
{ event ->
success = { event ->
if (event == null) {
call.respond(HttpStatusCode.NotModified)
} else {
@ -83,15 +89,17 @@ fun Application.main() {
call.respond(status, message)
}
},
{ call.respond(it.first, it.second) }
failure = { (status, message) ->
call.respond(status, message)
}
)
}
}
}
private fun readId(values: ValuesMap): Result<Long, DomainMessage> {
return values["id"]
private fun Parameters.readId(): Result<Long, DomainMessage> {
return this["id"]
?.toLongOrNull()
.toResultOr { CustomerRequired }
}

View File

@ -2,18 +2,22 @@ package com.github.michaelbull.result.example.model.domain
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
data class EmailAddress(
val address: String
) {
companion object {
private val pattern = ".+@.+\\..+".toRegex() // crude validation
private const val MAX_LENGTH = 20
private val PATTERN = ".+@.+\\..+".toRegex() // crude validation
fun create(address: String?) = when {
address == null || address.isBlank() -> Err(EmailRequired)
address.length > 20 -> Err(EmailTooLong)
!address.matches(pattern) -> Err(EmailInvalid)
else -> Ok(EmailAddress(address))
fun create(address: String?): Result<EmailAddress, DomainMessage> {
return when {
address.isNullOrBlank() -> Err(EmailRequired)
address.length > MAX_LENGTH -> Err(EmailTooLong)
!address.matches(PATTERN) -> Err(EmailInvalid)
else -> Ok(EmailAddress(address))
}
}
}
}

View File

@ -2,18 +2,23 @@ package com.github.michaelbull.result.example.model.domain
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
data class PersonalName(
val first: String,
val last: String
) {
companion object {
fun create(first: String?, last: String?) = when {
first == null || first.isBlank() -> Err(FirstNameRequired)
last == null || last.isBlank() -> Err(LastNameRequired)
first.length > 10 -> Err(FirstNameTooLong)
last.length > 10 -> Err(LastNameTooLong)
else -> Ok(PersonalName(first, last))
private const val MAX_LENGTH = 10
fun create(first: String?, last: String?): Result<PersonalName, DomainMessage> {
return when {
first.isNullOrBlank() -> Err(FirstNameRequired)
last.isNullOrBlank() -> Err(LastNameRequired)
first.length > MAX_LENGTH -> Err(FirstNameTooLong)
last.length > MAX_LENGTH -> Err(LastNameTooLong)
else -> Ok(PersonalName(first, last))
}
}
}
}

View File

@ -1,7 +1,5 @@
package com.github.michaelbull.result.example.service
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import com.github.michaelbull.result.andThen
import com.github.michaelbull.result.example.model.domain.Customer
@ -14,6 +12,7 @@ import com.github.michaelbull.result.example.model.domain.DomainMessage
import com.github.michaelbull.result.example.model.domain.EmailAddressChanged
import com.github.michaelbull.result.example.model.entity.CustomerEntity
import com.github.michaelbull.result.map
import com.github.michaelbull.result.mapAll
import com.github.michaelbull.result.mapBoth
import com.github.michaelbull.result.mapError
import com.github.michaelbull.result.toResultOr
@ -25,26 +24,18 @@ object CustomerService {
fun getAll(): Result<Collection<Customer>, DomainMessage> {
return Result.of(repository::findAll)
.mapError(::exceptionToDomainMessage)
.andThen { result: Collection<CustomerEntity> ->
Ok(result.map {
val customer = Customer.from(it)
when (customer) {
is Ok -> customer.value
is Err -> return customer
}
})
}
.mapAll(Customer.Companion::from)
}
fun getById(id: CustomerId): Result<Customer, DomainMessage> {
return getAll().andThen { it.findCustomer(id) }
return getAll().andThen { customers -> customers.findCustomer(id) }
}
fun upsert(customer: Customer): Result<DomainMessage?, DomainMessage> {
val entity = CustomerEntity.from(customer)
return getById(customer.id).mapBoth(
{ existing -> updateCustomer(entity, existing, customer) },
{ createCustomer(entity) }
success = { existing -> updateCustomer(entity, existing, customer) },
failure = { createCustomer(entity) }
)
}

View File

@ -1,6 +1,6 @@
ktor {
deployment {
port = 9000
port = 9090
}
application {

View File

@ -35,7 +35,7 @@ inline infix fun <V, E> Result<V, E>.orElse(transform: (E) -> Result<V, E>): Res
* otherwise this [Ok].
*/
inline infix fun <V, E> Result<V, E>.recover(transform: (E) -> V): Ok<V> {
return when(this) {
return when (this) {
is Ok -> this
is Err -> Ok(transform(error))
}