Skip to content

Commit

Permalink
Test behavior for const in computed and vice versa
Browse files Browse the repository at this point in the history
  • Loading branch information
MateuszKubuszok committed Dec 31, 2024
1 parent b2c3999 commit 777956c
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 28 deletions.
19 changes: 16 additions & 3 deletions chimney/src/main/scala/io/scalaland/chimney/partial/Path.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,22 @@ final case class Path(private val elements: List[PathElement]) extends AnyVal {
* @since 0.7.0
*/
def prepend(pathElement: PathElement): Path = elements match {
// Const shouldn't be modified (or we can overwrite several nested Consts with outer one, which is useless)
case (_: PathElement.Const) :: _ => this
case (h: PathElement.Computed) :: t => if (h.sealPath) this else Path(h :: pathElement :: t)
case _ => Path(pathElement :: elements)
case (h: PathElement.Computed) :: t =>
// sealed Computed should not be modified
if (h.sealPath || pathElement.isInstanceOf[PathElement.Computed]) this
// Computed inside Const should stay Computed - but it should be sealed again
else if (pathElement.isInstanceOf[PathElement.Const]) {
h.sealPath = true
this
}
// Unsealed Computed can be prepended
else Path(h :: pathElement :: t)
case _ =>
// If something prepends Const it should replace the content
if (pathElement.isInstanceOf[PathElement.Const]) Path(pathElement :: Nil)
else Path(pathElement :: elements)
}

/** Unseals the [[io.scalaland.chimney.partial.Path]] of current [[io.scalaland.chimney.partial.Error]].
Expand Down Expand Up @@ -54,7 +67,7 @@ final case class Path(private val elements: List[PathElement]) extends AnyVal {
var computedSuffix: String = null
while (it.hasNext) {
val curr = it.next()
if (computedSuffix == null && curr.isInstanceOf[PathElement.Computed]) {
if (computedSuffix == null && (curr.isInstanceOf[PathElement.Computed])) {
computedSuffix = curr.asString
} else {
if (sb.nonEmpty && PathElement.shouldPrependWithDot(curr)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,10 @@ object PathElement {
* @since 1.6.0
*/
final case class Computed(targetPath: String) extends PathElement {
// TODO: description
var sealPath: Boolean = true
override def asString: String = s"<computed for $targetPath>"

/** Flag preventing appending when the whole path was already precomputed. */
var sealPath: Boolean = true
}

/** Specifies if path element in conventional string representation should be prepended with a dot.
Expand All @@ -105,7 +106,9 @@ object PathElement {
case _: Index => false
case _: MapValue => false
case _: MapKey => true
// $COVERAGE-OFF$Required by exhaustive check but never really used in runtime
case _: Const => false
case _: Computed => false
// $COVERAGE-ON$
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,29 +61,138 @@ class PartialTransformerErrorPathSpec extends ChimneySpec {
case class Bar(inner: InnerBar, b: Int)
case class InnerBar(int1: Int, int2: Int, double: Double)

implicit val innerT: PartialTransformer[InnerFoo, InnerBar] = PartialTransformer
.define[InnerFoo, InnerBar]
.withFieldRenamed(_.str, _.int1)
.withFieldConstPartial(_.int2, intParserOpt.transform("notint"))
.withFieldComputedPartial(_.double, foo => partial.Result.fromOption(foo.str.parseDouble))
.buildTransformer

val result = Foo(InnerFoo("aaa"))
.intoPartial[Bar]
.withFieldConstPartial(_.b, intParserOpt.transform("bbb"))
.transform
result.asErrorPathMessages ==> Iterable(
"inner.str" -> partial.ErrorMessage.EmptyValue,
"<const for _.int2>" -> partial.ErrorMessage.EmptyValue,
"inner => <computed for _.double>" -> partial.ErrorMessage.EmptyValue,
"<const for _.b>" -> partial.ErrorMessage.EmptyValue
)
result.asErrorPathMessageStrings ==> Iterable(
"inner.str" -> "empty value",
"<const for _.int2>" -> "empty value",
"inner => <computed for _.double>" -> "empty value",
"<const for _.b>" -> "empty value"
)
locally {
// withFieldComputedPartial
implicit val innerT: PartialTransformer[InnerFoo, InnerBar] = PartialTransformer
.define[InnerFoo, InnerBar]
.withFieldRenamed(_.str, _.int1)
.withFieldConstPartial(_.int2, intParserOpt.transform("notint"))
.withFieldComputedPartial(_.double, foo => partial.Result.fromOption(foo.str.parseDouble))
.buildTransformer

val result = Foo(InnerFoo("aaa"))
.intoPartial[Bar]
.withFieldConstPartial(_.b, intParserOpt.transform("bbb"))
.transform
result.asErrorPathMessages ==> Iterable(
"inner.str" -> partial.ErrorMessage.EmptyValue,
"<const for _.int2>" -> partial.ErrorMessage.EmptyValue,
"inner => <computed for _.double>" -> partial.ErrorMessage.EmptyValue,
"<const for _.b>" -> partial.ErrorMessage.EmptyValue
)
result.asErrorPathMessageStrings ==> Iterable(
"inner.str" -> "empty value",
"<const for _.int2>" -> "empty value",
"inner => <computed for _.double>" -> "empty value",
"<const for _.b>" -> "empty value"
)

val result2 = List(Map("value" -> Foo(InnerFoo("aaa"))))
.intoPartial[List[Map[String, Bar]]]
.withFieldConstPartial(_.everyItem.everyMapValue.b, intParserOpt.transform("bbb"))
.transform
result2.asErrorPathMessages ==> Iterable(
"(0)(value).inner.str" -> partial.ErrorMessage.EmptyValue,
"<const for _.int2>" -> partial.ErrorMessage.EmptyValue,
"(0)(value).inner => <computed for _.double>" -> partial.ErrorMessage.EmptyValue,
"<const for _.everyItem.everyMapValue.b>" -> partial.ErrorMessage.EmptyValue
)
result2.asErrorPathMessageStrings ==> Iterable(
"(0)(value).inner.str" -> "empty value",
"<const for _.int2>" -> "empty value",
"(0)(value).inner => <computed for _.double>" -> "empty value",
"<const for _.everyItem.everyMapValue.b>" -> "empty value"
)

val result3 = List(Map("value" -> Foo(InnerFoo("aaa"))))
.intoPartial[List[Map[String, Bar]]]
.withFieldConstPartial(_.everyItem.everyMapValue.b, InnerFoo("aaa").transformIntoPartial[InnerBar].map(_ => 0))
.transform
result3.asErrorPathMessages ==> Iterable(
"(0)(value).inner.str" -> partial.ErrorMessage.EmptyValue,
"<const for _.int2>" -> partial.ErrorMessage.EmptyValue,
"(0)(value).inner => <computed for _.double>" -> partial.ErrorMessage.EmptyValue,
"<const for _.everyItem.everyMapValue.b>" -> partial.ErrorMessage.EmptyValue,
"<const for _.int2>" -> partial.ErrorMessage.EmptyValue,
"(0)(value) => <computed for _.double>" -> partial.ErrorMessage.EmptyValue
)
result3.asErrorPathMessageStrings ==> Iterable(
"(0)(value).inner.str" -> "empty value",
"<const for _.int2>" -> "empty value",
"(0)(value).inner => <computed for _.double>" -> "empty value",
"<const for _.everyItem.everyMapValue.b>" -> "empty value",
"<const for _.int2>" -> "empty value",
"(0)(value) => <computed for _.double>" -> "empty value"
)
}

locally {
// withFieldComputedPartialFrom
implicit val innerT: PartialTransformer[InnerFoo, InnerBar] = PartialTransformer
.define[InnerFoo, InnerBar]
.withFieldRenamed(_.str, _.int1)
.withFieldConstPartial(_.int2, intParserOpt.transform("notint"))
.withFieldComputedPartialFrom(_.str)(_.double, str => partial.Result.fromOption(str.parseDouble))
.buildTransformer

val result = Foo(InnerFoo("aaa"))
.intoPartial[Bar]
.withFieldConstPartial(_.b, intParserOpt.transform("bbb"))
.transform
result.asErrorPathMessages ==> Iterable(
"inner.str" -> partial.ErrorMessage.EmptyValue,
"<const for _.int2>" -> partial.ErrorMessage.EmptyValue,
"inner.str => <computed for _.double>" -> partial.ErrorMessage.EmptyValue,
"<const for _.b>" -> partial.ErrorMessage.EmptyValue
)
result.asErrorPathMessageStrings ==> Iterable(
"inner.str" -> "empty value",
"<const for _.int2>" -> "empty value",
"inner.str => <computed for _.double>" -> "empty value",
"<const for _.b>" -> "empty value"
)

val result2 = List(Map("value" -> Foo(InnerFoo("aaa"))))
.intoPartial[List[Map[String, Bar]]]
.withFieldConstPartial(_.everyItem.everyMapValue.b, intParserOpt.transform("bbb"))
.transform
result2.asErrorPathMessages ==> Iterable(
"(0)(value).inner.str" -> partial.ErrorMessage.EmptyValue,
"<const for _.int2>" -> partial.ErrorMessage.EmptyValue,
"(0)(value).inner.str => <computed for _.double>" -> partial.ErrorMessage.EmptyValue,
"<const for _.everyItem.everyMapValue.b>" -> partial.ErrorMessage.EmptyValue
)
result2.asErrorPathMessageStrings ==> Iterable(
"(0)(value).inner.str" -> "empty value",
"<const for _.int2>" -> "empty value",
"(0)(value).inner.str => <computed for _.double>" -> "empty value",
"<const for _.everyItem.everyMapValue.b>" -> "empty value"
)

val result3 = List(Map("value" -> Foo(InnerFoo("aaa"))))
.intoPartial[List[Map[String, Bar]]]
.withFieldComputedPartialFrom(_.everyItem.everyMapValue.inner)(
_.everyItem.everyMapValue.b,
_.transformIntoPartial[InnerBar].map(_ => 0)
)
.transform
result3.asErrorPathMessages ==> Iterable(
"(0)(value).inner.str" -> partial.ErrorMessage.EmptyValue,
"<const for _.int2>" -> partial.ErrorMessage.EmptyValue,
"(0)(value).inner.str => <computed for _.double>" -> partial.ErrorMessage.EmptyValue,
"(0)(value).str => <computed for _.everyItem.everyMapValue.b>" -> partial.ErrorMessage.EmptyValue,
"<const for _.int2>" -> partial.ErrorMessage.EmptyValue,
"(0)(value).str => <computed for _.double>" -> partial.ErrorMessage.EmptyValue
)
result3.asErrorPathMessageStrings ==> Iterable(
"(0)(value).inner.str" -> "empty value",
"<const for _.int2>" -> "empty value",
"(0)(value).inner.str => <computed for _.double>" -> "empty value",
"(0)(value).str => <computed for _.everyItem.everyMapValue.b>" -> "empty value",
"<const for _.int2>" -> "empty value",
"(0)(value).str => <computed for _.double>" -> "empty value"
)
}
}

test("Java Bean accessors error should contain path to the failed getter") {
Expand Down

0 comments on commit 777956c

Please sign in to comment.