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 { application {
mainClassName = "io.ktor.server.netty.DevelopmentEngine" mainClassName = "io.ktor.server.netty.EngineMain"
} }
repositories { repositories {

View File

@ -1,2 +1,2 @@
ktorVersion=0.9.0 ktorVersion=1.0.0-beta-3
logbackVersion=1.2.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.mapError
import com.github.michaelbull.result.toResultOr import com.github.michaelbull.result.toResultOr
import io.ktor.application.Application import io.ktor.application.Application
import io.ktor.application.ApplicationCall
import io.ktor.application.call import io.ktor.application.call
import io.ktor.application.install import io.ktor.application.install
import io.ktor.features.CallLogging import io.ktor.features.CallLogging
@ -35,12 +36,12 @@ import io.ktor.features.ContentNegotiation
import io.ktor.features.DefaultHeaders import io.ktor.features.DefaultHeaders
import io.ktor.gson.gson import io.ktor.gson.gson
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.http.Parameters
import io.ktor.request.receive import io.ktor.request.receive
import io.ktor.response.respond import io.ktor.response.respond
import io.ktor.routing.get import io.ktor.routing.get
import io.ktor.routing.post import io.ktor.routing.post
import io.ktor.routing.routing import io.ktor.routing.routing
import io.ktor.util.ValuesMap
fun Application.main() { fun Application.main() {
install(DefaultHeaders) install(DefaultHeaders)
@ -52,30 +53,35 @@ fun Application.main() {
} }
} }
routing { routing {
get("/customers/{id}") { get("/customers/{id}") {
readId(call.parameters) call.parameters.readId()
.andThen(CustomerId.Companion::create) .andThen(CustomerId.Companion::create)
.andThen(CustomerService::getById) .andThen(CustomerService::getById)
.mapError(::messageToResponse) .mapError(::messageToResponse)
.mapBoth( .mapBoth(
{ call.respond(HttpStatusCode.OK, CustomerDto.from(it)) }, success = { customer ->
{ call.respond(it.first, it.second) } call.respond(HttpStatusCode.OK, CustomerDto.from(customer))
},
failure = { (status, message) ->
call.respond(status, message)
}
) )
} }
post("/customers/{id}") { post("/customers/{id}") {
readId(call.parameters) call.parameters.readId()
.andThen { .andThen { id ->
val dto = call.receive<CustomerDto>() val dto = call.receive<CustomerDto>()
dto.id = it dto.id = id
Ok(dto) Ok(dto)
} }
.andThen(Customer.Companion::from) .andThen(Customer.Companion::from)
.andThen(CustomerService::upsert) .andThen(CustomerService::upsert)
.mapError(::messageToResponse) .mapError(::messageToResponse)
.mapBoth( .mapBoth(
{ event -> success = { event ->
if (event == null) { if (event == null) {
call.respond(HttpStatusCode.NotModified) call.respond(HttpStatusCode.NotModified)
} else { } else {
@ -83,15 +89,17 @@ fun Application.main() {
call.respond(status, message) 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> { private fun Parameters.readId(): Result<Long, DomainMessage> {
return values["id"] return this["id"]
?.toLongOrNull() ?.toLongOrNull()
.toResultOr { CustomerRequired } .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.Err
import com.github.michaelbull.result.Ok import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
data class EmailAddress( data class EmailAddress(
val address: String val address: String
) { ) {
companion object { 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 { fun create(address: String?): Result<EmailAddress, DomainMessage> {
address == null || address.isBlank() -> Err(EmailRequired) return when {
address.length > 20 -> Err(EmailTooLong) address.isNullOrBlank() -> Err(EmailRequired)
!address.matches(pattern) -> Err(EmailInvalid) address.length > MAX_LENGTH -> Err(EmailTooLong)
else -> Ok(EmailAddress(address)) !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.Err
import com.github.michaelbull.result.Ok import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
data class PersonalName( data class PersonalName(
val first: String, val first: String,
val last: String val last: String
) { ) {
companion object { companion object {
fun create(first: String?, last: String?) = when { private const val MAX_LENGTH = 10
first == null || first.isBlank() -> Err(FirstNameRequired)
last == null || last.isBlank() -> Err(LastNameRequired) fun create(first: String?, last: String?): Result<PersonalName, DomainMessage> {
first.length > 10 -> Err(FirstNameTooLong) return when {
last.length > 10 -> Err(LastNameTooLong) first.isNullOrBlank() -> Err(FirstNameRequired)
else -> Ok(PersonalName(first, last)) 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 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.Result
import com.github.michaelbull.result.andThen import com.github.michaelbull.result.andThen
import com.github.michaelbull.result.example.model.domain.Customer 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.domain.EmailAddressChanged
import com.github.michaelbull.result.example.model.entity.CustomerEntity import com.github.michaelbull.result.example.model.entity.CustomerEntity
import com.github.michaelbull.result.map import com.github.michaelbull.result.map
import com.github.michaelbull.result.mapAll
import com.github.michaelbull.result.mapBoth import com.github.michaelbull.result.mapBoth
import com.github.michaelbull.result.mapError import com.github.michaelbull.result.mapError
import com.github.michaelbull.result.toResultOr import com.github.michaelbull.result.toResultOr
@ -25,26 +24,18 @@ object CustomerService {
fun getAll(): Result<Collection<Customer>, DomainMessage> { fun getAll(): Result<Collection<Customer>, DomainMessage> {
return Result.of(repository::findAll) return Result.of(repository::findAll)
.mapError(::exceptionToDomainMessage) .mapError(::exceptionToDomainMessage)
.andThen { result: Collection<CustomerEntity> -> .mapAll(Customer.Companion::from)
Ok(result.map {
val customer = Customer.from(it)
when (customer) {
is Ok -> customer.value
is Err -> return customer
}
})
}
} }
fun getById(id: CustomerId): Result<Customer, DomainMessage> { 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> { fun upsert(customer: Customer): Result<DomainMessage?, DomainMessage> {
val entity = CustomerEntity.from(customer) val entity = CustomerEntity.from(customer)
return getById(customer.id).mapBoth( return getById(customer.id).mapBoth(
{ existing -> updateCustomer(entity, existing, customer) }, success = { existing -> updateCustomer(entity, existing, customer) },
{ createCustomer(entity) } failure = { createCustomer(entity) }
) )
} }

View File

@ -1,6 +1,6 @@
ktor { ktor {
deployment { deployment {
port = 9000 port = 9090
} }
application { 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]. * otherwise this [Ok].
*/ */
inline infix fun <V, E> Result<V, E>.recover(transform: (E) -> V): Ok<V> { inline infix fun <V, E> Result<V, E>.recover(transform: (E) -> V): Ok<V> {
return when(this) { return when (this) {
is Ok -> this is Ok -> this
is Err -> Ok(transform(error)) is Err -> Ok(transform(error))
} }