Releases: arainko/ducktape
ducktape 0.2.2
This release brings back lints for configs that override each other, for example:
final case class FieldSource(additionalArg: String, str: String)
val fieldSource = FieldSource("str-sourced", "str2")
val expected = TestClassWithAdditionalString(1, "str2", "str-computed")
testClass
.into[TestClassWithAdditionalString]
.transform(
Field.allMatching(fieldSource),
Field.allMatching(fieldSource),
)will result in a compilation warning that looks like this:
Configs for:
* TestClassWithAdditionalString.str
* TestClassWithAdditionalString.additionalArg
are being overriden by Field.allMatching(fieldSource) @ AppliedBuilderSuite.scala:185:41
It also introduces Mode#locally that allows you to use a Mode in a scope of a function so that it doesn't need to be bound to a given.
What's Changed
- [Issue#142] config linting by @arainko in #159
- Update munit to 1.0.0 by @scala-steward in #168
- Add 'Mode#locally' by @arainko in #169
Full Changelog: v0.2.1...v0.2.2
ducktape 0.2.1
This release adds a lint that rejects calls to non-Transformer.define* family of methods in given Transformer declarations (#165 )
What's Changed
- Update README.md versions by @arainko in #152
- Update scalafmt-core to 3.8.1 by @scala-steward in #153
- Update sbt-typelevel-ci-release, ... to 0.7.0 by @scala-steward in #160
- Update munit to 1.0.0-RC1 by @scala-steward in #161
- Update sbt to 1.10.0 by @scala-steward in #163
- Update sbt-typelevel-ci-release, ... to 0.7.1 by @scala-steward in #164
- [Issue#165] reject calls to _.to and _.into.transform (and their fallible counterparts) in given Transformer definitions by @arainko in #167
Full Changelog: v0.2.0...v0.2.1
ducktape 0.2.0
ducktape 0.2.0
First of all, head on over to the new docs site.
The library has been rebuilt from the ground-up to not rely on automatic derivation of Transformer and Transformer.Fallible and enable further development of more advanced features (the examples of which are nested configurations and support for regional configs like Field.fallbackToDefault - with more still to come). All of that should result in a much higher quality of code that gets generated underneath (for example, fallible transformations do not generate interim Transformer.Fallible instances anymore).
While this release is not binary-compatible with ducktape 0.1.x, it's aiming to be as source-compatible as possible. All the known breakages are documented in the Coming from 0.1.x section of the docs. There are also a bunch of deprecated forwarders with (hopefully) user-friendly tips on how to make the deprecation warnings go away.
New highlight features
-
ability to configure deeply nested transformations without having to resort to building out new
Transformerinstances, best showcased in theConfiguring transformationssection of the docs -
revamped error reporting - errors are now accumulated and shown at once and are supposed to be actionable e.g. in case of a field that's missing it'll give the user a hint and a path to that field for usage in one of the configuration options, for example:
No field 'name' found in MdocApp0.this.wire.PaymentMethod.Card @ Person.paymentMethods.element.at[MdocApp0.this.domain.Payment.Card].nameMore information is available in the
Motivating examplesection of the docs. -
new flavor of configuration options - regional configs:
Field.fallbackToDefaultfor falling back to default values in cases where a transformation couldn't be derived,Field.fallbackToNonefor falling back toNoneforOptionfields for which a transformation couldn't be derived
Regional configs can be made to work in only user-selected subregions of the transformations, eg.:
Field.fallbackToDefault.regional(_.field1.at[Case1])
will make it apply only 'below'
field1at it'sCase1subtype. More info and examples are available inConfiguring transformations
What's Changed
- Update versions in README by @arainko in #141
- Update sbt-typelevel-ci-release to 0.6.7 by @scala-steward in #148
- Update scalafmt-core to 3.8.0 by @scala-steward in #147
- Update munit to 1.0.0-M11 by @scala-steward in #146
- Update scala3-library, ... to 3.3.3 by @scala-steward in #145
- Update sbt-scalajs, scalajs-library_2.13, ... to 1.16.0 by @scala-steward in #144
Full Changelog: v0.2.0-RC1...v0.2.0
ducktape 0.2.0-RC1
ducktape 0.2.0-RC1
The first release candidate of the 0.2.x line - to be promoted to a final 0.2.0 release if nothing comes up in the next week.
Starting from this release the default branch will start pointing to the series/0.2.x branch.
The docs are now published on a docs page as opposed to being kept in a readme.
What's Changed
- Create docs website by @arainko in #136
- Rework fallible docs by @arainko in #137
- Add missing
privatemodifiers, link to docs in README by @arainko in #138 - Additional webpage fixes by @arainko in #140
Full Changelog: v0.2.0-M5...v0.2.0-RC1
ducktape 0.2.0-M5
ducktape 0.2.0-M5
This release bring a new encoding of accumulating transformations (well, the 'decoding', really) - the final step destructing the zipped Tuple is now represented as a chain of accessors, i.e. this:
(value: Tuple2[Type, Tuple2[Type, Type]]) => new Person(name = value._1, age = value._2._1, socialSecurityNo = value._2._2)
): Either[List[String], Person]): Either[List[String], Person])instead of a pattern match like this one:
value: Tuple2[Tuple2[Type, Type], Type]) =>
value match {
case Tuple2(Tuple2(name, age), socialSecurityNo) =>
new ValidatedPerson(name = name, age = age, socialSecurityNo = socialSecurityNo)
case x =>
throw new MatchError(x)
}
)This release is very likely to become an RC if nothing serious comes up in the next week.
What's Changed
- annotate
TransformerandTransformer.Falliblewith FunctionalInterface by @arainko in #124 - Revert "update deps" by @arainko in #125
- Revert "Revert "update deps"" by @arainko in #126
- Fix bad owners for fail fast and partially for accumulating by @arainko in #127
- dramatically simplify
ProductZipperby @arainko in #130 - Regional config docs by @arainko in #133
- More fallible docs by @arainko in #134
Full Changelog: v0.2.0-M4...v0.2.0-M5
ducktape 0.2.0-M4
This release is identical to 0.2.0-M3 in terms of features. Publishing should be fixed now.
What's Changed
Full Changelog: v0.2.0-M3...v0.2.0-M4
ducktape 0.2.0-M3
ducktape 0.2.0-M3
This release brings fallible transformations into the new backend.
There are some issues uncovered when compiling with -Xcheck-macros, namely:
- Both Accumulating and FailFast transformations report bad owners inside lambdas of non-fallible sub-transformations (eg. ihen transforming between Options, Collections, etc.) - for the time being -Xcheck-macros has been disabled to be pragmatic and actually deliver the feature set
- compiler crashes when trying to configure stuff under an .element path element with intoVia (but only during direct transformations, transforming with Transformer.define works in those cases as a workaround ()
- compiler crashes when using local classes when configuring some cases with Case.fallible*
What's Changed
- update version by @arainko in #105
- declutter
Plan.scalaby @arainko in #108 - Regional configs by @arainko in #109
- keep paths inside structures instead of plans by @arainko in #110
- Parametarize Plans with
Fallibleaka can this particular Plan have a fallible node by @arainko in #112 - [0.2.x] Fallible transformers by @arainko in #122
Full Changelog: v0.2.0-M2...v0.2.0-M3
ducktape 0.2.0-M2
ducktape 0.2.0-M2
The 0.2.x series comes with a lot of improvements, some of them being:
- support for configurations for nested transformations (previously these would need to be their own
Transformerinstance, no matter how basic the config was), - the library now reports all of the transformation errors all at once (with copy-pastable hints on how to fix a given error, eg.
No field 'name' found in MdocApp0.this.wire.PaymentMethod.Card @ Person.paymentMethods.element.at[MdocApp0.this.domain.Payment.Card].name
...and many others.
The 0.2.x series also retains source compatibility with the 0.1.x series to the fullest extent possible (there are SOME changes which will be documented in detail some time in the future) - but generally your code should compile and work as is.
This milestone release only contains support for total transformations, fallible transformations are coming soon.
Read on over to see what is actually possible now or read the docs and try it for yourself:
import java.time.Instant
import io.github.arainko.ducktape.*
// imagine this is a wire model of some kind - JSON, protobuf, avro, what have you...
object wire {
final case class Person(
firstName: String,
lastName: String,
paymentMethods: List[wire.PaymentMethod],
status: wire.Status,
updatedAt: Option[Instant],
)
enum Status:
case Registered, PendingRegistration, Removed
enum PaymentMethod:
case Card(name: String, digits: Long, expires: Instant)
case PayPal(email: String)
case Cash
}
object domain {
final case class Person( // <-- fields reshuffled
lastName: String,
firstName: String,
status: Option[domain.Status], // <-- 'status' in the domain model is optional
paymentMethods: Vector[domain.Payment], // <-- collection type changed from a List to a Vector
updatedAt: Option[Instant],
)
enum Status:
case Registered, PendingRegistration, Removed
case PendingRemoval // <-- additional enum case
enum Payment:
case Card(name: String, digits: Long, expires: Instant)
case PayPal(email: String)
case Cash
}
val wirePerson: wire.Person = wire.Person(
"John",
"Doe",
List(
wire.PaymentMethod.Cash,
wire.PaymentMethod.PayPal("john@doe.com"),
wire.PaymentMethod.Card("J. Doe", 12345, Instant.now)
),
wire.Status.PendingRegistration,
Some(Instant.ofEpochSecond(0))
)val domainPerson = wirePerson.to[domain.Person]
// domainPerson: Person = Person(
// lastName = "Doe",
// firstName = "John",
// status = Some(value = PendingRegistration),
// paymentMethods = Vector(
// Cash,
// PayPal(email = "john@doe.com"),
// Card(
// name = "J. Doe",
// digits = 12345L,
// expires = 2023-12-17T14:54:13.882678035Z
// )
// ),
// updatedAt = Some(value = 1970-01-01T00:00:00Z)
// )Click to see the generated code
(({
val paymentMethods$2: Vector[Payment] = MdocApp.this.wirePerson.paymentMethods
.map[Payment]((src: PaymentMethod) =>
if (src.isInstanceOf[Card])
new Card(
name = src.asInstanceOf[Card].name,
digits = src.asInstanceOf[Card].digits,
expires = src.asInstanceOf[Card].expires
)
else if (src.isInstanceOf[PayPal]) new PayPal(email = src.asInstanceOf[PayPal].email)
else if (src.isInstanceOf[Cash.type]) MdocApp.this.domain.Payment.Cash
else throw new RuntimeException("Unhandled condition encountered during Coproduct Transformer derivation")
)
.to[Vector[Payment]](iterableFactory[Payment])
val status$2: Some[Status] = Some.apply[Status](
if (MdocApp.this.wirePerson.status.isInstanceOf[Registered.type]) MdocApp.this.domain.Status.Registered
else if (MdocApp.this.wirePerson.status.isInstanceOf[PendingRegistration.type])
MdocApp.this.domain.Status.PendingRegistration
else if (MdocApp.this.wirePerson.status.isInstanceOf[Removed.type]) MdocApp.this.domain.Status.Removed
else throw new RuntimeException("Unhandled condition encountered during Coproduct Transformer derivation")
)
new Person(
lastName = MdocApp.this.wirePerson.lastName,
firstName = MdocApp.this.wirePerson.firstName,
status = status$2,
paymentMethods = paymentMethods$2,
updatedAt = MdocApp.this.wirePerson.updatedAt
)
}: Person): Person)But now imagine that your wire model differs ever so slightly from your domain model, maybe the wire model's PaymentMethod.Card doesn't have the name field for some inexplicable reason...
object wire {
final case class Person(
firstName: String,
lastName: String,
paymentMethods: List[wire.PaymentMethod],
status: wire.Status,
updatedAt: Option[Instant],
)
enum Status:
case Registered, PendingRegistration, Removed
enum PaymentMethod:
case Card(digits: Long, expires: Instant) // <-- poof, 'name' is gone
case PayPal(email: String)
case Cash
}
val wirePerson: wire.Person = wire.Person(
"John",
"Doe",
List(
wire.PaymentMethod.Cash,
wire.PaymentMethod.PayPal("john@doe.com"),
wire.PaymentMethod.Card(12345, Instant.now)
),
wire.Status.PendingRegistration,
Some(Instant.ofEpochSecond(0))
)...and when you try to transform between these two representations the compiler now yells at you.
val domainPerson = wirePerson.to[domain.Person]
// error:
// No field 'name' found in MdocApp0.this.wire.PaymentMethod.Card @ Person.paymentMethods.element.at[MdocApp0.this.domain.Payment.Card].name
// case Cash
// ^Now onto dealing with that, let's first examine the error message:
No field 'name' found in MdocApp0.this.wire.PaymentMethod.Card @ Person.paymentMethods.element.at[MdocApp0.this.domain.Payment.Card].name
especially the part after @:
Person.paymentMethods.element.at[MdocApp0.this.domain.Payment.Card].name
the thing above is basically a path to the field/subtype under which ducktape was not able to create a transformation, these are meant to be copy-pastable for when you're actually trying to fix the error, eg. by setting the name field to a constant value:
val domainPerson =
wirePerson
.into[domain.Person]
.transform(Field.const(_.paymentMethods.element.at[domain.Payment.Card].name, "CONST NAME"))
// domainPerson: Person = Person(
// lastName = "Doe",
// firstName = "John",
// status = Some(value = PendingRegistration),
// paymentMethods = Vector(
// Cash,
// PayPal(email = "john@doe.com"),
// Card(
// name = "CONST NAME",
// digits = 12345L,
// expires = 2023-12-17T14:54:13.890982787Z
// )
// ),
// updatedAt = Some(value = 1970-01-01T00:00:00Z)
// )Click to see the generated code
{
val AppliedBuilder_this: AppliedBuilder[Person, Person] = into[Person](MdocApp1.this.wirePerson1)[MdocApp1.this.domain.Person]
{
val value$proxy3: Person = AppliedBuilder_this.inline$value
{
val paymentMethods$4: Vector[Payment] = value$proxy3.paymentMethods
.map[Payment]((src: PaymentMethod) =>
if (src.isInstanceOf[Card])
new Card(name = "CONST NAME", digits = src.asInstanceOf[Card].digits, expires = src.asInstanceOf[Card].expires)
else if (src.isInstanceOf[PayPal]) new PayPal(email = src.asInstanceOf[PayPal].email)
else if (src.isInstanceOf[Cash.type]) MdocApp1.this.domain.Payment.Cash
else throw new RuntimeException("Unhandled condition encountered during Coproduct Transformer derivation")
)
.to[Vector[Payment]](iterableFactory[Payment])
val status$4: Some[Status] = Some.apply[Status](
if (value$proxy3.status.isInstanceOf[Registered.type]) MdocApp1.this.domain.Status.Registered
else if (value$proxy3.status.isInstanceOf[PendingRegistration.type]) MdocApp1.this.domain.Status.PendingRegistration
else if (value$proxy3.status.isInstanceOf[Removed.type]) MdocApp1.this.domain.Status.Removed
else throw new RuntimeException("Unhandled condition encountered during Coproduct Transformer derivation")
)
new Person(
lastName = value$proxy3.lastName,
firstName = value$proxy3.firstName,
status = status$4,
paymentMethods = paymentMethods$4,
updatedAt = value$proxy3.updatedAt
)
}: Person
}: Person
}ducktape 0.1.11
This is a bugfix release that fixes an issue with wrong flags being set when constructing zipped products. Shout-out to @WojciechMazur and the team working on the community build for finding and fixing this.
What's Changed
- update docs by @arainko in #73
- Use correct set of flags for
Symbol.newBindinZippedProductby @WojciechMazur in #76
New Contributors
- @WojciechMazur made their first contribution in #76
Full Changelog: v0.1.10...v0.1.11
ducktape 0.1.10
This release brings Transformer.Fallible automatic derivation for coproducts courtesy of @gregor-i along with new and shiny config options for your fallible coproduct transformations like Case.fallibleConst or Case.fallibleComputed.
This release also bumps the Scala version to 3.3.0 (a LTS release) which means all users of this lib also need to bump their projects to 3.3.0 (there's literally no reason not to as Scala 3 is backwards compatible).
What's Changed
- update docs to 0.1.9 by @arainko in #66
- [issue #45]create CONTRIBUTING.md by @arainko in #68
- FallibleTransformer derivation for coproducts by @gregor-i in #69
- [issue#70] fallible coproduct configurations by @arainko in #71
New Contributors
Full Changelog: v0.1.9...v0.1.10