Type Gymnastics with Builders - Part 8 - Show me the Code

Views
Article hero image

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))
  }
}
scala builder types series