You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/total_transformations/configuring_transformations.md
+285Lines changed: 285 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -201,6 +201,8 @@ What's worth noting is that any of the configuration options are purely a compil
201
201
|`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) |
202
202
|`Field.fallbackToDefault`| falls back to default field values but ONLY in case a transformation cannot be created |
203
203
|`Field.fallbackToNone`| falls back to `None` for `Option` fields for which a transformation cannot be created |
204
+
|`Field.modifySourceNames`| allows to transform names of fields on the source type side |
205
+
|`Field.modifyDestNames`| allows to transform names of fields on the dest type side |
204
206
205
207
---
206
208
@@ -479,6 +481,8 @@ Docs.printCode(
479
481
|:-----------------:|:-------------------:|
480
482
|`Case.const`| allows to supply a constant value for a given subtype of a coproduct |
481
483
|`Case.computed`| allows to supply a function of the selected source type to the expected destination type |
484
+
|`Case.modifySourceNames`| allows to transform names of enum/sealed trait children and case objects of the Source type |
485
+
|`Case.modifyDestNames`| allows to transform names of enum/sealed trait children and case objects of the Dest type |
482
486
483
487
---
484
488
@@ -533,6 +537,287 @@ Docs.printCode(
533
537
```
534
538
@:@
535
539
540
+
### Field/case name transformations
541
+
542
+
#### Changing field names
543
+
544
+
Let's establish a pair of case classes with a peculiar naming scheme first:
545
+
```scala mdoc:nest:silent
546
+
caseclassSource(int: Int, str: String)
547
+
caseclassDest(INT:Int, STR:String)
548
+
549
+
valsource=Source(1, "1")
550
+
```
551
+
552
+
Obviously, we wouldn't be able to map between those two case classes since their names do not match.
553
+
To make this work we can make use of one of the following configuration options:
554
+
555
+
*`Field.modifySourceNames` - modifies source field names according to the provided `Renamer`:
556
+
557
+
@:select(underlying-code-13)
558
+
@:choice(visible)
559
+
```scala mdoc
560
+
source
561
+
.into[Dest]
562
+
.transform(
563
+
Field.modifySourceNames(_.toUpperCase)
564
+
)
565
+
```
566
+
@:choice(generated)
567
+
```scala mdoc:passthrough
568
+
importio.github.arainko.ducktape.docs.*
569
+
570
+
Docs.printCode(
571
+
source
572
+
.into[Dest]
573
+
.transform(
574
+
Field.modifySourceNames(_.toUpperCase)
575
+
)
576
+
)
577
+
```
578
+
@:@
579
+
580
+
*`Field.modifyDestNames` - modifies destination field names according to the provided `Renamer`:
581
+
582
+
@:select(underlying-code-14)
583
+
@:choice(visible)
584
+
```scala mdoc
585
+
source
586
+
.into[Dest]
587
+
.transform(
588
+
Field.modifyDestNames(_.toLowerCase)
589
+
)
590
+
```
591
+
@:choice(generated)
592
+
```scala mdoc:passthrough
593
+
importio.github.arainko.ducktape.docs.*
594
+
595
+
Docs.printCode(
596
+
source
597
+
.into[Dest]
598
+
.transform(
599
+
Field.modifyDestNames(_.toLowerCase)
600
+
)
601
+
)
602
+
```
603
+
@:@
604
+
605
+
#### Changing case names
606
+
607
+
Sealed traits', enums' and case objects' names can also be changed, let's look at an example:
608
+
609
+
```scala mdoc:nest:silent
610
+
enumSource {
611
+
caseOne, Two, Three
612
+
}
613
+
614
+
enumDest {
615
+
caseONE, TWO, THREE
616
+
}
617
+
```
618
+
619
+
*`Case.modifyDestNames` - modifies destination case names according to the provided `Renamer`:
620
+
621
+
@:select(underlying-code-15)
622
+
@:choice(visible)
623
+
```scala mdoc
624
+
Source.One
625
+
.into[Dest]
626
+
.transform(
627
+
Case.modifyDestNames(_.toLowerCase.capitalize)
628
+
)
629
+
```
630
+
@:choice(generated)
631
+
```scala mdoc:passthrough
632
+
importio.github.arainko.ducktape.docs.*
633
+
634
+
Docs.printCode(
635
+
Source.One
636
+
.into[Dest]
637
+
.transform(
638
+
Case.modifyDestNames(_.toLowerCase.capitalize)
639
+
)
640
+
)
641
+
```
642
+
@:@
643
+
644
+
*`Case.modifySourceNames` - modifies source case names according to the provided `Renamer`:
645
+
646
+
@:select(underlying-code-16)
647
+
@:choice(visible)
648
+
```scala mdoc
649
+
Source.One
650
+
.into[Dest]
651
+
.transform(
652
+
Case.modifySourceNames(_.toUpperCase)
653
+
)
654
+
```
655
+
@:choice(generated)
656
+
```scala mdoc:passthrough
657
+
importio.github.arainko.ducktape.docs.*
658
+
659
+
Docs.printCode(
660
+
Source.One
661
+
.into[Dest]
662
+
.transform(
663
+
Case.modifySourceNames(_.toUpperCase)
664
+
)
665
+
)
666
+
```
667
+
@:@
668
+
669
+
#### Constraining the blast radius of name transformations
670
+
671
+
Let's take one of the config options that modifies names (`Field.modifyDestNames`) under closer inspection:
We can see that, apart from being a `Field[Source, Dest]` (a type for describing field configs), it is also a `Regional[Dest] & Local[Dest] & TypeSpecific` - these are 'config modifiers' and each one of them introduces a method on the config option. Let's take a closer look at all of them:
677
+
678
+
*`.regional` - constrains a config option to a certain region (i.e. the option will apply to the transformations 'underneath' the selected field/case):
If we were to use this modifier on a `Case` rename, we'd modify all case objects and the type names of all children of enums and sealed traits 'under' a type we choose.
712
+
713
+
*`.local` - constrains a config option to a certain local region (i.e. to a case class/children of an enum 'underneath' the selected field/case):
If we were to use this modifier on a `Case` rename it'd bubble down (is that a phrase?) to all subtypes of an enum/sealed trait we choose, but only in that specific case.
747
+
748
+
*`.typeSpecific` - constrains a config option to subtypes of the selected type:
.transform(Field.modifyDestNames(_.toUpperCase).typeSpecific[DestLevel1]) // <-- we use `.typeSpecifc` to only modify names for `DestLevel1` and not anywhere else
Name transformations are designed to look like operations on your run of the mill `String`, this however is not the full story.
784
+
One of the constrains of that design is that all of the operations (and their arguments) need to be known at compiletime so that the library can actually
785
+
lift that into an actual `String => String` function to apply on the field/case names.
786
+
787
+
This leads us to `Renamer` - a small DSL that describes the supported subset of `String` methods, defined as such:
788
+
```scala
789
+
sealedtraitRenamer {
790
+
// ...small excerpt of the actual Renamer...
791
+
792
+
/**
793
+
* Equivalent to `String#toLowerCase`
794
+
*/
795
+
deftoLowerCase:Renamer
796
+
797
+
/**
798
+
* Equivalent to the function `(str: String) => Pattern.compile(pattern).matcher(str).replaceAll(replacement)`
0 commit comments