Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extensions for Spray and Play JSON #20

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# https://scalameta.org/scalafmt/docs/installation.html#sbt
version = 3.7.14

# https://scalameta.org/scalafmt/docs/configuration.html#scala-dialects
runner.dialect = scala3

# https://docs.scala-lang.org/style/declarations.html#modifiers
rewrite.rules = [SortModifiers]
rewrite.sortModifiers.order = [
"`final`"
"`implicit`"
"`override`"
"`protected`"
"`private`"
"`lazy`"
]

//rewrite.rules = [RedundantBraces]
//rewrite.redundantBraces.stringInterpolation = true
//rewrite.redundantBraces.defnBodies = "all"
//rewrite.redundantBraces.methodBodies = true
//rewrite.redundantBraces.includeUnitMethods = true
//rewrite.redundantBraces.generalExpressions = true
//rewrite.redundantBraces.ifElseExpressions = true
//newlines.afterCurlyLambdaParams=squash

# https://scalameta.org/scalafmt/docs/configuration.html#imports
rewrite.rules = [Imports]
//rewrite.imports.sort = scalastyle
rewrite.imports.sort = ascii
rewrite.imports.expand = true
rewrite.imports.groups = [
["io.github.greenleafoss\\..*"],
["org.mongodb\\..*"],
["org.bson\\..*"],
["java\\..*"],
["scala\\..*"]
]

# https://scalameta.org/scalafmt/docs/configuration.html#maxcolumn
# 80| 90| 100| 110| 120|
maxColumn = 120

# https://scalameta.org/scalafmt/docs/configuration.html#top-level-presets
preset = IntelliJ

# https://scalameta.org/scalafmt/docs/configuration.html#alignpreset
align.preset=most
align.multiline = true
align.arrowEnumeratorGenerator = false
align.openParenCallSite = false
align.openParenDefnSite = true

optIn.configStyleArguments = true

verticalMultiline.atDefnSite = true
# https://scalameta.org/scalafmt/docs/configuration.html#after-only
# implicit
# override private val ctx: Context,
# private val ops: Ops
newlines.implicitParamListModifierForce = [after]
//newlines.implicitParamListModifierForce = [before, after]

# https://scalameta.org/scalafmt/docs/configuration.html#indentextendsite
indent.extendSite = 2

# https://scalameta.org/scalafmt/docs/configuration.html#indentdefnsite
indent.defnSite = 4

indent.fewerBraces = always



# https://scalameta.org/scalafmt/docs/configuration.html#trailing-commas
rewrite.trailingCommas.style = never

# https://scalameta.org/scalafmt/docs/configuration.html#binpacking
# doesn't apply binpacking to calls with fewer arguments
binPack.literalsMinArgCount = 3

# https://scalameta.org/scalafmt/docs/configuration.html#binpackparentconstructors
binPack.parentConstructors = Never

//newlines.beforeTemplateBodyIfBreakInParentCtors = true

# https://scalameta.org/scalafmt/docs/configuration.html#literal-argument-lists
# List(
# 1,
# 2,
# 3,
# )
binPack.literalArgumentLists = false
binPack.literalsSingleLine = false

docstrings.style = Asterisk
docstrings.style = keep
55 changes: 33 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,45 +5,54 @@
[![green-leaf-mongo Scala version support](https://index.scala-lang.org/greenleafoss/green-leaf-mongo/green-leaf-mongo/latest-by-scala-version.svg)](https://index.scala-lang.org/greenleafoss/green-leaf-mongo/green-leaf-mongo)

## Short description
This extension created on top of official [MongoDB Scala Driver](http://mongodb.github.io/mongo-scala-driver), allows to fully utilize [Spray JSON](https://github.com/spray/spray-json) and represents bidirectional serialization for case classes in BSON, as well as flexible DSL for [MongoDB query operators](https://docs.mongodb.com/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) 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.

## Usage
```scala
// build.sbt
// https://mvnrepository.com/artifact/io.github.greenleafoss/green-leaf-mongo
libraryDependencies += "io.github.greenleafoss" %% "green-leaf-mongo" % "0.1.16.1"

// https://mvnrepository.com/artifact/io.github.greenleafoss/green-leaf-mongo-core
// `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"

// https://mvnrepository.com/artifact/io.github.greenleafoss/green-leaf-mongo-play
libraryDependencies += "io.github.greenleafoss" %% "green-leaf-mongo-play" % "3.0"
```

## JSON and BSON protocols

`GreenLeafJsonProtocol` based on DefaultJsonProtocol from Spray JSON and allows to override predefined JsonFormats to make possible use custom seriallization in BSON format.
This trait also includes a few additional JsonFormats for _ZonedDateTime_, _ObjectId_, _scala Enumeration_ and _UUID_.
`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_.

`GreenLeafBsonProtocol` extends `GreenLeafJsonProtocol` and overrides _Long_, _ZonedDateTime_, _ObjectId_, _scala Enumeration_, _UUID_ and _Regex_ JSON formats to represent them in related BSON formats.
`PlayJsonProtocol` is a related extension for Play JSON library and `PlayJsonProtocol` for 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.

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
```scala 3
// MODEL
case class Test(id: ObjectId, i: Int, l: Long, b: Boolean, zdt: ZonedDateTime)

// JSON
trait TestJsonProtocol extends GreenLeafJsonProtocol {
implicit def testJf = jsonFormat5(Test)
}
trait TestJsonProtocol extends SprayBsonProtocol:
given testJsonFormat = jsonFormat5(Test)

object TestJsonProtocol extends TestJsonProtocol

// BSON
trait TestBsonProtocol extends TestJsonProtocol with GreenLeafBsonProtocol {
override implicit def testJf = jsonFormat(Test, "_id", "i", "l", "b", "zdt")
}
trait TestBsonProtocol extends SprayBsonProtocol:
given testBsonFormat = jsonFormat(Test, "_id", "i", "l", "b", "zdt")

object TestBsonProtocol extends TestBsonProtocol
```

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")

import TestJsonProtocol._
import TestJsonProtocol.given
println(obj.toJson.prettyPrint)
```
Output in this case will be:
Expand All @@ -62,7 +71,7 @@ Changing single line of import `TestJsonProtocol` to `TestBsonProtocol` allows u
```scala
val obj = Test(new ObjectId("5c72b799306e355b83ef3c86"), 1, 0x123456789L, true, "1970-01-01")

import TestBsonProtocol._
import TestBsonProtocol.given
println(obj.toJson.prettyPrint)
```

Expand All @@ -83,10 +92,10 @@ Output in this case will be:
}
```

Full code of the examples above available in `GreenLeafJsonAndBsonProtocolsTest`.
More examples available in implementation of **JsonProtocolSpec**/**BsonProtocolSpec** in Spray and Play project modules.

## GreenLeafMongoDsl
Import `GreenLeafMongoDsl._` 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/).
`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")
Expand All @@ -100,17 +109,19 @@ Import `GreenLeafMongoDsl._` makes it possible to write queries with a syntax th
"size" $nin ("S", "XXL")
$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 }
"price" $not { $gte (5.1) }
$nor( "price" $eq 1.99 , "qty" $lt 20, "sale" $eq true )
"qty" $exists true
"results" $elemMatch $and("product" $eq "xyz", "score" $gte 8)
// ...
```

More examples of queries available in `GreenLeafMongoDslTest`.
More examples of queries available in **GreenLeafMongoFilterOpsSpec**.


## GreenLeafMongoDao
`GreenLeafMongoDao` extends `GreenLeafMongoDsl` and provides simple DSL to transform Mongo's _Observable[Document]_ instances to _Future[Seq[T]]_, _Future[Option[T]]_ and _Future[T]_.
In addition this trait provides many useful generic methods such as _insert_, _getById_, _findById_, _updateById_, _replaceById_ and others.
You can find more details and examples in [EntityWithIdAsFieldDaoTest](https://github.com/GreenLeafOSS/green-leaf-mongo/blob/master/src/test/scala/io/github/greenleafoss/mongo/EntityWithIdAsFieldDaoTest.scala), [EntityWithIdAsObjectDaoTest](https://github.com/GreenLeafOSS/green-leaf-mongo/blob/master/src/test/scala/io/github/greenleafoss/mongo/EntityWithIdAsObjectDaoTest.scala), [EntityWithOptionalFieldsDaoTest](https://github.com/GreenLeafOSS/green-leaf-mongo/blob/master/src/test/scala/io/github/greenleafoss/mongo/EntityWithOptionalFieldsDaoTest.scala) and [EntityWithoutIdDaoTest](https://github.com/GreenLeafOSS/green-leaf-mongo/blob/master/src/test/scala/io/github/greenleafoss/mongo/EntityWithoutIdDaoTest.scala).
`GreenLeafMongoDao` extends `GreenLeafMongoObservableToFutureOps` with `GreenLeafMongoFilterOps` and provides simple DSL to transform Mongo's _Observable[Document]_ instances to _Future[Seq[T]]_, _Future[Option[T]]_ and _Future[T]_.
In addition, this trait provides many useful generic methods such as _insert_, _getById_, _findById_, _updateById_, _replaceById_ and others.
`SprayMongoDao`/`PlayMongoDao` are related implementations for Spray and Play JSON libraries.
You can find more details and examples in the dao tests.

147 changes: 87 additions & 60 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,64 +1,91 @@
// **************************************************
// SETTINGS
// **************************************************

lazy val commonSettings = Seq(
version := "3.0",
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,
|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")),
homepage := Some(url("https://github.com/GreenLeafOSS/green-leaf-mongo")),
organization := "io.github.greenleafoss",
organizationName := "greenleafoss",
organizationHomepage := Some(url("https://github.com/greenleafoss")),
developers := List(
Developer(
id = "lashchenko",
name = "Andrii Lashchenko",
email = "andrew.lashchenko@gmail.com",
url = url("https://github.com/lashchenko")
)
),
scmInfo := Some(
ScmInfo(
url("https://github.com/GreenLeafOSS/green-leaf-mongo"),
"scm:git@github.com:GreenLeafOSS/green-leaf-mongo.git"
)
),
publishTo := {
// https://central.sonatype.org/news/20210223_new-users-on-s01/
val nexus = "https://s01.oss.sonatype.org"
if (isSnapshot.value) Some("snapshots" at nexus + "/content/repositories/snapshots")
else Some("releases" at nexus + "/service/local/staging/deploy/maven2")
},

name := "green-leaf-mongo"

version := "0.1.16.1"

description := "This extension created on top of official MongoDB Scala Driver, allows to fully utilize Spray JSON and represents bidirectional serialization for case classes in BSON, as well as flexible DSL for MongoDB query operators, documents and collections."
licenses := List("Apache 2" -> new URL("http://www.apache.org/licenses/LICENSE-2.0.txt"))
homepage := Some(url("https://github.com/GreenLeafOSS/green-leaf-mongo"))

organization := "io.github.greenleafoss"
organizationName := "greenleafoss"
organizationHomepage := Some(url("https://github.com/greenleafoss"))

developers := List(
Developer(
id = "lashchenko",
name = "Andrii Lashchenko",
email = "andrew.lashchenko@gmail.com",
url = url("https://github.com/lashchenko")
)
)

scmInfo := Some(
ScmInfo(
url("https://github.com/GreenLeafOSS/green-leaf-mongo"),
"scm:git@github.com:GreenLeafOSS/green-leaf-mongo.git"
)
)

publishTo := {
// https://central.sonatype.org/news/20210223_new-users-on-s01/
val nexus = "https://s01.oss.sonatype.org"
if (isSnapshot.value) Some("snapshots" at nexus + "/content/repositories/snapshots")
else Some("releases" at nexus + "/service/local/staging/deploy/maven2")
}

// https://central.sonatype.org/news/20210223_new-users-on-s01/
sonatypeCredentialHost := "https://s01.oss.sonatype.org"

publishMavenStyle := true

publishConfiguration := publishConfiguration.value.withOverwrite(true)

scalaVersion := "3.3.0"

crossScalaVersions := Seq("2.12.18", "2.13.11")

scalacOptions ++= Seq(
"-deprecation"
sonatypeCredentialHost := "https://s01.oss.sonatype.org",
publishMavenStyle := true,
publishConfiguration := publishConfiguration.value.withOverwrite(true),
publishLocalConfiguration := publishLocalConfiguration.value.withOverwrite(true),
scalaVersion := "3.3.1",
scalacOptions ++= Seq(
"-deprecation",
"-feature",
"-explain"
),
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 += "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
)

Test / parallelExecution := false
Test / fork := true

libraryDependencies += "io.spray" %% "spray-json" % "1.3.6"
libraryDependencies += "org.mongodb.scala" %% "mongo-scala-driver" % "4.10.2" cross CrossVersion.for3Use2_13


libraryDependencies += "org.slf4j" % "slf4j-api" % "2.0.7"
libraryDependencies += "org.slf4j" % "slf4j-simple" % "2.0.7" % Test

libraryDependencies += "de.flapdoodle.embed" % "de.flapdoodle.embed.mongo" % "4.8.0" % Test
libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.16" % Test
libraryDependencies += "org.immutables" % "value" % "2.9.3" % Test
// **************************************************
// PROJECTS
// **************************************************

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)

lazy val spray = (project in file("spray"))
.settings(name := "green-leaf-mongo-spray")
.settings(commonSettings)
.settings(libraryDependencies += "io.spray" %% "spray-json" % "1.3.6")
.dependsOn(core % "compile->compile;test->test")

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")
.dependsOn(core % "compile->compile;test->test")

lazy val extensions: Seq[ProjectReference] = List[ProjectReference](spray, play)
lazy val aggregated: Seq[ProjectReference] = List[ProjectReference](core) ++ extensions

lazy val root = (project in file("."))
.settings(name := "green-leaf-mongo")
.settings(commonSettings)
// we don't need to publish core + spray + play together
.settings(publish / skip := true)
.aggregate(aggregated*)
.dependsOn(core % "compile->compile;test->test")
.dependsOn(extensions.map(_ % "compile->compile;compile->test")*)
Loading