Skip to content

Commit 8802150

Browse files
authored
Merge pull request #428 from AVSystem/mongoref-transparent
proper support for transparent wrappers in typed mongo
2 parents d97f4d6 + 022cd1a commit 8802150

File tree

7 files changed

+100
-33
lines changed

7 files changed

+100
-33
lines changed

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,17 @@ class MongoMacros(ctx: blackbox.Context) extends CodecMacroCommons(ctx) {
3333
}
3434
}
3535

36+
private def isTransparentUnwrap(prefixTpe: Type, fieldSym: Symbol): Boolean = {
37+
val sym = prefixTpe.typeSymbol
38+
sym.isClass && sym.asClass.isCaseClass && (primaryConstructorOf(prefixTpe).asMethod.paramLists match {
39+
case List(param) :: _ if param.name == fieldSym.name =>
40+
val paramTpe = fieldSym.typeSignatureIn(prefixTpe).finalResultType
41+
val wrappingTpe = getType(tq"$SerializationPkg.TransparentWrapping[$paramTpe, $prefixTpe]")
42+
inferImplicitValue(wrappingTpe) != EmptyTree
43+
case _ => false
44+
})
45+
}
46+
3647
def isOptionLike(fullTpe: Type, wrappedTpe: Type): Boolean =
3748
fullTpe != null && fullTpe != NoType && wrappedTpe != null && wrappedTpe != NoType &&
3849
c.inferImplicitValue(getType(tq"$CommonsPkg.meta.OptionLike.Aux[$fullTpe, $wrappedTpe]")) != EmptyTree
@@ -63,7 +74,9 @@ class MongoMacros(ctx: blackbox.Context) extends CodecMacroCommons(ctx) {
6374
val prefixTpe = prefix.tpe.widen
6475
val bodyTpe = body.tpe.widen
6576

66-
if (termSym.isCaseAccessor || isSealedHierarchySharedField(prefixTpe, body.symbol.asTerm))
77+
if (isTransparentUnwrap(prefixTpe, body.symbol.asTerm))
78+
q"$newPrefixRef.unwrap"
79+
else if (termSym.isCaseAccessor || isSealedHierarchySharedField(prefixTpe, body.symbol.asTerm))
6780
q"$newPrefixRef.asAdtRef.fieldRefFor[$bodyTpe](${name.decodedName.toString})"
6881
else if (name == TermName("get") && isOptionLike(prefixTpe, bodyTpe))
6982
q"$newPrefixRef.get"

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ private final class FilterDocBuilder(prefixPath: Opt[String], filterDocs: BsonAr
2626
appendToPrefix(prefix.rawPath).addOperator(MongoQueryOperator.Ne(optionLike.none, prefix.format))
2727
addImpliedFilters(prefix)
2828

29+
case MongoRef.TransparentUnwrap(prefix, _, _) =>
30+
addImpliedFilters(prefix)
31+
2932
case MongoRef.FieldRef(prefix, _, _, _) =>
3033
addImpliedFilters(prefix)
3134

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

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ sealed trait MongoFormat[T] {
3434
case union: MongoAdtFormat.UnionFormat[T] => union
3535
case _ => throw new IllegalArgumentException(
3636
"Encountered a non-union MongoFormat for an union type (sealed hierarchy) -" +
37-
"do you have any custom implicit MongoFormat for that type?"
37+
"do you have any custom implicit MongoFormat for that type?",
3838
)
3939
}
4040

@@ -44,51 +44,68 @@ sealed trait MongoFormat[T] {
4444
"Encountered a non-optional MongoFormat for an Option-like type - " +
4545
"do you have a custom implicit MongoFormat for that type?")
4646
}
47+
48+
def assumeTransparent[R]: MongoFormat.TransparentFormat[T, R] = this match {
49+
case transparent: MongoFormat.TransparentFormat[T@unchecked, R@unchecked] => transparent
50+
case _ => throw new IllegalArgumentException(
51+
"Encountered a non-transparent MongoFormat for a transparent wrapper type - " +
52+
"do you have a custom implicit MongoFormat for that type?")
53+
}
4754
}
4855
object MongoFormat extends MetadataCompanion[MongoFormat] with MongoFormatLowPriority {
4956
final case class Opaque[T](
50-
codec: GenCodec[T]
57+
codec: GenCodec[T],
5158
) extends MongoFormat[T]
5259

5360
final case class CollectionFormat[C[X] <: Iterable[X], T](
5461
codec: GenCodec[C[T]],
55-
elementFormat: MongoFormat[T]
62+
elementFormat: MongoFormat[T],
5663
) extends MongoFormat[C[T]]
5764

5865
final case class DictionaryFormat[M[X, Y] <: BMap[X, Y], K, V](
5966
codec: GenCodec[M[K, V]],
6067
keyCodec: GenKeyCodec[K],
61-
valueFormat: MongoFormat[V]
68+
valueFormat: MongoFormat[V],
6269
) extends MongoFormat[M[K, V]]
6370

6471
final case class TypedMapFormat[K[_]](
6572
codec: GenCodec[TypedMap[K]],
6673
keyCodec: GenKeyCodec[K[_]],
67-
valueFormats: MongoFormatMapping[K]
74+
valueFormats: MongoFormatMapping[K],
6875
) extends MongoFormat[TypedMap[K]]
6976

7077
final case class OptionalFormat[O, T](
7178
codec: GenCodec[O],
7279
optionLike: OptionLike.Aux[O, T],
73-
wrappedFormat: MongoFormat[T]
80+
wrappedFormat: MongoFormat[T],
7481
) extends MongoFormat[O]
7582

83+
final case class TransparentFormat[T, R](
84+
codec: GenCodec[T],
85+
wrapping: TransparentWrapping[R, T],
86+
wrappedFormat: MongoFormat[R],
87+
) extends MongoFormat[T]
88+
7689
implicit def collectionFormat[C[X] <: Iterable[X], T](
77-
implicit collectionCodec: GenCodec[C[T]], elementFormat: MongoFormat[T]
90+
implicit collectionCodec: GenCodec[C[T]], elementFormat: MongoFormat[T],
7891
): MongoFormat[C[T]] = CollectionFormat(collectionCodec, elementFormat)
7992

8093
implicit def dictionaryFormat[M[X, Y] <: BMap[X, Y], K, V](
81-
implicit mapCodec: GenCodec[M[K, V]], keyCodec: GenKeyCodec[K], valueFormat: MongoFormat[V]
94+
implicit mapCodec: GenCodec[M[K, V]], keyCodec: GenKeyCodec[K], valueFormat: MongoFormat[V],
8295
): MongoFormat[M[K, V]] = DictionaryFormat(mapCodec, keyCodec, valueFormat)
8396

8497
implicit def typedMapFormat[K[_]](
85-
implicit keyCodec: GenKeyCodec[K[_]], valueFormats: MongoFormatMapping[K]
98+
implicit keyCodec: GenKeyCodec[K[_]], valueFormats: MongoFormatMapping[K],
8699
): MongoFormat[TypedMap[K]] = TypedMapFormat[K](TypedMap.typedMapCodec, keyCodec, valueFormats)
87100

88101
implicit def optionalFormat[O, T](
89-
implicit optionLike: OptionLike.Aux[O, T], optionCodec: GenCodec[O], wrappedFormat: MongoFormat[T]
102+
implicit optionLike: OptionLike.Aux[O, T], optionCodec: GenCodec[O], wrappedFormat: MongoFormat[T],
90103
): MongoFormat[O] = OptionalFormat(optionCodec, optionLike, wrappedFormat)
91104

105+
implicit def transparentFormat[R, T](
106+
implicit codec: GenCodec[T], wrapping: TransparentWrapping[R, T], wrappedFormat: MongoFormat[R],
107+
): MongoFormat[T] = TransparentFormat(codec, wrapping, wrappedFormat)
108+
92109
implicit class collectionFormatOps[C[X] <: Iterable[X], T](private val format: MongoFormat[C[T]]) extends AnyVal {
93110
def assumeCollection: CollectionFormat[C, T] = format match {
94111
case coll: CollectionFormat[C@unchecked, T@unchecked] => coll
@@ -134,7 +151,7 @@ object MongoAdtFormat extends AdtMetadataCompanion[MongoAdtFormat] {
134151
@infer val codec: GenObjectCodec[T],
135152
@infer val dataClassTag: ClassTag[T],
136153
@reifyAnnot val flattenAnnot: flatten,
137-
@multi @adtCaseMetadata val cases: List[Case[_]]
154+
@multi @adtCaseMetadata val cases: List[Case[_]],
138155
) extends MongoAdtFormat[T] {
139156

140157
lazy val casesByClass: Map[Class[_], Case[_]] =
@@ -163,7 +180,7 @@ object MongoAdtFormat extends AdtMetadataCompanion[MongoAdtFormat] {
163180
@tailrec def loop(cases: List[Case[_]], rawName: Opt[String]): Unit = cases match {
164181
case cse :: tail =>
165182
val field = cse.getField(scalaFieldName).getOrElse(throw new NoSuchElementException(
166-
s"Field $scalaFieldName not found in at least one case class/object."
183+
s"Field $scalaFieldName not found in at least one case class/object.",
167184
))
168185
if (rawName.exists(_ != field.info.rawName)) {
169186
throw new IllegalArgumentException(s"Field $scalaFieldName has different raw name across case classes")
@@ -207,7 +224,7 @@ object MongoAdtFormat extends AdtMetadataCompanion[MongoAdtFormat] {
207224
@positioned(positioned.here)
208225
final class RecordFormat[T](
209226
@composite val record: RecordCase[T],
210-
@infer val codec: GenObjectCodec[T]
227+
@infer val codec: GenObjectCodec[T],
211228
) extends MongoAdtFormat[T] {
212229
def dataClassTag: ClassTag[T] = record.classTag
213230

@@ -218,7 +235,7 @@ object MongoAdtFormat extends AdtMetadataCompanion[MongoAdtFormat] {
218235
@positioned(positioned.here)
219236
final class SingletonFormat[T](
220237
@composite val singleton: SingletonCase[T],
221-
@infer val codec: GenObjectCodec[T]
238+
@infer val codec: GenObjectCodec[T],
222239
) extends MongoAdtFormat[T] {
223240
def dataClassTag: ClassTag[T] = singleton.classTag
224241

@@ -242,7 +259,7 @@ object MongoAdtFormat extends AdtMetadataCompanion[MongoAdtFormat] {
242259
@composite val info: GenCaseInfo[T],
243260
@infer val classTag: ClassTag[T],
244261
@multi @adtParamMetadata val fields: List[Field[_]],
245-
@multi @adtCaseSealedParentMetadata val sealedParents: List[SealedParent[_]]
262+
@multi @adtCaseSealedParentMetadata val sealedParents: List[SealedParent[_]],
246263
) extends Case[T] {
247264
def asAdtFormat(codec: GenObjectCodec[T]): MongoAdtFormat[T] =
248265
new RecordFormat(this, codec)
@@ -258,7 +275,7 @@ object MongoAdtFormat extends AdtMetadataCompanion[MongoAdtFormat] {
258275

259276
def fieldRefFor[E, T0](prefix: MongoRef[E, T], scalaFieldName: String): MongoPropertyRef[E, T0] = {
260277
val field = fieldsByScalaName.getOrElse(scalaFieldName,
261-
throw new NoSuchElementException(s"Field $scalaFieldName not found")
278+
throw new NoSuchElementException(s"Field $scalaFieldName not found"),
262279
).asInstanceOf[MongoAdtFormat.Field[T0]]
263280
prefix match {
264281
case fieldRef: MongoRef.FieldRef[E, _, T] if transparentWrapper =>
@@ -274,7 +291,7 @@ object MongoAdtFormat extends AdtMetadataCompanion[MongoAdtFormat] {
274291
@composite val info: GenCaseInfo[T],
275292
@infer val classTag: ClassTag[T],
276293
@multi @adtCaseSealedParentMetadata val sealedParents: List[SealedParent[_]],
277-
@infer @checked val value: ValueOf[T]
294+
@infer @checked val value: ValueOf[T],
278295
) extends Case[T] {
279296
def asAdtFormat(codec: GenObjectCodec[T]): MongoAdtFormat[T] =
280297
new SingletonFormat(this, codec)
@@ -290,7 +307,7 @@ object MongoAdtFormat extends AdtMetadataCompanion[MongoAdtFormat] {
290307
@composite val info: GenParamInfo[T],
291308
@optional @reifyDefaultValue defaultValue: Opt[DefaultValue[T]],
292309
@optional @reifyAnnot whenAbsentAnnot: Opt[whenAbsent[T]],
293-
@infer val format: MongoFormat.Lazy[T]
310+
@infer val format: MongoFormat.Lazy[T],
294311
) extends TypedMetadata[T] {
295312
lazy val fallbackBson: Opt[BsonValue] = {
296313
if (info.optional) Opt(BsonNull.VALUE)
@@ -301,13 +318,13 @@ object MongoAdtFormat extends AdtMetadataCompanion[MongoAdtFormat] {
301318

302319
final class SealedParent[T](
303320
@composite val info: GenUnionInfo[T],
304-
@infer val classTag: ClassTag[T]
321+
@infer val classTag: ClassTag[T],
305322
) extends TypedMetadata[T]
306323
}
307324

308325
final class MongoEntityMeta[E <: BaseMongoEntity](
309326
@infer val format: MongoAdtFormat[E],
310-
@infer val idMode: EntityIdMode[E, E#IDType]
327+
@infer val idMode: EntityIdMode[E, E#IDType],
311328
) {
312329
def idRef: MongoPropertyRef[E, E#IDType] = idMode.idRef(format)
313330
}

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

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import com.avsystem.commons.misc.TypedMap
77
import com.avsystem.commons.mongo.typed.MongoPropertyRef.Separator
88
import com.avsystem.commons.mongo.{BsonValueInput, KeyEscaper}
99
import com.avsystem.commons.serialization.GenCodec.ReadFailure
10+
import com.avsystem.commons.serialization.TransparentWrapping
1011
import org.bson.{BsonDocument, BsonValue}
1112

1213
/**
@@ -192,7 +193,7 @@ sealed trait MongoPropertyRef[E, T] extends MongoRef[E, T]
192193
private def computePath[T0](
193194
onlyUpToArray: Boolean,
194195
ref: MongoPropertyRef[E, T0],
195-
acc: List[String]
196+
acc: List[String],
196197
): List[String] = ref match {
197198
case FieldRef(_: MongoToplevelRef[_, _], fieldName, _, _) =>
198199
KeyEscaper.escape(fieldName) :: acc
@@ -207,6 +208,9 @@ sealed trait MongoPropertyRef[E, T] extends MongoRef[E, T]
207208
case GetFromOptional(prefix, _, _) =>
208209
computePath(onlyUpToArray, prefix, acc)
209210

211+
case TransparentUnwrap(prefix, _, _) =>
212+
computePath(onlyUpToArray, prefix, acc)
213+
210214
case PropertySubtypeRef(prefix, _, _, _) =>
211215
computePath(onlyUpToArray, prefix, acc)
212216
}
@@ -234,6 +238,9 @@ sealed trait MongoPropertyRef[E, T] extends MongoRef[E, T]
234238
case GetFromOptional(prefix, _, _) =>
235239
prefix.extractBson(doc)
236240

241+
case TransparentUnwrap(prefix, _, _) =>
242+
prefix.extractBson(doc)
243+
237244
case PropertySubtypeRef(prefix, _, _, _) =>
238245
prefix.extractBson(doc)
239246
}
@@ -274,12 +281,23 @@ object MongoPropertyRef {
274281
MongoRef.GetFromOptional(ref, format.wrappedFormat, format.optionLike)
275282
}
276283
}
284+
285+
implicit def transparentRefOps[E, T, R](ref: MongoPropertyRef[E, T])(implicit wrapping: TransparentWrapping[R, T]): TransparentRefOps[E, T, R] =
286+
new TransparentRefOps[E, T, R](ref)
287+
288+
class TransparentRefOps[E, T, R](private val ref: MongoPropertyRef[E, T]) extends AnyVal {
289+
def unwrap: MongoPropertyRef[E, R] = {
290+
val format = ref.format.assumeTransparent[R]
291+
MongoRef.TransparentUnwrap(ref, format.wrappedFormat, format.wrapping)
292+
}
293+
}
294+
277295
}
278296

279297
object MongoRef {
280298
// Deliberately not calling this IdentityRef so that it doesn't get confused with IdRef (for database ID field)
281299
final case class RootRef[T](
282-
format: MongoAdtFormat[T]
300+
format: MongoAdtFormat[T],
283301
) extends MongoToplevelRef[T, T] {
284302
def fullRef: RootRef[T] = this
285303
def compose[P](prefix: MongoRef[P, T]): MongoRef[P, T] = prefix
@@ -289,7 +307,7 @@ object MongoRef {
289307
fullRef: RootRef[E],
290308
caseFieldName: String,
291309
caseNames: List[String],
292-
format: MongoAdtFormat[T]
310+
format: MongoAdtFormat[T],
293311
) extends MongoToplevelRef[E, T] {
294312
def compose[P](prefix: MongoRef[P, E]): MongoRef[P, T] = prefix match {
295313
case _: MongoToplevelRef[P, E] =>
@@ -304,7 +322,7 @@ object MongoRef {
304322
prefix: MongoRef[E, E0],
305323
fieldName: String,
306324
format: MongoFormat[T],
307-
fallbackBson: Opt[BsonValue]
325+
fallbackBson: Opt[BsonValue],
308326
) extends MongoPropertyRef[E, T] {
309327
def compose[P](newPrefix: MongoRef[P, E]): MongoPropertyRef[P, T] =
310328
copy(prefix = this.prefix compose newPrefix)
@@ -313,7 +331,7 @@ object MongoRef {
313331
final case class ArrayIndexRef[E, C[X] <: Iterable[X], T](
314332
prefix: MongoPropertyRef[E, C[T]],
315333
index: Int,
316-
format: MongoFormat[T]
334+
format: MongoFormat[T],
317335
) extends MongoPropertyRef[E, T] {
318336
require(index >= 0, "array index must be non-negative")
319337
def compose[P](newPrefix: MongoRef[P, E]): MongoPropertyRef[P, T] =
@@ -323,17 +341,26 @@ object MongoRef {
323341
final case class GetFromOptional[E, O, T](
324342
prefix: MongoPropertyRef[E, O],
325343
format: MongoFormat[T],
326-
optionLike: OptionLike.Aux[O, T]
344+
optionLike: OptionLike.Aux[O, T],
327345
) extends MongoPropertyRef[E, T] {
328346
def compose[P](newPrefix: MongoRef[P, E]): MongoPropertyRef[P, T] =
329347
copy(prefix = prefix compose newPrefix)
330348
}
331349

350+
final case class TransparentUnwrap[E, R, T](
351+
prefix: MongoPropertyRef[E, T],
352+
format: MongoFormat[R],
353+
transparentWrapping: TransparentWrapping[R, T],
354+
) extends MongoPropertyRef[E, R] {
355+
def compose[P](newPrefix: MongoRef[P, E]): MongoPropertyRef[P, R] =
356+
copy(prefix = prefix compose newPrefix)
357+
}
358+
332359
final case class PropertySubtypeRef[E, T0, T <: T0](
333360
prefix: MongoPropertyRef[E, T0],
334361
caseFieldName: String,
335362
caseNames: List[String],
336-
format: MongoAdtFormat[T]
363+
format: MongoAdtFormat[T],
337364
) extends MongoPropertyRef[E, T] {
338365
def compose[P](newPrefix: MongoRef[P, E]): MongoPropertyRef[P, T] =
339366
copy(prefix = prefix compose newPrefix)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class MongoRefTest extends AnyFunSuite {
2424
assert(Rte.ref(_.inner.int).rawPath == "inner.int")
2525
assert(Rte.ref(_.complex.get.apply(InnerId("foo")).apply(5).int).rawPath == "complex.foo.5.int")
2626
assert(Rte.ref(_.complex).ref(_.get).ref(_.apply(InnerId("foo"))).ref(_.apply(5)).ref(_.int).rawPath == "complex.foo.5.int")
27+
assert(Rte.ref(_.props.map("key")).rawPath == "props.key")
2728
assert(Rte.ref(_.union.str).rawPath == "union.str")
2829
assert(Rte.ref(_.union.as[CaseOne]).rawPath == "union")
2930
assert(Rte.ref(_.union.as[CaseOne].str).rawPath == "union.str")

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ class TypedMongoCollectionTest extends AnyFunSuite with ScalaFutures with Before
5656
List(ir),
5757
Map(InnerId("iid") -> ir),
5858
Opt(Map(InnerId("iid") -> List(ir))),
59+
Props(Map("foo" -> "bar")),
5960
i % 3 match {
6061
case 0 => CaseOne(s"uid$i", "ustr", i % 2 == 0)
6162
case 1 => CaseTwo(s"uid$i", "ustr", i, Rte.Example)

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

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,16 @@ case class InnerRecord(
2626
strOpt: Opt[String],
2727
@optionalParam intOpt: Opt[Int],
2828
intList: List[Int],
29-
intMap: Map[String, Int]
29+
intMap: Map[String, Int],
3030
)
3131
object InnerRecord extends MongoDataCompanion[InnerRecord] {
3232
final val Example = InnerRecord(
3333
24, "istr", Opt("istropt"), Opt.Empty, List(3, 4, 5), Map("ione" -> 1, "ithree" -> 3))
3434
}
3535

36+
case class Props(map: Map[String, String]) extends AnyVal
37+
object Props extends TransparentWrapperCompanion[Map[String, String], Props]
38+
3639
case class RecordTestEntity(
3740
id: String,
3841
int: Int,
@@ -48,14 +51,16 @@ case class RecordTestEntity(
4851
innerList: List[InnerRecord],
4952
innerMap: Map[InnerId, InnerRecord],
5053
complex: Opt[Map[InnerId, List[InnerRecord]]],
51-
@transientDefault union: UnionTestEntity = CaseOne("uid", "ustr", data = false)
54+
props: Props,
55+
@transientDefault union: UnionTestEntity = CaseOne("uid", "ustr", data = false),
5256
) extends MongoEntity[String]
5357
object RecordTestEntity extends MongoEntityCompanion[RecordTestEntity] {
5458
final val Example = RecordTestEntity(
5559
"rid", 42, "str", Timestamp.Zero, Opt("stropt"), Opt.Empty,
5660
List(1, 2, 3), Map("one" -> 1, "two" -> 2), TypedMap(PKey.IntKey -> 42, PKey.InnerKey -> InnerRecord.Example),
5761
InnerRecord.Example, Opt(InnerRecord.Example), List(InnerRecord.Example),
58-
Map(InnerId("iid") -> InnerRecord.Example), Opt(Map(InnerId("iid") -> List(InnerRecord.Example)))
62+
Map(InnerId("iid") -> InnerRecord.Example), Opt(Map(InnerId("iid") -> List(InnerRecord.Example))),
63+
Props(Map.empty),
5964
)
6065
}
6166

@@ -75,12 +80,12 @@ object TestAutoId extends ObjectIdWrapperCompanion[TestAutoId]
7580

7681
case class RecordTestAutoIdEntity(
7782
str: String,
78-
int: Int
83+
int: Int,
7984
) extends AutoIdMongoEntity[TestAutoId]
8085
object RecordTestAutoIdEntity extends MongoEntityCompanion[RecordTestAutoIdEntity]
8186

8287
case class AutoObjectIdEntity(
8388
str: String,
84-
int: Int
89+
int: Int,
8590
) extends AutoIdMongoEntity[ObjectId]
8691
object AutoObjectIdEntity extends MongoEntityCompanion[AutoObjectIdEntity]

0 commit comments

Comments
 (0)