(minor) Version, Doc, and Minor updates. (#2)
All checks were successful
/ Build and Release Library (push) Successful in 2m11s

Reviewed-on: #2
This commit is contained in:
Pat Garrity 2025-03-23 15:34:25 +00:00
parent f127645860
commit e44334b364
15 changed files with 113 additions and 16 deletions

View file

@ -64,5 +64,6 @@ jobs:
sbtn coverageOff
sbtn clean
sbtn compile
sbtn doc
sbtn publish
fi

View file

@ -71,6 +71,7 @@ jobs:
sbtn coverageOff
sbtn clean
sbtn semVerWriteVersionToFile
sbtn doc
sbtn publish
fi
- name: 'Create Git Tag'

View file

@ -1,4 +1,4 @@
val scala3: String = "3.4.1"
val scala3: String = "3.6.4"
externalResolvers := Seq(
"Garrity Software Mirror" at "https://maven.garrity.co/releases",
@ -10,13 +10,16 @@ ThisBuild / versionScheme := Some("semver-spec")
ThisBuild / gsProjectName := "gs-config"
lazy val sharedSettings = Seq(
scalaVersion := scala3,
version := semVerSelected.value
scalaVersion := scala3,
version := semVerSelected.value,
coverageFailOnMinimum := true,
coverageMinimumStmtTotal := 100,
coverageMinimumBranchTotal := 100
)
lazy val testSettings = Seq(
libraryDependencies ++= Seq(
"org.scalameta" %% "munit" % "1.0.0-RC1" % Test
"org.scalameta" %% "munit" % "1.1.0" % Test
)
)
@ -27,6 +30,6 @@ lazy val `gs-config` = project
.settings(name := s"${gsProjectName.value}-v${semVerMajor.value}")
.settings(
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-effect" % "3.5.2"
"org.typelevel" %% "cats-effect" % "3.5.7"
)
)

View file

@ -1 +1 @@
sbt.version=1.9.9
sbt.version=1.10.11

View file

@ -28,6 +28,6 @@ externalResolvers := Seq(
"Garrity Software Releases" at "https://maven.garrity.co/gs"
)
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.11")
addSbtPlugin("gs" % "sbt-garrity-software" % "0.3.0")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.3.1")
addSbtPlugin("gs" % "sbt-garrity-software" % "0.5.0")
addSbtPlugin("gs" % "sbt-gs-semver" % "0.3.0")

View file

@ -9,7 +9,7 @@ import gs.config.v0.source.ConfigSource
import gs.config.v0.source.EnvironmentConfigSource
import gs.config.v0.source.MemoryConfigSource
/** Implementation of [[gs.config.Configuration]] that tracks every call to
/** Implementation of [[gs.config.v0.Configuration]] that tracks every call to
* `getValue` and reports on whether the query succeeded or failed.
*
* @param sources
@ -23,6 +23,8 @@ final class AuditedConfiguration[F[_]: Sync](
) extends BaseConfiguration[F]:
import AuditedConfiguration.Acc
/** @inheritDocs
*/
override def getValue[A: Configurable](
key: ConfigKey[A]
): F[Either[ConfigError, A]] =
@ -93,7 +95,8 @@ object AuditedConfiguration:
* sources.
*
* @param sources
* The list of [[ConfigSource]] backing this configuration.
* The list of [[gs.config.v0.source.ConfigSource]] backing this
* configuration.
* @return
* The new [[Configuration]] instance.
*/
@ -109,26 +112,73 @@ object AuditedConfiguration:
)
)
/** Start building a new [[AuditedConfiguration]] for some source.
*
* @param source
* The [[gs.config.v0.source.ConfigSource]] backing this configuration.
* @return
* New builder based on the given source.
*/
def forSource[F[_]: Sync](source: ConfigSource[F]): Builder[F] =
Builder[F](NonEmptyList.of(source))
/** Start building a new [[AuditedConfiguration]] based on the environment.
*
* @return
* New builder based on the environment.
*/
def forEnvironmentSource[F[_]: Sync]: Builder[F] =
Builder[F](NonEmptyList.of(new EnvironmentConfigSource[F]))
/** Start building a new [[AuditedConfiguration]] based on the given in-memory
* map of values.
*
* @param configs
* The configuration map.
* @return
* New builder based on the config map.
*/
def forMemorySource[F[_]: Sync](configs: Map[String, String]): Builder[F] =
Builder[F](NonEmptyList.of(new MemoryConfigSource[F](configs)))
/** Builder for [[AuditedConfiguration]].
*
* @param sources
* The (non-empty) list of sources for the eventual configuration.
*/
case class Builder[F[_]: Sync](sources: NonEmptyList[ConfigSource[F]]):
/** Add the environment as a source to this builder.
*
* @return
* This builder.
*/
def withEnvironmentSource(): Builder[F] =
copy(sources = this.sources.append(new EnvironmentConfigSource[F]))
/** Add a memory map as a source to this builder.
*
* @param configs
* The configuration map.
* @return
* This builder.
*/
def withMemorySource(configs: Map[String, String]): Builder[F] =
copy(sources = this.sources.append(new MemoryConfigSource[F](configs)))
/** Add the given source to this builder.
*
* @param source
* The [[gs.config.v0.source.ConfigSource]] to add.
* @return
* This builder.
*/
def withSource(source: ConfigSource[F]): Builder[F] =
copy(sources = this.sources.append(source))
/** @return
* A new [[AuditedConfiguration]] based on the configured sources.
*/
def build(): F[AuditedConfiguration[F]] =
ConfigManifest
.standard[F]

View file

@ -11,7 +11,7 @@ abstract class BaseConfiguration[F[_]] extends Configuration[F]:
): Either[ConfigError, A] =
key match
case ConfigKey.WithDefaultValue(_, defaultValue) =>
Right(defaultValue)
Right(defaultValue())
case _ =>
Left(ConfigError.MissingValue(key.name))

View file

@ -24,7 +24,8 @@ object ConfigError:
* @param candidateValue
* The raw value that could not be parsed.
* @param source
* The [[ConfigSource]] which provided the candidate value.
* The name of the [[gs.config.v0.source.ConfigSource]] which provided the
* candidate value.
*/
case class CannotParseValue(
configName: ConfigName,

View file

@ -37,5 +37,5 @@ object ConfigKey:
*/
case class WithDefaultValue[A: Configurable](
name: ConfigName,
defaultValue: A
defaultValue: () => A
) extends ConfigKey[A]

View file

@ -1,5 +1,8 @@
package gs.config.v0
import cats.Eq
import cats.Show
/** Uniquely names some piece of configuration. This structure does _not_
* attempt to support every possible use case, but supports some common cases
* for users that follow some basic rules. Please review conversion functions
@ -32,6 +35,13 @@ object ConfigName:
given CanEqual[ConfigName, ConfigName] = CanEqual.derived
given Eq[ConfigName] = (
x,
y
) => x == y
given Show[ConfigName] = _.unwrap()
extension (name: ConfigName)
/** Extract the unmodified string that backs this name.
*

View file

@ -5,6 +5,15 @@ import cats.effect.Sync
import cats.syntax.all.*
import gs.config.v0.ConfigName
/** A `ConfigManifest` tracks all queries for individual pieces of
* configuration. It is used in conjunction with an [[AuditedConfiguration]].
* Manifests can be queried by producing a _snapshot_ of interactions so far.
*
* See:
*
* - [[AuditedConfiguration]]
* - [[ConfigQueryResult]]
*/
trait ConfigManifest[F[_]]:
/** Retrieve a snapshot of the current state of this configuration manifest.
* This state tracks all configuration names that the caller attempted to
@ -46,9 +55,13 @@ object ConfigManifest:
private val manifest: Ref[F, Map[ConfigName, List[ConfigQueryResult]]]
) extends ConfigManifest[F]:
/** @inheritDocs
*/
override def snapshot(): F[Map[ConfigName, List[ConfigQueryResult]]] =
manifest.get
/** @inheritDocs
*/
override def record(
name: ConfigName,
query: ConfigQueryResult

View file

@ -14,7 +14,7 @@ object ConfigQueryResult:
/** Represents a query for some configuration that completed successfully.
*
* @param source
* The source which provided the value.
* The name of the source which provided the value.
* @param rawValue
* The raw value that the source returned.
*/

View file

@ -27,17 +27,32 @@ trait ConfigSource[F[_]]:
object ConfigSource:
/** Instantiate a new [[MemoryConfigSource]] based on the given map.
*
* @param configs
* The config map that should back the source.
* @return
* The new [[ConfigSource]].
*/
def inMemory[F[_]: Applicative](
configs: Map[String, String]
): ConfigSource[F] =
new MemoryConfigSource[F](configs)
/** @return
* A new [[EnvironmentConfigSource]].
*/
def environment[F[_]: Sync]: ConfigSource[F] =
new EnvironmentConfigSource[F]
/** @return
* An empty [[ConfigSource]].
*/
def empty[F[_]: Applicative]: ConfigSource[F] =
new Empty[F]
/** Default [[ConfigSource]] implementation that never returns a value.
*/
final class Empty[F[_]: Applicative] extends ConfigSource[F]:
/** @inheritDocs

View file

@ -1,5 +1,7 @@
package gs.config.v0
import cats.syntax.all.*
class ConfigNameTests extends munit.FunSuite:
test("should express some name as an environment variable") {
@ -14,6 +16,7 @@ class ConfigNameTests extends munit.FunSuite:
val expected = raw
val name = ConfigName(raw)
assertEquals(name.unwrap(), expected)
assertEquals(name.show, expected)
}
test("should support equality") {

View file

@ -143,7 +143,7 @@ class AuditedConfigurationTests extends GsSuite:
iotest("should audit the use of default values") {
val name = Names.KString
val defaultValue = "value"
val defaultValue = () => "value"
val key = ConfigKey.WithDefaultValue(name, defaultValue)
for
config <- Configuration
@ -153,7 +153,7 @@ class AuditedConfigurationTests extends GsSuite:
value <- config.getValue(key)
manifest <- config.manifest.snapshot()
yield
assertEquals(value, Right(defaultValue))
assertEquals(value, Right(defaultValue()))
assertEquals(
manifest.get(name),
Some(