diff --git a/README.md b/README.md index 4b490a6a..02287185 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,10 @@ If this project interests you, please drop a 🌟 - these things are worthless b ## Installation ```scala -libraryDependencies += "io.github.arainko" %% "ducktape" % "0.2.2" +libraryDependencies += "io.github.arainko" %% "ducktape" % "0.2.3" // or if you're using Scala.js or Scala Native -libraryDependencies += "io.github.arainko" %%% "ducktape" % "0.2.2" +libraryDependencies += "io.github.arainko" %%% "ducktape" % "0.2.3" ``` NOTE: the [version scheme](https://www.scala-lang.org/blog/2021/02/16/preventing-version-conflicts-with-versionscheme.html) is set to `early-semver` diff --git a/build.sbt b/build.sbt index 4f058d3c..39f3b978 100644 --- a/build.sbt +++ b/build.sbt @@ -79,7 +79,7 @@ lazy val docs = ) ++ ( // Going overboard with this since all selections are connected to each other (eg. you pick an option on of them) // then all of them will change, this caused screen jumps when looking at the generated code - (1 to 15).map(num => + (1 to 25).map(num => SelectionConfig( s"underlying-code-$num", ChoiceConfig("visible", "User visible code"), diff --git a/docs/fallible_transformations/basics.md b/docs/fallible_transformations/basics.md index 0046c647..5b2ae729 100644 --- a/docs/fallible_transformations/basics.md +++ b/docs/fallible_transformations/basics.md @@ -105,7 +105,7 @@ These will be used interchangably throughout the examples below, but if you want @:select(underlying-code-1) @:choice(visible) ```scala mdoc -given Mode.Accumulating.Either[String, List] with {} +given Mode.Accumulating.Either[String, List]() wirePerson.fallibleTo[domain.Person] ``` @@ -124,7 +124,7 @@ Read more about the rules under which the transformations are generated in a cha @:select(underlying-code-2) @:choice(visible) ```scala mdoc:nest -given Mode.FailFast.Either[String] with {} +given Mode.FailFast.Either[String]() wirePerson .into[domain.Person] @@ -161,7 +161,7 @@ Read more in the section about [configuring fallible transformations](configurin @:select(underlying-code-3) @:choice(visible) ```scala mdoc:nest -given Mode.Accumulating.Either[String, List] with {} +given Mode.Accumulating.Either[String, List]() wirePerson.fallibleVia(domain.Person.apply) ``` @@ -178,7 +178,7 @@ Docs.printCode(wirePerson.fallibleVia(domain.Person.apply)) @:select(underlying-code-4) @:choice(visible) ```scala mdoc:nest -given Mode.FailFast.Either[String] with {} +given Mode.FailFast.Either[String]() wirePerson .intoVia(domain.Person.apply) diff --git a/docs/fallible_transformations/configuring_fallible_transformations.md b/docs/fallible_transformations/configuring_fallible_transformations.md index 52a2c130..4e5d09cf 100644 --- a/docs/fallible_transformations/configuring_fallible_transformations.md +++ b/docs/fallible_transformations/configuring_fallible_transformations.md @@ -80,6 +80,13 @@ val good = wire.Person(name = "ValidName", age = 24, socialSecurityNo = "SOCIALN ### Product configurations +| **Name** | **Description** | +|:-----------------:|:-------------------:| +| `Field.fallibleConst` | a fallible variant of `Field.const` that allows for supplying values wrapped in an `F` | +| `Field.fallibleComputed` | a fallible variant of `Field.computed` that allows for supplying functions that return values wrapped in an `F` | + +--- + * `Field.fallibleConst` - a fallible variant of `Field.const` that allows for supplying values wrapped in an `F` @:select(underlying-code-1) @@ -87,7 +94,7 @@ val good = wire.Person(name = "ValidName", age = 24, socialSecurityNo = "SOCIALN ```scala mdoc:nest import io.github.arainko.ducktape.* -given Mode.Accumulating.Either[String, List] with {} +given Mode.Accumulating.Either[String, List]() bad .into[domain.Person] @@ -119,7 +126,7 @@ Docs.printCode( @:select(underlying-code-2) @:choice(visible) ```scala mdoc:nest -given Mode.Accumulating.Either[String, List] with {} +given Mode.Accumulating.Either[String, List]() bad .into[domain.Person] @@ -145,6 +152,13 @@ Docs.printCode( ### Coproduct configurations +| **Name** | **Description** | +|:-----------------:|:-------------------:| +| `Case.fallibleConst` | a fallible variant of `Case.const` that allows for supplying values wrapped in an `F` | +| `Case.fallibleComputed` | a fallible variant of `Case.computed` that allows for supplying functions that return values wrapped in an `F` | + +--- + Let's define a wire enum (pretend that it's coming from... somewhere) and a domain enum that doesn't exactly align with the wire one. ```scala mdoc:nest object wire: @@ -161,7 +175,7 @@ object domain: @:select(underlying-code-3) @:choice(visible) ```scala mdoc:nest -given Mode.FailFast.Either[String] with {} +given Mode.FailFast.Either[String]() wire.ReleaseKind.Single .into[domain.ReleaseKind] @@ -188,7 +202,7 @@ Docs.printCode( @:select(underlying-code-4) @:choice(visible) ```scala mdoc:nest -given Mode.FailFast.Either[String] with {} +given Mode.FailFast.Either[String]() // Type inference is tricky with this one. The function being passed in needs to be typed with the exact expected type. def handleSingle(value: wire.ReleaseKind): Either[String, domain.ReleaseKind] = @@ -230,7 +244,7 @@ object domain: @:select(underlying-code-5) @:choice(visible) ```scala mdoc:nest:silent -given Mode.Accumulating.Either[String, List] with {} +given Mode.Accumulating.Either[String, List]() val customAccumulating = Transformer @@ -258,7 +272,7 @@ And for the ones that are not keen on writing out method arguments: @:select(underlying-code-6) @:choice(visible) ```scala mdoc:nest:silent -given Mode.Accumulating.Either[String, List] with {} +given Mode.Accumulating.Either[String, List]() val customAccumulatingVia = Transformer diff --git a/docs/fallible_transformations/definition_of_transformer_fallible_and_mode.md b/docs/fallible_transformations/definition_of_transformer_fallible_and_mode.md index a7bad957..7afd2fb8 100644 --- a/docs/fallible_transformations/definition_of_transformer_fallible_and_mode.md +++ b/docs/fallible_transformations/definition_of_transformer_fallible_and_mode.md @@ -11,6 +11,8 @@ So a `Fallible` transformer takes a `Source` and gives back a `Dest` wrapped in ```scala sealed trait Mode[F[+x]] { + type Self[+A] = F[A] + def pure[A](value: A): F[A] def map[A, B](fa: F[A], f: A => B): F[B] @@ -20,6 +22,14 @@ sealed trait Mode[F[+x]] { transformation: A => F[B] )(using factory: Factory[B, BColl]): F[BColl] } + +object Mode { + inline def current(using mode: Mode[?]): mode.type = mode + + extension [F[+x], M <: Mode[F]](self: M) { + inline def locally[A](inline f: M ?=> A): A = f(using self) + } +} ``` Moving on to `Mode`, what exactly is it and why do we need it? So a `Mode[F]` is typeclass that gives us two bits of information: @@ -48,3 +58,23 @@ Each one of these exposes one operation that dictates its approach to errors, `f For accumulating transformations `ducktape` provides instances for `Either` with any subtype of `Iterable` on the left side, so that eg. `Mode.Accumulating[[A] =>> Either[List[String], A]]` is available out of the box (under the subclass of `Mode.Accumulating.Either[String, List]`). For fail fast transformations, instances for `Option` (`Mode.FailFast.Option`) and `Either` (`Mode.FailFast.Either`) are avaiable out of the box. + +As for the purpose of the `Self[+A]` type member, it's to enable use cases like these: + +```scala mdoc +import io.github.arainko.ducktape.* + +val source = + ( + Right(1), + Right("str"), + Right(List(3, 3, 3)), + Right(4) + ) + +Mode.Accumulating.either[String, List].locally { + source.fallibleTo[Tuple.InverseMap[source.type, Mode.current.Self]] +} +``` + +...where repeatedly referring to the `F` wrapper becomes really unwieldly - that type is known to the compiler at each call site so we make it work for us in conjunction with `Mode.current` which summons the `Mode[F]` instance in the current implicit scope. diff --git a/docs/fallible_transformations/making_the_most_out_of.md b/docs/fallible_transformations/making_the_most_out_of.md index 887e5e5a..922aec6f 100644 --- a/docs/fallible_transformations/making_the_most_out_of.md +++ b/docs/fallible_transformations/making_the_most_out_of.md @@ -64,7 +64,7 @@ Fallible transformations wrapped in some type `F` are derived automatically for @:select(underlying-code-1) @:choice(visible) ```scala mdoc -given Mode.Accumulating.Either[String, List] with {} +given Mode.Accumulating.Either[String, List]() bad.fallibleTo[Person] good.fallibleTo[Person] @@ -82,7 +82,7 @@ Same goes for instances that do fail fast transformations (you need `Mode.FailFa @:select(underlying-code-2) @:choice(visible) ```scala mdoc:nest -given Mode.FailFast.Either[String] with {} +given Mode.FailFast.Either[String]() bad.fallibleTo[Person] good.fallibleTo[Person] diff --git a/docs/index.md b/docs/index.md index bf738a80..cf624132 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,7 +4,7 @@ *ducktape* is a library for boilerplate-less and configurable transformations between case classes and enums/sealed traits for Scala 3. Directly inspired by [chimney](https://github.com/scalalandio/chimney). -If this project interests you, please drop a 🌟 - these things are worthless but give me a dopamine rush nonetheless. +If this project interests you, please [drop a 🌟](https://github.com/arainko/ducktape) - these things are worthless but give me a dopamine rush nonetheless. ## Installation ```scala @@ -231,3 +231,5 @@ Docs.printCode( @:@ Read more in the chapter dedicated to [configuring transformations](total_transformations/configuring_transformations.md). + +To get an idea of what transformations are actually supported head on over to [transformation rules](transformation_rules.md). diff --git a/docs/total_transformations/basics.md b/docs/total_transformations/basics.md index 4af42c20..9626ce52 100644 --- a/docs/total_transformations/basics.md +++ b/docs/total_transformations/basics.md @@ -145,3 +145,5 @@ Docs.printCode( @:@ Read more in the section about [configuring transformations](configuring_transformations.md). + +To get an idea of what transformations are actually supported head on over to [transformation rules](../transformation_rules.md). diff --git a/docs/total_transformations/configuring_transformations.md b/docs/total_transformations/configuring_transformations.md index 0e44b2ec..3b86aba0 100644 --- a/docs/total_transformations/configuring_transformations.md +++ b/docs/total_transformations/configuring_transformations.md @@ -1,9 +1,11 @@ ## Configuring transformations -### Introduction and explanation +### Introduction More often than not the models we work with daily do not map one-to-one with one another - let's define a wire/domain model pair that we'd like to transform. +#### Case classes and enums/sealed traits + @:select(model) @:choice(wire) ```scala mdoc @@ -110,6 +112,48 @@ Case.const(_.paymentMethods.element.at[wire.PaymentMethod.Transfer], domain.Paym path expressions are not limited to a single field, we can use these to dive as deep as we need for our config to be (paths inside Case configs operate on the source type) ``` +#### Tuples + +Additionally, if we were to define a transformation that uses tuples and wanted to configure one of the tuple elements we can do this in two ways - either use `.apply(N)` or `._(N + 1)` (like `._1` for the 0th element of the tuple) where N is the index of the tuple element: + +@:select(underlying-code-2) +@:choice(visible) +```scala mdoc +// using `.apply` to select the element +(1, List(2, 2, 2), 3, 4).into[(Int, Vector[Int], Int)].transform(Field.const(_.apply(2), 10)) + +// using the legacy accessors +(1, List(2, 2, 2), 3, 4).into[(Int, Vector[Int], Int)].transform(Field.const(_._3, 10)) +``` +@:choice(generated) +```scala mdoc:passthrough +Docs.printCode { + val source = (1, List(2, 2, 2), 3, 4) + +// using `.apply` to select the element +source.into[(Int, Vector[Int], Int)].transform(Field.const(_.apply(2), 10)) + +// using the legacy accessors +source.into[(Int, Vector[Int], Int)].transform(Field.const(_._3, 10)) +} +``` +@:@ + +For all intents and purposes these two ways of accessing tuple elements are equivalent with the exception of XXL tuples (i.e. tuples with more than 22 elements), these do not have legacy accessors and can only be configured with `.apply`. + +TL;DR here's the cheat sheet of the configuration path DSL: + +| **Input type** | **Config accessor** | +|:-----------------:|:-------------------:| +| Case class | `.fieldName` | +| Tuple (plain) | `._N / .apply(N)` | +| Tuple (XXL) | `.apply(N)` | +| Option/Collection | `.element` | +| F-wrapped value | `.element` | +| Enum/sealed trait | `.at[Subtype]` | + +### Explanation + So, is `.at` and `.element` another one of those extensions that will always pollute the namespace? Thankfully, no - let's look at how `Field.const` and `Case.const` are actually defined in the code: ```scala @@ -137,15 +181,28 @@ sealed trait Selector { extension [A](self: A) def at[B <: A]: B extension [Elem](self: Iterable[Elem] | Option[Elem]) def element: Elem + + extension [Elem, F[+x]](using Mode[F])(self: F[Elem]) def element: Elem } ``` -Which means that for a context function such as `Selector ?=> Dest => DestFieldTpe` the `Selector` brings in the neccessary extensions that allow us to pick and configure subtypes and elements under a collection or an `Option`, but only in the scope of that context function and not anywhere outside which means we do not pollute the outside world's namespace with these. +Which means that for a context function such as `Selector ?=> Dest => DestFieldTpe` the `Selector` brings in the neccessary extensions that allow us to pick and configure subtypes and elements under a collection or an `Option`(or any wrapper type `F[_]` given that it has an instance of `Mode[F]`), but only in the scope of that context function and not anywhere outside which means we do not pollute the outside world's namespace with these. What's worth noting is that any of the configuration options are purely a compiletime construct and are completely erased from the runtime representation (i.e. it's not possible to implement an instance of a `Selector` in a sane way since such an implementation would throw exceptions left and right but using it as a sort of a DSL for picking and choosing is completely fair game since it doesn't exist at runtime). ### Product configurations +| **Name** | **Description** | +|:-----------------:|:-------------------:| +| `Field.const` | allows to supply a constant value for a given field | +| `Field.computed` | allows to compute a value with a function the shape of `Dest => FieldTpe` | +| `Field.default` | only works when a field's got a default value defined (defaults are not taken into consideration by default) | +| `Field.allMatching` | allow to supply a field source whose fields will replace all matching fields in the destination (given that the names and the types match up) | +| `Field.fallbackToDefault` | falls back to default field values but ONLY in case a transformation cannot be created | +| `Field.fallbackToNone` | falls back to `None` for `Option` fields for which a transformation cannot be created | + +--- + Let's introduce another payment method (not part of any of the previous payment method ADTs, just a standalone case class). ```scala mdoc:silent @@ -157,7 +214,7 @@ val card: wire.PaymentMethod.Card = * `Field.const` - allows to supply a constant value for a given field -@:select(underlying-code-2) +@:select(underlying-code-3) @:choice(visible) ```scala mdoc card @@ -178,7 +235,7 @@ Docs.printCode( * `Field.computed` - allows to compute a value with a function the shape of `Dest => FieldTpe` -@:select(underlying-code-3) +@:select(underlying-code-4) @:choice(visible) ```scala mdoc card @@ -203,7 +260,7 @@ Docs.printCode( * `Field.default` - only works when a field's got a default value defined (defaults are not taken into consideration by default) -@:select(underlying-code-4) +@:select(underlying-code-5) @:choice(visible) ```scala mdoc card @@ -229,7 +286,7 @@ case class FieldSource(color: String, digits: Long, extra: Int) val source = FieldSource("magenta", 123445678, 23) ``` -@:select(underlying-code-5) +@:select(underlying-code-6) @:choice(visible) ```scala mdoc card @@ -260,7 +317,7 @@ case class DestLevel1(extra: String = "level1", str: String) val source = SourceToplevel(SourceLevel1("str"), 400) ``` -@:select(underlying-code-6) +@:select(underlying-code-7) @:choice(visible) ```scala mdoc source @@ -282,7 +339,7 @@ Docs.printCode( `Field.fallbackToDefault` is a `regional` config, which means that you can control the scope where it applies: -@:select(underlying-code-7) +@:select(underlying-code-8) @:choice(visible) ```scala mdoc source @@ -323,7 +380,7 @@ case class DestLevel1(extra: Option[String], str: String) val source = SourceToplevel(SourceLevel1("str"), Some(400)) ``` -@:select(underlying-code-8) +@:select(underlying-code-9) @:choice(visible) ```scala mdoc source @@ -345,7 +402,7 @@ Docs.printCode( `Field.fallbackToNone` is a `regional` config, which means that you can control the scope where it applies: -@:select(underlying-code-9) +@:select(underlying-code-10) @:choice(visible) ```scala mdoc source @@ -374,13 +431,20 @@ Docs.printCode( ### Coproduct configurations +| **Name** | **Description** | +|:-----------------:|:-------------------:| +| `Case.const` | allows to supply a constant value for a given subtype of a coproduct | +| `Case.computed` | allows to supply a function of the selected source type to the expected destination type | + +--- + ```scala mdoc val transfer = wire.PaymentMethod.Transfer("2764262") ``` * `Case.const` - allows to supply a constant value for a given subtype of a coproduct -@:select(underlying-code-10) +@:select(underlying-code-11) @:choice(visible) ```scala mdoc transfer @@ -400,9 +464,9 @@ Docs.printCode( ``` @:@ -* `Case.computed` - allow to supply a function of the selected source type to the expected destination type +* `Case.computed` - allows to supply a function of the selected source type to the expected destination type -@:select(underlying-code-11) +@:select(underlying-code-12) @:choice(visible) ```scala mdoc transfer diff --git a/docs/transformation_rules.md b/docs/transformation_rules.md index 1306e6a6..f9043b85 100644 --- a/docs/transformation_rules.md +++ b/docs/transformation_rules.md @@ -40,13 +40,69 @@ Docs.printCode(1.to[Int | String]) ``` @:@ -### 3. Mapping over an `Option` +### 3. Flatmapping over an arbitrary `F[_]` with a fallible transformation underneath (fallible transformations only) -Transforming between options comes down to mapping over it and recursively deriving a transformation for the value inside. +A value wrapped in an arbitrary `F[_]` can be flatmapped over given that: +* an instance of `Mode.FailFast[F]` is in scope, +* a fallible transformation can be derived for the type being wrapped @:select(underlying-code-3) @:choice(visible) ```scala mdoc + +case class Positive private (value: Int) + +object Positive { + given Transformer.Fallible[[a] =>> Either[String, a], Int, Positive] = + int => if (int < 0) Left("Lesser or equal to 0") else Right(Positive(int)) +} + +Mode.FailFast.either[String].locally { + Right(1).fallibleTo[Positive] +} +``` +@:choice(generated) +```scala mdoc:passthrough +Docs.printCode( + Mode.FailFast.either[String].locally { + Right(1).fallibleTo[Positive] + } +) +``` +@:@ + +### 4. Mapping over an arbitrary `F[_]` (fallible transformations only) + +A value wrapped in an arbitrary `F[_]` can be mapped over given that: +* an instance of `Mode[F]` is in scope, +* a transformation can be derived for the type being wrapped + +@:select(underlying-code-4) +@:choice(visible) +```scala mdoc + +Mode.FailFast.either[String].locally { + Right(1).fallibleTo[Option[Int]] +} +``` +@:choice(generated) +```scala mdoc:passthrough +Docs.printCode( + Mode.FailFast.either[String].locally { + Right(1).fallibleTo[Option[Int]] + } +) +``` +@:@ + + +### 5. Mapping over an `Option` + +Transforming between options comes down to mapping over it and recursively deriving a transformation for the value inside. + +@:select(underlying-code-5) +@:choice(visible) +```scala mdoc given Transformer[Int, String] = int => int.toString Option(1).to[Option[String]] @@ -57,11 +113,11 @@ Docs.printCode(Option(1).to[Option[String]]) ``` @:@ -### 4. Transforming and wrapping in an `Option` +### 6. Transforming and wrapping in an `Option` If a transformation between two types is possible then transforming between the source type and an `Option` of the destination type is just wrapping the transformation result in a `Some`. -@:select(underlying-code-4) +@:select(underlying-code-6) @:choice(visible) ```scala mdoc 1.to[Option[Int | String]] @@ -72,9 +128,9 @@ Docs.printCode(1.to[Option[Int | String]]) ``` @:@ -### 5. Mapping over and changing the collection type +### 7. Mapping over and changing the collection type -@:select(underlying-code-5) +@:select(underlying-code-7) @:choice(visible) ```scala mdoc:nest //`.to` is already a method on collections @@ -88,13 +144,13 @@ Docs.printCode(List(1, 2, 3, 4).convertTo[Vector[Int | String]]) ``` @:@ -### 6. Transforming between case classes +### 8. Transforming between case classes A source case class can be transformed into the destination case class given that: * source has fields whose names cover all of the destination's fields, * a transformation for the types corresponding to those fields can be derived. -@:select(underlying-code-6) +@:select(underlying-code-8) @:choice(visible) ```scala mdoc:reset-object import io.github.arainko.ducktape.* @@ -119,13 +175,89 @@ Docs.printCode( ``` @:@ -### 7. Transforming between enums/sealed traits +### 9. Transforming between case classes and tuples + +A source case class can be transformed into a tuple given that: +* the order of its fields align with the tuple's elements (the source's length CAN be greater than the destination tuple's length), +* a transformation for the types corresponding to the aligned fields and elements can be derived + +@:select(underlying-code-9) +@:choice(visible) +```scala mdoc:reset-object +import io.github.arainko.ducktape.* + +case class Source(field1: Int, field2: List[Int], field3: Int, field4: Int) + +Source(1, List(2, 2, 2), 3, 4).to[(Int, Vector[Int], Option[Int])] +``` + +@:choice(generated) +```scala mdoc:passthrough +import io.github.arainko.ducktape.docs.* + +Docs.printCode( + Source(1, List(2, 2, 2), 3, 4).to[(Int | String, Vector[Int], Option[Int])] +) +``` +@:@ + +### 10. Transforming between tuples and case classes + +A source tuple can be transformed into a case class given that: +* the order of its elements align with the case class' fields (the source's length CAN be greater than the destination's length), +* a transformation for the types corresponding to the aligned fields and elements can be derived + +@:select(underlying-code-10) +@:choice(visible) +```scala mdoc:reset-object +import io.github.arainko.ducktape.* + +case class Dest(field1: Int, field2: List[Int], field3: Option[Int]) + +(1, Vector(2, 2, 2), 3, 4).to[Dest] +``` + +@:choice(generated) +```scala mdoc:passthrough +import io.github.arainko.ducktape.docs.* + +Docs.printCode( + (1, Vector(2, 2, 2), 3, 4).to[Dest] +) +``` +@:@ + +### 11. Transforming between tuples + +A source tuple can be transformed into a destination tuple given that: +* the order of its elements align with the destination tuple's elements (the source's length CAN be greater than the destination's length), +* a transformation for the types corresponding to the aligned fields and elements can be derived + +@:select(underlying-code-11) +@:choice(visible) +```scala mdoc:reset-object +import io.github.arainko.ducktape.* + +(1, Vector(2, 2, 2), 3, 4).to[(Int, List[Int], Option[Int])] +``` + +@:choice(generated) +```scala mdoc:passthrough +import io.github.arainko.ducktape.docs.* + +Docs.printCode( + (1, Vector(2, 2, 2), 3, 4).to[(Int, List[Int], Option[Int])] +) +``` +@:@ + +### 12. Transforming between enums/sealed traits A source coproduct can be transformed into the destination coproduct given that: * destination's children have names that match all of the source's children, * a transformation between those two corresponding types can be derived. -@:select(underlying-code-7) +@:select(underlying-code-12) @:choice(visible) ```scala mdoc sealed trait PaymentMethod @@ -153,11 +285,11 @@ Docs.printCode((PaymentMethod.Cash: PaymentMethod).to[OtherPaymentMethod]) ``` @:@ -### 8. Same named singletons +### 13. Same named singletons Transformations between same named singletons come down to just reffering to the destination singleton. -@:select(underlying-code-8) +@:select(underlying-code-13) @:choice(visible) ```scala mdoc object example1 { @@ -178,9 +310,9 @@ Docs.printCode(example1.Singleton.to[example2.Singleton.type]) ``` @:@ -### 9. Unwrapping a value class +### 14. Unwrapping a value class -@:select(underlying-code-9) +@:select(underlying-code-14) @:choice(visible) ```scala mdoc case class Wrapper1(value: Int) extends AnyVal @@ -195,9 +327,9 @@ Docs.printCode(Wrapper1(1).to[Int]) ``` @:@ -### 10. Wrapping a value class +### 15. Wrapping a value class -@:select(underlying-code-10) +@:select(underlying-code-15) @:choice(visible) ```scala mdoc case class Wrapper2(value: Int) extends AnyVal @@ -212,15 +344,17 @@ Docs.printCode(1.to[Wrapper2]) ``` @:@ -### 11. Automatically derived `Transformer.Derived` +### 16. Automatically derived `Transformer.Derived` Instances of `Transformer.Derived` are automatically derived as a fallback to support use cases where a generic type (eg. a field of a case class) is unknown at definition site. Note that `Transformer[A, B] <: Transformer.Derived[A, B]` so any `Transformer` in scope is eligible to become a `Transformer.Derived`. -@:select(underlying-code-11) +@:select(underlying-code-16) @:choice(visible) -```scala mdoc +```scala mdoc:reset-object +import io.github.arainko.ducktape.* + final case class Source[A](field1: Int, field2: String, generic: A) final case class Dest[A](field1: Int, field2: String, generic: A) diff --git a/ducktape/src/test/scala/io/github/arainko/ducktape/fallible/accumulating/AccumulatingNestedConfigurationSuite.scala b/ducktape/src/test/scala/io/github/arainko/ducktape/fallible/accumulating/AccumulatingNestedConfigurationSuite.scala index da4a07f6..a4a0a7ed 100644 --- a/ducktape/src/test/scala/io/github/arainko/ducktape/fallible/accumulating/AccumulatingNestedConfigurationSuite.scala +++ b/ducktape/src/test/scala/io/github/arainko/ducktape/fallible/accumulating/AccumulatingNestedConfigurationSuite.scala @@ -7,7 +7,7 @@ import io.github.arainko.ducktape.internal.* import scala.annotation.nowarn class AccumulatingNestedConfigurationSuite extends DucktapeSuite { - private given F: Mode.Accumulating.Either[String, List] with {} + private given F: Mode.Accumulating.Either[String, List]() def fallibleComputation(value: Int) = Positive.accTransformer.transform(value) diff --git a/ducktape/src/test/scala/io/github/arainko/ducktape/fallible/failfast/FailFastNestedConfigurationSuite.scala b/ducktape/src/test/scala/io/github/arainko/ducktape/fallible/failfast/FailFastNestedConfigurationSuite.scala index 903be669..10187c52 100644 --- a/ducktape/src/test/scala/io/github/arainko/ducktape/fallible/failfast/FailFastNestedConfigurationSuite.scala +++ b/ducktape/src/test/scala/io/github/arainko/ducktape/fallible/failfast/FailFastNestedConfigurationSuite.scala @@ -7,7 +7,7 @@ import io.github.arainko.ducktape.internal.* import scala.annotation.nowarn class FailFastNestedConfigurationSuite extends DucktapeSuite { - private given F: Mode.FailFast.Either[List[String]] with {} + private given F: Mode.FailFast.Either[List[String]]() def fallibleComputation(value: Int) = Positive.accTransformer.transform(value) diff --git a/ducktape/src/test/scala/io/github/arainko/ducktape/issues/Issue165Suite.scala b/ducktape/src/test/scala/io/github/arainko/ducktape/issues/Issue165Suite.scala index 41bd1235..ebf67aeb 100644 --- a/ducktape/src/test/scala/io/github/arainko/ducktape/issues/Issue165Suite.scala +++ b/ducktape/src/test/scala/io/github/arainko/ducktape/issues/Issue165Suite.scala @@ -30,7 +30,7 @@ class Issue165Suite extends DucktapeSuite { test("rejects _.falibleTo in given Trasformer.Fallible definitions") { assertFailsToCompileWith { """ - given Mode.FailFast.Option with {} + given Mode.FailFast.Option() case class A(a: Int) case class B(b: Int) @@ -44,7 +44,7 @@ class Issue165Suite extends DucktapeSuite { test("rejects _.into.falible in given Trasformer.Fallible definitions") { assertFailsToCompileWith { """ - given Mode.FailFast.Option with {} + given Mode.FailFast.Option() case class A(a: Int) case class B(b: Int)