diff --git a/.github/workflows/scala.yml b/.github/workflows/scala.yml index 80fbdf1..cc23b66 100644 --- a/.github/workflows/scala.yml +++ b/.github/workflows/scala.yml @@ -20,11 +20,11 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Set up JDK 11 + - uses: actions/checkout@v4 + - name: Set up JDK 17 uses: actions/setup-java@v3 with: - java-version: '11' + java-version: '17' distribution: 'temurin' cache: 'sbt' - name: Run tests diff --git a/.scalafmt.conf b/.scalafmt.conf index 1f158a8..4e7b26a 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -30,7 +30,10 @@ rewrite.rules = [Imports] rewrite.imports.sort = ascii rewrite.imports.expand = true rewrite.imports.groups = [ - ["io.github.greenleafoss\\..*"], + ["io.github.greenleafoss.mongo.core\\..*"], + ["io.github.greenleafoss.mongo.spray\\..*"], + ["io.github.greenleafoss.mongo.play\\..*"], + ["io.github.greenleafoss.mongo.circe\\..*"], ["org.mongodb\\..*"], ["org.bson\\..*"], ["java\\..*"], diff --git a/README.md b/README.md index 06ad2da..d635224 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,11 @@ [![green-leaf-mongo-core](https://index.scala-lang.org/greenleafoss/green-leaf-mongo/green-leaf-mongo-core/latest-by-scala-version.svg)](https://index.scala-lang.org/greenleafoss/green-leaf-mongo/green-leaf-mongo-core) [![green-leaf-mongo-spray](https://index.scala-lang.org/greenleafoss/green-leaf-mongo/green-leaf-mongo-spray/latest-by-scala-version.svg)](https://index.scala-lang.org/greenleafoss/green-leaf-mongo/green-leaf-mongo-spray) [![green-leaf-mongo-play](https://index.scala-lang.org/greenleafoss/green-leaf-mongo/green-leaf-mongo-play/latest-by-scala-version.svg)](https://index.scala-lang.org/greenleafoss/green-leaf-mongo/green-leaf-mongo-play) +[![green-leaf-mongo-circe](https://index.scala-lang.org/greenleafoss/green-leaf-mongo/green-leaf-mongo-circe/latest-by-scala-version.svg)](https://index.scala-lang.org/greenleafoss/green-leaf-mongo/green-leaf-mongo-circe) ## Short description -This extension created on top of official [MongoDB Scala Driver](https://mongodb.github.io/mongo-scala-driver) and allows to fully utilize [Spray JSON](https://github.com/spray/spray-json) or [Play JSON](https://github.com/playframework/play-json) to represent bidirectional serialization for case classes in BSON, as well as flexible DSL for [MongoDB query operators](https://www.mongodb.com/docs/manual/reference/operator/query/), documents and collections. +This extension created on top of official [MongoDB Scala Driver](https://mongodb.github.io/mongo-scala-driver) and allows to fully utilize [Spray JSON](https://github.com/spray/spray-json), [Play JSON](https://github.com/playframework/play-json) or [Circe JSON](https://circe.github.io/circe/) to represent bidirectional serialization for case classes in BSON, as well as flexible DSL for [MongoDB query operators](https://www.mongodb.com/docs/manual/reference/operator/query/), documents and collections. It was introduced in 2019 - [Andrii Lashchenko at #ScalaUA - Spray JSON and MongoDB Queries: Insights and Simple Tricks ](https://www.youtube.com/watch?v=NBgKkQtydAo) @@ -25,10 +26,13 @@ Related slides available at https://www.slideshare.net/lashchenko/spray-json-and // `green-leaf-mongo-core` can be used if you want to create your own extension // https://mvnrepository.com/artifact/io.github.greenleafoss/green-leaf-mongo-spray -libraryDependencies += "io.github.greenleafoss" %% "green-leaf-mongo-spray" % "3.0" +libraryDependencies += "io.github.greenleafoss" %% "green-leaf-mongo-spray" % "3.1" // https://mvnrepository.com/artifact/io.github.greenleafoss/green-leaf-mongo-play -libraryDependencies += "io.github.greenleafoss" %% "green-leaf-mongo-play" % "3.0" +libraryDependencies += "io.github.greenleafoss" %% "green-leaf-mongo-play" % "3.1" + +// https://mvnrepository.com/artifact/io.github.greenleafoss/green-leaf-mongo-circe +libraryDependencies += "io.github.greenleafoss" %% "green-leaf-mongo-circe" % "3.1" ``` ## JSON and BSON protocols @@ -36,34 +40,71 @@ libraryDependencies += "io.github.greenleafoss" %% "green-leaf-mongo-play" % "3. `GreenLeafMongoJsonBasicFormats` based on DefaultJsonProtocol from Spray JSON and allows to override predefined JsonFormats to make possible use custom serialization in BSON format. This trait also includes a few additional JsonFormats for _LocalDate_, _LocalDateTime_, _ZonedDateTime_, _ObjectId_, _scala Enumeration_ and _UUID_. -`PlayJsonProtocol` is a related extension for the Play JSON library and `SprayJsonProtocol` for the Spray JSON library. +`CirceJsonProtocol` is a related extension for the Circe JSON library, `PlayJsonProtocol` is for the Play JSON library and `SprayJsonProtocol` for the Spray JSON library. -`SprayBsonProtocol`/`PlayBsonProtocol` extends related JsonProtocols and overrides _Int_, _Long_, _BigDecimal_, _LocalDate_, _LocalDateTime_, _ZonedDateTime_, _ObjectId_, _scala Enumeration_, _UUID_ and _Regex_ JSON formats to represent them in related BSON (MongoDB Extended JSON V2) formats https://www.mongodb.com/docs/manual/reference/mongodb-extended-json/#mongodb-extended-json-v2-usage. +`SprayBsonProtocol`/`PlayBsonProtocol`/`CirceBsonProtocol` extends related JsonProtocols and overrides _Int_, _Long_, _BigDecimal_, _LocalDate_, _LocalDateTime_, _ZonedDateTime_, _ObjectId_, _scala Enumeration_, _UUID_ and _Regex_ JSON formats to represent them in related BSON (MongoDB Extended JSON V2) formats https://www.mongodb.com/docs/manual/reference/mongodb-extended-json/#mongodb-extended-json-v2-usage. These base protocols allow to simply (de)serialize this instance to and from both JSON and BSON the same way as in Spray JSON: ```scala 3 // MODEL -case class Test(_id: ObjectId, i: Int, l: Long, b: Boolean, zdt: ZonedDateTime) +final case class Test(_id: ObjectId, i: Int, l: Long, b: Boolean, zdt: ZonedDateTime) // JSON trait TestJsonProtocol extends SprayJsonProtocol: - given testJsonFormat = jsonFormat5(Test) + given testJsonFormat: JsonFormat[Test] = jsonFormat5(Test) object TestJsonProtocol extends TestJsonProtocol // BSON object TestBsonProtocol extends TestJsonProtocol with SprayBsonProtocol ``` +or Play JSON +```scala 3 +// MODEL +final case class Test(_id: ObjectId, i: Int, l: Long, b: Boolean, zdt: ZonedDateTime) + +// JSON +trait TestJsonProtocol extends PlayJsonProtocol: + given testJsonFormat: Format[Test] = Json.format[Test] + +object TestJsonProtocol extends TestJsonProtocol + +// BSON +object TestBsonProtocol extends TestJsonProtocol with PlayBsonProtocol +``` +or Circe +```scala 3 +// MODEL +final case class Test(_id: ObjectId, i: Int, l: Long, b: Boolean, zdt: ZonedDateTime) + +// JSON +trait TestJsonProtocol extends CirceJsonProtocol: + given testJsonFormat: Codec[Test] = deriveCodec + +object TestJsonProtocol extends TestJsonProtocol + +// BSON +object TestBsonProtocol extends TestJsonProtocol with CirceBsonProtocol +``` Once protocols defined, we can make instance of Test case class and use TestJsonProtocol to print related JSON: ```scala -val obj = Test(new ObjectId("5c72b799306e355b83ef3c86"), 1, 0x123456789L, true, "1970-01-01") +Test(new ObjectId("5c72b799306e355b83ef3c86"), 1, 0x123456789L, true, "1970-01-01T00:00:00Z".parseZonedDateTime) +// Spray JSON import TestJsonProtocol.given println(obj.toJson.prettyPrint) + +// Play JSON +import TestJsonProtocol.given +println(Json.prettyPrint(Json.toJson(obj))) + +// Circe JSON +import TestJsonProtocol.given +println(obj.asJson) ``` Output in this case will be: -```js +```json { "_id": "5c72b799306e355b83ef3c86", "i": 1, @@ -76,14 +117,23 @@ Output in this case will be: Changing single line of import `TestJsonProtocol` to `TestBsonProtocol` allows us to (de)serialize this instance to and from BSON: ```scala -val obj = Test(new ObjectId("5c72b799306e355b83ef3c86"), 1, 0x123456789L, true, "1970-01-01") +Test(new ObjectId("5c72b799306e355b83ef3c86"), 1, 0x123456789L, true, "1970-01-01T00:00:00Z".parseZonedDateTime) +// Spray JSON import TestBsonProtocol.given println(obj.toJson.prettyPrint) + +// Play JSON +import TestBsonProtocol.given +println(Json.prettyPrint(Json.toJson(obj))) + +// Circe JSON +import TestBsonProtocol.given +println(obj.asJson) ``` Output in this case will be: -```js +```json { "_id": { "$oid": "5c72b799306e355b83ef3c86" @@ -103,27 +153,57 @@ Output in this case will be: } ``` -More examples available in implementation of **JsonProtocolSpec**/**BsonProtocolSpec** in Spray and Play project modules. +More examples available in implementation of **JsonProtocolSpec**/**BsonProtocolSpec** in Spray, Play and Circe project modules. ## GreenLeafMongoDsl `GreenLeafMongoFilterOps` makes it possible to write queries with a syntax that is more close to real queries in MongoDB, as was implemented in [Casbah Query DSL](http://mongodb.github.io/casbah/3.1/reference/query_dsl/). ```scala -"size" $all ("S", "M", "L") +"size" $all Seq("S", "M", "L") +// {"size": {"$all": ["S", "M", "L"]}} + "price" $eq 10 +// {"price": {"$eq": 10}} + "price" $gt 10 +// {"price": {"$gt": 10}} + "price" $gte 10 -"size" $in ("S", "M", "L") +// {"price": {"$gte": 10}} + +"size" $in Seq("S", "M", "L") +// {"size": {"$in": ["S", "M", "L"]}} + "price" $lt 100 +// {"price": {"$lt": 100}} + "price" $lte 100 +// {"price": {"$lte": 100}} + "price" $ne 1000 -"size" $nin ("S", "XXL") +// {"price": {"$ne": 1000}} + +"size" $nin Seq("S", "XXL") +// {"size": {"$nin": ["S", "XXL"]}} + $or( "price" $lt 5, "price" $gt 1, "promotion" $eq true ) +// {"$or": [{"price": {"$lt": 5}}, {"price": {"$gt": 1}}, {"promotion": {"$eq": true}}]} + $and( "price" $lt 5, "price" $gt 1, "stock" $gte 1 ) -"price" $not { $gte (5.1) } +// {"$and": [{"price": {"$lt": 5}}, {"price": {"$gt": 1}}, {"stock": {"$gte": 1}}]} + +"price" $not { $gte (5.1) } +// {"price": {"$not": {"$gte": 5.1}}} + $nor( "price" $eq 1.99 , "qty" $lt 20, "sale" $eq true ) +// {"$nor": [{"price": {"$eq": 1.99}}, {"qty": {"$lt": 20}}, {"sale": {"$eq": true}}]} + "qty" $exists true +// {"qty": {"$exists": true}} + "results" $elemMatch $and("product" $eq "xyz", "score" $gte 8) +// {"results": {"$elemMatch": {"$and": [{"product": {"$eq": "xyz"}}, {"score": {"$gte": 8}}]}}} + // ... ``` diff --git a/build.sbt b/build.sbt index ea8f9c3..6d803e3 100644 --- a/build.sbt +++ b/build.sbt @@ -3,11 +3,11 @@ // ************************************************** lazy val commonSettings = Seq( - version := "3.0", + version := "3.1", description := """ |This extension created on top of official MongoDB Scala Driver. - |It allows to fully utilize Spray JSON and represents bidirectional serialization for case classes in BSON, + |It allows to fully utilize Spray, Play or Circe JSON and represents bidirectional serialization for case classes in BSON, |as well as flexible DSL for MongoDB query operators, documents and collections. |""".stripMargin, licenses := List("Apache 2" -> new URL("https://www.apache.org/licenses/LICENSE-2.0.txt")), @@ -50,11 +50,11 @@ lazy val commonSettings = Seq( scalafmtOnCompile := true, Test / parallelExecution := false, Test / fork := true, - libraryDependencies += "org.slf4j" % "slf4j-api" % "2.0.7", - libraryDependencies += "org.slf4j" % "slf4j-simple" % "2.0.7" % Test, + libraryDependencies += "org.slf4j" % "slf4j-api" % "2.0.12", + libraryDependencies += "org.slf4j" % "slf4j-simple" % "2.0.13" % Test, libraryDependencies += "de.flapdoodle.embed" % "de.flapdoodle.embed.mongo" % "3.5.4" % Test, - libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.15" % Test, - libraryDependencies += "org.immutables" % "value" % "2.9.2" % Test + libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.18" % Test, + libraryDependencies += "org.immutables" % "value" % "2.10.1" % Test ) // ************************************************** @@ -64,7 +64,7 @@ lazy val commonSettings = Seq( lazy val core = (project in file("core")) .settings(name := "green-leaf-mongo-core") .settings(commonSettings) - .settings(libraryDependencies += "org.mongodb.scala" %% "mongo-scala-driver" % "4.9.0" cross CrossVersion.for3Use2_13) + .settings(libraryDependencies += "org.mongodb.scala" %% "mongo-scala-driver" % "5.1.2" cross CrossVersion.for3Use2_13) lazy val spray = (project in file("spray")) .settings(name := "green-leaf-mongo-spray") @@ -75,10 +75,18 @@ lazy val spray = (project in file("spray")) lazy val play = (project in file("play")) .settings(name := "green-leaf-mongo-play") .settings(commonSettings) - .settings(libraryDependencies += "com.typesafe.play" %% "play-json" % "2.10.1") + .settings(libraryDependencies += "com.typesafe.play" %% "play-json" % "2.10.5") .dependsOn(core % "compile->compile;test->test") -lazy val extensions: Seq[ProjectReference] = List[ProjectReference](spray, play) +lazy val circe = (project in file("circe")) + .settings(name := "green-leaf-mongo-circe") + .settings(commonSettings) + .settings(libraryDependencies += "io.circe" %% "circe-core" % "0.14.7") + .settings(libraryDependencies += "io.circe" %% "circe-generic" % "0.14.7") + .settings(libraryDependencies += "io.circe" %% "circe-parser" % "0.14.7") + .dependsOn(core % "compile->compile;test->test") + +lazy val extensions: Seq[ProjectReference] = List[ProjectReference](spray, play, circe) lazy val aggregated: Seq[ProjectReference] = List[ProjectReference](core) ++ extensions lazy val root = (project in file(".")) diff --git a/circe/src/main/scala/io/github/greenleafoss/mongo/circe/bson/CirceBsonProtocol.scala b/circe/src/main/scala/io/github/greenleafoss/mongo/circe/bson/CirceBsonProtocol.scala new file mode 100644 index 0000000..b0af053 --- /dev/null +++ b/circe/src/main/scala/io/github/greenleafoss/mongo/circe/bson/CirceBsonProtocol.scala @@ -0,0 +1,179 @@ +package io.github.greenleafoss.mongo.circe.bson + +import io.github.greenleafoss.mongo.core.util.ZonedDateTimeOps.* + +import io.github.greenleafoss.mongo.circe.json.CirceJsonProtocol + +import org.mongodb.scala.bson.ObjectId + +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.ZoneOffset +import java.time.ZonedDateTime + +import scala.jdk.CollectionConverters.* +import scala.util +import scala.util.matching.Regex + +import io.circe.Decoder +import io.circe.Decoder.Result +import io.circe.HCursor +import io.circe.Json as CirceJson + +trait CirceBsonProtocol extends CirceJsonProtocol: + + /** + * https://www.mongodb.com/docs/manual/reference/mongodb-extended-json/#mongodb-bsontype-Int32 + * {{{ + * { "$numberInt": "" } + * }}} + * */ + override protected def formatInt: JsonFormat[Int] = new JsonFormat[Int]: + override def apply(c: HCursor): Result[Int] = + c.get[String]($numberInt).map(_.toInt).orElse(Decoder.decodeInt(c)) + + override def apply(a: Int): Json = + CirceJson.obj($numberInt -> CirceJson.fromString(a.toString)) + + /** + * https://www.mongodb.com/docs/manual/reference/mongodb-extended-json/#mongodb-bsontype-Int64 + * {{{ + * { "$numberLong": "" } + * }}} + */ + override protected def formatLong: JsonFormat[Long] = new JsonFormat[Long]: + override def apply(c: HCursor): Result[Long] = { + c.get[String]($numberLong) + .map(_.toLong) + .orElse(IntJsonFormat(c).map(_.toLong)) + .orElse(Decoder.decodeLong(c)) + } + + override def apply(a: Long): Json = + CirceJson.obj($numberLong -> CirceJson.fromString(a.toString)) + + /** + * https://www.mongodb.com/docs/manual/reference/mongodb-extended-json/#mongodb-bsontype-Double + * {{{ + * {"$numberDouble": "" } + * }}} + */ + override protected def formatFloat: JsonFormat[Float] = new JsonFormat[Float]: + override def apply(c: HCursor): Result[Float] = + c.get[String]($numberDouble).map(java.lang.Float.valueOf).map(Float.unbox).orElse(Decoder.decodeFloat(c)) + + override def apply(a: Float): Json = + CirceJson.obj($numberDouble -> CirceJson.fromString(a.toString)) + + /** + * https://www.mongodb.com/docs/manual/reference/mongodb-extended-json/#mongodb-bsontype-Double + * {{{ + * {"$numberDouble": "" } + * }}} + */ + override protected def formatDouble: JsonFormat[Double] = new JsonFormat[Double]: + override def apply(c: HCursor): Result[Double] = + c.get[String]($numberDouble) + .map(java.lang.Double.valueOf) + .map(Double.unbox) + .orElse(Decoder.decodeDouble(c)) + + override def apply(a: Double): Json = + CirceJson.obj($numberDouble -> CirceJson.fromString(a.toString)) + + /** + * https://www.mongodb.com/docs/manual/reference/mongodb-extended-json/#mongodb-bsontype-Decimal128 + * {{{ + * { "$numberDecimal": "" } + * }}} + */ + override protected def formatBigDecimal: JsonFormat[BigDecimal] = new JsonFormat[BigDecimal]: + override def apply(c: HCursor): Result[BigDecimal] = + c.get[String]($numberDecimal) + .map(BigDecimal.apply) + .orElse(IntJsonFormat.tryDecode(c.downField($numberInt)).map(BigDecimal.apply)) + .orElse(LongJsonFormat.tryDecode(c.downField($numberLong)).map(BigDecimal.apply)) + .orElse(DoubleJsonFormat.tryDecode(c.downField($numberDouble)).map(BigDecimal.apply)) + .orElse(Decoder.decodeBigDecimal(c)) + + override def apply(a: BigDecimal): Json = CirceJson.obj($numberDecimal -> CirceJson.fromString(a.toString)) + + /** + * https://www.mongodb.com/docs/upcoming/reference/mongodb-extended-json/#mongodb-bsontype-Date + * {{{ + * {"$date": {"$numberLong": ""}} + * }}} + */ + override protected def formatZonedDateTime: JsonFormat[ZonedDateTime] = new JsonFormat[ZonedDateTime]: + override def apply(c: HCursor): Result[ZonedDateTime] = + c.downField($date) + .get[String]($numberLong) + .map(_.toLong.asZonedDateTime()) + .orElse(Decoder.decodeZonedDateTime(c)) + + override def apply(a: ZonedDateTime): CirceJson = + CirceJson.obj($date -> LongJsonFormat(a.toEpochMilli)) + + /** + * https://www.mongodb.com/docs/upcoming/reference/mongodb-extended-json/#mongodb-bsontype-Date + * {{{ + * {"$date": {"$numberLong": ""}} + * }}} + */ + override protected def formatLocalDateTime: JsonFormat[LocalDateTime] = new JsonFormat[LocalDateTime]: + override def apply(c: HCursor): Result[LocalDateTime] = + ZonedDateTimeJsonFormat(c).map(LocalDateTime.from) + + override def apply(a: LocalDateTime): CirceJson = + ZonedDateTimeJsonFormat(ZonedDateTime.from(a.atZone(ZoneOffset.UTC))) + + /** + * https://www.mongodb.com/docs/upcoming/reference/mongodb-extended-json/#mongodb-bsontype-Date + * {{{ + * {"$date": {"$numberLong": ""}} + * }}} + */ + override protected def formatLocalDate: JsonFormat[LocalDate] = new JsonFormat[LocalDate]: + override def apply(c: HCursor): Result[LocalDate] = + LocalDateTimeJsonFormat(c).map(LocalDate.from) + + override def apply(a: LocalDate): CirceJson = + LocalDateTimeJsonFormat(a.atStartOfDay()) + + /** + * https://www.mongodb.com/docs/manual/reference/mongodb-extended-json/#mongodb-bsontype-ObjectId + * {{{ + * { "$oid": "" } + * }}} + */ + override protected def formatObjectId: JsonFormat[ObjectId] = new JsonFormat[ObjectId]: + override def apply(c: HCursor): Result[ObjectId] = + c.get[String]($oid).map(oid => new ObjectId(oid)) + + override def apply(a: ObjectId): CirceJson = + CirceJson.obj($oid -> CirceJson.fromString(a.toString)) + + /** + * https://www.mongodb.com/docs/manual/reference/mongodb-extended-json/#mongodb-bsontype-Regular-Expression + * {{{ + * { + * "$regularExpression": { + * "pattern": "", + * "options": "" + * } + * } + * }}} + */ + given RegexBsonFormat: JsonFormat[Regex] = new JsonFormat[Regex]: + override def apply(c: HCursor): Result[Regex] = + c.downField($regularExpression).get[String]("pattern").map(_.r) + + override def apply(a: Regex): CirceJson = + CirceJson.obj( + $regularExpression -> CirceJson.obj( + "pattern" -> CirceJson.fromString(a.toString), + "options" -> CirceJson.fromString("") + ) + ) + +object CirceBsonProtocol extends CirceBsonProtocol diff --git a/circe/src/main/scala/io/github/greenleafoss/mongo/circe/dao/CirceMongoDao.scala b/circe/src/main/scala/io/github/greenleafoss/mongo/circe/dao/CirceMongoDao.scala new file mode 100644 index 0000000..ecb46b8 --- /dev/null +++ b/circe/src/main/scala/io/github/greenleafoss/mongo/circe/dao/CirceMongoDao.scala @@ -0,0 +1,14 @@ +package io.github.greenleafoss.mongo.circe.dao + +import io.github.greenleafoss.mongo.core.dao.GreenLeafMongoDao + +import io.github.greenleafoss.mongo.circe.util.CirceJsonBsonOps + +import scala.concurrent.ExecutionContext + +trait CirceMongoDao[Id, E]( + using + override protected val ec: ExecutionContext) + extends GreenLeafMongoDao[Id, E] + with CirceMongoDaoProtocol[Id, E] + with CirceJsonBsonOps diff --git a/circe/src/main/scala/io/github/greenleafoss/mongo/circe/dao/CirceMongoDaoProtocol.scala b/circe/src/main/scala/io/github/greenleafoss/mongo/circe/dao/CirceMongoDaoProtocol.scala new file mode 100644 index 0000000..c0b8870 --- /dev/null +++ b/circe/src/main/scala/io/github/greenleafoss/mongo/circe/dao/CirceMongoDaoProtocol.scala @@ -0,0 +1,8 @@ +package io.github.greenleafoss.mongo.circe.dao + +import io.github.greenleafoss.mongo.core.dao.GreenLeafMongoDaoProtocol + +import io.github.greenleafoss.mongo.circe.bson.CirceBsonProtocol +import io.github.greenleafoss.mongo.circe.util.CirceJsonBsonOps + +trait CirceMongoDaoProtocol[Id, E] extends GreenLeafMongoDaoProtocol[Id, E] with CirceBsonProtocol with CirceJsonBsonOps diff --git a/circe/src/main/scala/io/github/greenleafoss/mongo/circe/dao/CirceMongoDaoProtocolObjectId.scala b/circe/src/main/scala/io/github/greenleafoss/mongo/circe/dao/CirceMongoDaoProtocolObjectId.scala new file mode 100644 index 0000000..4722c58 --- /dev/null +++ b/circe/src/main/scala/io/github/greenleafoss/mongo/circe/dao/CirceMongoDaoProtocolObjectId.scala @@ -0,0 +1,8 @@ +package io.github.greenleafoss.mongo.circe.dao + +import io.github.greenleafoss.mongo.circe.util.CirceJsonBsonOps.JsonFormat + +import org.mongodb.scala.bson.ObjectId + +trait CirceMongoDaoProtocolObjectId[E] extends CirceMongoDaoProtocol[ObjectId, E]: + override protected given idFormat: JsonFormat[ObjectId] = ObjectIdJsonFormat diff --git a/circe/src/main/scala/io/github/greenleafoss/mongo/circe/json/CirceJsonProtocol.scala b/circe/src/main/scala/io/github/greenleafoss/mongo/circe/json/CirceJsonProtocol.scala new file mode 100644 index 0000000..529f498 --- /dev/null +++ b/circe/src/main/scala/io/github/greenleafoss/mongo/circe/json/CirceJsonProtocol.scala @@ -0,0 +1,80 @@ +package io.github.greenleafoss.mongo.circe.json + +import io.github.greenleafoss.mongo.core.json.GreenLeafMongoJsonBasicFormats +import io.github.greenleafoss.mongo.core.util.ZonedDateTimeOps + +import io.github.greenleafoss.mongo.circe.util.CirceJsonBsonOps + +import org.mongodb.scala.bson.ObjectId + +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.ZonedDateTime +import java.util.UUID + +import io.circe.Codec +import io.circe.Decoder +import io.circe.Decoder.Result +import io.circe.Encoder +import io.circe.HCursor + +trait CirceJsonProtocol extends GreenLeafMongoJsonBasicFormats with CirceJsonBsonOps: + + override protected def formatInt: JsonFormat[Int] = + Codec.from(Decoder.decodeInt, Encoder.encodeInt) + + override protected def formatLong: JsonFormat[Long] = + Codec.from(Decoder.decodeLong, Encoder.encodeLong) + + override protected def formatFloat: JsonFormat[Float] = + Codec.from(Decoder.decodeFloat, Encoder.encodeFloat) + + override protected def formatDouble: JsonFormat[Double] = + Codec.from(Decoder.decodeDouble, Encoder.encodeDouble) + + override protected def formatByte: JsonFormat[Byte] = + Codec.from(Decoder.decodeByte, Encoder.encodeByte) + + override protected def formatShort: JsonFormat[Short] = + Codec.from(Decoder.decodeShort, Encoder.encodeShort) + + override protected def formatBigDecimal: JsonFormat[BigDecimal] = + Codec.from(Decoder.decodeBigDecimal, Encoder.encodeBigDecimal) + + override protected def formatBigInt: JsonFormat[BigInt] = + Codec.from(Decoder.decodeBigInt, Encoder.encodeBigInt) + + override protected def formatUnit: JsonFormat[Unit] = + Codec.from(Decoder.decodeUnit, Encoder.encodeUnit) + + override protected def formatBoolean: JsonFormat[Boolean] = + Codec.from(Decoder.decodeBoolean, Encoder.encodeBoolean) + + override protected def formatChar: JsonFormat[Char] = + Codec.from(Decoder.decodeChar, Encoder.encodeChar) + + override protected def formatString: JsonFormat[String] = + Codec.from(Decoder.decodeString, Encoder.encodeString) + + override protected def formatSymbol: JsonFormat[Symbol] = ??? + + override protected def formatLocalDate: JsonFormat[LocalDate] = + Codec.from(Decoder.decodeLocalDate, Encoder.encodeLocalDate) + + override protected def formatLocalDateTime: JsonFormat[LocalDateTime] = + Codec.from(Decoder.decodeLocalDateTime, Encoder.encodeLocalDateTime) + + override protected def formatZonedDateTime: JsonFormat[ZonedDateTime] = + Codec.from(Decoder.decodeZonedDateTime, Encoder.encodeZonedDateTime) + + override protected def formatUUID: JsonFormat[UUID] = + Codec.from(Decoder.decodeUUID, Encoder.encodeUUID) + + override protected def formatObjectId: JsonFormat[ObjectId] = new JsonFormat[ObjectId]: + override def apply(c: HCursor): Result[ObjectId] = + StringJsonFormat(c).map(oid => new ObjectId(oid)) + + override def apply(a: ObjectId): Json = + StringJsonFormat(a.toString) + +object CirceJsonProtocol extends CirceJsonProtocol diff --git a/circe/src/main/scala/io/github/greenleafoss/mongo/circe/util/CirceJsonBsonOps.scala b/circe/src/main/scala/io/github/greenleafoss/mongo/circe/util/CirceJsonBsonOps.scala new file mode 100644 index 0000000..e0d1c96 --- /dev/null +++ b/circe/src/main/scala/io/github/greenleafoss/mongo/circe/util/CirceJsonBsonOps.scala @@ -0,0 +1,95 @@ +package io.github.greenleafoss.mongo.circe.util + +import io.github.greenleafoss.mongo.core.util.GreenLeafJsonBsonOps +import io.github.greenleafoss.mongo.core.util.GreenLeafJsonBsonOps.JsonBsonErr +import io.github.greenleafoss.mongo.core.util.ZonedDateTimeOps.* + +import org.mongodb.scala.bson.BsonArray +import org.mongodb.scala.bson.BsonBoolean +import org.mongodb.scala.bson.BsonDateTime +import org.mongodb.scala.bson.BsonDecimal128 +import org.mongodb.scala.bson.BsonDocument +import org.mongodb.scala.bson.BsonDouble +import org.mongodb.scala.bson.BsonInt32 +import org.mongodb.scala.bson.BsonInt64 +import org.mongodb.scala.bson.BsonNull +import org.mongodb.scala.bson.BsonObjectId +import org.mongodb.scala.bson.BsonString +import org.mongodb.scala.bson.BsonValue +import org.mongodb.scala.bson.ObjectId + +import java.time.ZonedDateTime + +import scala.jdk.CollectionConverters.* +import scala.language.implicitConversions + +import io.circe.* +import io.circe.Json as CirceJson +import io.circe.parser.* +import io.circe.syntax.* + +trait CirceJsonBsonOps extends GreenLeafJsonBsonOps: + + // ************************************************** + // FORMATS + // ************************************************** + + override type JsonFormat[E] = Codec[E] + override type Json = CirceJson + + // ************************************************** + // JSON + // ************************************************** + + extension (string: String) + override def parseJson: Json = parse(string) match + case Right(json) => json + case Left(err) => throw err + + extension [E: JsonFormat](e: E) override def convertToJson: Json = e.asJson + + extension (json: Json) + override def convertTo[E: JsonFormat]: E = + decode[E](json.noSpacesSortKeys) match + case Right(res) => res + case Left(err) => throw err + + import io.github.greenleafoss.mongo.circe.bson.CirceBsonProtocol.* + import io.github.greenleafoss.mongo.circe.bson.CirceBsonProtocol.given + + override protected def convertJsonToBson(json: Json): BsonValue = json match + case x if x.isObject => + x.asObject match + case Some(x: JsonObject) if x.contains($oid) => BsonObjectId(json.convertTo[ObjectId]) + case Some(x: JsonObject) if x.contains($date) => BsonDateTime(json.convertTo[ZonedDateTime].toEpochMilli) + case Some(x: JsonObject) if x.contains($numberDecimal) => BsonDecimal128(json.convertTo[BigDecimal]) + case Some(x: JsonObject) if x.contains($numberDouble) => BsonDouble(json.convertTo[Double]) + case Some(x: JsonObject) if x.contains($numberLong) => BsonInt64(json.convertTo[Long]) + case Some(x: JsonObject) if x.contains($numberInt) => BsonInt32(json.convertTo[Int]) + case Some(x: JsonObject) => BsonDocument(x.toMap.map { case (k, v) => k -> convertJsonToBson(v) }) + case _ => throw JsonBsonErr(s"Unknown input in JSON to BSON: $json") + case x if x.isArray => BsonArray.fromIterable(x.asArray.getOrElse(Vector.empty).map(convertJsonToBson)) + case x if x.isBoolean => BsonBoolean(x.asBoolean.getOrElse(false)) + case x if x.isString => BsonString(x.asString.getOrElse("")) + case x if x.isNull => BsonNull() + case _ => throw JsonBsonErr(s"Unknown input in JSON to BSON: $json") + + // ************************************************** + // BSON + // ************************************************** + + override protected def convertBsonToJson(bson: BsonValue): Json = bson match + case x: BsonDocument => x.toJson(jws).parseJson + case x: BsonArray => CirceJson.fromValues(x.getValues.iterator().asScala.map(convertBsonToJson).toVector) + case x: BsonDateTime => x.getValue.asZonedDateTime().convertToJson + case x: BsonString => x.getValue.convertToJson + case x: BsonBoolean => x.getValue.convertToJson + case x: BsonObjectId => x.getValue.convertToJson + case x: BsonInt32 => x.getValue.convertToJson + case x: BsonInt64 => x.getValue.convertToJson + case x: BsonDouble => x.getValue.convertToJson + case x: BsonDecimal128 => BigDecimal(x.decimal128Value().bigDecimalValue()).convertToJson + case _: BsonNull => CirceJson.Null + case _ => throw JsonBsonErr(s"Unknown input in BSON to JSON: $bson") + +object CirceJsonBsonOps extends CirceJsonBsonOps diff --git a/circe/src/test/scala/io/github/greenleafoss/mongo/circe/bson/CirceBsonFormatSpec.scala b/circe/src/test/scala/io/github/greenleafoss/mongo/circe/bson/CirceBsonFormatSpec.scala new file mode 100644 index 0000000..5d49b42 --- /dev/null +++ b/circe/src/test/scala/io/github/greenleafoss/mongo/circe/bson/CirceBsonFormatSpec.scala @@ -0,0 +1,5 @@ +package io.github.greenleafoss.mongo.circe.bson + +import io.github.greenleafoss.mongo.core.bson.BsonProtocolSpec + +class CirceBsonFormatSpec extends BsonProtocolSpec with CirceModelBsonProtocol diff --git a/circe/src/test/scala/io/github/greenleafoss/mongo/circe/bson/CirceModelBsonProtocol.scala b/circe/src/test/scala/io/github/greenleafoss/mongo/circe/bson/CirceModelBsonProtocol.scala new file mode 100644 index 0000000..2895d15 --- /dev/null +++ b/circe/src/test/scala/io/github/greenleafoss/mongo/circe/bson/CirceModelBsonProtocol.scala @@ -0,0 +1,5 @@ +package io.github.greenleafoss.mongo.circe.bson + +import io.github.greenleafoss.mongo.circe.json.CirceModelJsonProtocol + +trait CirceModelBsonProtocol extends CirceModelJsonProtocol with CirceBsonProtocol diff --git a/circe/src/test/scala/io/github/greenleafoss/mongo/circe/dao/CirceEntityWithIdAsFieldDaoSpec.scala b/circe/src/test/scala/io/github/greenleafoss/mongo/circe/dao/CirceEntityWithIdAsFieldDaoSpec.scala new file mode 100644 index 0000000..3dfba58 --- /dev/null +++ b/circe/src/test/scala/io/github/greenleafoss/mongo/circe/dao/CirceEntityWithIdAsFieldDaoSpec.scala @@ -0,0 +1,26 @@ +package io.github.greenleafoss.mongo.circe.dao + +import io.github.greenleafoss.mongo.core.dao.EntityWithIdAsFieldDaoSpec +import io.github.greenleafoss.mongo.core.dao.EntityWithIdAsFieldDaoSpec.* + +import io.circe.Codec +import io.circe.Decoder +import io.circe.Encoder + +class CirceEntityWithIdAsFieldDaoSpec extends EntityWithIdAsFieldDaoSpec: + + private trait PlayBuildingModelBsonProtocol + extends BuildingModelBsonProtocol + with CirceMongoDaoProtocol[Long, Building]: + override given idFormat: JsonFormat[Long] = formatLong + override given eFormat: JsonFormat[Building] = + Codec.from( + Decoder.forProduct6("_id", "name", "height", "floors", "year", "address")(Building.apply), + Encoder.forProduct6("_id", "name", "height", "floors", "year", "address")(b => + (b.id, b.name, b.height, b.floors, b.year, b.address) + ) + ) + + private class PlayBuildingDao extends BuildingDao with PlayBuildingModelBsonProtocol + + override protected def newBuildingDao: BuildingDao = PlayBuildingDao() diff --git a/circe/src/test/scala/io/github/greenleafoss/mongo/circe/dao/CirceEntityWithIdAsObjectDaoSpec.scala b/circe/src/test/scala/io/github/greenleafoss/mongo/circe/dao/CirceEntityWithIdAsObjectDaoSpec.scala new file mode 100644 index 0000000..57f6b9e --- /dev/null +++ b/circe/src/test/scala/io/github/greenleafoss/mongo/circe/dao/CirceEntityWithIdAsObjectDaoSpec.scala @@ -0,0 +1,45 @@ +package io.github.greenleafoss.mongo.circe.dao + +import io.github.greenleafoss.mongo.core.dao.EntityWithIdAsObjectDaoSpec +import io.github.greenleafoss.mongo.core.dao.EntityWithIdAsObjectDaoSpec.* +import io.github.greenleafoss.mongo.core.dao.EntityWithIdAsObjectDaoSpec.Currency + +import java.time.ZonedDateTime + +import scala.util.Try + +import io.circe.Codec +import io.circe.Decoder +import io.circe.Encoder +import io.circe.KeyDecoder +import io.circe.KeyEncoder +import io.circe.generic.semiauto.deriveCodec + +class CirceEntityWithIdAsObjectDaoSpec extends EntityWithIdAsObjectDaoSpec: + private trait PlayExchangeRateDaoBsonProtocol + extends ExchangeRateDaoBsonProtocol + with CirceMongoDaoProtocol[ExchangeRateId, ExchangeRate]: + override given CurrencyFormat: JsonFormat[Currency.Currency] = + Codec.from( + Decoder.decodeString.map(Currency.withName), + Encoder.encodeString.contramap(_.toString) + ) + + given keyDecoder: KeyDecoder[Currency.Currency] = + KeyDecoder.instance(x => Try(Currency.withName(x)).toOption) + + given keyEncoder: KeyEncoder[Currency.Currency] = + KeyEncoder[String].contramap(_.toString) + + override given idFormat: JsonFormat[ExchangeRateId] = deriveCodec + + // override given eFormat: JsonFormat[ExchangeRate] = deriveCodec + override given eFormat: JsonFormat[ExchangeRate] = + Codec.from( + Decoder.forProduct3("_id", "rates", "updated")(ExchangeRate.apply), + Encoder.forProduct3("_id", "rates", "updated")(x => (x.id, x.rates, x.updated)) + ) + + private class PlayExchangeRateDao extends ExchangeRateDao with PlayExchangeRateDaoBsonProtocol + + override protected def newExchangeRateDao: ExchangeRateDao = PlayExchangeRateDao() diff --git a/circe/src/test/scala/io/github/greenleafoss/mongo/circe/dao/CirceEntityWithOptionalFieldsDaoSpec.scala b/circe/src/test/scala/io/github/greenleafoss/mongo/circe/dao/CirceEntityWithOptionalFieldsDaoSpec.scala new file mode 100644 index 0000000..ab75e0d --- /dev/null +++ b/circe/src/test/scala/io/github/greenleafoss/mongo/circe/dao/CirceEntityWithOptionalFieldsDaoSpec.scala @@ -0,0 +1,25 @@ +package io.github.greenleafoss.mongo.circe.dao + +import io.github.greenleafoss.mongo.core.dao.EntityWithOptionalFieldsDaoSpec +import io.github.greenleafoss.mongo.core.dao.EntityWithOptionalFieldsDaoSpec.* + +import io.circe.Codec +import io.circe.Decoder +import io.circe.Encoder +import io.circe.generic.semiauto.deriveCodec + +class CirceEntityWithOptionalFieldsDaoSpec extends EntityWithOptionalFieldsDaoSpec: + + private trait PlayGeoModelDaoBsonProtocol + extends GeoModelDaoBsonProtocol + with CirceMongoDaoProtocol[GeoKey, GeoRecord]: + override protected given idFormat: JsonFormat[GeoKey] = deriveCodec + override protected given eFormat: JsonFormat[GeoRecord] = + Codec.from( + Decoder.forProduct3("_id", "name", "population")(GeoRecord.apply), + // Encoder.forProduct3("_id", "name", "population")(Tuple.fromProductTyped) + Encoder.forProduct3("_id", "name", "population")(x => (x.key, x.name, x.population)) + ) + + private class PlayGeoModelDao extends GeoModelDao with PlayGeoModelDaoBsonProtocol + override protected def newGeoModelDao: GeoModelDao = PlayGeoModelDao() diff --git a/circe/src/test/scala/io/github/greenleafoss/mongo/circe/dao/CirceEntityWithoutIdDaoSpec.scala b/circe/src/test/scala/io/github/greenleafoss/mongo/circe/dao/CirceEntityWithoutIdDaoSpec.scala new file mode 100644 index 0000000..23e0b9e --- /dev/null +++ b/circe/src/test/scala/io/github/greenleafoss/mongo/circe/dao/CirceEntityWithoutIdDaoSpec.scala @@ -0,0 +1,26 @@ +package io.github.greenleafoss.mongo.circe.dao + +import io.github.greenleafoss.mongo.core.dao.EntityWithoutIdDaoSpec +import io.github.greenleafoss.mongo.core.dao.EntityWithoutIdDaoSpec.* +import io.github.greenleafoss.mongo.core.dao.EntityWithoutIdDaoSpec.EventSource.EventSource + +import io.circe.Codec +import io.circe.Decoder +import io.circe.Encoder +import io.circe.Json +import io.circe.generic.semiauto.deriveCodec + +class CirceEntityWithoutIdDaoSpec extends EntityWithoutIdDaoSpec: + + private trait CirceEventDaoBsonProtocol extends EventDaoBsonProtocol with CirceMongoDaoProtocolObjectId[Event]: + override given EventSourceFormat: JsonFormat[EventSource.EventSource] = + Codec.from( + Decoder.decodeString.map(EventSource.withName), + Encoder.encodeString.contramap(_.toString) + ) + + override given eFormat: JsonFormat[Event] = deriveCodec + + private class CirceEventDao extends EventDao with CirceEventDaoBsonProtocol + + override protected def newEventDao: EventDao = CirceEventDao() diff --git a/circe/src/test/scala/io/github/greenleafoss/mongo/circe/filter/CirceFilterSpec.scala b/circe/src/test/scala/io/github/greenleafoss/mongo/circe/filter/CirceFilterSpec.scala new file mode 100644 index 0000000..887ebd0 --- /dev/null +++ b/circe/src/test/scala/io/github/greenleafoss/mongo/circe/filter/CirceFilterSpec.scala @@ -0,0 +1,12 @@ +package io.github.greenleafoss.mongo.circe.filter + +import io.github.greenleafoss.mongo.core.filter.GreenLeafMongoFilterOpsSpec + +import io.github.greenleafoss.mongo.circe.bson.CirceBsonProtocol +import io.github.greenleafoss.mongo.circe.util.CirceJsonBsonOps + +import scala.language.implicitConversions + +import org.scalatest.wordspec.AnyWordSpec + +class CirceFilterSpec extends GreenLeafMongoFilterOpsSpec with CirceBsonProtocol with CirceJsonBsonOps diff --git a/circe/src/test/scala/io/github/greenleafoss/mongo/circe/json/CirceJsonFormatSpec.scala b/circe/src/test/scala/io/github/greenleafoss/mongo/circe/json/CirceJsonFormatSpec.scala new file mode 100644 index 0000000..1df0b86 --- /dev/null +++ b/circe/src/test/scala/io/github/greenleafoss/mongo/circe/json/CirceJsonFormatSpec.scala @@ -0,0 +1,5 @@ +package io.github.greenleafoss.mongo.circe.json + +import io.github.greenleafoss.mongo.core.json.JsonProtocolSpec + +class CirceJsonFormatSpec extends JsonProtocolSpec with CirceModelJsonProtocol diff --git a/circe/src/test/scala/io/github/greenleafoss/mongo/circe/json/CirceModelJsonProtocol.scala b/circe/src/test/scala/io/github/greenleafoss/mongo/circe/json/CirceModelJsonProtocol.scala new file mode 100644 index 0000000..3eba74e --- /dev/null +++ b/circe/src/test/scala/io/github/greenleafoss/mongo/circe/json/CirceModelJsonProtocol.scala @@ -0,0 +1,10 @@ +package io.github.greenleafoss.mongo.circe.json + +import io.github.greenleafoss.mongo.core.model.Model + +import io.github.greenleafoss.mongo.circe.json.CirceJsonProtocol + +import io.circe.generic.semiauto.deriveCodec + +trait CirceModelJsonProtocol extends CirceJsonProtocol: + given modelJsonFormat: JsonFormat[Model] = deriveCodec diff --git a/core/src/test/scala/io/github/greenleafoss/mongo/core/filter/GreenLeafMongoFilterOpsSpec.scala b/core/src/test/scala/io/github/greenleafoss/mongo/core/filter/GreenLeafMongoFilterOpsSpec.scala index f5a8383..bc6f09b 100644 --- a/core/src/test/scala/io/github/greenleafoss/mongo/core/filter/GreenLeafMongoFilterOpsSpec.scala +++ b/core/src/test/scala/io/github/greenleafoss/mongo/core/filter/GreenLeafMongoFilterOpsSpec.scala @@ -271,6 +271,7 @@ trait GreenLeafMongoFilterOpsSpec extends AnyWordSpec with Matchers with GreenLe "$elemMatch" in: // https://mongodb.com/docs/manual/reference/operator/query/elemMatch/ + // { results: { $elemMatch: { product: "xyz", score: { $gte: 8 } } } } ("results" $elemMatch $and("product" $eq "xyz", "score" $gte 8)) shouldBe """{ results: { $elemMatch: { $and: [ { product: { $eq: "xyz" } }, { score: { $gte : 8 } }] } } }""".parseBson diff --git a/play/src/main/scala/io/github/greenleafoss/mongo/play/bson/PlayBsonProtocol.scala b/play/src/main/scala/io/github/greenleafoss/mongo/play/bson/PlayBsonProtocol.scala index 9df883f..338fa63 100644 --- a/play/src/main/scala/io/github/greenleafoss/mongo/play/bson/PlayBsonProtocol.scala +++ b/play/src/main/scala/io/github/greenleafoss/mongo/play/bson/PlayBsonProtocol.scala @@ -1,6 +1,7 @@ package io.github.greenleafoss.mongo.play.bson import io.github.greenleafoss.mongo.core.util.ZonedDateTimeOps.* + import io.github.greenleafoss.mongo.play.json.PlayJsonProtocol import org.mongodb.scala.bson.ObjectId diff --git a/play/src/main/scala/io/github/greenleafoss/mongo/play/dao/PlayMongoDao.scala b/play/src/main/scala/io/github/greenleafoss/mongo/play/dao/PlayMongoDao.scala index 7d21b58..9d2bd9d 100644 --- a/play/src/main/scala/io/github/greenleafoss/mongo/play/dao/PlayMongoDao.scala +++ b/play/src/main/scala/io/github/greenleafoss/mongo/play/dao/PlayMongoDao.scala @@ -1,6 +1,7 @@ package io.github.greenleafoss.mongo.play.dao import io.github.greenleafoss.mongo.core.dao.GreenLeafMongoDao + import io.github.greenleafoss.mongo.play.util.PlayJsonBsonOps import scala.concurrent.ExecutionContext diff --git a/play/src/main/scala/io/github/greenleafoss/mongo/play/dao/PlayMongoDaoProtocol.scala b/play/src/main/scala/io/github/greenleafoss/mongo/play/dao/PlayMongoDaoProtocol.scala index 926200f..5bf846a 100644 --- a/play/src/main/scala/io/github/greenleafoss/mongo/play/dao/PlayMongoDaoProtocol.scala +++ b/play/src/main/scala/io/github/greenleafoss/mongo/play/dao/PlayMongoDaoProtocol.scala @@ -1,6 +1,7 @@ package io.github.greenleafoss.mongo.play.dao import io.github.greenleafoss.mongo.core.dao.GreenLeafMongoDaoProtocol + import io.github.greenleafoss.mongo.play.bson.PlayBsonProtocol import io.github.greenleafoss.mongo.play.util.PlayJsonBsonOps diff --git a/play/src/main/scala/io/github/greenleafoss/mongo/play/json/PlayJsonProtocol.scala b/play/src/main/scala/io/github/greenleafoss/mongo/play/json/PlayJsonProtocol.scala index eb6bf25..ec8d0d5 100644 --- a/play/src/main/scala/io/github/greenleafoss/mongo/play/json/PlayJsonProtocol.scala +++ b/play/src/main/scala/io/github/greenleafoss/mongo/play/json/PlayJsonProtocol.scala @@ -2,6 +2,7 @@ package io.github.greenleafoss.mongo.play.json import io.github.greenleafoss.mongo.core.json.GreenLeafMongoJsonBasicFormats import io.github.greenleafoss.mongo.core.util.ZonedDateTimeOps + import io.github.greenleafoss.mongo.play.util.PlayJsonBsonOps import org.mongodb.scala.bson.ObjectId @@ -13,6 +14,7 @@ import java.util.UUID import play.api.libs.json.* import play.api.libs.json.given + trait PlayJsonProtocol extends GreenLeafMongoJsonBasicFormats with PlayJsonBsonOps: override protected def formatInt: JsonFormat[Int] = Format(Reads.IntReads, Writes.IntWrites) diff --git a/play/src/main/scala/io/github/greenleafoss/mongo/play/util/PlayJsonBsonOps.scala b/play/src/main/scala/io/github/greenleafoss/mongo/play/util/PlayJsonBsonOps.scala index 7351a50..650803e 100644 --- a/play/src/main/scala/io/github/greenleafoss/mongo/play/util/PlayJsonBsonOps.scala +++ b/play/src/main/scala/io/github/greenleafoss/mongo/play/util/PlayJsonBsonOps.scala @@ -13,24 +13,17 @@ import org.mongodb.scala.bson.BsonDouble import org.mongodb.scala.bson.BsonInt32 import org.mongodb.scala.bson.BsonInt64 import org.mongodb.scala.bson.BsonNull -import org.mongodb.scala.bson.BsonNumber import org.mongodb.scala.bson.BsonObjectId import org.mongodb.scala.bson.BsonString import org.mongodb.scala.bson.BsonValue import org.mongodb.scala.bson.ObjectId -import org.bson.json.JsonMode -import org.bson.json.JsonWriterSettings - import java.time.ZonedDateTime -import java.util.Date import scala.jdk.CollectionConverters.* import scala.language.implicitConversions -import scala.util.chaining.* import play.api.libs.json.* -import play.api.libs.json.given trait PlayJsonBsonOps extends GreenLeafJsonBsonOps: @@ -84,5 +77,4 @@ trait PlayJsonBsonOps extends GreenLeafJsonBsonOps: case _: BsonNull => JsNull case _ => throw JsonBsonErr(s"Unknown input in BSON to JSON: $bson") - object PlayJsonBsonOps extends PlayJsonBsonOps diff --git a/play/src/test/scala/io/github/greenleafoss/mongo/play/filter/PlayFilterSpec.scala b/play/src/test/scala/io/github/greenleafoss/mongo/play/filter/PlayFilterSpec.scala index a91e5f2..fbc4af1 100644 --- a/play/src/test/scala/io/github/greenleafoss/mongo/play/filter/PlayFilterSpec.scala +++ b/play/src/test/scala/io/github/greenleafoss/mongo/play/filter/PlayFilterSpec.scala @@ -1,6 +1,7 @@ package io.github.greenleafoss.mongo.play.filter import io.github.greenleafoss.mongo.core.filter.GreenLeafMongoFilterOpsSpec + import io.github.greenleafoss.mongo.play.bson.PlayBsonProtocol import io.github.greenleafoss.mongo.play.util.PlayJsonBsonOps diff --git a/project/build.properties b/project/build.properties index dde206f..cb409aa 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.9.6 \ No newline at end of file +sbt.version=1.10.1 \ No newline at end of file diff --git a/spray/src/main/scala/io/github/greenleafoss/mongo/spray/bson/SprayBsonProtocol.scala b/spray/src/main/scala/io/github/greenleafoss/mongo/spray/bson/SprayBsonProtocol.scala index c9c1218..7e315ea 100644 --- a/spray/src/main/scala/io/github/greenleafoss/mongo/spray/bson/SprayBsonProtocol.scala +++ b/spray/src/main/scala/io/github/greenleafoss/mongo/spray/bson/SprayBsonProtocol.scala @@ -1,6 +1,7 @@ package io.github.greenleafoss.mongo.spray.bson import io.github.greenleafoss.mongo.core.util.ZonedDateTimeOps.* + import io.github.greenleafoss.mongo.spray.json.SprayJsonProtocol import org.mongodb.scala.bson.ObjectId diff --git a/spray/src/main/scala/io/github/greenleafoss/mongo/spray/dao/SprayMongoDao.scala b/spray/src/main/scala/io/github/greenleafoss/mongo/spray/dao/SprayMongoDao.scala index 2408f69..38bd697 100644 --- a/spray/src/main/scala/io/github/greenleafoss/mongo/spray/dao/SprayMongoDao.scala +++ b/spray/src/main/scala/io/github/greenleafoss/mongo/spray/dao/SprayMongoDao.scala @@ -1,6 +1,7 @@ package io.github.greenleafoss.mongo.spray.dao import io.github.greenleafoss.mongo.core.dao.GreenLeafMongoDao + import io.github.greenleafoss.mongo.spray.util.SprayJsonBsonOps import scala.concurrent.ExecutionContext diff --git a/spray/src/main/scala/io/github/greenleafoss/mongo/spray/dao/SprayMongoDaoProtocol.scala b/spray/src/main/scala/io/github/greenleafoss/mongo/spray/dao/SprayMongoDaoProtocol.scala index f0f9d5e..6af0656 100644 --- a/spray/src/main/scala/io/github/greenleafoss/mongo/spray/dao/SprayMongoDaoProtocol.scala +++ b/spray/src/main/scala/io/github/greenleafoss/mongo/spray/dao/SprayMongoDaoProtocol.scala @@ -1,6 +1,7 @@ package io.github.greenleafoss.mongo.spray.dao import io.github.greenleafoss.mongo.core.dao.GreenLeafMongoDaoProtocol + import io.github.greenleafoss.mongo.spray.bson.SprayBsonProtocol import io.github.greenleafoss.mongo.spray.util.SprayJsonBsonOps diff --git a/spray/src/main/scala/io/github/greenleafoss/mongo/spray/json/SprayJsonProtocol.scala b/spray/src/main/scala/io/github/greenleafoss/mongo/spray/json/SprayJsonProtocol.scala index 9d8b502..3692661 100644 --- a/spray/src/main/scala/io/github/greenleafoss/mongo/spray/json/SprayJsonProtocol.scala +++ b/spray/src/main/scala/io/github/greenleafoss/mongo/spray/json/SprayJsonProtocol.scala @@ -4,6 +4,7 @@ import io.github.greenleafoss.mongo.core.json.GreenLeafMongoJsonBasicFormats import io.github.greenleafoss.mongo.core.util.LocalDateOps.* import io.github.greenleafoss.mongo.core.util.LocalDateTimeOps.* import io.github.greenleafoss.mongo.core.util.ZonedDateTimeOps.* + import io.github.greenleafoss.mongo.spray.util.SprayJsonBsonOps import org.mongodb.scala.bson.ObjectId diff --git a/spray/src/main/scala/io/github/greenleafoss/mongo/spray/util/SprayJsonBsonOps.scala b/spray/src/main/scala/io/github/greenleafoss/mongo/spray/util/SprayJsonBsonOps.scala index f23bd84..8787b40 100644 --- a/spray/src/main/scala/io/github/greenleafoss/mongo/spray/util/SprayJsonBsonOps.scala +++ b/spray/src/main/scala/io/github/greenleafoss/mongo/spray/util/SprayJsonBsonOps.scala @@ -4,7 +4,6 @@ import io.github.greenleafoss.mongo.core.util.GreenLeafJsonBsonOps import io.github.greenleafoss.mongo.core.util.GreenLeafJsonBsonOps.JsonBsonErr import io.github.greenleafoss.mongo.core.util.ZonedDateTimeOps import io.github.greenleafoss.mongo.core.util.ZonedDateTimeOps.* -import io.github.greenleafoss.mongo.spray.bson.SprayBsonProtocol import org.mongodb.scala.bson.BsonArray import org.mongodb.scala.bson.BsonBoolean @@ -20,15 +19,10 @@ import org.mongodb.scala.bson.BsonString import org.mongodb.scala.bson.BsonValue import org.mongodb.scala.bson.ObjectId -import org.bson.json.JsonMode -import org.bson.json.JsonWriterSettings - import java.time.ZonedDateTime -import java.util.Date import scala.jdk.CollectionConverters.* import scala.language.implicitConversions -import scala.util.chaining.* import spray.json.* import spray.json.DefaultJsonProtocol.* @@ -50,8 +44,9 @@ trait SprayJsonBsonOps extends GreenLeafJsonBsonOps: extension [E: JsonFormat](e: E) override def convertToJson: Json = e.toJson extension (json: Json) override def convertTo[E: JsonFormat]: E = json.convertTo[E] - import SprayBsonProtocol.* - import SprayBsonProtocol.given + import io.github.greenleafoss.mongo.spray.bson.SprayBsonProtocol.* + import io.github.greenleafoss.mongo.spray.bson.SprayBsonProtocol.given + override protected def convertJsonToBson(json: Json): BsonValue = json match case JsObject(x) if x.contains($oid) => BsonObjectId(json.convertTo[ObjectId]) case JsObject(x) if x.contains($date) => BsonDateTime(json.convertTo[ZonedDateTime].toEpochMilli) diff --git a/spray/src/test/scala/io/github/greenleafoss/mongo/spray/dao/SprayEntityWithoutIdDaoSpec.scala b/spray/src/test/scala/io/github/greenleafoss/mongo/spray/dao/SprayEntityWithoutIdDaoSpec.scala index 2a9f382..a405674 100644 --- a/spray/src/test/scala/io/github/greenleafoss/mongo/spray/dao/SprayEntityWithoutIdDaoSpec.scala +++ b/spray/src/test/scala/io/github/greenleafoss/mongo/spray/dao/SprayEntityWithoutIdDaoSpec.scala @@ -3,6 +3,7 @@ package io.github.greenleafoss.mongo.spray.dao import io.github.greenleafoss.mongo.core.dao.EntityWithoutIdDaoSpec import io.github.greenleafoss.mongo.core.dao.EntityWithoutIdDaoSpec.* import io.github.greenleafoss.mongo.core.dao.EntityWithoutIdDaoSpec.EventSource.EventSource + import io.github.greenleafoss.mongo.spray.json.SprayJsonProtocol import spray.json.* diff --git a/spray/src/test/scala/io/github/greenleafoss/mongo/spray/filter/SprayFilterSpec.scala b/spray/src/test/scala/io/github/greenleafoss/mongo/spray/filter/SprayFilterSpec.scala index 53a5338..c653487 100644 --- a/spray/src/test/scala/io/github/greenleafoss/mongo/spray/filter/SprayFilterSpec.scala +++ b/spray/src/test/scala/io/github/greenleafoss/mongo/spray/filter/SprayFilterSpec.scala @@ -1,6 +1,7 @@ package io.github.greenleafoss.mongo.spray.filter import io.github.greenleafoss.mongo.core.filter.GreenLeafMongoFilterOpsSpec + import io.github.greenleafoss.mongo.spray.bson.SprayBsonProtocol import io.github.greenleafoss.mongo.spray.util.SprayJsonBsonOps