Welcome to the post-credits bonus post!
Here’s the full code of the final version of AppBuilder
package builder
import cats.data.{NonEmptyChain, NonEmptyList}
import models._
final class AppBuilder[
F[_],
H <: Option[String],
P <: Option[Int],
MC <: Option[Int],
CT <: Option[Int],
A <: Option[Auth[F]],
U <: Option[Users[F]],
B <: Option[Books[F]],
HC <: Option[NonEmptyChain[HealthCheck[F]]]
] private[builder](
private[builder] val hostOpt: H,
private[builder] val portOpt: P,
private[builder] val maxConnectionsOpt: MC,
private[builder] val connectionTimeoutOpt: CT,
private[builder] val authOpt: A,
private[builder] val usersOpt: U,
private[builder] val booksOpt: B,
private[builder] val healthChecksOpt: HC
) {
private def copy[
H2 <: Option[String],
P2 <: Option[Int],
MC2 <: Option[Int],
CT2 <: Option[Int],
A2 <: Option[Auth[F]],
U2 <: Option[Users[F]],
B2 <: Option[Books[F]],
HC2 <: Option[NonEmptyChain[HealthCheck[F]]]
](
hostOpt: H2 = hostOpt,
portOpt: P2 = portOpt,
maxConnectionsOpt: MC2 = maxConnectionsOpt,
connectionTimeoutOpt: CT2 = connectionTimeoutOpt,
authOpt: A2 = authOpt,
usersOpt: U2 = usersOpt,
booksOpt: B2 = booksOpt,
healthChecksOpt: HC2 = healthChecksOpt
): AppBuilder[F, H2, P2, MC2, CT2, A2, U2, B2, HC2] = new AppBuilder(hostOpt, portOpt, maxConnectionsOpt, connectionTimeoutOpt, authOpt, usersOpt, booksOpt, healthChecksOpt)
private[builder] def withHost(value: String): AppBuilder[F, Some[String], P, MC, CT, A, U, B, HC] =
copy(hostOpt = Some(value))
private[builder] def withPort(value: Int): AppBuilder[F, H, Some[Int], MC, CT, A, U, B, HC] =
copy(portOpt = Some(value))
private[builder] def withMaxConnections(value: Int): AppBuilder[F, H, P, Some[Int], CT, A, U, B, HC] =
copy(maxConnectionsOpt = Some(value))
private[builder] def withConnectionTimeout(value: Int): AppBuilder[F, H, P, MC, Some[Int], A, U, B, HC] =
copy(connectionTimeoutOpt = Some(value))
private[builder] def withAuth(value: Auth[F]): AppBuilder[F, H, P, MC, CT, Some[Auth[F]], U, B, HC] =
copy(authOpt = Some(value))
private[builder] def withUsers(value: Users[F]): AppBuilder[F, H, P, MC, CT, A, Some[Users[F]], B, HC] =
copy(usersOpt = Some(value))
private[builder] def withBooks(value: Books[F]): AppBuilder[F, H, P, MC, CT, A, U, Some[Books[F]], HC] =
copy(booksOpt = Some(value))
private[builder] def withHealthChecks(value: NonEmptyChain[HealthCheck[F]]): AppBuilder[F, H, P, MC, CT, A, U, B, Some[NonEmptyChain[HealthCheck[F]]]] =
copy(healthChecksOpt = Some(healthChecksOpt.fold(value)(_.concat(value))))
def build(implicit whenReady: AppBuilder.WhenReady[F, H, P, MC, CT, U, B, HC]): AppConfig[F] =
whenReady.build(this)
}
object AppBuilder {
type Empty[F[_]] = AppBuilder[
F,
None.type,
None.type,
None.type,
None.type,
None.type,
None.type,
None.type,
None.type
]
def init[F[_]]: Empty[F] = new AppBuilder(None, None, None, None, None, None, None, None)
@scala.annotation.implicitNotFound(
"""Unable to build an AppConfig[+A], one or more fields are missing
host: ${H}
port: ${P}
maxConnections: ${MC}
connectionTimeout: ${CT}
users: ${U}
books: ${B}
healthChecks: ${HC}
""")
sealed trait WhenReady[
F[_],
H <: Option[String],
P <: Option[Int],
MC <: Option[Int],
CT <: Option[Int],
U <: Option[Users[F]],
B <: Option[Books[F]],
HC <: Option[NonEmptyChain[HealthCheck[F]]]
] {
def build[A <: Option[Auth[F]]](builder: AppBuilder[F, H, P, MC, CT, A, U, B, HC]): AppConfig[F]
}
object WhenReady {
implicit def allFieldsPresent[F[_]]: WhenReady[
F,
Some[String],
Some[Int],
Some[Int],
Some[Int],
Some[Users[F]],
Some[Books[F]],
Some[NonEmptyChain[HealthCheck[F]]]
] = new WhenReady[
F,
Some[String],
Some[Int],
Some[Int],
Some[Int],
Some[Users[F]],
Some[Books[F]],
Some[NonEmptyChain[HealthCheck[F]]]
] {
override def build[A <: Option[Auth[F]]](builder: AppBuilder[F, Some[String], Some[Int], Some[Int], Some[Int], A, Some[Users[F]], Some[Books[F]], Some[NonEmptyChain[HealthCheck[F]]]]): AppConfig[F] =
AppConfig[F](
host = builder.hostOpt.value,
port = builder.portOpt.value,
maxConnections = builder.maxConnectionsOpt.value,
connectionTimeout = builder.connectionTimeoutOpt.value,
users = builder.usersOpt.value,
books = builder.booksOpt.value,
healthChecks = builder.healthChecksOpt.value.toNonEmptyList,
)
}
}
implicit final class AppBuilderHostOps[
F[_],
P <: Option[Int]
](private val builder: AppBuilder[F, None.type, P, None.type, None.type, None.type, None.type, None.type, None.type]) extends AnyVal {
def host(h: String): AppBuilder[F, Some[String], P, None.type, None.type, None.type, None.type, None.type, None.type] = builder.withHost(h)
}
implicit final class AppBuilderPortOps[
F[_],
H <: Option[String]
](private val builder: AppBuilder[F, H, None.type, None.type, None.type, None.type, None.type, None.type, None.type]) extends AnyVal {
def port(p: Int): AppBuilder[F, H, Some[Int], None.type, None.type, None.type, None.type, None.type, None.type] = builder.withPort(p)
}
implicit final class AppBuilderMaxConnOps[
F[_],
H <: Option[String],
P <: Option[Int],
CT <: Option[Int],
HC <: Option[NonEmptyChain[HealthCheck[F]]]
](private val builder: AppBuilder[F, H, P, None.type, CT, None.type, None.type, None.type, HC]) extends AnyVal {
def maxConnections(m: Int): AppBuilder[F, H, P, Some[Int], CT, None.type, None.type, None.type, HC] = builder.withMaxConnections(m)
}
implicit final class AppBuilderConnTimeoutOps[
F[_],
H <: Option[String],
P <: Option[Int],
MC <: Option[Int],
HC <: Option[NonEmptyChain[HealthCheck[F]]]
](private val builder: AppBuilder[F, H, P, MC, None.type, None.type, None.type, None.type, HC]) extends AnyVal {
def connectionTimeoutSeconds(ct: Int): AppBuilder[F, H, P, MC, Some[Int], None.type, None.type, None.type, HC] = builder.withConnectionTimeout(ct)
}
implicit final class AppBuilderAuthOps[
F[_],
H <: Option[String],
P <: Option[Int],
MC <: Option[Int],
CT <: Option[Int],
U <: Option[Users[F]],
B <: Option[Books[F]],
HC <: Option[NonEmptyChain[HealthCheck[F]]]
](private val builder: AppBuilder[F, H, P, MC, CT, None.type, U, B, HC]) extends AnyVal {
def auth(a: Auth[F]): AppBuilder[F, H, P, MC, CT, Some[Auth[F]], U, B, HC] = builder.withAuth(a)
}
implicit final class AppBuilderUsersOps[
F[_],
H <: Option[String],
P <: Option[Int],
MC <: Option[Int],
CT <: Option[Int],
A <: Option[Auth[F]],
B <: Option[Books[F]],
HC <: Option[NonEmptyChain[HealthCheck[F]]]
](private val builder: AppBuilder[F, H, P, MC, CT, A, None.type, B, HC]) extends AnyVal {
def users(u: Users[F]): AppBuilder[F, H, P, MC, CT, A, Some[Users[F]], B, HC] = builder.withUsers(u)
}
implicit final class AppBuilderUsersFromAuthOps[
F[_],
H <: Option[String],
P <: Option[Int],
MC <: Option[Int],
CT <: Option[Int],
A <: Option[Auth[F]],
B <: Option[Books[F]],
HC <: Option[NonEmptyChain[HealthCheck[F]]]
](private val builder: AppBuilder[F, H, P, MC, CT, Some[Auth[F]], None.type, B, HC]) extends AnyVal {
def usersFromAuth(f: Auth[F] => Users[F]): AppBuilder[F, H, P, MC, CT, Some[Auth[F]], Some[Users[F]], B, Some[NonEmptyChain[HealthCheck[F]]]] = {
val u = f(builder.authOpt.value)
builder
.withUsers(u)
.withHealthChecks(NonEmptyChain.one {
HealthCheck.users(builder.authOpt.value, u)
})
}
}
implicit final class AppBuilderBooksOps[
F[_],
H <: Option[String],
P <: Option[Int],
MC <: Option[Int],
CT <: Option[Int],
A <: Option[Auth[F]],
U <: Option[Users[F]],
HC <: Option[NonEmptyChain[HealthCheck[F]]]
](private val builder: AppBuilder[F, H, P, MC, CT, A, U, None.type, HC]) extends AnyVal {
def books(b: Books[F]): AppBuilder[F, H, P, MC, CT, A, U, Some[Books[F]], HC] = builder.withBooks(b)
}
implicit final class AppBuilderBooksFromAuthOps[
F[_],
H <: Option[String],
P <: Option[Int],
MC <: Option[Int],
CT <: Option[Int],
HC <: Option[NonEmptyChain[HealthCheck[F]]]
](private val builder: AppBuilder[F, H, P, MC, CT, Some[Auth[F]], Some[Users[F]], None.type, HC]) extends AnyVal {
def booksFromAuth(f: (Auth[F], Users[F]) => Books[F]): AppBuilder[F, H, P, MC, CT, Some[Auth[F]], Some[Users[F]], Some[Books[F]], Some[NonEmptyChain[HealthCheck[F]]]] = {
val b = f(builder.authOpt.value, builder.usersOpt.value)
builder
.withBooks(b)
.withHealthChecks(NonEmptyChain.one {
HealthCheck.books(builder.authOpt.value, b)
})
}
}
implicit final class AppBuilderHealthChecksOps[
F[_],
A <: Option[Auth[F]],
HC <: Option[NonEmptyChain[HealthCheck[F]]]
](private val builder: AppBuilder[F, Some[String], Some[Int], Some[Int], Some[Int], A, Some[Users[F]], Some[Books[F]], HC]) extends AnyVal {
def healthCheck(hc: HealthCheck[F]): AppBuilder[F, Some[String], Some[Int], Some[Int], Some[Int], A, Some[Users[F]], Some[Books[F]], Some[NonEmptyChain[HealthCheck[F]]]] =
builder.withHealthChecks(NonEmptyChain.one(hc))
def healthChecks(hc: NonEmptyList[HealthCheck[F]]): AppBuilder[F, Some[String], Some[Int], Some[Int], Some[Int], A, Some[Users[F]], Some[Books[F]], Some[NonEmptyChain[HealthCheck[F]]]] =
builder.withHealthChecks(NonEmptyChain.fromNonEmptyList(hc))
}
}