(patch) series of updates and beginning to write tests

This commit is contained in:
Pat Garrity 2024-04-30 22:15:24 -05:00
parent eddc5d5ae8
commit 962cae1268
Signed by: pfm
GPG key ID: 5CA5D21BAB7F3A76
13 changed files with 117 additions and 9 deletions

View file

@ -11,6 +11,6 @@ repos:
description: Enforces using only 'LF' line endings. description: Enforces using only 'LF' line endings.
- id: trailing-whitespace - id: trailing-whitespace
- repo: https://git.garrity.co/garrity-software/gs-pre-commit-scala - repo: https://git.garrity.co/garrity-software/gs-pre-commit-scala
rev: v1.0.0 rev: v1.0.1
hooks: hooks:
- id: scalafmt - id: scalafmt

View file

@ -1,5 +1,5 @@
// See: https://github.com/scalameta/scalafmt/tags for the latest tags. // See: https://github.com/scalameta/scalafmt/tags for the latest tags.
version = 3.7.17 version = 3.8.1
runner.dialect = scala3 runner.dialect = scala3
maxColumn = 80 maxColumn = 80

View file

@ -16,7 +16,7 @@ lazy val sharedSettings = Seq(
lazy val testSettings = Seq( lazy val testSettings = Seq(
libraryDependencies ++= Seq( libraryDependencies ++= Seq(
"org.scalameta" %% "munit" % "1.0.0-M10" % Test "org.scalameta" %% "munit" % "1.0.0-RC1" % Test
) )
) )

View file

@ -89,7 +89,7 @@ object AuditedConfiguration:
*/ */
def forSources[F[_]: Sync]( def forSources[F[_]: Sync](
sources: NonEmptyList[ConfigSource[F]] sources: NonEmptyList[ConfigSource[F]]
): F[Configuration[F]] = ): F[AuditedConfiguration[F]] =
ConfigManifest ConfigManifest
.standard[F] .standard[F]
.map(manifest => .map(manifest =>
@ -119,7 +119,7 @@ object AuditedConfiguration:
def withSource(source: ConfigSource[F]): Builder[F] = def withSource(source: ConfigSource[F]): Builder[F] =
copy(sources = this.sources.append(source)) copy(sources = this.sources.append(source))
def build(): F[Configuration[F]] = def build(): F[AuditedConfiguration[F]] =
ConfigManifest ConfigManifest
.standard[F] .standard[F]
.map(manifest => .map(manifest =>

View file

@ -6,6 +6,8 @@ package gs.config.v0
sealed trait ConfigError sealed trait ConfigError
object ConfigError: object ConfigError:
given CanEqual[ConfigError, ConfigError] = CanEqual.derived
/** 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.
* *

View file

@ -24,23 +24,33 @@ object Configurable:
): Configurable[A] = C ): Configurable[A] = C
given Configurable[String] with given Configurable[String] with
/** @inheritDocs
*/
def parse(raw: String): Option[String] = Some(raw) def parse(raw: String): Option[String] = Some(raw)
given Configurable[Int] with given Configurable[Int] with
/** @inheritDocs
*/
def parse(raw: String): Option[Int] = Try(raw.toInt).toOption def parse(raw: String): Option[Int] = Try(raw.toInt).toOption
given Configurable[Long] with given Configurable[Long] with
/** @inheritDocs
*/
def parse(raw: String): Option[Long] = Try(raw.toLong).toOption def parse(raw: String): Option[Long] = Try(raw.toLong).toOption
given Configurable[Boolean] with given Configurable[Boolean] with
/** @inheritDocs
*/
def parse(raw: String): Option[Boolean] = Try(raw.toBoolean).toOption def parse(raw: String): Option[Boolean] = Try(raw.toBoolean).toOption
given Configurable[LocalDate] with given Configurable[LocalDate] with
/** @inheritDocs
*/
def parse(raw: String): Option[LocalDate] = def parse(raw: String): Option[LocalDate] =
Try(LocalDate.parse(raw)).toOption Try(LocalDate.parse(raw)).toOption
given Configurable[Instant] with given Configurable[Instant] with
/** @inheritDocs
def parse(raw: String): Option[Instant] = */
Try(Instant.parse(raw)).toOption def parse(raw: String): Option[Instant] = Try(Instant.parse(raw)).toOption

View file

@ -1,5 +1,6 @@
package gs.config.v0 package gs.config.v0
import cats.data.EitherT
import cats.effect.Sync import cats.effect.Sync
import gs.config.v0.source.ConfigSource import gs.config.v0.source.ConfigSource
@ -16,6 +17,19 @@ trait Configuration[F[_]]:
*/ */
def getValue[A: Configurable](key: ConfigKey[A]): F[Either[ConfigError, A]] def getValue[A: Configurable](key: ConfigKey[A]): F[Either[ConfigError, A]]
/** Retrieve a value based on some key. Return an `EitherT` as the response,
* rather than `F[Either[ConfigError, A]]`
*
* @param key
* The key that identifies the piece of configuration to retrieve.
* @return
* The value, or an error if no value is present. Expressed as an
* `EitherT`.
*/
def getValueT[A: Configurable](
key: ConfigKey[A]
): EitherT[F, ConfigError, A] = EitherT(getValue(key))
object Configuration: object Configuration:
/** Start building a new [[AuditedConfiguration]]. /** Start building a new [[AuditedConfiguration]].
@ -34,7 +48,7 @@ object Configuration:
* @return * @return
* The new [[Configuration]]. * The new [[Configuration]].
*/ */
def auditedEnvironmentOnly[F[_]: Sync]: F[Configuration[F]] = def auditedEnvironmentOnly[F[_]: Sync]: F[AuditedConfiguration[F]] =
AuditedConfiguration.forEnvironmentSource[F].build() AuditedConfiguration.forEnvironmentSource[F].build()
end Configuration end Configuration

View file

@ -9,6 +9,8 @@ sealed trait ConfigQueryResult
object ConfigQueryResult: object ConfigQueryResult:
given CanEqual[ConfigQueryResult, ConfigQueryResult] = CanEqual.derived
/** Represents a query for some configuration that completed successfully. /** Represents a query for some configuration that completed successfully.
* *
* @param source * @param source

View file

@ -40,9 +40,13 @@ object ConfigSource:
final class Empty[F[_]: Applicative] extends ConfigSource[F]: final class Empty[F[_]: Applicative] extends ConfigSource[F]:
/** @inheritDocs
*/
override def getValue(key: ConfigKey[?]): F[Option[String]] = override def getValue(key: ConfigKey[?]): F[Option[String]] =
Applicative[F].pure(None) Applicative[F].pure(None)
/** @inheritDocs
*/
override val name: String = "empty" override val name: String = "empty"
end ConfigSource end ConfigSource

View file

@ -8,9 +8,13 @@ import gs.config.v0.ConfigKey
*/ */
final class EnvironmentConfigSource[F[_]: Sync] extends ConfigSource[F]: final class EnvironmentConfigSource[F[_]: Sync] extends ConfigSource[F]:
/** @inheritDocs
*/
override def getValue( override def getValue(
key: ConfigKey[?] key: ConfigKey[?]
): F[Option[String]] = ): F[Option[String]] =
Sync[F].delay(sys.env.get(key.name.toEnvironmentVariable())) Sync[F].delay(sys.env.get(key.name.toEnvironmentVariable()))
/** @inheritDocs
*/
override val name: String = "environment" override val name: String = "environment"

View file

@ -16,6 +16,8 @@ final class MemoryConfigSource[F[_]: Applicative](
) extends ConfigSource[F]: ) extends ConfigSource[F]:
val id: UUID = UUID.randomUUID() val id: UUID = UUID.randomUUID()
/** @inheritDocs
*/
override def getValue( override def getValue(
key: ConfigKey[?] key: ConfigKey[?]
): F[Option[String]] = ): F[Option[String]] =
@ -24,4 +26,6 @@ final class MemoryConfigSource[F[_]: Applicative](
.get(key.name.toRawString()) .get(key.name.toRawString())
) )
/** @inheritDocs
*/
override lazy val name: String = s"in-memory-$id" override lazy val name: String = s"in-memory-$id"

View file

@ -0,0 +1,17 @@
package gs.config.v0
import cats.effect.IO
import cats.effect.unsafe.IORuntime
abstract class GsSuite extends munit.FunSuite:
given IORuntime = IORuntime.global
def iotest(
name: String
)(
body: => IO[Any]
)(
implicit
loc: munit.Location
): Unit =
test(new munit.TestOptions(name))(body.unsafeRunSync())

View file

@ -0,0 +1,51 @@
package gs.config.v0.audit
import cats.effect.IO
import gs.config.v0.ConfigError
import gs.config.v0.ConfigKey
import gs.config.v0.ConfigName
import gs.config.v0.Configuration
import gs.config.v0.GsSuite
import gs.config.v0.source.ConfigSource
class AuditedConfigurationTests extends GsSuite:
import AuditedConfigurationTests.*
iotest(
"should not return values, but should record attempts to find, when no config exists"
) {
for
config <- Configuration
.audited(ConfigSource.inMemory[IO](Map.empty))
.build()
string <- config.getValue(Keys.KString)
manifest <- config.manifest.snapshot()
yield
assert(string == Left(ConfigError.MissingValue(Names.KString)))
assert(
manifest.get(Names.KString) == Some(
List(
ConfigQueryResult.Failure(
sources = List(config.sources.head.name),
error = ConfigError.MissingValue(Names.KString)
)
)
)
)
}
object AuditedConfigurationTests:
object Names:
val KString: ConfigName = ConfigName("string")
end Names
object Keys:
val KString: ConfigKey[String] = ConfigKey.Required[String](Names.KString)
end Keys
end AuditedConfigurationTests