loicknuchel / SafeQL

Compare 5e76e1e ... +0 ... 4d7a553


@@ -48,12 +48,12 @@
Loading
48 48
  private val scalaReader: Reader[ScalaConf] =
49 49
    Reader.flagOpt("dir").and(
50 50
      Reader.flagOpt("package"),
51 -
      Reader.flagOpt("identifiers").mapTry(_.map(s => IdentifierStrategy.byName(s).toRight(InvalidEnumValue(s, IdentifierStrategy.all.map(_.toString)))).sequence),
51 +
      Reader.flagOpt("identifiers").mapTry((a, p) => a.map(s => IdentifierStrategy.byName(s).toRight(InvalidEnumValue(s, IdentifierStrategy.all.map(_.toString), p))).sequence),
52 52
      Reader.flagOpt("config")
53 53
    ).map { case (dir, pkg, idf, conf) => ScalaConf(dir, pkg, idf, conf) }
54 54
  private val writerReader: Reader[WriterConf] = Reader.flag("output").on {
55 -
    case "scala" => scalaReader
56 -
    case v => Reader.error(s"Unknown output '$v'")
55 +
    case ("scala", _) => scalaReader
56 +
    case (v, p) => Reader.error(s"Unknown output '$v'", p)
57 57
  }.map { case (_, w) => w }
58 58
59 59
  private val genReader: Reader[GenConf] =

@@ -9,7 +9,7 @@
Loading
9 9
10 10
  def read(args: Array[String]): Result[A] = read(Params(args))
11 11
12 -
  def read(args: String): Result[A] = read(args.split(" ").map(_.trim).filter(_.nonEmpty))
12 +
  def read(args: String): Result[A] = read(Params(args))
13 13
14 14
  def and[B](r: Reader[B]): Reader[(A, B)] = And(this, r)
15 15
@@ -32,21 +32,21 @@
Loading
32 32
33 33
  def map[B](f: A => B): Reader[B] = Map(this, f)
34 34
35 -
  def mapTry[B](f: A => Either[ArgError, B]): Reader[B] = MapTry(this, f)
35 +
  def mapTry[B](f: (A, Params) => Either[ArgError, B]): Reader[B] = MapTry(this, f)
36 36
37 -
  def validate(p: A => Boolean, err: A => ArgError): Reader[A] = Filter(this, p, err)
37 +
  def validate(p: A => Boolean, err: (A, Params) => ArgError): Reader[A] = Filter(this, p, err)
38 38
39 -
  def validate(p: A => Boolean, validation: Option[String]): Reader[A] = validate(p, ValidationError(_, validation))
39 +
  def validate(p: A => Boolean, validation: Option[String]): Reader[A] = validate(p, ValidationError(_, validation, _))
40 40
41 41
  def validate(p: A => Boolean, validation: String): Reader[A] = validate(p, Some(validation))
42 42
43 43
  def validate(p: A => Boolean): Reader[A] = validate(p, None)
44 44
45 -
  def inEnum[B >: A](set: Set[B]): Reader[B] = validate(a => set.contains(a), InvalidEnumValue(_, set))
45 +
  def inEnum[B >: A](set: Set[B]): Reader[B] = validate(set.contains(_), InvalidEnumValue(_, set, _))
46 46
47 47
  def inEnum[B >: A](v: B, o: B*): Reader[B] = inEnum((v :: o.toList).toSet)
48 48
49 -
  def on[B](f: A => Reader[B]): Reader[(A, B)] = Dynamic(this, f)
49 +
  def on[B](f: (A, Params) => Reader[B]): Reader[(A, B)] = Dynamic(this, f)
50 50
}
51 51
52 52
object Reader {
@@ -70,10 +70,10 @@
Loading
70 70
71 71
  def error[A](errs: ArgError): Error[A] = Error[A](errs)
72 72
73 -
  def error[A](e: String): Error[A] = error(CustomError(e))
73 +
  def error[A](e: String, p: Params): Error[A] = error(CustomError(e, p))
74 74
75 75
  final case class Arg(pos: Int, name: Option[String], values: Option[Set[String]]) extends Reader[String] {
76 -
    override def read(params: Params): Result[String] = params.read(this).filter(v => values.forall(_.contains(v)), InvalidEnumValue(_, values.getOrElse(Set())))
76 +
    override def read(params: Params): Result[String] = params.read(this).filter(v => values.forall(_.contains(v)), InvalidEnumValue(_, values.getOrElse(Set()), _))
77 77
78 78
    // ugly, I don't have a better way to mix covariance and String reader
79 79
    override def inEnum[B >: String](set: Set[B]): Reader[B] = Arg(pos, name, Some(set.asInstanceOf[Set[String]]))
@@ -108,20 +108,15 @@
Loading
108 108
  }
109 109
110 110
  final case class And[A, B](ra: Reader[A], rb: Reader[B]) extends Reader[(A, B)] {
111 -
    override def read(params: Params): Result[(A, B)] = ra.read(params).chain((_, p) => rb.read(p))
111 +
    override def read(params: Params): Result[(A, B)] = ra.read(params).and(rb.read)
112 112
  }
113 113
114 114
  final case class And2[A, B, C](ra: Reader[A], rb: Reader[B], rc: Reader[C]) extends Reader[(A, B, C)] {
115 -
    override def read(params: Params): Result[(A, B, C)] = ra.read(params)
116 -
      .chain((_, p) => rb.read(p))
117 -
      .chain((_, p) => rc.read(p)).map { case ((a, b), c) => (a, b, c) }
115 +
    override def read(params: Params): Result[(A, B, C)] = ra.read(params).and(rb.read).and(rc.read).map { case ((a, b), c) => (a, b, c) }
118 116
  }
119 117
120 118
  final case class And3[A, B, C, D](ra: Reader[A], rb: Reader[B], rc: Reader[C], rd: Reader[D]) extends Reader[(A, B, C, D)] {
121 -
    override def read(params: Params): Result[(A, B, C, D)] = ra.read(params)
122 -
      .chain((_, p) => rb.read(p))
123 -
      .chain((_, p) => rc.read(p))
124 -
      .chain((_, p) => rd.read(p)).map { case (((a, b), c), d) => (a, b, c, d) }
119 +
    override def read(params: Params): Result[(A, B, C, D)] = ra.read(params).and(rb.read).and(rc.read).and(rd.read).map { case (((a, b), c), d) => (a, b, c, d) }
125 120
  }
126 121
127 122
  final case class Or[A](r1: Reader[A], r2: Reader[A]) extends Reader[A] {
@@ -140,20 +135,20 @@
Loading
140 135
    override def read(params: Params): Result[B] = r.read(params).map(f)
141 136
  }
142 137
143 -
  final case class MapTry[A, B](r: Reader[A], f: A => Either[ArgError, B]) extends Reader[B] {
144 -
    override def read(params: Params): Result[B] = r.read(params).flatMap((a, p) => Result.from(f(a), p))
138 +
  final case class MapTry[A, B](r: Reader[A], f: (A, Params) => Either[ArgError, B]) extends Reader[B] {
139 +
    override def read(params: Params): Result[B] = r.read(params).flatMap((a, p) => f(a, p).fold(Result(_), Result(_, p)))
145 140
  }
146 141
147 -
  final case class Filter[A](r: Reader[A], f: A => Boolean, e: A => ArgError) extends Reader[A] {
142 +
  final case class Filter[A](r: Reader[A], f: A => Boolean, e: (A, Params) => ArgError) extends Reader[A] {
148 143
    override def read(params: Params): Result[A] = r.read(params).filter(f, e)
149 144
  }
150 145
151 -
  final case class Dynamic[A, B](r: Reader[A], f: A => Reader[B]) extends Reader[(A, B)] {
152 -
    override def read(params: Params): Result[(A, B)] = r.read(params).chain((a, p) => f(a).read(p))
146 +
  final case class Dynamic[A, B](r: Reader[A], f: (A, Params) => Reader[B]) extends Reader[(A, B)] {
147 +
    override def read(params: Params): Result[(A, B)] = r.read(params).chain((a, p) => f(a, p).read(p))
153 148
  }
154 149
155 150
  final case class Error[A](errs: ArgError) extends Reader[A] {
156 -
    override def read(params: Params): Result[A] = Result.Failure(errs, params)
151 +
    override def read(params: Params): Result[A] = Result(errs)
157 152
  }
158 153
159 154
}

@@ -4,67 +4,75 @@
Loading
4 4
import fr.loicknuchel.scalargs.ArgError._
5 5
import fr.loicknuchel.scalargs.Reader.{Arg, ArgOpt, Flag, FlagBool, FlagCheck, FlagList, FlagNel, FlagOpt}
6 6
7 +
// TODO alternative names: Args, Opts, Ctx
7 8
case class Params(args: List[(Int, String)], flags: Map[String, List[String]], readArgs: Set[Int], readFlags: Set[String]) {
8 -
  private def addArg(arg: String): Params = copy(args = args :+ (args.length -> arg))
9 -
10 -
  private def addFlag(flag: String): Params = copy(flags = flags + (flag -> flags.getOrElse(flag, List())))
9 +
  def size: Int = readArgs.size + readFlags.size
11 10
12 -
  private def addFlagValue(flag: String, value: String): Params = copy(flags = flags + (flag -> (flags.getOrElse(flag, List()) :+ value)))
11 +
  def nonEmpty: Boolean = readArgs.nonEmpty || readFlags.nonEmpty
13 12
14 -
  def read(a: Arg): Result[String] = arg(a.pos, a.name).flatMap {
15 -
    case (None, p) => Result.Failure(ArgumentNotFound(a.pos, a.name, a.values), p)
16 -
    case (Some(v), p) => Result.Success(v, p)
13 +
  def read(a: Arg): Result[String] = readArg(a.pos, a.name).flatMap {
14 +
    case (None, _) => Result(ArgumentNotFound(a.pos, a.name, a.values, this))
15 +
    case (Some(v), p) => Result(v, p)
17 16
  }
18 17
19 -
  def read(a: ArgOpt): Result[Option[String]] = arg(a.pos, a.name)
18 +
  def read(a: ArgOpt): Result[Option[String]] = readArg(a.pos, a.name)
20 19
21 -
  def read(f: Flag): Result[String] = flag(f.name).flatMap {
22 -
    case (None, p) => Result.Failure(FlagNotFound(f.name), p)
23 -
    case (Some(List()), p) => Result.Failure(NoFlagValue(f.name), p)
24 -
    case (Some(List(v)), p) => Result.Success(v, p)
25 -
    case (Some(l), p) => Result.Failure(UniqueFlagHasMultipleValues(f.name, l), p)
20 +
  def read(f: Flag): Result[String] = readFlag(f.name).flatMap {
21 +
    case (None, _) => Result(FlagNotFound(f.name, this))
22 +
    case (Some(List()), _) => Result(NoFlagValue(f.name, this))
23 +
    case (Some(List(v)), p) => Result(v, p)
24 +
    case (Some(l), _) => Result(UniqueFlagHasMultipleValues(f.name, l, this))
26 25
  }
27 26
28 -
  def read(f: FlagOpt): Result[Option[String]] = flag(f.name).flatMap {
29 -
    case (None, p) => Result.Success(None, p)
30 -
    case (Some(List()), p) => Result.Failure(NoFlagValue(f.name), p)
31 -
    case (Some(List(v)), p) => Result.Success(Some(v), p)
32 -
    case (Some(l), p) => Result.Failure(UniqueFlagHasMultipleValues(f.name, l), p)
27 +
  def read(f: FlagOpt): Result[Option[String]] = readFlag(f.name).flatMap {
28 +
    case (None, p) => Result(None, p)
29 +
    case (Some(List()), _) => Result(NoFlagValue(f.name, this))
30 +
    case (Some(List(v)), p) => Result(Some(v), p)
31 +
    case (Some(l), _) => Result(UniqueFlagHasMultipleValues(f.name, l, this))
33 32
  }
34 33
35 -
  def read(f: FlagList): Result[List[String]] = flag(f.name).flatMap {
36 -
    case (Some(l), p) => Result.Success(l, p)
37 -
    case (None, p) => Result.Failure(FlagNotFound(f.name), p)
34 +
  def read(f: FlagList): Result[List[String]] = readFlag(f.name).flatMap {
35 +
    case (Some(l), p) => Result(l, p)
36 +
    case (None, _) => Result(FlagNotFound(f.name, this))
38 37
  }
39 38
40 -
  def read(f: FlagNel): Result[NonEmptyList[String]] = flag(f.name).flatMap {
41 -
    case (None, p) => Result.Failure(FlagNotFound(f.name), p)
42 -
    case (Some(List()), p) => Result.Failure(NoFlagValue(f.name), p)
43 -
    case (Some(List(v)), p) => Result.Success(NonEmptyList.of(v), p)
44 -
    case (Some(l), p) => Result.Success(NonEmptyList.fromListUnsafe(l), p)
39 +
  def read(f: FlagNel): Result[NonEmptyList[String]] = readFlag(f.name).flatMap {
40 +
    case (None, _) => Result(FlagNotFound(f.name, this))
41 +
    case (Some(List()), _) => Result(NoFlagValue(f.name, this))
42 +
    case (Some(List(v)), p) => Result(NonEmptyList.of(v), p)
43 +
    case (Some(l), p) => Result(NonEmptyList.fromListUnsafe(l), p)
45 44
  }
46 45
47 -
  def read(f: FlagBool): Result[Boolean] = flag(f.name).flatMap {
48 -
    case (None, p) => Result.Success(false, p)
49 -
    case (Some(List()), p) => Result.Success(true, p)
50 -
    case (Some(List(v)), p) => Result.Failure(FlagHasValue(f.name, v), p)
51 -
    case (Some(l), p) => Result.Failure(EmptyFlagHasMultipleValues(f.name, l), p)
46 +
  def read(f: FlagBool): Result[Boolean] = readFlag(f.name).flatMap {
47 +
    case (None, p) => Result(false, p)
48 +
    case (Some(List()), p) => Result(true, p)
49 +
    case (Some(List(v)), _) => Result(FlagHasValue(f.name, v, this))
50 +
    case (Some(l), _) => Result(EmptyFlagHasMultipleValues(f.name, l, this))
52 51
  }
53 52
54 -
  def read(f: FlagCheck): Result[Unit] = flag(f.name).flatMap {
55 -
    case (None, p) => Result.Failure(FlagNotFound(f.name), p)
56 -
    case (Some(List()), p) => Result.Success((), p)
57 -
    case (Some(List(v)), p) => Result.Failure(FlagHasValue(f.name, v), p)
58 -
    case (Some(l), p) => Result.Failure(EmptyFlagHasMultipleValues(f.name, l), p)
53 +
  def read(f: FlagCheck): Result[Unit] = readFlag(f.name).flatMap {
54 +
    case (None, _) => Result(FlagNotFound(f.name, this))
55 +
    case (Some(List()), p) => Result((), p)
56 +
    case (Some(List(v)), _) => Result(FlagHasValue(f.name, v, this))
57 +
    case (Some(l), _) => Result(EmptyFlagHasMultipleValues(f.name, l, this))
59 58
  }
60 59
61 -
  private def arg(pos: Int, name: Option[String]): Result[Option[String]] =
62 -
    if (readArgs.contains(pos)) Result.Failure(ArgumentReadTwice(pos, name), this)
63 -
    else Result.Success(args.find(_._1 == pos).map(_._2), copy(readArgs = readArgs + pos))
60 +
  def arg(a: Int*): Params = copy(readArgs = readArgs ++ a) // only for tests
61 +
  def flag(f: String*): Params = copy(readFlags = readFlags ++ f) // only for tests
62 +
63 +
  private def readArg(pos: Int, name: Option[String]): Result[Option[String]] =
64 +
    if (readArgs.contains(pos)) Result(ArgumentReadTwice(pos, name, this))
65 +
    else Result(args.find(_._1 == pos).map(_._2), arg(pos))
66 +
67 +
  private def readFlag(name: String): Result[Option[List[String]]] =
68 +
    if (readFlags.contains(name)) Result(FlagReadTwice(name, this))
69 +
    else Result(flags.get(name), flag(name))
64 70
65 -
  private def flag(name: String): Result[Option[List[String]]] =
66 -
    if (readFlags.contains(name)) Result.Failure(FlagReadTwice(name), this)
67 -
    else Result.Success(flags.get(name), copy(readFlags = readFlags + name))
71 +
  private def addArg(arg: String): Params = copy(args = args :+ (args.length -> arg))
72 +
73 +
  private def addFlag(flag: String): Params = copy(flags = flags + (flag -> flags.getOrElse(flag, List())))
74 +
75 +
  private def addFlagValue(flag: String, value: String): Params = copy(flags = flags + (flag -> (flags.getOrElse(flag, List()) :+ value)))
68 76
}
69 77
70 78
object Params {
@@ -84,5 +92,5 @@
Loading
84 92
    }._2
85 93
  }
86 94
87 -
  def apply(args: String): Params = Params(args.split(" "))
95 +
  def apply(args: String): Params = Params(args.split(" ").map(_.trim).filter(_.nonEmpty))
88 96
}

@@ -1,6 +1,10 @@
Loading
1 1
package fr.loicknuchel.scalargs
2 2
3 +
import cats.data.NonEmptyList
4 +
3 5
sealed trait ArgError {
6 +
  val params: Params
7 +
4 8
  def getMessage: String
5 9
}
6 10
@@ -11,37 +15,79 @@
Loading
11 15
  }
12 16
13 17
  final case class ArgumentNotFound(pos: Int,
14 -
                                    name: Option[String] = None,
15 -
                                    values: Option[Set[String]] = None) extends ArgError {
16 -
    override def getMessage: String = s"Missing argument ${pos + 1}${name.map(n => s" ($n${valuesMessage.map(m => s", $m").getOrElse("")})").getOrElse(valuesMessage.map(m => s" ($m)").getOrElse(""))}"
18 +
                                    name: Option[String],
19 +
                                    values: Option[Set[String]],
20 +
                                    params: Params) extends ArgError {
21 +
    override def getMessage: String = s"Missing ${argName(pos, name)}${possibleValues(values)}"
22 +
  }
23 +
24 +
  object ArgumentNotFound {
25 +
    def apply(pos: Int, params: Params): ArgumentNotFound = new ArgumentNotFound(pos, None, None, params)
26 +
27 +
    def apply(pos: Int, name: String, params: Params): ArgumentNotFound = new ArgumentNotFound(pos, Some(name), None, params)
17 28
18 -
    private def valuesMessage: Option[String] = values.map(v => s"possible values: ${v.mkString(", ")}")
29 +
    def apply(pos: Int, values: Set[String], params: Params): ArgumentNotFound = new ArgumentNotFound(pos, None, Some(values), params)
30 +
31 +
    def apply(pos: Int, name: String, values: Set[String], params: Params): ArgumentNotFound = new ArgumentNotFound(pos, Some(name), Some(values), params)
19 32
  }
20 33
21 -
  final case class ArgumentReadTwice(pos: Int, name: Option[String] = None) extends BasicArgError(s"Argument ${pos + 1}${name.map(n => s" ($n)").getOrElse("")} already read")
34 +
  final case class FlagNotFound(name: String, params: Params) extends BasicArgError(s"Missing ${flagName(name)}")
35 +
36 +
  final case class ArgumentReadTwice(pos: Int, name: Option[String], params: Params) extends BasicArgError(s"${argName(pos, name).capitalize} should not be used twice")
22 37
23 -
  final case class FlagNotFound(name: String) extends BasicArgError(s"Missing flag --$name")
38 +
  object ArgumentReadTwice {
39 +
    def apply(pos: Int, params: Params): ArgumentReadTwice = new ArgumentReadTwice(pos, None, params)
40 +
  }
24 41
25 -
  final case class FlagReadTwice(name: String) extends BasicArgError(s"Flag --$name already read")
42 +
  final case class FlagReadTwice(name: String, params: Params) extends BasicArgError(s"${flagName(name)} should not be used twice")
26 43
27 -
  final case class NoFlagValue(name: String) extends BasicArgError(s"Flag --$name is present but has no value")
44 +
  final case class NoFlagValue(name: String, params: Params) extends BasicArgError(s"Missing a value for ${flagName(name)}")
28 45
29 -
  final case class FlagHasValue(name: String, value: String) extends BasicArgError(s"Flag --$name has '$value' as value but expects no value")
46 +
  final case class FlagHasValue(name: String, value: String, params: Params) extends BasicArgError(s"${flagName(name)} has '$value' as value but expects no value")
30 47
31 -
  final case class EmptyFlagHasMultipleValues(name: String, values: List[String]) extends BasicArgError(s"Flag --$name has multiple values (${values.mkString(", ")}) but expects none")
48 +
  final case class EmptyFlagHasMultipleValues(name: String, values: List[String], params: Params) extends BasicArgError(s"${flagName(name)} has multiple values (${values.mkString(", ")}) but expects none")
32 49
33 -
  final case class UniqueFlagHasMultipleValues(name: String, values: List[String]) extends BasicArgError(s"Flag --$name has multiple values (${values.mkString(", ")}) but expects one")
50 +
  final case class UniqueFlagHasMultipleValues(name: String, values: List[String], params: Params) extends BasicArgError(s"${flagName(name)} has multiple values (${values.mkString(", ")}) but expects only one")
34 51
35 -
  final case class ValidationError[A](value: A, validation: Option[String] = None) extends BasicArgError(validation.map(_ + s" (value: $value)").getOrElse(s"Value '$value' did not pass validation"))
52 +
  final case class ValidationError[A](value: A, validation: Option[String], params: Params) extends BasicArgError(validation.map(_ + s" (value: $value)").getOrElse(s"Value '$value' did not pass validation"))
36 53
37 -
  final case class InvalidEnumValue[A](value: A, values: Set[A]) extends BasicArgError(s"Value '$value' is not in the allowed values: ${values.mkString(", ")}")
54 +
  object ValidationError {
55 +
    def apply[A](value: A, params: Params): ValidationError[A] = new ValidationError(value, None, params)
38 56
39 -
  final case class NoValidAlternative(e1: ArgError, e2: ArgError, tail: List[ArgError] = List()) extends BasicArgError(s"Invalid arguments, here are your options (fix the error you want):${(e1 :: e2 :: tail).map("\n  - " + _.getMessage).mkString}")
57 +
    def apply[A](value: A, validation: String, params: Params): ValidationError[A] = new ValidationError(value, Some(validation), params)
58 +
  }
59 +
60 +
  final case class InvalidEnumValue[A](value: A, values: Set[A], params: Params) extends BasicArgError(s"Value '$value' is not allowed${possibleValues(Some(values))}")
61 +
62 +
  // TODO replace InvalidEnumValue once a Reader can differentiate arg & flag
63 +
  // final case class InvalidEnumValueForArgument[A](pos: Int, name: Option[String], value: A, values: Set[A], params: Params) extends BasicArgError(s"Value '$value' is not allowed for ${argName(pos, name)}${possibleValues(Some(values))}")
64 +
  // final case class InvalidEnumValueForFlag[A](name: String, value: A, values: Set[A], params: Params) extends BasicArgError(s"Value '$value' is not allowed for ${flagName(name)}${possibleValues(Some(values))}")
65 +
66 +
  final case class NoValidAlternative(e1: ArgError, e2: ArgError, tail: List[ArgError], params: Params) extends ArgError {
67 +
    override def getMessage: String = getRelevantErrors match {
68 +
      case NonEmptyList(e, List()) => e.getMessage
69 +
      case errs => s"Invalid arguments, here are your options (fix the error you want):${errs.map(e => "\n" + addIndentation("- " + e.getMessage)).toList.mkString}"
70 +
    }
71 +
72 +
    private def getRelevantErrors: NonEmptyList[ArgError] = {
73 +
      // keep only deepest errors (most relevant)
74 +
      val all = NonEmptyList.of(e1, e2) ++ tail
75 +
      val max = all.toList.map(_.params.size).max
76 +
      NonEmptyList.fromListUnsafe(all.filter(_.params.size == max)) // it's safe because max is always present somewhere
77 +
    }
78 +
79 +
    private def addIndentation(text: String): String = text.split("\n").map("  " + _).mkString("\n")
80 +
  }
40 81
41 82
  object NoValidAlternative {
42 -
    def apply(e1: ArgError, e2: ArgError, others: ArgError*): ArgError = NoValidAlternative(e1, e2, others.toList)
83 +
    def apply(e1: ArgError, e2: ArgError, others: ArgError*): ArgError = NoValidAlternative(e1, e2, others.toList, (e1 :: e2 :: others.toList).map(_.params).maxBy(_.size))
43 84
  }
44 85
45 -
  final case class CustomError(value: String) extends BasicArgError(value)
86 +
  final case class CustomError(value: String, params: Params) extends BasicArgError(value)
87 +
88 +
  private def argName(pos: Int, name: Option[String]): String = s"argument ${pos + 1}${name.map(n => s" ($n)").getOrElse("")}"
89 +
90 +
  private def flagName(name: String): String = s"--$name flag"
46 91
92 +
  private def possibleValues[A](values: Option[Set[A]]): String = values.map(v => s", possible values: ${v.mkString(", ")}").getOrElse("")
47 93
}

@@ -9,12 +9,13 @@
Loading
9 9
10 10
  def flatMap[B](f: (A, Params) => Result[B]): Result[B]
11 11
12 -
  // like `flatMap` but keep the previous result
12 +
  def and[B](f: Params => Result[B]): Result[(A, B)] = flatMap((a, p) => f(p).map(b => (a, b)))
13 +
13 14
  def chain[B](f: (A, Params) => Result[B]): Result[(A, B)] = flatMap((a, p) => f(a, p).map(b => (a, b)))
14 15
15 -
  def filter(p: A => Boolean, e: A => ArgError): Result[A]
16 +
  def filter(p: A => Boolean, e: (A, Params) => ArgError): Result[A]
16 17
17 -
  def filter(p: A => Boolean, validation: Option[String]): Result[A] = filter(p, a => ValidationError(a, validation))
18 +
  def filter(p: A => Boolean, validation: Option[String]): Result[A] = filter(p, ValidationError(_, validation, _))
18 19
19 20
  def filter(p: A => Boolean, validation: String): Result[A] = filter(p, Some(validation))
20 21
@@ -26,76 +27,64 @@
Loading
26 27
27 28
  def orElse[B >: A](r1: => Result[B], r2: => Result[B], r3: => Result[B]): Result[B]
28 29
29 -
  def get: Option[A]
30 -
31 -
  def err: Option[ArgError]
32 -
33 30
  def toEither: Either[ArgError, A]
34 31
}
35 32
36 33
object Result {
37 34
38 -
  def apply[A](value: A, params: Params): Result[A] = Success(value, params)
35 +
  def apply[A](value: A, params: Params): Success[A] = Success(value, params)
39 36
40 -
  def fail[A](e: ArgError, params: Params): Result[A] = Failure(e, params)
41 -
42 -
  def from[A](e: Either[ArgError, A], params: Params): Result[A] = e.fold(Result.Failure(_, params), Result.Success(_, params))
37 +
  def apply(error: ArgError): Failure = Failure(error)
43 38
44 39
  case class Success[A](value: A, params: Params) extends Result[A] {
45 40
    override def map[B](f: A => B): Result[B] = Success(f(value), params)
46 41
47 42
    override def flatMap[B](f: (A, Params) => Result[B]): Result[B] = f(value, params)
48 43
49 -
    override def filter(p: A => Boolean, e: A => ArgError): Result[A] = if (p(value)) this else Failure(e(value), params)
44 +
    override def filter(p: A => Boolean, e: (A, Params) => ArgError): Result[A] = if (p(value)) this else Failure(e(value, params))
50 45
51 46
    override def orElse[B >: A](r: => Result[B]): Result[B] = this
52 47
53 48
    override def orElse[B >: A](r1: => Result[B], r2: => Result[B]): Result[B] = this
54 49
55 50
    override def orElse[B >: A](r1: => Result[B], r2: => Result[B], r3: => Result[B]): Result[B] = this
56 51
57 -
    override def get: Option[A] = Some(value)
58 -
59 -
    override def err: Option[ArgError] = None
60 -
61 52
    override def toEither: Either[ArgError, A] = Right(value)
62 53
  }
63 54
64 -
  case class Failure(error: ArgError, params: Params) extends Result[Nothing] {
55 +
  case class Failure(error: ArgError) extends Result[Nothing] {
56 +
    val params: Params = error.params
57 +
65 58
    override def map[B](f: Nothing => B): Result[B] = this
66 59
67 60
    override def flatMap[B](f: (Nothing, Params) => Result[B]): Result[B] = this
68 61
69 -
    override def filter(p: Nothing => Boolean, e: Nothing => ArgError): Result[Nothing] = this
62 +
    override def filter(p: Nothing => Boolean, e: (Nothing, Params) => ArgError): Result[Nothing] = this
70 63
71 64
    override def orElse[B >: Nothing](r: => Result[B]): Result[B] = r match {
72 65
      case res: Success[_] => res
73 -
      case Result.Failure(e, _) => Result.Failure(NoValidAlternative(error, e), params)
66 +
      case Failure(e) => Result(NoValidAlternative(error, e))
74 67
    }
75 68
76 69
    override def orElse[B >: Nothing](r1: => Result[B], r2: => Result[B]): Result[B] = r1 match {
77 70
      case res: Success[_] => res
78 -
      case Result.Failure(e1, _) => r2 match {
71 +
      case Failure(e1) => r2 match {
79 72
        case res: Success[_] => res
80 -
        case Result.Failure(e2, _) => Result.Failure(NoValidAlternative(error, e1, List(e2)), params)
73 +
        case Failure(e2) => Result(NoValidAlternative(error, e1, e2))
81 74
      }
82 75
    }
83 76
84 77
    override def orElse[B >: Nothing](r1: => Result[B], r2: => Result[B], r3: => Result[B]): Result[B] = r1 match {
85 78
      case res: Success[_] => res
86 -
      case Result.Failure(e1, _) => r2 match {
79 +
      case Failure(e1) => r2 match {
87 80
        case res: Success[_] => res
88 -
        case Result.Failure(e2, _) => r3 match {
81 +
        case Failure(e2) => r3 match {
89 82
          case res: Success[_] => res
90 -
          case Result.Failure(e3, _) => Result.Failure(NoValidAlternative(error, e1, List(e2, e3)), params)
83 +
          case Failure(e3) => Result(NoValidAlternative(error, e1, e2, e3))
91 84
        }
92 85
      }
93 86
    }
94 87
95 -
    override def get: Option[Nothing] = None
96 -
97 -
    override def err: Option[ArgError] = Some(error)
98 -
99 88
    override def toEither: Either[ArgError, Nothing] = Left(error)
100 89
  }
101 90

Everything is accounted for!

No changes detected that need to be reviewed.
What changes does Codecov check for?
Lines, not adjusted in diff, that have changed coverage data.
Files that introduced coverage data that had none before.
Files that have missing coverage data that once were tracked.
Files Coverage
src/main/scala/fr/loicknuchel 0.37% 97.43%
Project Totals (23 files) 97.43%
Loading