-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
move mongo-derivation-3 to mongo-3, separating the MongoFormat implem…
…entation from mongo-core making it a fully standalone scala3 module
- Loading branch information
Showing
23 changed files
with
298 additions
and
269 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
libraryDependencies ++= Seq( | ||
"org.mongodb" % "mongodb-driver-core" % "5.1.2" // tracking http://mongodb.github.io/mongo-java-driver/ | ||
) |
22 changes: 22 additions & 0 deletions
22
mongo/mongo-3/src/main/scala/io/sphere/mongo/catsinstances/catsinstances.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package io.sphere.mongo | ||
|
||
import _root_.cats.Invariant | ||
import io.sphere.mongo.format.MongoFormat | ||
|
||
/** Cats instances for [[MongoFormat]] | ||
*/ | ||
package object catsinstances extends MongoFormatInstances | ||
|
||
trait MongoFormatInstances { | ||
implicit val catsInvariantForMongoFormat: Invariant[MongoFormat] = | ||
new MongoFormatInvariant | ||
} | ||
|
||
class MongoFormatInvariant extends Invariant[MongoFormat] { | ||
override def imap[A, B](fa: MongoFormat[A])(f: A => B)(g: B => A): MongoFormat[B] = | ||
new MongoFormat[B] { | ||
override def toMongoValue(b: B): Any = fa.toMongoValue(g(b)) | ||
override def fromMongoValue(any: Any): B = f(fa.fromMongoValue(any)) | ||
override val fieldNames: Vector[String] = fa.fieldNames | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package io.sphere.mongo | ||
|
||
import io.sphere.mongo.generic | ||
import io.sphere.mongo.format.MongoFormat | ||
|
||
def toMongo[A: MongoFormat](a: A): Any = summon[MongoFormat[A]].toMongoValue(a) | ||
|
||
def fromMongo[A: MongoFormat](any: Any): A = summon[MongoFormat[A]].fromMongoValue(any) |
152 changes: 152 additions & 0 deletions
152
mongo/mongo-3/src/main/scala/io/sphere/mongo/format/MongoFormat.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
package io.sphere.mongo.format | ||
|
||
import com.mongodb.BasicDBObject | ||
import io.sphere.mongo.generic.{AnnotationReader, Field} | ||
import org.bson.types.ObjectId | ||
|
||
import java.util.UUID | ||
import java.util.regex.Pattern | ||
import scala.deriving.Mirror | ||
|
||
object MongoNothing | ||
|
||
type SimpleMongoType = UUID | String | ObjectId | Short | Int | Long | Float | Double | Boolean | | ||
Pattern | ||
|
||
trait MongoFormat[A] extends Serializable { | ||
def toMongoValue(a: A): Any | ||
def fromMongoValue(mongoType: Any): A | ||
|
||
// /** needed JSON fields - ignored if empty */ | ||
val fieldNames: Vector[String] = MongoFormat.emptyFields | ||
|
||
def default: Option[A] = None | ||
} | ||
final class NativeMongoFormat[A] extends MongoFormat[A] { | ||
def toMongoValue(a: A): Any = a | ||
def fromMongoValue(any: Any): A = any.asInstanceOf[A] | ||
} | ||
|
||
inline def deriveMongoFormat[A](using Mirror.Of[A]): MongoFormat[A] = MongoFormat.derived | ||
|
||
object MongoFormat { | ||
inline def apply[A: MongoFormat]: MongoFormat[A] = summon | ||
|
||
private val emptyFields: Vector[String] = Vector.empty | ||
|
||
inline given derived[A](using Mirror.Of[A]): MongoFormat[A] = Derivation.derived | ||
|
||
private def addField(bson: BasicDBObject, field: Field, mongoType: Any): Unit = | ||
if (!field.ignored) | ||
mongoType match { | ||
case s: SimpleMongoType => bson.put(field.name, s) | ||
case innerBson: BasicDBObject => | ||
if (field.embedded) innerBson.entrySet().forEach(p => bson.put(p.getKey, p.getValue)) | ||
else bson.put(field.name, innerBson) | ||
case MongoNothing => | ||
} | ||
|
||
private object Derivation { | ||
|
||
import scala.compiletime.{constValue, constValueTuple, erasedValue, summonInline} | ||
|
||
inline def derived[A](using m: Mirror.Of[A]): MongoFormat[A] = | ||
inline m match { | ||
case s: Mirror.SumOf[A] => deriveTrait(s) | ||
case p: Mirror.ProductOf[A] => deriveCaseClass(p) | ||
} | ||
|
||
inline private def deriveTrait[A](mirrorOfSum: Mirror.SumOf[A]): MongoFormat[A] = | ||
new MongoFormat[A] { | ||
private val traitMetaData = AnnotationReader.readTraitMetaData[A] | ||
private val typeHintMap = traitMetaData.subtypes.collect { | ||
case (name, classMeta) if classMeta.typeHint.isDefined => | ||
name -> classMeta.typeHint.get | ||
} | ||
private val reverseTypeHintMap = typeHintMap.map((on, n) => (n, on)) | ||
private val formatters = summonFormatters[mirrorOfSum.MirroredElemTypes] | ||
private val names = constValueTuple[mirrorOfSum.MirroredElemLabels].productIterator.toVector | ||
.asInstanceOf[Vector[String]] | ||
private val formattersByTypeName = names.zip(formatters).toMap | ||
|
||
override def toMongoValue(a: A): Any = { | ||
// we never get a trait here, only classes, it's safe to assume Product | ||
val originalTypeName = a.asInstanceOf[Product].productPrefix | ||
val typeName = typeHintMap.getOrElse(originalTypeName, originalTypeName) | ||
val bson = | ||
formattersByTypeName(originalTypeName).toMongoValue(a).asInstanceOf[BasicDBObject] | ||
bson.put(traitMetaData.typeDiscriminator, typeName) | ||
bson | ||
} | ||
|
||
override def fromMongoValue(bson: Any): A = | ||
bson match { | ||
case bson: BasicDBObject => | ||
val typeName = bson.get(traitMetaData.typeDiscriminator).asInstanceOf[String] | ||
val originalTypeName = reverseTypeHintMap.getOrElse(typeName, typeName) | ||
formattersByTypeName(originalTypeName).fromMongoValue(bson).asInstanceOf[A] | ||
case x => | ||
throw new Exception(s"BsonObject is expected for a Trait subtype, instead got $x") | ||
} | ||
} | ||
|
||
inline private def deriveCaseClass[A](mirrorOfProduct: Mirror.ProductOf[A]): MongoFormat[A] = | ||
new MongoFormat[A] { | ||
private val caseClassMetaData = AnnotationReader.readCaseClassMetaData[A] | ||
private val formatters = summonFormatters[mirrorOfProduct.MirroredElemTypes] | ||
private val fieldsAndFormatters = caseClassMetaData.fields.zip(formatters) | ||
|
||
override val fieldNames: Vector[String] = fieldsAndFormatters.flatMap((field, formatter) => | ||
if (field.embedded) formatter.fieldNames :+ field.rawName | ||
else Vector(field.rawName)) | ||
|
||
override def toMongoValue(a: A): Any = { | ||
val bson = new BasicDBObject() | ||
val values = a.asInstanceOf[Product].productIterator | ||
formatters.zip(values).zip(caseClassMetaData.fields).foreach { | ||
case ((format, value), field) => | ||
addField(bson, field, format.toMongoValue(value)) | ||
} | ||
bson | ||
} | ||
|
||
override def fromMongoValue(mongoType: Any): A = | ||
mongoType match { | ||
case bson: BasicDBObject => | ||
val fields = fieldsAndFormatters | ||
.map { case (field, format) => | ||
def defaultValue = field.defaultArgument.orElse(format.default) | ||
|
||
if (field.ignored) | ||
defaultValue.getOrElse { | ||
throw new Exception( | ||
s"Missing default parameter value for ignored field `${field.name}` on deserialization.") | ||
} | ||
else if (field.embedded) format.fromMongoValue(bson) | ||
else { | ||
val value = bson.get(field.name) | ||
if (value ne null) format.fromMongoValue(value.asInstanceOf[Any]) | ||
else | ||
defaultValue.getOrElse { | ||
throw new Exception( | ||
s"Missing required field '${field.name}' on deserialization.") | ||
} | ||
} | ||
} | ||
val tuple = Tuple.fromArray(fields.toArray) | ||
mirrorOfProduct.fromTuple(tuple.asInstanceOf[mirrorOfProduct.MirroredElemTypes]) | ||
|
||
case x => throw new Exception(s"BasicDBObject is expected for a class, instead got: $x") | ||
} | ||
} | ||
|
||
inline private def summonFormatters[T <: Tuple]: Vector[MongoFormat[Any]] = | ||
inline erasedValue[T] match { | ||
case _: EmptyTuple => Vector.empty | ||
case _: (t *: ts) => | ||
summonInline[MongoFormat[t]] | ||
.asInstanceOf[MongoFormat[Any]] +: summonFormatters[ts] | ||
} | ||
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
39 changes: 39 additions & 0 deletions
39
mongo/mongo-3/src/main/scala/io/sphere/mongo/generic/DefaultMongoFormats.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package io.sphere.mongo.generic | ||
|
||
import com.mongodb.BasicDBObject | ||
import io.sphere.mongo.format.{MongoFormat, MongoNothing, NativeMongoFormat, SimpleMongoType} | ||
|
||
object DefaultMongoFormats extends DefaultMongoFormats {} | ||
|
||
trait DefaultMongoFormats { | ||
given MongoFormat[Int] = new NativeMongoFormat[Int] | ||
|
||
given MongoFormat[String] = new NativeMongoFormat[String] | ||
|
||
given MongoFormat[Boolean] = new NativeMongoFormat[Boolean] | ||
|
||
given [A](using MongoFormat[A]): MongoFormat[Option[A]] = | ||
new MongoFormat[Option[A]] { | ||
override def toMongoValue(a: Option[A]): Any = | ||
a match { | ||
case Some(value) => summon[MongoFormat[A]].toMongoValue(value) | ||
case None => MongoNothing | ||
} | ||
|
||
override def fromMongoValue(mongoType: Any): Option[A] = { | ||
val fieldNames = summon[MongoFormat[A]].fieldNames | ||
if (mongoType == null) None | ||
else | ||
mongoType match { | ||
case s: SimpleMongoType => Some(summon[MongoFormat[A]].fromMongoValue(s)) | ||
case bson: BasicDBObject => | ||
val bsonFieldNames = bson.keySet().toArray | ||
if (fieldNames.nonEmpty && bsonFieldNames.intersect(fieldNames).isEmpty) None | ||
else Some(summon[MongoFormat[A]].fromMongoValue(bson)) | ||
case MongoNothing => None // This can't happen, but it makes the compiler happy | ||
} | ||
} | ||
|
||
override def default: Option[Option[A]] = Some(None) | ||
} | ||
} |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.