Skip to content

Commit 73377cb

Browse files
committed
WIP
1 parent 545d2e1 commit 73377cb

8 files changed

Lines changed: 776 additions & 644 deletions

File tree

Lines changed: 122 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,44 @@
11
package com.flowforge.config
22

3-
import cats.data.{NonEmptyList, ValidatedNel}
4-
import cats.effect.{Sync, Resource}
3+
import cats.data.{ NonEmptyList, ValidatedNel }
4+
import cats.effect.{ Resource, Sync }
55
import cats.syntax.all._
6-
import com.typesafe.config.{Config, ConfigFactory}
6+
import com.typesafe.config.{ Config, ConfigFactory }
77
import scala.concurrent.duration._
8-
import scala.util.{Try, Success, Failure}
8+
import scala.util.{ Failure, Success, Try }
99

1010
/**
11-
* Type-safe configuration management system - replacement for CCM.
12-
* Provides compile-time safe configuration loading with validation.
11+
* Type-safe configuration management system - replacement for CCM. Provides compile-time safe
12+
* configuration loading with validation.
1313
*/
1414
trait ConfigurationManagement[F[_]] {
15-
15+
1616
/**
17-
* Load type-safe configuration with validation.
18-
* Returns all validation errors if configuration is invalid.
17+
* Load type-safe configuration with validation. Returns all validation errors if configuration is
18+
* invalid.
1919
*/
2020
def loadTypeSafeConfig[T: ConfigDecoder](key: String): F[ValidatedNel[ConfigError, T]]
21-
21+
2222
/**
2323
* Load optional configuration - returns None if key doesn't exist.
2424
*/
2525
def loadOptionalConfig[T: ConfigDecoder](key: String): F[Option[T]]
26-
26+
2727
/**
2828
* Watch configuration for changes - returns stream of updates.
2929
*/
3030
def watchConfig[T: ConfigDecoder](key: String): fs2.Stream[F, T]
31-
31+
3232
/**
3333
* Refresh configuration from source.
3434
*/
3535
def refreshConfig: F[Unit]
36-
36+
3737
/**
3838
* Load configuration for specific environment.
3939
*/
4040
def loadForEnvironment[T: ConfigDecoder](
41-
key: String,
41+
key: String,
4242
env: Environment
4343
): F[ValidatedNel[ConfigError, T]]
4444
}
@@ -61,15 +61,15 @@ object ConfigError {
6161
case class MissingKey(key: String) extends ConfigError {
6262
override def message: String = s"Configuration key '$key' is missing"
6363
}
64-
64+
6565
case class InvalidType(key: String, expected: String, actual: String) extends ConfigError {
6666
override def message: String = s"Configuration key '$key' expected $expected but got $actual"
6767
}
68-
68+
6969
case class ValidationFailed(key: String, reason: String) extends ConfigError {
7070
override def message: String = s"Configuration validation failed for '$key': $reason"
7171
}
72-
72+
7373
case class ParseError(key: String, error: String) extends ConfigError {
7474
override def message: String = s"Failed to parse configuration '$key': $error"
7575
}
@@ -82,14 +82,14 @@ sealed trait Environment extends Product with Serializable
8282

8383
object Environment {
8484
case object Development extends Environment
85-
case object Staging extends Environment
86-
case object Production extends Environment
87-
85+
case object Staging extends Environment
86+
case object Production extends Environment
87+
8888
def fromString(env: String): Option[Environment] = env.toLowerCase match {
8989
case "dev" | "development" => Some(Development)
90-
case "staging" | "stage" => Some(Staging)
90+
case "staging" | "stage" => Some(Staging)
9191
case "prod" | "production" => Some(Production)
92-
case _ => None
92+
case _ => None
9393
}
9494
}
9595

@@ -172,66 +172,64 @@ case class AuditConfig(
172172
)
173173

174174
object ConfigurationManagement {
175-
175+
176176
/**
177177
* Create ConfigurationManagement instance.
178178
*/
179-
def apply[F[_]: ConfigurationManagement]: ConfigurationManagement[F] =
179+
def apply[F[_]: ConfigurationManagement]: ConfigurationManagement[F] =
180180
implicitly[ConfigurationManagement[F]]
181-
181+
182182
/**
183183
* Default implementation using Typesafe Config.
184184
*/
185-
implicit def forTypesafeConfig[F[_]: Sync]: ConfigurationManagement[F] =
185+
implicit def forTypesafeConfig[F[_]: Sync]: ConfigurationManagement[F] =
186186
new TypesafeConfigManagement[F]
187-
187+
188188
private class TypesafeConfigManagement[F[_]: Sync] extends ConfigurationManagement[F] {
189-
189+
190190
private val config = ConfigFactory.load()
191-
192-
override def loadTypeSafeConfig[T: ConfigDecoder](key: String): F[ValidatedNel[ConfigError, T]] = {
191+
192+
override def loadTypeSafeConfig[T: ConfigDecoder](
193+
key: String
194+
): F[ValidatedNel[ConfigError, T]] =
193195
Sync[F].delay {
194196
if (config.hasPath(key)) {
195197
ConfigDecoder[T].decode(config, key)
196198
} else {
197199
ConfigError.MissingKey(key).invalidNel
198200
}
199201
}
200-
}
201-
202-
override def loadOptionalConfig[T: ConfigDecoder](key: String): F[Option[T]] = {
202+
203+
override def loadOptionalConfig[T: ConfigDecoder](key: String): F[Option[T]] =
203204
Sync[F].delay {
204205
if (config.hasPath(key)) {
205206
ConfigDecoder[T].decode(config, key).toOption
206207
} else {
207208
None
208209
}
209210
}
210-
}
211-
212-
override def watchConfig[T: ConfigDecoder](key: String): fs2.Stream[F, T] = {
211+
212+
override def watchConfig[T: ConfigDecoder](key: String): fs2.Stream[F, T] =
213213
// TODO: Implement configuration watching with file system monitoring
214214
fs2.Stream.empty
215-
}
216-
217-
override def refreshConfig: F[Unit] = {
215+
216+
override def refreshConfig: F[Unit] =
218217
Sync[F].delay {
219218
// TODO: Implement configuration refresh
220219
()
221220
}
222-
}
223-
221+
224222
override def loadForEnvironment[T: ConfigDecoder](
225-
key: String,
223+
key: String,
226224
env: Environment
227225
): F[ValidatedNel[ConfigError, T]] = {
228226
val envKey = s"${env.toString.toLowerCase}.$key"
229227
loadTypeSafeConfig[T](envKey).flatMap { result =>
230228
result match {
231-
case cats.data.Validated.Invalid(_) =>
229+
case cats.data.Validated.Invalid(_) =>
232230
// Fall back to non-environment specific key
233231
loadTypeSafeConfig[T](key)
234-
case valid =>
232+
case valid =>
235233
Sync[F].pure(valid)
236234
}
237235
}
@@ -243,64 +241,65 @@ object ConfigurationManagement {
243241
* Configuration decoder instances for common types.
244242
*/
245243
object ConfigDecoder {
246-
244+
247245
def apply[T: ConfigDecoder]: ConfigDecoder[T] = implicitly[ConfigDecoder[T]]
248-
246+
249247
// Basic type decoders
250248
implicit val stringDecoder: ConfigDecoder[String] = new ConfigDecoder[String] {
251-
override def decode(config: Config, path: String): ValidatedNel[ConfigError, String] = {
249+
override def decode(config: Config, path: String): ValidatedNel[ConfigError, String] =
252250
Try(config.getString(path)) match {
253251
case Success(value) => value.validNel
254-
case Failure(ex) => ConfigError.ParseError(path, ex.getMessage).invalidNel
252+
case Failure(ex) => ConfigError.ParseError(path, ex.getMessage).invalidNel
255253
}
256-
}
257254
}
258-
255+
259256
implicit val intDecoder: ConfigDecoder[Int] = new ConfigDecoder[Int] {
260-
override def decode(config: Config, path: String): ValidatedNel[ConfigError, Int] = {
257+
override def decode(config: Config, path: String): ValidatedNel[ConfigError, Int] =
261258
Try(config.getInt(path)) match {
262259
case Success(value) => value.validNel
263-
case Failure(ex) => ConfigError.ParseError(path, ex.getMessage).invalidNel
260+
case Failure(ex) => ConfigError.ParseError(path, ex.getMessage).invalidNel
264261
}
265-
}
266262
}
267-
263+
268264
implicit val booleanDecoder: ConfigDecoder[Boolean] = new ConfigDecoder[Boolean] {
269-
override def decode(config: Config, path: String): ValidatedNel[ConfigError, Boolean] = {
265+
override def decode(config: Config, path: String): ValidatedNel[ConfigError, Boolean] =
270266
Try(config.getBoolean(path)) match {
271267
case Success(value) => value.validNel
272-
case Failure(ex) => ConfigError.ParseError(path, ex.getMessage).invalidNel
268+
case Failure(ex) => ConfigError.ParseError(path, ex.getMessage).invalidNel
273269
}
274-
}
275270
}
276-
277-
implicit val finiteDurationDecoder: ConfigDecoder[FiniteDuration] = new ConfigDecoder[FiniteDuration] {
278-
override def decode(config: Config, path: String): ValidatedNel[ConfigError, FiniteDuration] = {
279-
Try(config.getDuration(path).toMillis.millis) match {
280-
case Success(value) => value.validNel
281-
case Failure(ex) => ConfigError.ParseError(path, ex.getMessage).invalidNel
282-
}
271+
272+
implicit val finiteDurationDecoder: ConfigDecoder[FiniteDuration] =
273+
new ConfigDecoder[FiniteDuration] {
274+
override def decode(config: Config, path: String): ValidatedNel[ConfigError, FiniteDuration] =
275+
Try(config.getDuration(path).toMillis.millis) match {
276+
case Success(value) => value.validNel
277+
case Failure(ex) => ConfigError.ParseError(path, ex.getMessage).invalidNel
278+
}
283279
}
284-
}
285-
280+
286281
// Complex type decoders
287-
implicit val pipelineConfigDecoder: ConfigDecoder[PipelineConfig] = new ConfigDecoder[PipelineConfig] {
288-
override def decode(config: Config, path: String): ValidatedNel[ConfigError, PipelineConfig] = {
289-
val pipelineConfig = config.getConfig(path)
290-
291-
(
292-
stringDecoder.decode(pipelineConfig, "name"),
293-
intDecoder.decode(pipelineConfig, "batchSize"),
294-
intDecoder.decode(pipelineConfig, "parallelism"),
295-
finiteDurationDecoder.decode(pipelineConfig, "timeout")
296-
).mapN(PipelineConfig.apply)
282+
implicit val pipelineConfigDecoder: ConfigDecoder[PipelineConfig] =
283+
new ConfigDecoder[PipelineConfig] {
284+
override def decode(
285+
config: Config,
286+
path: String
287+
): ValidatedNel[ConfigError, PipelineConfig] = {
288+
val pipelineConfig = config.getConfig(path)
289+
290+
(
291+
stringDecoder.decode(pipelineConfig, "name"),
292+
intDecoder.decode(pipelineConfig, "batchSize"),
293+
intDecoder.decode(pipelineConfig, "parallelism"),
294+
finiteDurationDecoder.decode(pipelineConfig, "timeout")
295+
).mapN(PipelineConfig.apply)
296+
}
297297
}
298-
}
299-
298+
300299
implicit val sparkConfigDecoder: ConfigDecoder[SparkConfig] = new ConfigDecoder[SparkConfig] {
301300
override def decode(config: Config, path: String): ValidatedNel[ConfigError, SparkConfig] = {
302301
val sparkConfig = config.getConfig(path)
303-
302+
304303
(
305304
stringDecoder.decode(sparkConfig, "appName"),
306305
stringDecoder.decode(sparkConfig, "master"),
@@ -309,56 +308,67 @@ object ConfigDecoder {
309308
).mapN(SparkConfig.apply)
310309
}
311310
}
312-
311+
313312
// TODO: Add decoders for other configuration types as needed
314313
implicit val engineConfigDecoder: ConfigDecoder[EngineConfig] = new ConfigDecoder[EngineConfig] {
315314
override def decode(config: Config, path: String): ValidatedNel[ConfigError, EngineConfig] = {
316315
val engineConfig = config.getConfig(path)
317316
sparkConfigDecoder.decode(engineConfig, "spark").map(spark => EngineConfig(spark))
318317
}
319318
}
320-
321-
implicit val monitoringConfigDecoder: ConfigDecoder[MonitoringConfig] = new ConfigDecoder[MonitoringConfig] {
322-
override def decode(config: Config, path: String): ValidatedNel[ConfigError, MonitoringConfig] = {
323-
val monitoringConfig = config.getConfig(path)
324-
325-
(
326-
booleanDecoder.decode(monitoringConfig, "enableMetrics"),
327-
booleanDecoder.decode(monitoringConfig, "enableTracing"),
328-
intDecoder.decode(monitoringConfig, "metricsPort")
329-
).mapN(MonitoringConfig.apply)
319+
320+
implicit val monitoringConfigDecoder: ConfigDecoder[MonitoringConfig] =
321+
new ConfigDecoder[MonitoringConfig] {
322+
override def decode(
323+
config: Config,
324+
path: String
325+
): ValidatedNel[ConfigError, MonitoringConfig] = {
326+
val monitoringConfig = config.getConfig(path)
327+
328+
(
329+
booleanDecoder.decode(monitoringConfig, "enableMetrics"),
330+
booleanDecoder.decode(monitoringConfig, "enableTracing"),
331+
intDecoder.decode(monitoringConfig, "metricsPort")
332+
).mapN(MonitoringConfig.apply)
333+
}
330334
}
331-
}
332-
335+
333336
implicit val auditConfigDecoder: ConfigDecoder[AuditConfig] = new ConfigDecoder[AuditConfig] {
334337
override def decode(config: Config, path: String): ValidatedNel[ConfigError, AuditConfig] = {
335338
val auditConfig = config.getConfig(path)
336-
339+
337340
(
338341
booleanDecoder.decode(auditConfig, "enableAuditing"),
339342
stringDecoder.decode(auditConfig, "auditLogPath")
340343
).mapN(AuditConfig.apply)
341344
}
342345
}
343-
346+
344347
// Placeholder for connector config decoder (to be implemented when connectors are built)
345-
implicit val connectorConfigDecoder: ConfigDecoder[ConnectorConfig] = new ConfigDecoder[ConnectorConfig] {
346-
override def decode(config: Config, path: String): ValidatedNel[ConfigError, ConnectorConfig] = {
347-
ConnectorConfig().validNel // Empty for now
348+
implicit val connectorConfigDecoder: ConfigDecoder[ConnectorConfig] =
349+
new ConfigDecoder[ConnectorConfig] {
350+
override def decode(
351+
config: Config,
352+
path: String
353+
): ValidatedNel[ConfigError, ConnectorConfig] =
354+
ConnectorConfig().validNel // Empty for now
348355
}
349-
}
350-
351-
implicit val flowForgeConfigDecoder: ConfigDecoder[FlowForgeConfig] = new ConfigDecoder[FlowForgeConfig] {
352-
override def decode(config: Config, path: String): ValidatedNel[ConfigError, FlowForgeConfig] = {
353-
val flowforgeConfig = config.getConfig(path)
354-
355-
(
356-
pipelineConfigDecoder.decode(flowforgeConfig, "pipeline"),
357-
engineConfigDecoder.decode(flowforgeConfig, "engines"),
358-
connectorConfigDecoder.decode(flowforgeConfig, "connectors"),
359-
monitoringConfigDecoder.decode(flowforgeConfig, "monitoring"),
360-
auditConfigDecoder.decode(flowforgeConfig, "audit")
361-
).mapN(FlowForgeConfig.apply)
356+
357+
implicit val flowForgeConfigDecoder: ConfigDecoder[FlowForgeConfig] =
358+
new ConfigDecoder[FlowForgeConfig] {
359+
override def decode(
360+
config: Config,
361+
path: String
362+
): ValidatedNel[ConfigError, FlowForgeConfig] = {
363+
val flowforgeConfig = config.getConfig(path)
364+
365+
(
366+
pipelineConfigDecoder.decode(flowforgeConfig, "pipeline"),
367+
engineConfigDecoder.decode(flowforgeConfig, "engines"),
368+
connectorConfigDecoder.decode(flowforgeConfig, "connectors"),
369+
monitoringConfigDecoder.decode(flowforgeConfig, "monitoring"),
370+
auditConfigDecoder.decode(flowforgeConfig, "audit")
371+
).mapN(FlowForgeConfig.apply)
372+
}
362373
}
363-
}
364-
}
374+
}

0 commit comments

Comments
 (0)