More rework and expansion, have a complete audited implementation. Need more docs.
This commit is contained in:
		
							parent
							
								
									5a977acacb
								
							
						
					
					
						commit
						d51890f5bf
					
				
					 12 changed files with 274 additions and 81 deletions
				
			
		
							
								
								
									
										15
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										15
									
								
								README.md
									
										
									
									
									
								
							|  | @ -8,11 +8,18 @@ Configuration library for Scala 3. | ||||||
| 
 | 
 | ||||||
| ## Usage | ## Usage | ||||||
| 
 | 
 | ||||||
| This library is not yet published. | This artifact is available in the Garrity Software Maven repository. | ||||||
| 
 | 
 | ||||||
| ```scala | ```scala | ||||||
| object GS { | externalResolvers += | ||||||
|   val Config: ModuleID = |   "Garrity Software Releases" at "https://maven.garrity.co/releases" | ||||||
|  | 
 | ||||||
|  | val GsConfig: ModuleID = | ||||||
|   "gs" %% "gs-config-v0" % "0.1.0" |   "gs" %% "gs-config-v0" % "0.1.0" | ||||||
| } |  | ||||||
| ``` | ``` | ||||||
|  | 
 | ||||||
|  | TODO: | ||||||
|  | 
 | ||||||
|  | - Refactor -- move current config implementations to "raw" implementations. | ||||||
|  | - Create the notion of an audited configuration. | ||||||
|  | - Create the notion of an audited configuration with a list of sources. | ||||||
|  |  | ||||||
|  | @ -101,8 +101,8 @@ lazy val publishSettings = Seq( | ||||||
|   publishTo := { |   publishTo := { | ||||||
|     val repo = "https://maven.garrity.co/" |     val repo = "https://maven.garrity.co/" | ||||||
|     if (SelectedVersion.endsWith("SNAPSHOT")) |     if (SelectedVersion.endsWith("SNAPSHOT")) | ||||||
|       Some("snapshots" at repo + "snapshots") |       Some("Garrity Software Maven" at repo + "snapshots") | ||||||
|     else Some("releases" at repo + "releases") |     else Some("Garrity Software Maven" at repo + "releases") | ||||||
|   } |   } | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										88
									
								
								src/main/scala/gs/config/AuditedConfiguration.scala
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								src/main/scala/gs/config/AuditedConfiguration.scala
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,88 @@ | ||||||
|  | package gs.config | ||||||
|  | 
 | ||||||
|  | import cats.effect.Sync | ||||||
|  | import cats.syntax.all.* | ||||||
|  | import gs.config.audit.ConfigManifest | ||||||
|  | import gs.config.audit.ConfigQueryResult | ||||||
|  | import gs.config.source.ConfigSource | ||||||
|  | 
 | ||||||
|  | final class AuditedConfiguration[F[_]: Sync]( | ||||||
|  |   val sources: List[ConfigSource[F]], | ||||||
|  |   val manifest: ConfigManifest[F] | ||||||
|  | ) extends BaseConfiguration[F]: | ||||||
|  |   import AuditedConfiguration.Acc | ||||||
|  | 
 | ||||||
|  |   override def getValue[A: Configurable]( | ||||||
|  |     key: ConfigKey[A] | ||||||
|  |   ): F[Either[ConfigError, A]] = | ||||||
|  |     findAndParse(key) | ||||||
|  |       .map { acc => | ||||||
|  |         acc -> ( | ||||||
|  |           acc.result match | ||||||
|  |             case None         => handleMissingValue(key) | ||||||
|  |             case Some(result) => parse(key, result, acc.lastAttemptedSource) | ||||||
|  |         ) | ||||||
|  |       } | ||||||
|  |       .flatMap { case (acc, result) => audit(key, result, acc).as(result) } | ||||||
|  | 
 | ||||||
|  |   private def findAndParse[A: Configurable]( | ||||||
|  |     key: ConfigKey[A] | ||||||
|  |   ): F[Acc] = | ||||||
|  |     sources.foldLeft(Acc.empty[F]) { | ||||||
|  |       ( | ||||||
|  |         acc, | ||||||
|  |         src | ||||||
|  |       ) => | ||||||
|  |         acc.flatMap { data => | ||||||
|  |           if data.hasResult then Sync[F].pure(data) | ||||||
|  |           else | ||||||
|  |             src.getValue(key).map { | ||||||
|  |               case None    => data.appendSource(src.name) | ||||||
|  |               case Some(v) => data.appendSource(src.name).withResult(v) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |   private def audit( | ||||||
|  |     key: ConfigKey[?], | ||||||
|  |     result: Either[ConfigError, ?], | ||||||
|  |     acc: Acc | ||||||
|  |   ): F[Unit] = | ||||||
|  |     result match | ||||||
|  |       case Left(error) => | ||||||
|  |         manifest.record( | ||||||
|  |           name = key.name, | ||||||
|  |           queryResult = ConfigQueryResult.Failure( | ||||||
|  |             sources = acc.sources.toList, | ||||||
|  |             error = error | ||||||
|  |           ) | ||||||
|  |         ) | ||||||
|  |       case Right(_) => | ||||||
|  |         manifest.record( | ||||||
|  |           name = key.name, | ||||||
|  |           queryResult = ConfigQueryResult.Success( | ||||||
|  |             source = acc.lastAttemptedSource, | ||||||
|  |             rawValue = acc.result.getOrElse("") | ||||||
|  |           ) | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  | object AuditedConfiguration: | ||||||
|  | 
 | ||||||
|  |   private case class Acc( | ||||||
|  |     sources: Vector[String], | ||||||
|  |     result: Option[String] | ||||||
|  |   ): | ||||||
|  |     def hasResult: Boolean = result.isDefined | ||||||
|  | 
 | ||||||
|  |     def lastAttemptedSource: String = sources.lastOption.getOrElse("") | ||||||
|  | 
 | ||||||
|  |     def withResult(r: String): Acc = | ||||||
|  |       if hasResult then this else this.copy(sources, Some(r)) | ||||||
|  | 
 | ||||||
|  |     def appendSource(s: String): Acc = | ||||||
|  |       if hasResult then this else this.copy(sources.appended(s), result) | ||||||
|  | 
 | ||||||
|  |   private object Acc: | ||||||
|  |     def empty[F[_]: Sync]: F[Acc] = Sync[F].pure(Acc(Vector.empty, None)) | ||||||
|  | 
 | ||||||
|  | end AuditedConfiguration | ||||||
							
								
								
									
										32
									
								
								src/main/scala/gs/config/BaseConfiguration.scala
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/main/scala/gs/config/BaseConfiguration.scala
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,32 @@ | ||||||
|  | package gs.config | ||||||
|  | 
 | ||||||
|  | /** Base class for most [[Configuration]] implementations. Provides standard | ||||||
|  |   * support for properly handling default values and parsing strings to the | ||||||
|  |   * correct type, returning the correct errors. | ||||||
|  |   */ | ||||||
|  | abstract class BaseConfiguration[F[_]] extends Configuration[F]: | ||||||
|  | 
 | ||||||
|  |   protected def handleMissingValue[A]( | ||||||
|  |     key: ConfigKey[A] | ||||||
|  |   ): Either[ConfigError, A] = | ||||||
|  |     key match | ||||||
|  |       case ConfigKey.WithDefaultValue(_, defaultValue) => | ||||||
|  |         Right(defaultValue) | ||||||
|  |       case _ => | ||||||
|  |         Left(ConfigError.MissingValue(key.name)) | ||||||
|  | 
 | ||||||
|  |   protected def parse[A: Configurable]( | ||||||
|  |     key: ConfigKey[A], | ||||||
|  |     raw: String, | ||||||
|  |     sourceName: String | ||||||
|  |   ): Either[ConfigError, A] = | ||||||
|  |     Configurable[A].parse(raw) match | ||||||
|  |       case None => | ||||||
|  |         Left( | ||||||
|  |           ConfigError.CannotParseValue( | ||||||
|  |             configName = key.name, | ||||||
|  |             candidateValue = raw, | ||||||
|  |             sourceName = sourceName | ||||||
|  |           ) | ||||||
|  |         ) | ||||||
|  |       case Some(parsed) => Right(parsed) | ||||||
|  | @ -1,7 +1,5 @@ | ||||||
| package gs.config | package gs.config | ||||||
| 
 | 
 | ||||||
| import gs.config.audit.ConfigSource |  | ||||||
| 
 |  | ||||||
| /** Error hierarchy for the `gs-config` library. Indicates that something went | /** Error hierarchy for the `gs-config` library. Indicates that something went | ||||||
|   * wrong while attempting to load configuration values. |   * wrong while attempting to load configuration values. | ||||||
|   */ |   */ | ||||||
|  | @ -11,25 +9,25 @@ object ConfigError: | ||||||
|   /** Attempted to retreive the value for some [[ConfigKey]], but no value could |   /** Attempted to retreive the value for some [[ConfigKey]], but no value could | ||||||
|     * be found. |     * be found. | ||||||
|     * |     * | ||||||
|     * @param name |     * @param configName | ||||||
|     *   The name of the configuration value which was not found. |     *   The name of the configuration value which was not found. | ||||||
|     */ |     */ | ||||||
|   case class MissingValue(name: ConfigName) extends ConfigError |   case class MissingValue(configName: ConfigName) extends ConfigError | ||||||
| 
 | 
 | ||||||
|   /** Found a value for some [[ConfigKey]], but that value could not be parsed |   /** Found a value for some [[ConfigKey]], but that value could not be parsed | ||||||
|     * as the appropriate type. |     * as the appropriate type. | ||||||
|     * |     * | ||||||
|     * @param name |     * @param configName | ||||||
|     *   The name of the configuration value which could not be parsed. |     *   The name of the configuration value which could not be parsed. | ||||||
|     * @param candidate |     * @param candidateValue | ||||||
|     *   The raw value that could not be parsed. |     *   The raw value that could not be parsed. | ||||||
|     * @param source |     * @param source | ||||||
|     *   The [[ConfigSource]] which provided the candidate value. |     *   The [[ConfigSource]] which provided the candidate value. | ||||||
|     */ |     */ | ||||||
|   case class CannotParseValue( |   case class CannotParseValue( | ||||||
|     name: ConfigName, |     configName: ConfigName, | ||||||
|     candidate: String, |     candidateValue: String, | ||||||
|     source: ConfigSource |     sourceName: String | ||||||
|   ) extends ConfigError |   ) extends ConfigError | ||||||
| 
 | 
 | ||||||
| end ConfigError | end ConfigError | ||||||
|  |  | ||||||
|  | @ -1,24 +1,4 @@ | ||||||
| package gs.config | package gs.config | ||||||
| 
 | 
 | ||||||
| import gs.config.audit.ConfigSource |  | ||||||
| 
 |  | ||||||
| /** Interface for loading configuration values. |  | ||||||
|   * |  | ||||||
|   * This interface is **not** intended to be used with sensitive information. Do |  | ||||||
|   * not use this interface for loading passwords, encryption keys, or any other |  | ||||||
|   * sensitive information. |  | ||||||
|   */ |  | ||||||
| trait Configuration[F[_]]: | trait Configuration[F[_]]: | ||||||
|   /** Retrieve the value for the specified key. |  | ||||||
|     * |  | ||||||
|     * @param key |  | ||||||
|     *   The key which defines the piece of configuration. |  | ||||||
|     * @return |  | ||||||
|     *   The value, or an error if no value can be retrieved. |  | ||||||
|     */ |  | ||||||
|   def getValue[A: Configurable](key: ConfigKey[A]): F[Either[ConfigError, A]] |   def getValue[A: Configurable](key: ConfigKey[A]): F[Either[ConfigError, A]] | ||||||
| 
 |  | ||||||
|   /** @return |  | ||||||
|     *   The backing source for this configuration. |  | ||||||
|     */ |  | ||||||
|   def source: ConfigSource |  | ||||||
|  |  | ||||||
|  | @ -2,6 +2,7 @@ package gs.config.audit | ||||||
| 
 | 
 | ||||||
| import cats.effect.Ref | import cats.effect.Ref | ||||||
| import cats.effect.Sync | import cats.effect.Sync | ||||||
|  | import cats.syntax.all.* | ||||||
| import gs.config.ConfigName | import gs.config.ConfigName | ||||||
| 
 | 
 | ||||||
| trait ConfigManifest[F[_]]: | trait ConfigManifest[F[_]]: | ||||||
|  | @ -12,44 +13,66 @@ trait ConfigManifest[F[_]]: | ||||||
|     * @return |     * @return | ||||||
|     *   The current state of this manifest. |     *   The current state of this manifest. | ||||||
|     */ |     */ | ||||||
|   def snapshot(): F[Map[ConfigName, List[ConfigQuery]]] |   def snapshot(): F[Map[ConfigName, List[ConfigQueryResult]]] | ||||||
| 
 | 
 | ||||||
|   /** Record a query for some [[ConfigName]] in this manifest. |   /** Record a query for some [[ConfigName]] in this manifest. | ||||||
|     * |     * | ||||||
|     * @param name |     * @param name | ||||||
|     *   The [[ConfigName]] that was queried. |     *   The [[ConfigName]] that was queried. | ||||||
|     * @param query |     * @param queryResult | ||||||
|     *   The [[ConfigQuery]] describing the result. |     *   The [[ConfigQueryResult]] describing the result. | ||||||
|     * @return |     * @return | ||||||
|     *   Side-effect indicating that the query was recorded. |     *   Side-effect indicating that the query was recorded. | ||||||
|     */ |     */ | ||||||
|   def record( |   def record( | ||||||
|     name: ConfigName, |     name: ConfigName, | ||||||
|     query: ConfigQuery |     queryResult: ConfigQueryResult | ||||||
|   ): F[Unit] |   ): F[Unit] | ||||||
| 
 | 
 | ||||||
| object ConfigManifest: | object ConfigManifest: | ||||||
| 
 | 
 | ||||||
|  |   /** Instantiate a new, empty, standard manifest. | ||||||
|  |     */ | ||||||
|  |   def standard[F[_]: Sync]: F[ConfigManifest[F]] = | ||||||
|  |     Standard.initialize[F] | ||||||
|  | 
 | ||||||
|  |   /** Standard implementation of [[ConfigManifest]]. Collects an in-memory | ||||||
|  |     * collection to audit configuration access. | ||||||
|  |     * | ||||||
|  |     * @param manifest | ||||||
|  |     *   The underlying manifest. | ||||||
|  |     */ | ||||||
|   final class Standard[F[_]: Sync] private ( |   final class Standard[F[_]: Sync] private ( | ||||||
|     private val manifest: Ref[F, Map[ConfigName, List[ConfigQuery]]] |     private val manifest: Ref[F, Map[ConfigName, List[ConfigQueryResult]]] | ||||||
|   ) extends ConfigManifest[F]: |   ) extends ConfigManifest[F]: | ||||||
| 
 | 
 | ||||||
|     override def snapshot(): F[Map[ConfigName, List[ConfigQuery]]] = |     override def snapshot(): F[Map[ConfigName, List[ConfigQueryResult]]] = | ||||||
|       manifest.get |       manifest.get | ||||||
| 
 | 
 | ||||||
|     override def record( |     override def record( | ||||||
|       name: ConfigName, |       name: ConfigName, | ||||||
|       query: ConfigQuery |       query: ConfigQueryResult | ||||||
|     ): F[Unit] = |     ): F[Unit] = | ||||||
|       manifest.update(addQueryToName(name, query, _)) |       manifest.update(addQueryToName(name, query, _)) | ||||||
| 
 | 
 | ||||||
|     private def addQueryToName( |     private def addQueryToName( | ||||||
|       name: ConfigName, |       name: ConfigName, | ||||||
|       query: ConfigQuery, |       query: ConfigQueryResult, | ||||||
|       state: Map[ConfigName, List[ConfigQuery]] |       state: Map[ConfigName, List[ConfigQueryResult]] | ||||||
|     ): Map[ConfigName, List[ConfigQuery]] = |     ): Map[ConfigName, List[ConfigQueryResult]] = | ||||||
|       state.get(name) match |       state.get(name) match | ||||||
|         case Some(queries) => state.updated(name, queries ++ List(query)) |         case Some(queries) => state.updated(name, queries ++ List(query)) | ||||||
|         case None          => state + (name -> List(query)) |         case None          => state + (name -> List(query)) | ||||||
| 
 | 
 | ||||||
|  |   object Standard: | ||||||
|  | 
 | ||||||
|  |     /** Instantiate a new, empty, standard manifest. | ||||||
|  |       */ | ||||||
|  |     def initialize[F[_]: Sync]: F[ConfigManifest[F]] = | ||||||
|  |       Ref | ||||||
|  |         .of(Map.empty[ConfigName, List[ConfigQueryResult]]) | ||||||
|  |         .map(m => new Standard[F](m)) | ||||||
|  | 
 | ||||||
|  |   end Standard | ||||||
|  | 
 | ||||||
| end ConfigManifest | end ConfigManifest | ||||||
|  |  | ||||||
|  | @ -5,9 +5,9 @@ import gs.config.ConfigError | ||||||
| /** Describes queries used to find configuration. Used for auditing purposes and | /** Describes queries used to find configuration. Used for auditing purposes and | ||||||
|   * is captured by [[ConfigManifest]]. |   * is captured by [[ConfigManifest]]. | ||||||
|   */ |   */ | ||||||
| sealed trait ConfigQuery | sealed trait ConfigQueryResult | ||||||
| 
 | 
 | ||||||
| object ConfigQuery: | object ConfigQueryResult: | ||||||
| 
 | 
 | ||||||
|   /** Represents a query for some configuration that completed successfully. |   /** Represents a query for some configuration that completed successfully. | ||||||
|     * |     * | ||||||
|  | @ -16,10 +16,10 @@ object ConfigQuery: | ||||||
|     * @param rawValue |     * @param rawValue | ||||||
|     *   The raw value that the source returned. |     *   The raw value that the source returned. | ||||||
|     */ |     */ | ||||||
|   case class SuccessfulQuery( |   case class Success( | ||||||
|     source: ConfigSource, |     source: String, | ||||||
|     rawValue: String |     rawValue: String | ||||||
|   ) extends ConfigQuery |   ) extends ConfigQueryResult | ||||||
| 
 | 
 | ||||||
|   /** Represents a query for some configuration that failed. |   /** Represents a query for some configuration that failed. | ||||||
|     * |     * | ||||||
|  | @ -29,9 +29,9 @@ object ConfigQuery: | ||||||
|     * @param error |     * @param error | ||||||
|     *   The reason why this query failed. |     *   The reason why this query failed. | ||||||
|     */ |     */ | ||||||
|   case class FailedQuery( |   case class Failure( | ||||||
|     sources: List[ConfigSource], |     sources: List[String], | ||||||
|     error: ConfigError |     error: ConfigError | ||||||
|   ) |   ) extends ConfigQueryResult | ||||||
| 
 | 
 | ||||||
| end ConfigQuery | end ConfigQueryResult | ||||||
|  | @ -1,26 +0,0 @@ | ||||||
| package gs.config.audit |  | ||||||
| 
 |  | ||||||
| /** Represents some _source_ of configuration data. This might be the system |  | ||||||
|   * environment, a properties file, application runtime properties, or some |  | ||||||
|   * other source. |  | ||||||
|   * |  | ||||||
|   * This type is used for auditing purposes. Each [[gs.config.Configuration]] |  | ||||||
|   * has a source, and the source which retrieves (or fails to retrieve) some |  | ||||||
|   * value is recorded in the [[ConfigManifest]]. |  | ||||||
|   */ |  | ||||||
| opaque type ConfigSource = String |  | ||||||
| 
 |  | ||||||
| object ConfigSource: |  | ||||||
| 
 |  | ||||||
|   /** Instantiate a new `ConfigSource`. |  | ||||||
|     * |  | ||||||
|     * @param source |  | ||||||
|     *   The name of the source. |  | ||||||
|     * @return |  | ||||||
|     *   The new instance. |  | ||||||
|     */ |  | ||||||
|   def apply(source: String): ConfigSource = source |  | ||||||
| 
 |  | ||||||
|   given CanEqual[ConfigSource, ConfigSource] = CanEqual.derived |  | ||||||
| 
 |  | ||||||
| end ConfigSource |  | ||||||
							
								
								
									
										48
									
								
								src/main/scala/gs/config/source/ConfigSource.scala
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/main/scala/gs/config/source/ConfigSource.scala
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,48 @@ | ||||||
|  | package gs.config.source | ||||||
|  | 
 | ||||||
|  | import cats.Applicative | ||||||
|  | import cats.effect.Sync | ||||||
|  | import gs.config.ConfigKey | ||||||
|  | 
 | ||||||
|  | /** Interface for loading raw configuration values. | ||||||
|  |   * | ||||||
|  |   * This interface is **not** intended to be used with sensitive information. Do | ||||||
|  |   * not use this interface for loading passwords, encryption keys, or any other | ||||||
|  |   * sensitive information. | ||||||
|  |   */ | ||||||
|  | trait ConfigSource[F[_]]: | ||||||
|  |   /** Retrieve the value for the specified key. | ||||||
|  |     * | ||||||
|  |     * @param key | ||||||
|  |     *   The key which defines the piece of configuration. | ||||||
|  |     * @return | ||||||
|  |     *   The raw value, or an error if no value can be retrieved. | ||||||
|  |     */ | ||||||
|  |   def getValue(key: ConfigKey[?]): F[Option[String]] | ||||||
|  | 
 | ||||||
|  |   /** @return | ||||||
|  |     *   The name of this source. | ||||||
|  |     */ | ||||||
|  |   def name: String | ||||||
|  | 
 | ||||||
|  | object ConfigSource: | ||||||
|  | 
 | ||||||
|  |   def inMemory[F[_]: Applicative]( | ||||||
|  |     configs: Map[String, String] | ||||||
|  |   ): ConfigSource[F] = | ||||||
|  |     new MemoryConfigSource[F](configs) | ||||||
|  | 
 | ||||||
|  |   def environment[F[_]: Sync]: ConfigSource[F] = | ||||||
|  |     new EnvironmentConfigSource[F] | ||||||
|  | 
 | ||||||
|  |   def empty[F[_]: Applicative]: ConfigSource[F] = | ||||||
|  |     new Empty[F] | ||||||
|  | 
 | ||||||
|  |   final class Empty[F[_]: Applicative] extends ConfigSource[F]: | ||||||
|  | 
 | ||||||
|  |     override def getValue(key: ConfigKey[?]): F[Option[String]] = | ||||||
|  |       Applicative[F].pure(None) | ||||||
|  | 
 | ||||||
|  |     override val name: String = "empty" | ||||||
|  | 
 | ||||||
|  | end ConfigSource | ||||||
|  | @ -0,0 +1,16 @@ | ||||||
|  | package gs.config.source | ||||||
|  | 
 | ||||||
|  | import cats.effect.Sync | ||||||
|  | import gs.config.ConfigKey | ||||||
|  | 
 | ||||||
|  | /** Environment variable implementation of [[ConfigSource]]. Pulls all values | ||||||
|  |   * from the system environment that was passed to this process. | ||||||
|  |   */ | ||||||
|  | final class EnvironmentConfigSource[F[_]: Sync] extends ConfigSource[F]: | ||||||
|  | 
 | ||||||
|  |   override def getValue( | ||||||
|  |     key: ConfigKey[?] | ||||||
|  |   ): F[Option[String]] = | ||||||
|  |     Sync[F].delay(sys.env.get(key.name.toEnvironmentVariable())) | ||||||
|  | 
 | ||||||
|  |   override val name: String = "environment" | ||||||
							
								
								
									
										27
									
								
								src/main/scala/gs/config/source/MemoryConfigSource.scala
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/main/scala/gs/config/source/MemoryConfigSource.scala
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,27 @@ | ||||||
|  | package gs.config.source | ||||||
|  | 
 | ||||||
|  | import cats.Applicative | ||||||
|  | import gs.config.ConfigKey | ||||||
|  | import java.util.UUID | ||||||
|  | 
 | ||||||
|  | /** In-memory implementation based on an immutable map. | ||||||
|  |   * | ||||||
|  |   * The raw value of the [[ConfigName]] is used for lookups (`toRawString()`). | ||||||
|  |   * | ||||||
|  |   * @param configs | ||||||
|  |   *   The configurations to provide. | ||||||
|  |   */ | ||||||
|  | final class MemoryConfigSource[F[_]: Applicative]( | ||||||
|  |   private val configs: Map[String, String] | ||||||
|  | ) extends ConfigSource[F]: | ||||||
|  |   val id: UUID = UUID.randomUUID() | ||||||
|  | 
 | ||||||
|  |   override def getValue( | ||||||
|  |     key: ConfigKey[?] | ||||||
|  |   ): F[Option[String]] = | ||||||
|  |     Applicative[F].pure( | ||||||
|  |       configs | ||||||
|  |         .get(key.name.toRawString()) | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |   override lazy val name: String = s"in-memory-$id" | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue