diff --git a/modules/core/src/main/scala/pillars/AdminServer.scala b/modules/core/src/main/scala/pillars/AdminServer.scala index 5b7efb48c..b5f73851b 100644 --- a/modules/core/src/main/scala/pillars/AdminServer.scala +++ b/modules/core/src/main/scala/pillars/AdminServer.scala @@ -49,7 +49,7 @@ object AdminServer: enabled: Boolean, http: HttpServer.Config = defaultHttp, openApi: HttpServer.Config.OpenAPI = HttpServer.Config.OpenAPI() - ) + ) extends pillars.Config given Configuration = Configuration.default.withKebabCaseMemberNames.withKebabCaseConstructorNames.withDefaults given Codec[Config] = Codec.AsObject.derivedConfigured diff --git a/modules/core/src/main/scala/pillars/ApiServer.scala b/modules/core/src/main/scala/pillars/ApiServer.scala index d00ee813f..e797c2b1b 100644 --- a/modules/core/src/main/scala/pillars/ApiServer.scala +++ b/modules/core/src/main/scala/pillars/ApiServer.scala @@ -56,7 +56,7 @@ object ApiServer: enabled: Boolean, http: HttpServer.Config = defaultHttp, openApi: HttpServer.Config.OpenAPI = HttpServer.Config.OpenAPI() - ) + ) extends pillars.Config given Configuration = Configuration.default.withKebabCaseMemberNames.withKebabCaseConstructorNames.withDefaults given Codec[Config] = Codec.AsObject.derivedConfigured diff --git a/modules/core/src/main/scala/pillars/Config.scala b/modules/core/src/main/scala/pillars/Config.scala index cceaf68b2..3cd424585 100644 --- a/modules/core/src/main/scala/pillars/Config.scala +++ b/modules/core/src/main/scala/pillars/Config.scala @@ -24,6 +24,8 @@ import scodec.bits.ByteVector def config[F[_]](using p: Pillars[F]): Config.PillarsConfig = p.config +trait Config + object Config: case class PillarsConfig( name: App.Name, @@ -31,7 +33,7 @@ object Config: api: ApiServer.Config, admin: AdminServer.Config, observability: Observability.Config - ) + ) extends pillars.Config object PillarsConfig: given Configuration = Configuration.default.withKebabCaseMemberNames.withKebabCaseConstructorNames.withDefaults diff --git a/modules/core/src/main/scala/pillars/HttpServer.scala b/modules/core/src/main/scala/pillars/HttpServer.scala index 018f881e4..b6840e64b 100644 --- a/modules/core/src/main/scala/pillars/HttpServer.scala +++ b/modules/core/src/main/scala/pillars/HttpServer.scala @@ -127,7 +127,7 @@ object HttpServer: host: Host, port: Port, logging: Logging.HttpConfig = Logging.HttpConfig() - ) + ) extends pillars.Config object Config: given Configuration = Configuration.default.withKebabCaseMemberNames.withKebabCaseConstructorNames.withDefaults diff --git a/modules/core/src/main/scala/pillars/Logging.scala b/modules/core/src/main/scala/pillars/Logging.scala index 0dacf8839..2a155da97 100644 --- a/modules/core/src/main/scala/pillars/Logging.scala +++ b/modules/core/src/main/scala/pillars/Logging.scala @@ -130,7 +130,7 @@ object Logging: format: Logging.Format = Logging.Format.Enhanced, output: Logging.Output = Logging.Output.Console, excludeHikari: Boolean = false - ) + ) extends pillars.Config object Config: given Configuration = Configuration.default.withKebabCaseMemberNames.withKebabCaseConstructorNames.withDefaults @@ -147,7 +147,7 @@ object Logging: level: Level = Level.Debug, headers: Boolean = false, body: Boolean = true - ): + ) extends pillars.Config: def logAction[F[_]: Sync]: Option[String => F[Unit]] = Some(scribe.cats.effect[F].log(level, MDC.instance, _)) end HttpConfig diff --git a/modules/core/src/main/scala/pillars/Observability.scala b/modules/core/src/main/scala/pillars/Observability.scala index b4cc5eb3f..4b7c58e64 100644 --- a/modules/core/src/main/scala/pillars/Observability.scala +++ b/modules/core/src/main/scala/pillars/Observability.scala @@ -67,7 +67,7 @@ object Observability: metrics: Config.Metrics = Config.Metrics(), traces: Config.Traces = Config.Traces(), serviceName: ServiceName = ServiceName("pillars") - ) + ) extends pillars.Config object Config: given Configuration = Configuration.default.withKebabCaseMemberNames.withKebabCaseConstructorNames.withDefaults diff --git a/modules/core/src/main/scala/pillars/modules.scala b/modules/core/src/main/scala/pillars/modules.scala index 4552be1a9..2277ec09c 100644 --- a/modules/core/src/main/scala/pillars/modules.scala +++ b/modules/core/src/main/scala/pillars/modules.scala @@ -10,10 +10,13 @@ import pillars.probes.Probe import scribe.Scribe trait Module[F[_]]: + type ModuleConfig <: Config def probes: List[Probe[F]] = Nil def adminControllers: List[Controller[F]] = Nil + def config: ModuleConfig + end Module object Module: diff --git a/modules/core/src/main/scala/pillars/probes.scala b/modules/core/src/main/scala/pillars/probes.scala index 4080ad4f9..dac0774f0 100644 --- a/modules/core/src/main/scala/pillars/probes.scala +++ b/modules/core/src/main/scala/pillars/probes.scala @@ -131,7 +131,7 @@ object probes: timeout: FiniteDuration = 5.seconds, interval: FiniteDuration = 10.seconds, failureCount: Int = 3 - ) + ) extends pillars.Config object ProbeConfig: given Configuration = Configuration.default.withKebabCaseMemberNames.withKebabCaseConstructorNames.withDefaults diff --git a/modules/db-doobie/src/main/scala/pillars/db_doobie/db.scala b/modules/db-doobie/src/main/scala/pillars/db_doobie/db.scala index eba396738..ffc61468f 100644 --- a/modules/db-doobie/src/main/scala/pillars/db_doobie/db.scala +++ b/modules/db-doobie/src/main/scala/pillars/db_doobie/db.scala @@ -25,7 +25,8 @@ import pillars.Modules import pillars.Pillars import pillars.probes.* -final case class DB[F[_]: MonadCancelThrow](transactor: Transactor[F]) extends Module[F]: +final case class DB[F[_]: MonadCancelThrow](config: DatabaseConfig, transactor: Transactor[F]) extends Module[F]: + override type ModuleConfig = DatabaseConfig override def probes: List[Probe[F]] = val probe = new Probe[F]: @@ -56,7 +57,7 @@ class DBLoader extends Loader: config <- Resource.eval(reader.read[DatabaseConfig]("db")) _ <- Resource.eval(logger.info("DB module loaded")) xa <- HikariTransactor.fromHikariConfig[F](config.toHikariConfig) - yield DB(xa) + yield DB(config, xa) end for end load end DBLoader @@ -72,7 +73,7 @@ final case class DatabaseConfig( statementCache: StatementCacheConfig = StatementCacheConfig(), debug: Boolean = false, probe: ProbeConfig -): +) extends pillars.Config: def toHikariConfig: HikariConfig = val cfg = new HikariConfig cfg.setDriverClassName(driverClassName) diff --git a/modules/db-migration/src/main/scala/pillars/db/migrations/migrations.scala b/modules/db-migration/src/main/scala/pillars/db/migrations/migrations.scala index fabce73fc..c184c686e 100644 --- a/modules/db-migration/src/main/scala/pillars/db/migrations/migrations.scala +++ b/modules/db-migration/src/main/scala/pillars/db/migrations/migrations.scala @@ -25,6 +25,8 @@ import pillars.logger final case class DBMigration[F[_]: Async: Console: Tracer: Network: Files]( config: MigrationConfig ) extends Module[F]: + override type ModuleConfig = MigrationConfig + private def flyway(schema: DatabaseSchema, table: DatabaseTable, location: String) = Flyway .configure() .loggers("slf4j") @@ -101,7 +103,7 @@ final case class MigrationConfig( systemSchema: DatabaseSchema = DatabaseSchema.public, appSchema: DatabaseSchema = DatabaseSchema.public, baselineVersion: String = "0" -) +) extends pillars.Config object MigrationConfig: given Configuration = Configuration.default.withKebabCaseMemberNames.withKebabCaseConstructorNames.withDefaults diff --git a/modules/db-skunk/src/main/scala/pillars/db/db.scala b/modules/db-skunk/src/main/scala/pillars/db/db.scala index b9689e6a9..902efb7b3 100644 --- a/modules/db-skunk/src/main/scala/pillars/db/db.scala +++ b/modules/db-skunk/src/main/scala/pillars/db/db.scala @@ -30,7 +30,10 @@ import skunk.util.Typer def sessions[F[_]](using p: Pillars[F]): DB[F] = p.module[DB[F]](DB.Key) -final case class DB[F[_]: Async: Network: Tracer: Console](pool: Resource[F, Session[F]]) extends Module[F]: +final case class DB[F[_]: Async: Network: Tracer: Console](config: DatabaseConfig, pool: Resource[F, Session[F]]) + extends Module[F]: + + override type ModuleConfig = DatabaseConfig export pool.* override def probes: List[Probe[F]] = @@ -76,7 +79,7 @@ class DBLoader extends Loader: ssl = config.ssl ) _ <- Resource.eval(logger.info("DB module loaded")) - yield DB(poolRes) + yield DB(config, poolRes) end for end load end DBLoader @@ -102,7 +105,7 @@ final case class DatabaseConfig( parseCache: Int = 1024, readTimeout: Duration = Duration.Inf, redactionStrategy: RedactionStrategy = RedactionStrategy.OptIn -) +) extends pillars.Config object DatabaseConfig: given Configuration = Configuration.default.withKebabCaseMemberNames.withKebabCaseConstructorNames.withDefaults diff --git a/modules/flags/src/main/scala/pillars/flags/FlagManager.scala b/modules/flags/src/main/scala/pillars/flags/FlagManager.scala index af99384a8..1f0bde29e 100644 --- a/modules/flags/src/main/scala/pillars/flags/FlagManager.scala +++ b/modules/flags/src/main/scala/pillars/flags/FlagManager.scala @@ -16,7 +16,9 @@ import pillars.Modules import pillars.Pillars trait FlagManager[F[_]: Sync] extends Module[F]: + override type ModuleConfig = FlagsConfig def isEnabled(flag: Flag): F[Boolean] + def config: FlagsConfig def getFlag(name: Flag): F[Option[FeatureFlag]] def flags: F[List[FeatureFlag]] @@ -36,9 +38,10 @@ object FlagManager: case object Key extends Module.Key: def name: String = "feature-flags" end Key - def noop[F[_]: Sync]: FlagManager[F] = + def noop[F[_]: Sync](conf: FlagsConfig): FlagManager[F] = new FlagManager[F]: def isEnabled(flag: Flag): F[Boolean] = false.pure[F] + override def config: FlagsConfig = conf def getFlag(name: Flag): F[Option[FeatureFlag]] = None.pure[F] def flags: F[List[FeatureFlag]] = List.empty.pure[F] private[flags] def setStatus(flag: Flag, status: Status) = None.pure[F] @@ -64,16 +67,18 @@ class FlagManagerLoader extends Loader: yield manager end load - private[flags] def createManager[F[_]: Async: Network: Tracer: Console](config: FlagsConfig): F[FlagManager[F]] = - if !config.enabled then Sync[F].pure(FlagManager.noop[F]) + private[flags] def createManager[F[_]: Async: Network: Tracer: Console](conf: FlagsConfig): F[FlagManager[F]] = + if !conf.enabled then Sync[F].pure(FlagManager.noop[F](conf)) else - val flags = config.flags.groupBy(_.name).map((name, flags) => name -> flags.head) + val flags = conf.flags.groupBy(_.name).map((name, flags) => name -> flags.head) Ref .of[F, Map[Flag, FeatureFlag]](flags) .map: ref => new FlagManager[F]: def flags: F[List[FeatureFlag]] = ref.get.map(_.values.toList) + override def config: FlagsConfig = conf + def getFlag(name: Flag): F[Option[FeatureFlag]] = ref.get.map(_.get(name)) diff --git a/modules/flags/src/main/scala/pillars/flags/FlagsConfig.scala b/modules/flags/src/main/scala/pillars/flags/FlagsConfig.scala index 562e58ed8..b5a7925b5 100644 --- a/modules/flags/src/main/scala/pillars/flags/FlagsConfig.scala +++ b/modules/flags/src/main/scala/pillars/flags/FlagsConfig.scala @@ -5,4 +5,4 @@ import io.circe.Codec final case class FlagsConfig( enabled: Boolean = true, flags: List[FeatureFlag] = List.empty -) derives Codec.AsObject +) extends pillars.Config derives Codec.AsObject diff --git a/modules/http-client/src/main/scala/pillars/httpclient/httpclient.scala b/modules/http-client/src/main/scala/pillars/httpclient/httpclient.scala index 0115f0c8c..7cafe20c1 100644 --- a/modules/http-client/src/main/scala/pillars/httpclient/httpclient.scala +++ b/modules/http-client/src/main/scala/pillars/httpclient/httpclient.scala @@ -74,15 +74,16 @@ class Loader extends pillars.Loader: |> metrics.middleware |> logging |> followRedirect - |> HttpClient.apply + |> HttpClient(conf) _ <- Resource.eval(logger.info("HTTP client module loaded")) yield client end for end load end Loader -final case class HttpClient[F[_]: Async](client: org.http4s.client.Client[F]) +final case class HttpClient[F[_]: Async](config: HttpClient.Config)(client: org.http4s.client.Client[F]) extends pillars.Module[F]: + override type ModuleConfig = HttpClient.Config export client.* private val interpreter = Http4sClientInterpreter[F](Http4sClientOptions.default) @@ -129,7 +130,8 @@ object HttpClient: followRedirect: Boolean = true, userAgent: `User-Agent` = Config.defaultUserAgent, logging: Logging.HttpConfig = Logging.HttpConfig() - ) + ) extends pillars.Config + object Config: given Configuration = Configuration.default.withKebabCaseMemberNames.withKebabCaseConstructorNames.withDefaults given Decoder[`User-Agent`] = Decoder.decodeString.emap(s => diff --git a/modules/http-client/src/test/scala/pillars/httpclient/HttpClientSuite.scala b/modules/http-client/src/test/scala/pillars/httpclient/HttpClientSuite.scala index 6e496d35d..ce1168443 100644 --- a/modules/http-client/src/test/scala/pillars/httpclient/HttpClientSuite.scala +++ b/modules/http-client/src/test/scala/pillars/httpclient/HttpClientSuite.scala @@ -7,7 +7,7 @@ import org.http4s.client.Client import sttp.tapir.* class HttpClientSuite extends munit.CatsEffectSuite, munit.Http4sMUnitSyntax: - val fixture = Client.partialFixture(client => Resource.pure(HttpClient(client))) { + val fixture = Client.partialFixture(client => Resource.pure(HttpClient(HttpClient.Config())(client))) { case GET -> Root / "success" / input => Ok("Success") case GET -> Root / "error" => NotFound("Error") } diff --git a/modules/rabbitmq-fs2/src/main/scala/pillars/rabbitmq/fs2/rabbitmq.scala b/modules/rabbitmq-fs2/src/main/scala/pillars/rabbitmq/fs2/rabbitmq.scala index d7278416a..17b30c827 100644 --- a/modules/rabbitmq-fs2/src/main/scala/pillars/rabbitmq/fs2/rabbitmq.scala +++ b/modules/rabbitmq-fs2/src/main/scala/pillars/rabbitmq/fs2/rabbitmq.scala @@ -33,7 +33,8 @@ import scala.language.postfixOps extension [F[_]](p: Pillars[F]) def rabbit: RabbitMQ[F] = p.module[RabbitMQ[F]](RabbitMQ.Key) -final case class RabbitMQ[F[_]: Async](client: RabbitClient[F]) extends Module[F]: +final case class RabbitMQ[F[_]: Async](config: RabbitMQConfig, client: RabbitClient[F]) extends Module[F]: + override type ModuleConfig = RabbitMQConfig export client.* override def probes: List[Probe[F]] = @@ -51,7 +52,7 @@ object RabbitMQ: def apply[F[_]](using p: Pillars[F]): RabbitMQ[F] = p.module[RabbitMQ[F]](RabbitMQ.Key) def apply[F[_]: Async](config: RabbitMQConfig): Resource[F, RabbitMQ[F]] = - RabbitClient.default[F](config.convert).resource.map(apply) + RabbitClient.default[F](config.convert).resource.map(apply(config, _)) end RabbitMQ @@ -89,7 +90,7 @@ case class RabbitMQConfig( requestedHeartbeat: FiniteDuration = 60 seconds, automaticRecovery: Boolean = true, clientProvidedConnectionName: Option[RabbitMQConnectionName] = None -) +) extends pillars.Config object RabbitMQConfig: given Configuration = Configuration.default.withKebabCaseMemberNames.withKebabCaseConstructorNames.withDefaults diff --git a/modules/redis-rediculous/src/main/scala/pillars/redis_rediculous/redis.scala b/modules/redis-rediculous/src/main/scala/pillars/redis_rediculous/redis.scala index ac88f3985..3da8b40e9 100644 --- a/modules/redis-rediculous/src/main/scala/pillars/redis_rediculous/redis.scala +++ b/modules/redis-rediculous/src/main/scala/pillars/redis_rediculous/redis.scala @@ -27,8 +27,10 @@ import pillars.probes.* extension [F[_]](p: Pillars[F]) def redis: Redis[F] = p.module[Redis[F]](Redis.Key) -final case class Redis[F[_]: MonadCancelThrow](connection: Resource[F, RedisConnection[F]])(using c: Concurrent[F]) - extends Module[F]: +final case class Redis[F[_]: MonadCancelThrow](config: RedisConfig, connection: Resource[F, RedisConnection[F]])(using + c: Concurrent[F] +) extends Module[F]: + override type ModuleConfig = RedisConfig export connection.* override def probes: List[Probe[F]] = @@ -63,6 +65,7 @@ class RedisLoader extends Loader: _ <- Resource.eval(logger.info("Loading Redis module")) config <- Resource.eval(reader.read[RedisConfig]("redis")) connection = Redis( + config, RedisConnection.queued[F] .withHost(config.host) .withPort(config.port) @@ -86,7 +89,7 @@ final case class RedisConfig( username: Option[RedisUser], password: RedisPassword, probe: ProbeConfig -) +) extends pillars.Config object RedisConfig: given Configuration = Configuration.default.withKebabCaseMemberNames.withKebabCaseConstructorNames.withDefaults