Skip to content

Commit 44928df

Browse files
author
Roman Janusz
committed
added support for TypedMap in typed mongo API
1 parent 576e364 commit 44928df

File tree

11 files changed

+135
-44
lines changed

11 files changed

+135
-44
lines changed

commons-core/src/main/scala/com/avsystem/commons/misc/TypedMap.scala

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,30 @@ package com.avsystem.commons
22
package misc
33

44
import com.avsystem.commons.misc.TypedMap.GenCodecMapping
5-
import com.avsystem.commons.serialization.{GenCodec, GenKeyCodec, ObjectInput, ObjectOutput}
5+
import com.avsystem.commons.serialization._
66

77
class TypedMap[K[_]](val raw: Map[K[_], Any]) extends AnyVal {
8-
def apply[T](key: K[T]): Option[T] =
9-
raw.get(key).map(_.asInstanceOf[T])
8+
def apply[T](key: K[T]): T =
9+
raw(key).asInstanceOf[T]
10+
11+
def get[T](key: K[T]): Option[T] =
12+
raw.get(key).asInstanceOf[Option[T]]
13+
14+
def getOrElse[T](key: K[T], defaultValue: => T): T =
15+
get(key).getOrElse(defaultValue)
1016

1117
def updated[T](key: K[T], value: T): TypedMap[K] =
1218
new TypedMap[K](raw.updated(key, value))
1319

20+
def ++(other: TypedMap[K]): TypedMap[K] =
21+
new TypedMap[K](raw ++ other.raw)
22+
23+
def keys: Iterable[K[_]] = raw.keys
24+
def keysIterator: Iterator[K[_]] = raw.keysIterator
25+
def keySet: Set[K[_]] = raw.keySet
26+
27+
def size: Int = raw.size
28+
1429
override def toString = s"TypedMap($raw)"
1530
}
1631

@@ -33,39 +48,46 @@ object TypedMap {
3348
def valueCodec[T](key: K[T]): GenCodec[T]
3449
}
3550

36-
implicit def typedMapCodec[K[_]](implicit keyCodec: GenKeyCodec[K[_]], codecMapping: GenCodecMapping[K]): GenCodec[TypedMap[K]] =
51+
implicit def typedMapCodec[K[_]](implicit
52+
keyCodec: GenKeyCodec[K[_]],
53+
codecMapping: GenCodecMapping[K]
54+
): GenObjectCodec[TypedMap[K]] =
3755
new GenCodec.ObjectCodec[TypedMap[K]] {
3856
def nullable = false
3957
def readObject(input: ObjectInput): TypedMap[K] = {
4058
val rawBuilder = Map.newBuilder[K[_], Any]
59+
input.knownSize match {
60+
case -1 =>
61+
case size => rawBuilder.sizeHint(size)
62+
}
4163
while (input.hasNext) {
4264
val fieldInput = input.nextField()
4365
val key = keyCodec.read(fieldInput.fieldName)
4466
rawBuilder += ((key, codecMapping.valueCodec(key).read(fieldInput)))
4567
}
4668
new TypedMap[K](rawBuilder.result())
4769
}
48-
def writeObject(output: ObjectOutput, typedMap: TypedMap[K]): Unit =
70+
def writeObject(output: ObjectOutput, typedMap: TypedMap[K]): Unit = {
71+
output.declareSizeOf(typedMap.raw)
4972
typedMap.raw.foreach { case (key, value) =>
50-
codecMapping.valueCodec(key.asInstanceOf[K[Any]]).write(output.writeField(keyCodec.write(key)), value)
73+
val valueCodec = codecMapping.valueCodec(key.asInstanceOf[K[Any]])
74+
valueCodec.write(output.writeField(keyCodec.write(key)), value)
5175
}
76+
}
5277
}
5378
}
5479

5580
/**
56-
* Base class for sealed enums which can be used as key type for a [[TypedMap]]. It also ensures that
57-
* the [[TypedMap]] using [[TypedKey]] as a key type will always have a `GenCodec`.
81+
* Base class for key types of [[TypedMap]] (typically enums parameterized by value type).
82+
* Provides an instance of [[GenCodecMapping]] which is necessary for [[GenCodec]] instance for [[TypedMap]] that
83+
* uses this key type.
5884
*/
59-
abstract class TypedKey[T](implicit val valueCodec: GenCodec[T])
60-
trait TypedKeyCompanion[K[X] <: TypedKey[X]] extends SealedEnumCompanion[K[_]] {
61-
/**
62-
* `GenKeyCodec` for typed key `K`.
63-
* You can implement this with `GenKeyCodec.forSealedEnum` macro
64-
*/
65-
implicit def keyCodec: GenKeyCodec[K[_]]
66-
67-
implicit val codecMapping: GenCodecMapping[K] =
85+
trait TypedKey[T] {
86+
def valueCodec: GenCodec[T]
87+
}
88+
object TypedKey {
89+
implicit def codecMapping[K[X] <: TypedKey[X]]: GenCodecMapping[K] =
6890
new GenCodecMapping[K] {
69-
def valueCodec[T](key: K[T]) = key.valueCodec
91+
def valueCodec[T](key: K[T]): GenCodec[T] = key.valueCodec
7092
}
7193
}

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

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -434,21 +434,9 @@ object GenCodec extends RecursiveAutoCodecs with TupleGenCodecs {
434434
implicit lazy val BytesCodec: GenCodec[Bytes] =
435435
GenCodec.nullableSimple(i => Bytes(i.readBinary()), (o, b) => o.writeBinary(b.bytes))
436436

437-
private def declareSize(o: SequentialOutput, coll: BIterable[_]): Unit =
438-
o.sizePolicy match {
439-
case SizePolicy.Ignored =>
440-
case SizePolicy.Optional =>
441-
coll.knownSize match {
442-
case -1 =>
443-
case size => o.declareSize(size)
444-
}
445-
case SizePolicy.Required =>
446-
o.declareSize(coll.size)
447-
}
448-
449437
private implicit class IterableOps[A](private val coll: BIterable[A]) extends AnyVal {
450438
def writeToList(lo: ListOutput)(implicit writer: GenCodec[A]): Unit = {
451-
declareSize(lo, coll)
439+
lo.declareSizeOf(coll)
452440
coll.foreach(new (A => Unit) {
453441
private var idx = 0
454442
def apply(a: A): Unit = {
@@ -463,7 +451,7 @@ object GenCodec extends RecursiveAutoCodecs with TupleGenCodecs {
463451

464452
private implicit class PairIterableOps[A, B](private val coll: BIterable[(A, B)]) extends AnyVal {
465453
def writeToObject(oo: ObjectOutput)(implicit keyWriter: GenKeyCodec[A], writer: GenCodec[B]): Unit = {
466-
declareSize(oo, coll)
454+
oo.declareSizeOf(coll)
467455
coll.foreach { case (key, value) =>
468456
val fieldName = keyWriter.write(key)
469457
try writer.write(oo.writeField(fieldName), value) catch {

commons-core/src/main/scala/com/avsystem/commons/serialization/InputOutput.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,21 @@ trait SequentialOutput extends Any with AcceptsCustomEvents {
176176
* have been written. This method MUST always be called after list/object writing has been finished.
177177
*/
178178
def finish(): Unit
179+
180+
/**
181+
* Based on given collection's `knownSize` and [[sizePolicy]], declares the size
182+
* of this output as size of this collection if it is either cheap or required to do so.
183+
*/
184+
final def declareSizeOf(coll: BIterable[_]): Unit = sizePolicy match {
185+
case SizePolicy.Ignored =>
186+
case SizePolicy.Optional =>
187+
coll.knownSize match {
188+
case -1 =>
189+
case size => declareSize(size)
190+
}
191+
case SizePolicy.Required =>
192+
declareSize(coll.size)
193+
}
179194
}
180195
/**
181196
* Represents an abstract sink for serialization of sequences of values. Any [[ListOutput]] instance

commons-core/src/test/scala/com/avsystem/commons/serialization/CodecTestData.scala

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@ package com.avsystem.commons
22
package serialization
33

44
import com.avsystem.commons.annotation.AnnotationAggregate
5-
import com.avsystem.commons.meta.MacroInstances
6-
import com.avsystem.commons.meta.AutoOptionalParams
7-
import com.avsystem.commons.misc.{TypedKey, TypedKeyCompanion}
5+
import com.avsystem.commons.meta.{AutoOptionalParams, MacroInstances}
6+
import com.avsystem.commons.misc.{AutoNamedEnum, NamedEnumCompanion, TypedKey}
87

98
object CodecTestData {
109
def col[T <: JCollection[Int]](col: T): T = {
@@ -300,15 +299,14 @@ object CodecTestData {
300299
implicit val codec: GenCodec[KeyEnumz] = GenCodec.forSealedEnum[KeyEnumz]
301300
}
302301

303-
sealed abstract class SealedKey[T: GenCodec] extends TypedKey[T]
304-
object SealedKey extends TypedKeyCompanion[SealedKey] {
302+
sealed abstract class SealedKey[T](implicit val valueCodec: GenCodec[T]) extends TypedKey[T] with AutoNamedEnum
303+
object SealedKey extends NamedEnumCompanion[SealedKey[_]] {
305304
@name("StrKey")
306305
case object StringKey extends SealedKey[String]
307306
case object IntKey extends SealedKey[Int]
308307
case object BooleanKey extends SealedKey[Boolean]
309308

310309
val values: List[SealedKey[_]] = caseObjects
311-
implicit def keyCodec: GenKeyCodec[SealedKey[_]] = GenKeyCodec.forSealedEnum[SealedKey[_]]
312310
}
313311

314312
@flatten("kejs") sealed trait CustomizedSeal

commons-macros/src/main/scala/com/avsystem/commons/macros/serialization/MongoMacros.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class MongoMacros(ctx: blackbox.Context) extends CodecMacroCommons(ctx) {
1212
lazy val SeqApplySym: Symbol = typeOf[scala.collection.Seq[Any]].member(TermName("apply"))
1313
lazy val SeqHeadRef: Symbol = typeOf[scala.collection.Seq[Any]].member(TermName("head"))
1414
lazy val MapApplySym: Symbol = typeOf[scala.collection.Map[Any, Any]].member(TermName("apply"))
15+
lazy val TypedMapApplySym: Symbol = getType(tq"$MiscPkg.TypedMap[$ScalaPkg.Any]").member(TermName("apply"))
1516
lazy val AdtAsSym: Symbol = getType(tq"$MongoTypedPkg.AbstractMongoDataCompanion[Any, Any]#macroDslExtensions").member(TermName("as"))
1617

1718
// check if some symbol is an abstract method of a sealed trait/class implemented in every case class
@@ -74,6 +75,11 @@ class MongoMacros(ctx: blackbox.Context) extends CodecMacroCommons(ctx) {
7475

7576
q"${extractRefStep(prefix)}.apply($argument)"
7677

78+
case Apply(TypeApply(Select(prefix, TermName("apply")), List(valueTpeTree)), List(argument))
79+
if overridesAnyOf(body.symbol, TypedMapApplySym) =>
80+
81+
q"${extractRefStep(prefix)}.apply[$valueTpeTree]($argument)"
82+
7783
case _ =>
7884
invalid(body)
7985
}

commons-mongo/jvm/src/main/scala/com/avsystem/commons/mongo/typed/MongoFormat.scala

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package mongo.typed
33

44
import com.avsystem.commons.annotation.positioned
55
import com.avsystem.commons.meta._
6-
import com.avsystem.commons.misc.ValueOf
6+
import com.avsystem.commons.misc.{TypedMap, ValueOf}
77
import com.avsystem.commons.mongo.{BsonValueInput, BsonValueOutput}
88
import com.avsystem.commons.serialization._
99
import org.bson.{BsonNull, BsonValue}
@@ -61,6 +61,12 @@ object MongoFormat extends MetadataCompanion[MongoFormat] with MongoFormatLowPri
6161
valueFormat: MongoFormat[V]
6262
) extends MongoFormat[M[K, V]]
6363

64+
final case class TypedMapFormat[K[_]](
65+
codec: GenCodec[TypedMap[K]],
66+
keyCodec: GenKeyCodec[K[_]],
67+
valueFormats: MongoFormatMapping[K]
68+
) extends MongoFormat[TypedMap[K]]
69+
6470
final case class OptionalFormat[O, T](
6571
codec: GenCodec[O],
6672
optionLike: OptionLike.Aux[O, T],
@@ -75,6 +81,10 @@ object MongoFormat extends MetadataCompanion[MongoFormat] with MongoFormatLowPri
7581
implicit mapCodec: GenCodec[M[K, V]], keyCodec: GenKeyCodec[K], valueFormat: MongoFormat[V]
7682
): MongoFormat[M[K, V]] = DictionaryFormat(mapCodec, keyCodec, valueFormat)
7783

84+
implicit def typedMapFormat[K[_]](
85+
implicit keyCodec: GenKeyCodec[K[_]], valueFormats: MongoFormatMapping[K]
86+
): MongoFormat[TypedMap[K]] = TypedMapFormat[K](TypedMap.typedMapCodec, keyCodec, valueFormats)
87+
7888
implicit def optionalFormat[O, T](
7989
implicit optionLike: OptionLike.Aux[O, T], optionCodec: GenCodec[O], wrappedFormat: MongoFormat[T]
8090
): MongoFormat[O] = OptionalFormat(optionCodec, optionLike, wrappedFormat)
@@ -96,6 +106,15 @@ object MongoFormat extends MetadataCompanion[MongoFormat] with MongoFormatLowPri
96106
"do you have a custom implicit MongoFormat for that type?")
97107
}
98108
}
109+
110+
implicit class typedMapFormatOps[K[_]](private val format: MongoFormat[TypedMap[K]]) extends AnyVal {
111+
def assumeTypedMap: TypedMapFormat[K] = format match {
112+
case typedMap: TypedMapFormat[K] => typedMap
113+
case _ => throw new IllegalArgumentException(
114+
"Encountered a non-typed-map MongoFormat for a TypedMap type - " +
115+
"do you have a custom implicit MongoFormat for that type?")
116+
}
117+
}
99118
}
100119
trait MongoFormatLowPriority { this: MongoFormat.type =>
101120
implicit def leafFormat[T: GenCodec]: MongoFormat[T] = Opaque(GenCodec[T])

commons-mongo/jvm/src/main/scala/com/avsystem/commons/mongo/typed/MongoRef.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package mongo.typed
33

44
import com.avsystem.commons.annotation.macroPrivate
55
import com.avsystem.commons.meta.OptionLike
6+
import com.avsystem.commons.misc.TypedMap
67
import com.avsystem.commons.mongo.typed.MongoPropertyRef.Separator
78
import com.avsystem.commons.mongo.{BsonValueInput, KeyEscaper}
89
import com.avsystem.commons.serialization.GenCodec.ReadFailure
@@ -257,6 +258,13 @@ object MongoPropertyRef {
257258
}
258259
}
259260

261+
implicit class TypedMapRefOps[E, K[_]](private val ref: MongoPropertyRef[E, TypedMap[K]]) extends AnyVal {
262+
def apply[T](key: K[T]): MongoPropertyRef[E, T] = {
263+
val tmFormat = ref.format.assumeTypedMap
264+
MongoRef.FieldRef(ref, tmFormat.keyCodec.write(key), tmFormat.valueFormats.valueFormat(key), Opt.Empty)
265+
}
266+
}
267+
260268
implicit def optionalRefOps[E, O, T](ref: MongoPropertyRef[E, O])(implicit optionLike: OptionLike.Aux[O, T]): OptionalRefOps[E, O, T] =
261269
new OptionalRefOps[E, O, T](ref)
262270

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.avsystem.commons
2+
package mongo.typed
3+
4+
import com.avsystem.commons.misc.TypedKey
5+
import com.avsystem.commons.misc.TypedMap.GenCodecMapping
6+
import com.avsystem.commons.serialization.GenCodec
7+
8+
trait MongoFormatMapping[K[_]] extends GenCodecMapping[K] {
9+
def valueFormat[T](key: K[T]): MongoFormat[T]
10+
override def valueCodec[T](key: K[T]): GenCodec[T] = valueFormat(key).codec
11+
}
12+
13+
trait MongoTypedKey[T] extends TypedKey[T] {
14+
def valueFormat: MongoFormat[T]
15+
override def valueCodec: GenCodec[T] = valueFormat.codec
16+
}
17+
object MongoTypedKey {
18+
implicit def mongoFormatMapping[K[X] <: MongoTypedKey[X]]: MongoFormatMapping[K] =
19+
new MongoFormatMapping[K] {
20+
override def valueFormat[T](key: K[T]): MongoFormat[T] = key.valueFormat
21+
}
22+
}

commons-mongo/jvm/src/test/scala/com/avsystem/commons/mongo/typed/MongoRefTest.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ class MongoRefTest extends AnyFunSuite {
1919
assert(Rte.ref(_.intList(1)).rawPath == "intList.1")
2020
assert(Rte.ref(_.intMap("key")).rawPath == "intMap.key")
2121
assert(Rte.ref(_.intMap("weird.key")).rawPath == "intMap.weird\\_key")
22+
assert(Rte.ref(_.typedMap(PKey.IntKey)).rawPath == "typedMap.IntKey")
23+
assert(Rte.ref(_.typedMap(PKey.InnerKey).int).rawPath == "typedMap.InnerKey.int")
2224
assert(Rte.ref(_.inner.int).rawPath == "inner.int")
2325
assert(Rte.ref(_.complex.get.apply(InnerId("foo")).apply(5).int).rawPath == "complex.foo.5.int")
2426
assert(Rte.ref(_.complex).ref(_.get).ref(_.apply(InnerId("foo"))).ref(_.apply(5)).ref(_.int).rawPath == "complex.foo.5.int")

commons-mongo/jvm/src/test/scala/com/avsystem/commons/mongo/typed/TypedMongoCollectionTest.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.avsystem.commons
22
package mongo.typed
33

4-
import com.avsystem.commons.misc.Timestamp
4+
import com.avsystem.commons.misc.{Timestamp, TypedMap}
55
import com.avsystem.commons.mongo.BsonValueInput
66
import com.mongodb.client.model.Aggregates
77
import com.mongodb.reactivestreams.client.MongoClients
@@ -51,6 +51,7 @@ class TypedMongoCollectionTest extends AnyFunSuite with ScalaFutures with Before
5151
Opt(i % 10).filter(_ % 2 == 0),
5252
List.range(0, i),
5353
Map("one" -> 1, "two" -> 2),
54+
TypedMap(PKey.IntKey -> i, PKey.InnerKey -> ir),
5455
ir,
5556
Opt(ir),
5657
List(ir),

0 commit comments

Comments
 (0)