mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-22 02:18:26 +00:00
Refactor config validation
This commit is contained in:
@ -28,20 +28,21 @@ object ConfigFactory {
|
||||
* the default config
|
||||
*/
|
||||
def default[F[_]: Async, C: ClassTag: ConfigReader](logger: Logger[F], atPath: String)(
|
||||
args: List[String]
|
||||
args: List[String],
|
||||
validation: Validation[C]
|
||||
): F[C] =
|
||||
findFileFromArgs(args).flatMap {
|
||||
case Some(file) =>
|
||||
logger.info(s"Using config file: $file") *>
|
||||
readFile[F, C](file, atPath)
|
||||
readFile[F, C](file, atPath).map(validation.validOrThrow)
|
||||
case None =>
|
||||
checkSystemProperty.value.flatMap {
|
||||
case Some(file) =>
|
||||
logger.info(s"Using config file from system property: $file") *>
|
||||
readConfig(atPath)
|
||||
readConfig(atPath).map(validation.validOrThrow)
|
||||
case None =>
|
||||
logger.info("Using config from environment variables!") *>
|
||||
readEnv(atPath)
|
||||
readEnv(atPath).map(validation.validOrThrow)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2020 Eike K. & Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package docspell.config
|
||||
|
||||
import cats._
|
||||
import cats.data.{NonEmptyChain, Validated, ValidatedNec}
|
||||
import cats.implicits._
|
||||
|
||||
final case class Validation[C](run: C => ValidatedNec[String, C]) {
|
||||
|
||||
def validOrThrow(c: C): C =
|
||||
run(c) match {
|
||||
case Validated.Valid(cfg) => cfg
|
||||
case Validated.Invalid(errs) =>
|
||||
val msg = errs.toList.mkString("- ", "\n- ", "\n")
|
||||
throw sys.error(s"\n\n$msg")
|
||||
}
|
||||
|
||||
def andThen(next: Validation[C]): Validation[C] =
|
||||
Validation(c =>
|
||||
run(c) match {
|
||||
case Validated.Valid(c2) => next.run(c2)
|
||||
case f: Validated.Invalid[NonEmptyChain[String]] =>
|
||||
next.run(c) match {
|
||||
case Validated.Valid(_) => f
|
||||
case Validated.Invalid(errs2) =>
|
||||
Validation.invalid(f.e ++ errs2)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
object Validation {
|
||||
|
||||
def flatten[C](run: C => Validation[C]): Validation[C] =
|
||||
Validation(c => run(c).run(c))
|
||||
|
||||
def failWhen[C](isInvalid: C => Boolean, msg: => String): Validation[C] =
|
||||
Validation(c => if (isInvalid(c)) invalid(msg) else valid(c))
|
||||
|
||||
def okWhen[C](isValid: C => Boolean, msg: => String): Validation[C] =
|
||||
Validation(c => if (isValid(c)) valid(c) else invalid(msg))
|
||||
|
||||
def valid[C](c: C): ValidatedNec[String, C] =
|
||||
Validated.validNec(c)
|
||||
|
||||
def invalid[C](msgs: NonEmptyChain[String]): ValidatedNec[String, C] =
|
||||
Validated.Invalid(msgs)
|
||||
|
||||
def invalid[C](msg: String, msgs: String*): ValidatedNec[String, C] =
|
||||
Validated.Invalid(NonEmptyChain(msg, msgs: _*))
|
||||
|
||||
def asValid[C]: Validation[C] =
|
||||
Validation(c => valid(c))
|
||||
|
||||
def insert[C](c: C): Validation[C] =
|
||||
Validation(_ => valid(c))
|
||||
|
||||
def error[C](msg: String, msgs: String*): Validation[C] =
|
||||
Validation(_ => invalid(msg, msgs: _*))
|
||||
|
||||
implicit def validationMonoid[C]: Monoid[Validation[C]] =
|
||||
Monoid.instance(asValid, (v1, v2) => v1.andThen(v2))
|
||||
|
||||
def of[C](v1: Validation[C], vn: Validation[C]*): Validation[C] =
|
||||
Monoid[Validation[C]].combineAll(v1 :: vn.toList)
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2020 Eike K. & Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package docspell.config
|
||||
|
||||
import munit.FunSuite
|
||||
|
||||
class ValidationTest extends FunSuite {
|
||||
|
||||
test("thread value through validations") {
|
||||
val v1 = Validation[Int](n => Validation.valid(n + 1))
|
||||
assertEquals(v1.validOrThrow(0), 1)
|
||||
assertEquals(Validation.of(v1, v1, v1).validOrThrow(0), 3)
|
||||
}
|
||||
|
||||
test("fail if there is at least one error") {
|
||||
val v1 = Validation[Int](n => Validation.valid(n + 1))
|
||||
val v2 = Validation.error[Int]("error")
|
||||
assertEquals(Validation.of(v1, v2).run(0), Validation.invalid("error"))
|
||||
assertEquals(Validation.of(v2, v1).run(0), Validation.invalid("error"))
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user