Implement contracts
This commit is contained in:
parent
31014d1a0c
commit
efdc50eb1d
33 changed files with 879 additions and 173 deletions
|
|
@ -1,6 +1,7 @@
|
|||
package lu.foyer
|
||||
|
||||
import lu.foyer.clients.*
|
||||
import lu.foyer.contracts.*
|
||||
import zio.*
|
||||
import zio.Console.*
|
||||
import zio.http.*
|
||||
|
|
@ -10,17 +11,26 @@ import zio.http.endpoint.*
|
|||
import zio.http.endpoint.openapi.OpenAPIGen
|
||||
import zio.http.endpoint.openapi.SwaggerUI
|
||||
import zio.schema.*
|
||||
import zio.http.Middleware.cors
|
||||
import zio.http.Middleware.CorsConfig
|
||||
import zio.http.Header.AccessControlAllowOrigin
|
||||
|
||||
import java.net.URI
|
||||
import java.time.LocalDate
|
||||
import java.util.UUID
|
||||
|
||||
object HttpServer:
|
||||
|
||||
val corsConfig = CorsConfig(_ => Some(AccessControlAllowOrigin.All))
|
||||
|
||||
def routes =
|
||||
for
|
||||
client <- ZIO.service[ClientController]
|
||||
openAPI = OpenAPIGen.fromEndpoints(client.endpoints)
|
||||
yield client.routes @@ Middleware.debug ++ SwaggerUI.routes("docs" / "openapi", openAPI)
|
||||
client <- ZIO.service[ClientController]
|
||||
contract <- ZIO.service[ContractController]
|
||||
openAPI = OpenAPIGen.fromEndpoints(client.endpoints ++ contract.endpoints)
|
||||
yield (client.routes ++ contract.routes)
|
||||
@@ cors(corsConfig) @@ Middleware.debug
|
||||
++ SwaggerUI.routes("docs" / "openapi", openAPI)
|
||||
|
||||
object App extends ZIOAppDefault:
|
||||
val app =
|
||||
|
|
@ -31,9 +41,17 @@ object App extends ZIOAppDefault:
|
|||
|
||||
override def run = app.provide(
|
||||
CommandEngine.layer[ClientCommand, ClientEvent, ClientState],
|
||||
CommandEngine.layer[ContractCommand, ContractEvent, ContractState],
|
||||
ClientHandlers.layer,
|
||||
ContractHandlers.layer,
|
||||
ClientReducer.layer,
|
||||
ContractReducer.layer,
|
||||
ClientEventRepositoryInMemory.layer,
|
||||
ContractEventRepositoryInMemory.layer,
|
||||
ClientStateRepositoryInMemory.layer,
|
||||
ClientController.layer
|
||||
ContractStateRepositoryInMemory.layer,
|
||||
ClientController.layer,
|
||||
ContractController.layer,
|
||||
PremiumServiceImpl.layer,
|
||||
EmployeeServiceImpl.layer
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package lu.foyer
|
||||
|
||||
import lu.foyer.JsonApiResponse.One
|
||||
import zio.*
|
||||
import zio.Console.*
|
||||
import zio.http.*
|
||||
|
|
@ -11,34 +12,27 @@ import zio.schema.*
|
|||
import java.net.URI
|
||||
import java.time.LocalDate
|
||||
import java.util.UUID
|
||||
import lu.foyer.JsonApiResponse.One
|
||||
|
||||
trait CommandEngineController[Command: Schema, Event: Schema, State: Schema](
|
||||
domain: String,
|
||||
entityName: String)
|
||||
trait CommandEngineController[Command: Schema, Event: Schema, State: Schema]
|
||||
extends JsonApiController:
|
||||
|
||||
def commandEngine: CommandEngine[Command, Event, State]
|
||||
|
||||
val onthology = s"$domain:$entityName"
|
||||
|
||||
private val fetchMany =
|
||||
private lazy val fetchMany =
|
||||
Endpoint(Method.GET / entityName)
|
||||
.query(HttpCodec.query[Page])
|
||||
// .query(HttpCodec.query[Page])
|
||||
.jsonApiMany[State]
|
||||
|
||||
private val fetchOne =
|
||||
private lazy val fetchOne =
|
||||
Endpoint(Method.GET / entityName / uuid("entityId"))
|
||||
.jsonApiOne[State]
|
||||
|
||||
private val fetchEventsMany =
|
||||
private lazy val fetchEventsMany =
|
||||
Endpoint(Method.GET / entityName / uuid("entityId") / "events")
|
||||
.query(HttpCodec.query[Page])
|
||||
// .query(HttpCodec.query[Page])
|
||||
.jsonApiMany[Event]
|
||||
|
||||
private val fetchEventsOne: Endpoint[(UUID, UUID), (UUID, UUID), JsonApiResponse.Error, One[
|
||||
Event
|
||||
], zio.http.endpoint.AuthType.None.type] =
|
||||
private lazy val fetchEventsOne =
|
||||
Endpoint(Method.GET / entityName / uuid("entityId") / "events" / uuid("eventId"))
|
||||
.jsonApiOne[Event]
|
||||
|
||||
|
|
@ -51,7 +45,7 @@ trait CommandEngineController[Command: Schema, Event: Schema, State: Schema](
|
|||
given Schema[Command] = handler.commandSchema.asInstanceOf[Schema[Command]]
|
||||
val endpoint = Endpoint(Method.POST / entityName / "commands" / handler.name)
|
||||
.in[Command]
|
||||
.jsonApiOne[Event]
|
||||
.jsonApiOneWithStatus[Event](Status.Created)
|
||||
val route = endpoint.implementJsonApiOneEvent(command =>
|
||||
for
|
||||
entityId <- Random.nextUUID
|
||||
|
|
@ -73,32 +67,36 @@ trait CommandEngineController[Command: Schema, Event: Schema, State: Schema](
|
|||
)
|
||||
(endpoint, route)
|
||||
|
||||
private val (commands, commandsRoutes) = generateCommands.unzip
|
||||
private lazy val (commands, commandsRoutes) = generateCommands.unzip
|
||||
|
||||
private val fetchManyRoute =
|
||||
fetchMany.implementJsonApiManyEntity(commandEngine.stateRepo.fetchMany)
|
||||
private lazy val fetchManyRoute =
|
||||
fetchMany.implementJsonApiManyEntity(_ =>
|
||||
commandEngine.stateRepo.fetchMany(Page(None, None, totals = Some(true)))
|
||||
)
|
||||
|
||||
private val fetchOneRoute =
|
||||
private lazy val fetchOneRoute =
|
||||
fetchOne.implementJsonApiOneEntity(commandEngine.stateRepo.fetchOne)
|
||||
|
||||
private val fetchEventsManyRoute =
|
||||
fetchEventsMany.implementJsonApiManyEvent(commandEngine.eventRepo.fetchMany(_, _))
|
||||
private lazy val fetchEventsManyRoute =
|
||||
fetchEventsMany.implementJsonApiManyEvent(entityId =>
|
||||
commandEngine.eventRepo.fetchMany(entityId, Page(None, None, totals = Some(true)))
|
||||
)
|
||||
|
||||
private val fetchEventsOneRoute =
|
||||
private lazy val fetchEventsOneRoute =
|
||||
fetchEventsOne.implementJsonApiOneEvent(commandEngine.eventRepo.fetchOne(_, _))
|
||||
|
||||
val endpoints = List(
|
||||
lazy val endpoints = List(
|
||||
fetchMany,
|
||||
fetchOne,
|
||||
fetchEventsMany,
|
||||
fetchEventsOne
|
||||
) ++ commands
|
||||
|
||||
val routes = Routes(
|
||||
lazy val routes = (Routes(
|
||||
fetchManyRoute,
|
||||
fetchOneRoute,
|
||||
fetchEventsManyRoute,
|
||||
fetchEventsOneRoute
|
||||
) ++ Routes.fromIterable(commandsRoutes)
|
||||
) ++ Routes.fromIterable(commandsRoutes)) @@ proxyHeadersAspect
|
||||
|
||||
end CommandEngineController
|
||||
|
|
|
|||
|
|
@ -1,41 +1,45 @@
|
|||
package lu.foyer
|
||||
|
||||
import lu.foyer.JsonApiResponse.Many
|
||||
import lu.foyer.JsonApiResponse.One
|
||||
import zio.*
|
||||
import zio.schema.*
|
||||
import zio.http.*
|
||||
import zio.http.Header.Forwarded
|
||||
import zio.http.codec.*
|
||||
import zio.http.codec.PathCodec.path
|
||||
import zio.http.endpoint.*
|
||||
import lu.foyer.JsonApiResponse.Many
|
||||
import lu.foyer.JsonApiResponse.One
|
||||
import zio.schema.*
|
||||
import zio.schema.annotation.discriminatorName
|
||||
import zio.schema.annotation.fieldName
|
||||
|
||||
import java.util.UUID
|
||||
import scala.annotation.targetName
|
||||
import zio.schema.annotation.discriminatorName
|
||||
|
||||
object JsonApiResponse:
|
||||
|
||||
case class One[T](
|
||||
data: Entity[T],
|
||||
links: Links)
|
||||
case class One[T](data: Entity[T]) derives Schema
|
||||
|
||||
case class Many[T](data: List[Entity[T]], links: Map[String, String], meta: Meta) derives Schema
|
||||
|
||||
case class Meta(totalRecords: Option[Long], totalPages: Option[Long], page: Page) derives Schema
|
||||
case class Page(number: Int, size: Int) derives Schema
|
||||
|
||||
case class Entity[T](
|
||||
`type`: String,
|
||||
id: UUID,
|
||||
attributes: T,
|
||||
relationships: Option[Relationships] = None,
|
||||
links: Map[String, String])
|
||||
derives Schema
|
||||
|
||||
case class Many[T](
|
||||
data: List[Entity[T]],
|
||||
links: Links,
|
||||
meta: Meta)
|
||||
derives Schema
|
||||
|
||||
case class Links(
|
||||
self: String,
|
||||
first: Option[String] = None,
|
||||
prev: Option[String] = None,
|
||||
next: Option[String] = None,
|
||||
last: Option[String] = None)
|
||||
derives Schema
|
||||
|
||||
case class Meta(totalRecords: Option[Long], totalPages: Option[Long]) derives Schema
|
||||
|
||||
case class Entity[T](id: UUID, `type`: String, attributes: T) derives Schema
|
||||
case class Relationships(_entity: RelationshipsEntity) derives Schema
|
||||
object Relationships:
|
||||
def apply(id: UUID, `type`: String, entityUrl: String): Relationships = Relationships(
|
||||
RelationshipsEntity(RelationshipsData(id, `type`), RelationshipsLinks(entityUrl))
|
||||
)
|
||||
case class RelationshipsEntity(data: RelationshipsData, links: RelationshipsLinks) derives Schema
|
||||
case class RelationshipsData(id: UUID, `type`: String) derives Schema
|
||||
case class RelationshipsLinks(related: String) derives Schema
|
||||
|
||||
@discriminatorName("errorType")
|
||||
enum Error(title: String) derives Schema:
|
||||
|
|
@ -46,16 +50,47 @@ object JsonApiResponse:
|
|||
given Schema[Error.InternalServerError] = DeriveSchema.gen
|
||||
end JsonApiResponse
|
||||
|
||||
trait JsonApiController:
|
||||
final case class ProxyHeaders(
|
||||
protocol: Option[String],
|
||||
host: Option[String],
|
||||
prefix: Option[String])
|
||||
derives Schema:
|
||||
def rootUrl =
|
||||
(for
|
||||
proto <- protocol
|
||||
host <- host
|
||||
prefix <- prefix
|
||||
yield s"$proto://$host$prefix")
|
||||
.getOrElse("")
|
||||
|
||||
trait JsonApiController:
|
||||
def onthology: String
|
||||
def entityName: String
|
||||
def allEntities: List[String]
|
||||
|
||||
val proxyHeadersAspect = HandlerAspect.interceptIncomingHandler(
|
||||
Handler.fromFunction[Request](request =>
|
||||
(
|
||||
request,
|
||||
ProxyHeaders(
|
||||
protocol = request.headers.get("X-Forwarded-Proto"),
|
||||
host = request.headers.get("X-Forwarded-Host"),
|
||||
prefix = request.headers.get("X-Forwarded-Prefix")
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
extension [PathInput, Input, Auth <: AuthType](
|
||||
endpoint: Endpoint[PathInput, Input, ZNothing, ZNothing, AuthType.None]
|
||||
)
|
||||
def jsonApiOne[Output: Schema] =
|
||||
inline def jsonApiOne[Output: Schema]
|
||||
: Endpoint[PathInput, Input, JsonApiResponse.Error, One[Output], AuthType.None.type] =
|
||||
jsonApiOneWithStatus(Status.Ok)
|
||||
|
||||
def jsonApiOneWithStatus[Output: Schema](status: Status) =
|
||||
endpoint
|
||||
.out[JsonApiResponse.One[Output]]
|
||||
.out[JsonApiResponse.One[Output]](status)
|
||||
.outErrors[JsonApiResponse.Error](
|
||||
HttpCodec.error[JsonApiResponse.Error.NotFound](Status.NotFound),
|
||||
HttpCodec.error[JsonApiResponse.Error.InternalServerError](Status.InternalServerError)
|
||||
|
|
@ -71,36 +106,70 @@ trait JsonApiController:
|
|||
def implementJsonApiOne[Env, A](
|
||||
f: Input => RIO[Env, Option[A]],
|
||||
getId: A => UUID,
|
||||
getEntity: A => Output
|
||||
getEntity: A => Output,
|
||||
onthology: String = this.onthology,
|
||||
links: (A, ProxyHeaders) => Map[String, String] = (_: A, _: ProxyHeaders) => Map.empty,
|
||||
relationships: (A, ProxyHeaders) => Option[JsonApiResponse.Relationships] = (
|
||||
_: A,
|
||||
_: ProxyHeaders
|
||||
) => None
|
||||
)(implicit trace: Trace
|
||||
): Route[Env, Nothing] =
|
||||
): Route[Env & ProxyHeaders, Nothing] =
|
||||
endpoint.implement(input =>
|
||||
f(input)
|
||||
.mapError(e => JsonApiResponse.Error.InternalServerError(e.getMessage))
|
||||
.someOrFail(JsonApiResponse.Error.NotFound(input.toString))
|
||||
.map(item =>
|
||||
JsonApiResponse.One(
|
||||
JsonApiResponse.Entity(
|
||||
id = getId(item),
|
||||
`type` = onthology,
|
||||
attributes = getEntity(item)
|
||||
),
|
||||
JsonApiResponse.Links("https://api.example.org") // TODO
|
||||
)
|
||||
for
|
||||
item <- f(input)
|
||||
.mapError(e => JsonApiResponse.Error.InternalServerError(e.getMessage))
|
||||
.flatMap {
|
||||
case Some(paged) => ZIO.succeed(paged)
|
||||
case None => ZIO.fail(JsonApiResponse.Error.NotFound(input.toString))
|
||||
}
|
||||
headers <- ZIO.service[ProxyHeaders]
|
||||
yield JsonApiResponse.One(
|
||||
JsonApiResponse.Entity(
|
||||
id = getId(item),
|
||||
`type` = onthology,
|
||||
attributes = getEntity(item),
|
||||
relationships = relationships(item, headers),
|
||||
links = links(item, headers)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def implementJsonApiOneEntity[Env](
|
||||
f: Input => RIO[Env, Option[Entity[Output]]]
|
||||
)(implicit trace: Trace
|
||||
): Route[Env, Nothing] =
|
||||
implementJsonApiOne(f, _.entityId, _.data)
|
||||
): Route[Env & ProxyHeaders, Nothing] =
|
||||
implementJsonApiOne(
|
||||
f,
|
||||
_.entityId,
|
||||
_.data,
|
||||
links = (e, headers) =>
|
||||
Map(
|
||||
"self" -> s"${headers.rootUrl}/$entityName/${e.entityId}",
|
||||
"events" -> s"${headers.rootUrl}/$entityName/${e.entityId}/events"
|
||||
)
|
||||
)
|
||||
|
||||
def implementJsonApiOneEvent[Env](
|
||||
f: Input => RIO[Env, Option[Event[Output]]]
|
||||
)(implicit trace: Trace
|
||||
): Route[Env, Nothing] =
|
||||
implementJsonApiOne(f, _.eventId, _.data)
|
||||
): Route[Env & ProxyHeaders, Nothing] =
|
||||
implementJsonApiOne(
|
||||
f,
|
||||
_.eventId,
|
||||
_.data,
|
||||
onthology + ":event",
|
||||
links = (e, headers) =>
|
||||
Map("self" -> s"${headers.rootUrl}/$entityName/${e.entityId}/events/${e.eventId}"),
|
||||
relationships = (e, headers) =>
|
||||
Some(
|
||||
JsonApiResponse.Relationships(
|
||||
e.entityId,
|
||||
onthology,
|
||||
s"${headers.rootUrl}/$entityName/${e.entityId}"
|
||||
)
|
||||
)
|
||||
)
|
||||
end extension
|
||||
|
||||
extension [PathInput, Input, Output, Auth <: AuthType](
|
||||
|
|
@ -109,33 +178,60 @@ trait JsonApiController:
|
|||
def implementJsonApiMany[Env, A](
|
||||
f: Input => RIO[Env, Paged[A]],
|
||||
getId: A => UUID,
|
||||
getEntity: A => Output
|
||||
getEntity: A => Output,
|
||||
links: (A, ProxyHeaders) => Map[String, String] = (_: A, _: ProxyHeaders) => Map.empty
|
||||
)(implicit trace: Trace
|
||||
): Route[Env, Nothing] =
|
||||
endpoint.implement(
|
||||
f(_)
|
||||
.map(paged =>
|
||||
JsonApiResponse.Many(
|
||||
paged.items.map(item =>
|
||||
JsonApiResponse.Entity(id = getId(item), `type` = onthology, getEntity(item))
|
||||
),
|
||||
JsonApiResponse.Links("https://api.example.org"),
|
||||
meta = JsonApiResponse.Meta(paged.totals, None) // TODO
|
||||
): Route[Env & ProxyHeaders, Nothing] =
|
||||
endpoint.implement(input =>
|
||||
(for
|
||||
paged <- f(input)
|
||||
headers <- ZIO.service[ProxyHeaders]
|
||||
yield JsonApiResponse.Many(
|
||||
paged.items.map(item =>
|
||||
JsonApiResponse.Entity(
|
||||
id = getId(item),
|
||||
`type` = onthology,
|
||||
attributes = getEntity(item),
|
||||
links = links(item, headers)
|
||||
)
|
||||
),
|
||||
links = Map( // TODO compute proper pagination
|
||||
"self" -> s"${headers.rootUrl}/$entityName",
|
||||
"prev" -> s"${headers.rootUrl}/$entityName?page[number]=0&page[size]=10",
|
||||
"first" -> s"${headers.rootUrl}/$entityName?page[number]=0&page[size]=10",
|
||||
"next" -> s"${headers.rootUrl}/$entityName?page[number]=0&page[size]=10",
|
||||
"last" -> s"${headers.rootUrl}/$entityName?page[number]=0&page[size]=10"
|
||||
)
|
||||
++ allEntities.map(e => e -> s"${headers.rootUrl}/$e"),
|
||||
meta = JsonApiResponse
|
||||
.Meta(
|
||||
totalRecords = paged.totals,
|
||||
totalPages = Some(1),
|
||||
page = JsonApiResponse.Page(number = 0, size = 10)
|
||||
)
|
||||
))
|
||||
.mapError(e => JsonApiResponse.Error.InternalServerError(e.getMessage))
|
||||
)
|
||||
|
||||
inline def implementJsonApiManyEntity[Env](
|
||||
f: Input => RIO[Env, Paged[Entity[Output]]]
|
||||
)(implicit trace: Trace
|
||||
): Route[Env, Nothing] =
|
||||
implementJsonApiMany(f, _.entityId, _.data)
|
||||
): Route[Env & ProxyHeaders, Nothing] =
|
||||
implementJsonApiMany(
|
||||
f,
|
||||
_.entityId,
|
||||
_.data,
|
||||
links = (e, headers) =>
|
||||
Map(
|
||||
"self" -> s"${headers.rootUrl}/$entityName/${e.entityId}",
|
||||
"events" -> s"${headers.rootUrl}/$entityName/${e.entityId}/events"
|
||||
)
|
||||
)
|
||||
|
||||
inline def implementJsonApiManyEvent[Env](
|
||||
f: Input => RIO[Env, Paged[Event[Output]]]
|
||||
)(implicit trace: Trace
|
||||
): Route[Env, Nothing] =
|
||||
): Route[Env & ProxyHeaders, Nothing] =
|
||||
implementJsonApiMany(f, _.eventId, _.data)
|
||||
|
||||
end extension
|
||||
|
|
|
|||
|
|
@ -13,12 +13,11 @@ import java.net.URI
|
|||
import java.time.LocalDate
|
||||
import java.util.UUID
|
||||
|
||||
class ClientController(
|
||||
override val commandEngine: CommandEngine[ClientCommand, ClientEvent, ClientState])
|
||||
extends CommandEngineController[ClientCommand, ClientEvent, ClientState](
|
||||
"api:example:insurance",
|
||||
"client"
|
||||
)
|
||||
class ClientController(val commandEngine: CommandEngine[ClientCommand, ClientEvent, ClientState])
|
||||
extends CommandEngineController[ClientCommand, ClientEvent, ClientState]:
|
||||
override val onthology = "org:example:insurance:client"
|
||||
override val entityName = "clients"
|
||||
override val allEntities = List("clients", "contracts")
|
||||
|
||||
object ClientController:
|
||||
val layer = ZLayer.fromFunction(ClientController.apply)
|
||||
|
|
|
|||
24
api/src/lu/foyer/contracts/ContractController.scala
Normal file
24
api/src/lu/foyer/contracts/ContractController.scala
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
package lu.foyer
|
||||
package contracts
|
||||
|
||||
import zio.*
|
||||
import zio.Console.*
|
||||
import zio.http.*
|
||||
import zio.http.codec.*
|
||||
import zio.http.codec.PathCodec.path
|
||||
import zio.http.endpoint.*
|
||||
import zio.schema.*
|
||||
|
||||
import java.net.URI
|
||||
import java.time.LocalDate
|
||||
import java.util.UUID
|
||||
|
||||
class ContractController(
|
||||
val commandEngine: CommandEngine[ContractCommand, ContractEvent, ContractState])
|
||||
extends CommandEngineController[ContractCommand, ContractEvent, ContractState]:
|
||||
override val onthology = "org:example:insurance:contract"
|
||||
override val entityName = "contracts"
|
||||
override val allEntities = List("clients", "contracts")
|
||||
|
||||
object ContractController:
|
||||
val layer = ZLayer.fromFunction(ContractController.apply)
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package lu.foyer
|
||||
package contracts
|
||||
|
||||
import java.util.UUID
|
||||
import zio.*
|
||||
|
||||
class ContractEventRepositoryInMemory(events: Ref[Map[UUID, Event[ContractEvent]]])
|
||||
extends EventRepository[ContractEvent]
|
||||
with InMemoryRepository[Event[ContractEvent]](events):
|
||||
def fetchOne(entityId: UUID, eventId: UUID): Task[Option[Event[ContractEvent]]] =
|
||||
events.get.map(_.get(eventId))
|
||||
def fetchMany(entityId: UUID, page: Page): Task[Paged[Event[ContractEvent]]] =
|
||||
events.get
|
||||
.map(entities =>
|
||||
val items = entities.values
|
||||
.filter(_.entityId == entityId)
|
||||
.drop(page.number.getOrElse(0) * page.size.getOrElse(50))
|
||||
.take(page.size.getOrElse(50))
|
||||
Paged(items.toList, if page.totals.getOrElse(false) then Some(entities.size) else None)
|
||||
)
|
||||
|
||||
object ContractEventRepositoryInMemory:
|
||||
val layer = ZLayer.fromZIO(Ref.make(Map.empty).map(ContractEventRepositoryInMemory(_)))
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package lu.foyer
|
||||
package contracts
|
||||
|
||||
import java.util.UUID
|
||||
import zio.*
|
||||
|
||||
class ContractStateRepositoryInMemory(clients: Ref[Map[UUID, Entity[ContractState]]])
|
||||
extends StateRepository[ContractState]
|
||||
with InMemoryRepository[Entity[ContractState]](clients)
|
||||
|
||||
object ContractStateRepositoryInMemory:
|
||||
val layer = ZLayer.fromZIO(Ref.make(Map.empty).map(ContractStateRepositoryInMemory(_)))
|
||||
24
api/src/lu/foyer/contracts/EmployeeServiceImpl.scala
Normal file
24
api/src/lu/foyer/contracts/EmployeeServiceImpl.scala
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
package lu.foyer
|
||||
package contracts
|
||||
|
||||
import java.util.UUID
|
||||
import zio.*
|
||||
import lu.foyer.clients.Address
|
||||
import scala.math.BigDecimal.RoundingMode
|
||||
import java.util.Currency
|
||||
import lu.foyer.clients.Country
|
||||
|
||||
object EmployeeServiceImpl extends EmployeeService:
|
||||
|
||||
def fetchOne(subject: String): Either[String, Employee] =
|
||||
if subject == "usr:top" then
|
||||
Right(
|
||||
Employee(
|
||||
subject,
|
||||
EmployeeDisplayName.assume("MEIER Christoph"),
|
||||
Email.assume("top@foyer.lu")
|
||||
)
|
||||
)
|
||||
else Left("Invalid Employee")
|
||||
|
||||
val layer = ZLayer.succeed(EmployeeServiceImpl)
|
||||
53
api/src/lu/foyer/contracts/PremiumServiceImpl.scala
Normal file
53
api/src/lu/foyer/contracts/PremiumServiceImpl.scala
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
package lu.foyer
|
||||
package contracts
|
||||
|
||||
import java.util.UUID
|
||||
import zio.*
|
||||
import lu.foyer.clients.Address
|
||||
import scala.math.BigDecimal.RoundingMode
|
||||
import java.util.Currency
|
||||
import lu.foyer.clients.Country
|
||||
|
||||
object PremiumServiceImpl extends PremiumService:
|
||||
private val EUR = Currency.getInstance("EUR")
|
||||
|
||||
private val brands =
|
||||
Map(
|
||||
"renault" -> BigDecimal(0.5),
|
||||
"fiat" -> BigDecimal(0.6),
|
||||
"ford" -> BigDecimal(0.7),
|
||||
"nissan" -> BigDecimal(0.75),
|
||||
"peugeot" -> BigDecimal(0.8),
|
||||
"volkswagen" -> BigDecimal(1.1),
|
||||
"audi" -> BigDecimal(1.25),
|
||||
"bmw" -> BigDecimal(1.25),
|
||||
"mercedes" -> BigDecimal(1.25),
|
||||
"ferrari" -> BigDecimal(2.3),
|
||||
"bugatti" -> BigDecimal(2.6),
|
||||
"tesla" -> BigDecimal(5.0)
|
||||
)
|
||||
|
||||
private val countries =
|
||||
Map(Country.CH -> BigDecimal(0.8), Country.LU -> BigDecimal(1.2))
|
||||
|
||||
private def computeAmountEUR(base: BigDecimal, brand: VehicleBrand, country: Country)
|
||||
: BigDecimal =
|
||||
val brandMultiplier = brands.getOrElse(brand.toLowerCase, BigDecimal(1))
|
||||
val countryMultiplier = countries.getOrElse(country, BigDecimal(1))
|
||||
val amount = base * brandMultiplier * countryMultiplier
|
||||
amount.setScale(EUR.getDefaultFractionDigits, RoundingMode.HALF_UP).rounded
|
||||
|
||||
override def computePremium(formula: FormulaType, vehicle: Vehicle, residentialAddress: Address)
|
||||
: Either[String, Amount] =
|
||||
if vehicle.insuredValue.value > formula.maxCoverage then
|
||||
Left(s"The $formula formula only covers amounts up to ${formula.maxCoverage} EUR")
|
||||
else
|
||||
Right(
|
||||
Amount(
|
||||
computeAmountEUR(formula.basePremium, vehicle.brand, residentialAddress.country),
|
||||
EUR
|
||||
)
|
||||
)
|
||||
|
||||
val layer = ZLayer.succeed(PremiumServiceImpl)
|
||||
end PremiumServiceImpl
|
||||
Loading…
Add table
Add a link
Reference in a new issue