Skip to content

Commit 49fecc1

Browse files
committed
Merge branch 'master' into fork/scala-steward/update/scala-compiler-2.13.15
2 parents 3219229 + af0e222 commit 49fecc1

File tree

16 files changed

+583
-63
lines changed

16 files changed

+583
-63
lines changed

core/src/main/scala/com/avsystem/commons/serialization/GenCodec.scala

+6-4
Original file line numberDiff line numberDiff line change
@@ -312,11 +312,13 @@ object GenCodec extends RecursiveAutoCodecs with TupleGenCodecs {
312312
}
313313

314314
trait SizedCodec[T] extends GenCodec[T] {
315-
def size(value: T): Int
315+
def size(value: T): Int = size(value, Opt.Empty)
316+
317+
def size(value: T, output: Opt[SequentialOutput]): Int
316318

317319
protected final def declareSizeFor(output: SequentialOutput, value: T): Unit =
318320
if (output.sizePolicy != SizePolicy.Ignored) {
319-
output.declareSize(size(value))
321+
output.declareSize(size(value, output.opt))
320322
}
321323
}
322324

@@ -336,8 +338,8 @@ object GenCodec extends RecursiveAutoCodecs with TupleGenCodecs {
336338
object OOOFieldsObjectCodec {
337339
// this was introduced so that transparent wrapper cases are possible in flat sealed hierarchies
338340
final class Transformed[A, B](val wrapped: OOOFieldsObjectCodec[B], onWrite: A => B, onRead: B => A) extends OOOFieldsObjectCodec[A] {
339-
def size(value: A): Int =
340-
wrapped.size(onWrite(value))
341+
def size(value: A, output: Opt[SequentialOutput]): Int =
342+
wrapped.size(onWrite(value), output)
341343

342344
def readObject(input: ObjectInput, outOfOrderFields: FieldValues): A =
343345
onRead(wrapped.readObject(input, outOfOrderFields))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.avsystem.commons
2+
package serialization
3+
4+
/**
5+
* Instructs [[GenCodec]] to <b>ignore</b> the [[transientDefault]] annotation when serializing a case class.
6+
* This ensures that even if a field's value is the same as its default, it will be <b>included</b> in the serialized
7+
* representation. Deserialization behavior remains <b>unchanged</b>. If a field is missing from the input, the default
8+
* value will be used as usual.
9+
*
10+
* This marker can be helpful when using the same model class in multiple contexts with different serialization
11+
* formats that have conflicting requirements for handling default values.
12+
*
13+
* @see [[CustomMarkersOutputWrapper]] for an easy way to add markers to existing [[Output]] implementations
14+
*/
15+
object IgnoreTransientDefaultMarker extends CustomEventMarker[Unit]

core/src/main/scala/com/avsystem/commons/serialization/cbor/CborOptimizedCodecs.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ class OOOFieldCborRawKeysCodec[T](stdObjectCodec: OOOFieldsObjectCodec[T], keyCo
8787
stdObjectCodec.writeFields(output, value)
8888
}
8989

90-
def size(value: T): Int = stdObjectCodec.size(value)
90+
def size(value: T, output: Opt[SequentialOutput]): Int = stdObjectCodec.size(value, output)
9191
def nullable: Boolean = stdObjectCodec.nullable
9292
}
9393

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package com.avsystem.commons
2+
package serialization
3+
4+
trait AcceptsAdditionalCustomMarkers extends AcceptsCustomEvents {
5+
6+
protected def markers: Set[CustomEventMarker[?]]
7+
8+
override def customEvent[T](marker: CustomEventMarker[T], event: T): Boolean =
9+
markers(marker) || super.customEvent(marker, event)
10+
}
11+
12+
/**
13+
* [[Input]] implementation that adds additional markers [[CustomEventMarker]] to the provided [[Input]] instance
14+
*/
15+
final class CustomMarkersInputWrapper private(
16+
override protected val wrapped: Input,
17+
override protected val markers: Set[CustomEventMarker[?]],
18+
) extends InputWrapper with AcceptsAdditionalCustomMarkers {
19+
20+
override def readList(): ListInput =
21+
new CustomMarkersInputWrapper.AdjustedListInput(super.readList(), markers)
22+
23+
override def readObject(): ObjectInput =
24+
new CustomMarkersInputWrapper.AdjustedObjectInput(super.readObject(), markers)
25+
}
26+
object CustomMarkersInputWrapper {
27+
def apply(input: Input, markers: CustomEventMarker[?]*): CustomMarkersInputWrapper =
28+
CustomMarkersInputWrapper(input, markers.toSet)
29+
30+
def apply(input: Input, markers: Set[CustomEventMarker[?]]): CustomMarkersInputWrapper =
31+
new CustomMarkersInputWrapper(input, markers)
32+
33+
private final class AdjustedListInput(
34+
override protected val wrapped: ListInput,
35+
override protected val markers: Set[CustomEventMarker[?]],
36+
) extends ListInputWrapper with AcceptsAdditionalCustomMarkers {
37+
override def nextElement(): Input = new CustomMarkersInputWrapper(super.nextElement(), markers)
38+
}
39+
40+
private final class AdjustedFieldInput(
41+
override protected val wrapped: FieldInput,
42+
override protected val markers: Set[CustomEventMarker[?]],
43+
) extends FieldInputWrapper with AcceptsAdditionalCustomMarkers {
44+
45+
override def readList(): ListInput = new AdjustedListInput(super.readList(), markers)
46+
override def readObject(): ObjectInput = new AdjustedObjectInput(super.readObject(), markers)
47+
}
48+
49+
private final class AdjustedObjectInput(
50+
override protected val wrapped: ObjectInput,
51+
override protected val markers: Set[CustomEventMarker[?]],
52+
) extends ObjectInputWrapper with AcceptsAdditionalCustomMarkers {
53+
54+
override def nextField(): FieldInput = new AdjustedFieldInput(super.nextField(), markers)
55+
override def peekField(name: String): Opt[FieldInput] =
56+
super.peekField(name).map(new AdjustedFieldInput(_, markers))
57+
}
58+
}
59+
60+
/**
61+
* [[Output]] implementation that adds additional markers [[CustomEventMarker]] to the provided [[Output]] instance
62+
*/
63+
final class CustomMarkersOutputWrapper private(
64+
override protected val wrapped: Output,
65+
override protected val markers: Set[CustomEventMarker[?]],
66+
) extends OutputWrapper with AcceptsAdditionalCustomMarkers {
67+
68+
override def writeSimple(): SimpleOutput =
69+
new CustomMarkersOutputWrapper.AdjustedSimpleOutput(super.writeSimple(), markers)
70+
71+
override def writeList(): ListOutput =
72+
new CustomMarkersOutputWrapper.AdjustedListOutput(super.writeList(), markers)
73+
74+
override def writeObject(): ObjectOutput =
75+
new CustomMarkersOutputWrapper.AdjustedObjectOutput(super.writeObject(), markers)
76+
}
77+
78+
object CustomMarkersOutputWrapper {
79+
def apply(output: Output, markers: CustomEventMarker[?]*): CustomMarkersOutputWrapper =
80+
CustomMarkersOutputWrapper(output, markers.toSet)
81+
82+
def apply(output: Output, markers: Set[CustomEventMarker[?]]): CustomMarkersOutputWrapper =
83+
new CustomMarkersOutputWrapper(output, markers)
84+
85+
private final class AdjustedSimpleOutput(
86+
override protected val wrapped: SimpleOutput,
87+
override protected val markers: Set[CustomEventMarker[?]],
88+
) extends SimpleOutputWrapper with AcceptsAdditionalCustomMarkers
89+
90+
private final class AdjustedListOutput(
91+
override protected val wrapped: ListOutput,
92+
override protected val markers: Set[CustomEventMarker[?]],
93+
) extends ListOutputWrapper with AcceptsAdditionalCustomMarkers {
94+
95+
override def writeElement(): Output =
96+
new CustomMarkersOutputWrapper(super.writeElement(), markers)
97+
}
98+
99+
private final class AdjustedObjectOutput(
100+
override protected val wrapped: ObjectOutput,
101+
override protected val markers: Set[CustomEventMarker[?]],
102+
) extends ObjectOutputWrapper with AcceptsAdditionalCustomMarkers {
103+
104+
override def writeField(key: String): Output =
105+
new CustomMarkersOutputWrapper(super.writeField(key), markers)
106+
}
107+
}

core/src/main/scala/com/avsystem/commons/serialization/macroCodecs.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class SingletonCodec[T <: Singleton](
1212
) extends ErrorReportingCodec[T] with OOOFieldsObjectCodec[T] {
1313
final def nullable = true
1414
final def readObject(input: ObjectInput, outOfOrderFields: FieldValues): T = singletonValue
15-
def size(value: T): Int = 0
15+
def size(value: T, output: Opt[SequentialOutput]): Int = 0
1616
def writeFields(output: ObjectOutput, value: T): Unit = ()
1717
}
1818

@@ -109,7 +109,7 @@ abstract class ProductCodec[T <: Product](
109109
nullable: Boolean,
110110
fieldNames: Array[String]
111111
) extends ApplyUnapplyCodec[T](typeRepr, nullable, fieldNames) {
112-
def size(value: T): Int = value.productArity
112+
def size(value: T, output: Opt[SequentialOutput]): Int = value.productArity
113113

114114
final def writeFields(output: ObjectOutput, value: T): Unit = {
115115
val size = value.productArity
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package com.avsystem.commons
2+
package serialization
3+
4+
import com.avsystem.commons.serialization.CodecTestData.HasDefaults
5+
6+
object IgnoreTransientDefaultMarkerTest {
7+
final case class NestedHasDefaults(
8+
@transientDefault flag: Boolean = false,
9+
obj: HasDefaults,
10+
list: Seq[HasDefaults],
11+
@transientDefault defaultObj: HasDefaults = HasDefaults(),
12+
)
13+
object NestedHasDefaults extends HasGenCodec[NestedHasDefaults]
14+
15+
final case class HasOptParam(
16+
@transientDefault flag: Boolean = false,
17+
@optionalParam str: Opt[String] = Opt.Empty,
18+
)
19+
object HasOptParam extends HasGenCodec[HasOptParam]
20+
}
21+
22+
class IgnoreTransientDefaultMarkerTest extends AbstractCodecTest {
23+
import IgnoreTransientDefaultMarkerTest.*
24+
25+
override type Raw = Any
26+
27+
def writeToOutput(write: Output => Unit): Any = {
28+
var result: Any = null
29+
write(CustomMarkersOutputWrapper(new SimpleValueOutput(v => result = v), IgnoreTransientDefaultMarker))
30+
result
31+
}
32+
33+
def createInput(raw: Any): Input =
34+
CustomMarkersInputWrapper(new SimpleValueInput(raw), IgnoreTransientDefaultMarker)
35+
36+
test("write case class with default values") {
37+
testWrite(HasDefaults(str = "lol"), Map("str" -> "lol", "int" -> 42))
38+
testWrite(HasDefaults(43, "lol"), Map("int" -> 43, "str" -> "lol"))
39+
testWrite(HasDefaults(str = null), Map("str" -> null, "int" -> 42))
40+
testWrite(HasDefaults(str = "dafuq"), Map("str" -> "dafuq", "int" -> 42))
41+
}
42+
43+
//noinspection RedundantDefaultArgument
44+
test("read case class with default values") {
45+
testRead(Map("str" -> "lol", "int" -> 42), HasDefaults(str = "lol", int = 42))
46+
testRead(Map("str" -> "lol"), HasDefaults(str = "lol", int = 42))
47+
testRead(Map("int" -> 43, "str" -> "lol"), HasDefaults(int = 43, str = "lol"))
48+
testRead(Map("str" -> null, "int" -> 42), HasDefaults(str = null, int = 42))
49+
testRead(Map("str" -> null), HasDefaults(str = null, int = 42))
50+
testRead(Map(), HasDefaults(str = "dafuq", int = 42))
51+
}
52+
53+
test("write case class with opt values") {
54+
testWrite(HasOptParam(str = "lol".opt), Map("flag" -> false, "str" -> "lol"))
55+
testWrite(HasOptParam(), Map("flag" -> false))
56+
}
57+
58+
//noinspection RedundantDefaultArgument
59+
test("write nested case class with default values") {
60+
testWrite(
61+
value = NestedHasDefaults(
62+
flag = false,
63+
obj = HasDefaults(str = "lol"),
64+
list = Seq(HasDefaults(int = 43)),
65+
defaultObj = HasDefaults(),
66+
),
67+
expectedRepr = Map(
68+
"flag" -> false,
69+
"defaultObj" -> Map[String, Any]("str" -> "kek", "int" -> 42),
70+
"obj" -> Map[String, Any]("str" -> "lol", "int" -> 42),
71+
"list" -> List(Map[String, Any]("str" -> "kek", "int" -> 43)),
72+
),
73+
)
74+
}
75+
}

core/src/test/scala/com/avsystem/commons/serialization/ObjectSizeTest.scala

+45-3
Original file line numberDiff line numberDiff line change
@@ -3,35 +3,77 @@ package serialization
33

44
import org.scalatest.funsuite.AnyFunSuite
55

6-
case class RecordWithDefaults(
6+
final case class RecordWithDefaults(
77
@transientDefault a: String = "",
88
b: Int = 42
99
) {
1010
@generated def c: String = s"$a-$b"
1111
}
1212
object RecordWithDefaults extends HasApplyUnapplyCodec[RecordWithDefaults]
1313

14-
class CustomRecordWithDefaults(val a: String, val b: Int)
14+
final class CustomRecordWithDefaults(val a: String, val b: Int)
1515
object CustomRecordWithDefaults extends HasApplyUnapplyCodec[CustomRecordWithDefaults] {
1616
def apply(@transientDefault a: String = "", b: Int = 42): CustomRecordWithDefaults =
1717
new CustomRecordWithDefaults(a, b)
1818
def unapply(crwd: CustomRecordWithDefaults): Opt[(String, Int)] =
1919
Opt((crwd.a, crwd.b))
2020
}
2121

22-
class CustomWrapper(val a: String)
22+
final class CustomWrapper(val a: String)
2323
object CustomWrapper extends HasApplyUnapplyCodec[CustomWrapper] {
2424
def apply(@transientDefault a: String = ""): CustomWrapper = new CustomWrapper(a)
2525
def unapply(cw: CustomWrapper): Opt[String] = Opt(cw.a)
2626
}
2727

28+
final case class RecordWithOpts(
29+
@optionalParam abc: Opt[String] = Opt.Empty,
30+
@transientDefault flag: Opt[Boolean] = Opt.Empty,
31+
b: Int = 42,
32+
)
33+
object RecordWithOpts extends HasApplyUnapplyCodec[RecordWithOpts]
34+
35+
final case class SingleFieldRecordWithOpts(@optionalParam abc: Opt[String] = Opt.Empty)
36+
object SingleFieldRecordWithOpts extends HasApplyUnapplyCodec[SingleFieldRecordWithOpts]
37+
38+
final case class SingleFieldRecordWithTD(@transientDefault abc: String = "abc")
39+
object SingleFieldRecordWithTD extends HasApplyUnapplyCodec[SingleFieldRecordWithTD]
40+
2841
class ObjectSizeTest extends AnyFunSuite {
2942
test("computing object size") {
3043
assert(RecordWithDefaults.codec.size(RecordWithDefaults()) == 2)
3144
assert(RecordWithDefaults.codec.size(RecordWithDefaults("fuu")) == 3)
45+
assert(RecordWithOpts.codec.size(RecordWithOpts("abc".opt)) == 2)
46+
assert(RecordWithOpts.codec.size(RecordWithOpts("abc".opt, true.opt)) == 3)
47+
assert(RecordWithOpts.codec.size(RecordWithOpts()) == 1)
48+
assert(SingleFieldRecordWithOpts.codec.size(SingleFieldRecordWithOpts()) == 0)
49+
assert(SingleFieldRecordWithOpts.codec.size(SingleFieldRecordWithOpts("abc".opt)) == 1)
50+
assert(SingleFieldRecordWithTD.codec.size(SingleFieldRecordWithTD()) == 0)
51+
assert(SingleFieldRecordWithTD.codec.size(SingleFieldRecordWithTD("haha")) == 1)
3252
assert(CustomRecordWithDefaults.codec.size(CustomRecordWithDefaults()) == 1)
3353
assert(CustomRecordWithDefaults.codec.size(CustomRecordWithDefaults("fuu")) == 2)
3454
assert(CustomWrapper.codec.size(CustomWrapper()) == 0)
3555
assert(CustomWrapper.codec.size(CustomWrapper("fuu")) == 1)
3656
}
57+
58+
test("computing object size with custom output") {
59+
val defaultIgnoringOutput = new SequentialOutput {
60+
override def customEvent[T](marker: CustomEventMarker[T], event: T): Boolean =
61+
marker match {
62+
case IgnoreTransientDefaultMarker => true
63+
case _ => super.customEvent(marker, event)
64+
}
65+
override def finish(): Unit = ()
66+
}
67+
assert(RecordWithDefaults.codec.size(RecordWithDefaults(), defaultIgnoringOutput.opt) == 3)
68+
assert(RecordWithDefaults.codec.size(RecordWithDefaults("fuu"), defaultIgnoringOutput.opt) == 3)
69+
assert(RecordWithOpts.codec.size(RecordWithOpts("abc".opt), defaultIgnoringOutput.opt) == 3)
70+
assert(RecordWithOpts.codec.size(RecordWithOpts("abc".opt, true.opt), defaultIgnoringOutput.opt) == 3)
71+
assert(RecordWithOpts.codec.size(RecordWithOpts(), defaultIgnoringOutput.opt) == 2)
72+
assert(SingleFieldRecordWithOpts.codec.size(SingleFieldRecordWithOpts(), defaultIgnoringOutput.opt) == 0) // @optionalParam field should NOT be counted
73+
assert(SingleFieldRecordWithOpts.codec.size(SingleFieldRecordWithOpts("abc".opt), defaultIgnoringOutput.opt) == 1)
74+
assert(SingleFieldRecordWithTD.codec.size(SingleFieldRecordWithTD(), defaultIgnoringOutput.opt) == 1) // @transientDefault field should be counted
75+
assert(SingleFieldRecordWithTD.codec.size(SingleFieldRecordWithTD("haha"), defaultIgnoringOutput.opt) == 1)
76+
assert(CustomRecordWithDefaults.codec.size(CustomRecordWithDefaults(), defaultIgnoringOutput.opt) == 2)
77+
assert(CustomRecordWithDefaults.codec.size(CustomRecordWithDefaults("fuu"), defaultIgnoringOutput.opt) == 2)
78+
}
3779
}

0 commit comments

Comments
 (0)