Skip to content

Commit

Permalink
Add readers and writers instances for cats non-empty collections (tet…
Browse files Browse the repository at this point in the history
…hys-json#284)

* Add readers and writers instances for cats non-empty collections

* fix problem with SortedSet

* up version to 0.28.4

* renamed module name to tethys-cats
  • Loading branch information
Roman-Statsura authored and goshacodes committed Aug 28, 2024
1 parent eb7fdfd commit b2ecae3
Show file tree
Hide file tree
Showing 8 changed files with 219 additions and 2 deletions.
16 changes: 14 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ lazy val scala3 = "3.3.0"
ThisBuild / scalaVersion := scala3

lazy val commonSettings = Seq(
version := "0.28.2",
version := "0.28.4",
organization := "com.tethys-json",
licenses := Seq("Apache-2.0" -> url("https://www.apache.org/licenses/LICENSE-2.0")),
homepage := Some(url("https://github.com/tethys-json/tethys")),
Expand Down Expand Up @@ -86,7 +86,7 @@ lazy val tethys = project.in(file("."))
crossScalaVersions := Seq.empty,
commonSettings
)
.aggregate(core, `macro-derivation`, `jackson-211`, `jackson-212`, `jackson-213`, json4s, circe, refined, enumeratum)
.aggregate(core, `macro-derivation`, `jackson-211`, `jackson-212`, `jackson-213`, json4s, circe, refined, enumeratum, cats)

lazy val modules = file("modules")

Expand All @@ -106,6 +106,18 @@ lazy val core = project.in(modules / "core")
libraryDependencies ++= addScalaReflect(scalaVersion.value)
)

lazy val cats = project.in(modules / "cats")
.settings(crossScalaSettings)
.settings(commonSettings)
.settings(testSettings)
.settings(
name := "tethys-cats",
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-core" % "2.10.0",
)
)
.dependsOn(core)

lazy val `macro-derivation` = project.in(modules / "macro-derivation")
.settings(crossScalaSettings)
.settings(commonSettings)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package tethys.cats

import cats.data.NonEmptySet
import tethys.JsonReader
import tethys.readers.{FieldName, ReaderError}
import tethys.readers.tokens.TokenIterator

import scala.collection.immutable.{Seq, SortedSet}

trait NonEmptySetReader {

implicit def readerForNes[T: JsonReader: Ordering]
: JsonReader[NonEmptySet[T]] =
new JsonReader[NonEmptySet[T]] {
override def read(
it: TokenIterator
)(implicit fieldName: FieldName): NonEmptySet[T] =
NonEmptySet.fromSet(SortedSet(JsonReader[Seq[T]].read(it): _*)) match {
case Some(value) => value
case None =>
ReaderError.wrongJson(
s"Seq is empty and can't be converted to NonEmptySet"
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package tethys.cats

import cats.data.NonEmptySet
import tethys.JsonReader
import tethys.readers.{FieldName, ReaderError}
import tethys.readers.tokens.TokenIterator

import scala.collection.immutable.SortedSet

trait NonEmptySetReader {

implicit def readerForNes[T: JsonReader: Ordering]
: JsonReader[NonEmptySet[T]] =
new JsonReader[NonEmptySet[T]] {
override def read(
it: TokenIterator
)(implicit fieldName: FieldName): NonEmptySet[T] =
NonEmptySet.fromSet(SortedSet.from(JsonReader[Seq[T]].read(it))) match {
case Some(value) => value
case None =>
ReaderError.wrongJson(
s"Seq is empty and can't be converted to NonEmptySet"
)
}
}
}
26 changes: 26 additions & 0 deletions modules/cats/src/main/scala-3/tethys.cats/NonEmptySetReader.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package tethys.cats

import cats.data.NonEmptySet
import tethys.JsonReader
import tethys.readers.{FieldName, ReaderError}
import tethys.readers.tokens.TokenIterator

import scala.collection.immutable.SortedSet

trait NonEmptySetReader {

implicit def readerForNes[T: JsonReader: Ordering]
: JsonReader[NonEmptySet[T]] =
new JsonReader[NonEmptySet[T]] {
override def read(
it: TokenIterator
)(implicit fieldName: FieldName): NonEmptySet[T] =
NonEmptySet.fromSet(SortedSet.from(JsonReader[Seq[T]].read(it))) match {
case Some(value) => value
case None =>
ReaderError.wrongJson(
s"Seq is empty and can't be converted to NonEmptySet"
)
}
}
}
6 changes: 6 additions & 0 deletions modules/cats/src/main/scala/tethys/cats/instances.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package tethys.cats

import tethys.cats.readers.CatsReaders
import tethys.cats.writers.CatsWriters

object instances extends CatsReaders with CatsWriters with NonEmptySetReader
56 changes: 56 additions & 0 deletions modules/cats/src/main/scala/tethys/cats/readers/CatsReaders.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package tethys.cats.readers

import cats.data._
import tethys.readers.tokens.TokenIterator
import tethys.readers.{FieldName, ReaderError}
import tethys.JsonReader
import tethys.JsonReader.iterableReader

trait CatsReaders {

implicit def readerForNel[T: JsonReader]: JsonReader[NonEmptyList[T]] =
new JsonReader[NonEmptyList[T]] {
override def read(
it: TokenIterator
)(implicit fieldName: FieldName): NonEmptyList[T] =
NonEmptyList.fromList(JsonReader[List[T]].read(it)) match {
case Some(value) => value
case None =>
ReaderError.wrongJson(
s"List is empty and can't be converted to NonEmptyList"
)
}
}

implicit def readerForNev[T: JsonReader]: JsonReader[NonEmptyVector[T]] =
new JsonReader[NonEmptyVector[T]] {
override def read(
it: TokenIterator
)(implicit fieldName: FieldName): NonEmptyVector[T] =
NonEmptyVector.fromVector(JsonReader[Vector[T]].read(it)) match {
case Some(value) => value
case None =>
ReaderError.wrongJson(
s"Vector is empty and can't be converted to NonEmptyVector"
)
}
}

implicit def readerForChain[T: JsonReader]: JsonReader[Chain[T]] =
JsonReader[Seq[T]].map(Chain.fromIterableOnce)

implicit def readerForNec[T: JsonReader]: JsonReader[NonEmptyChain[T]] =
new JsonReader[NonEmptyChain[T]] {
override def read(
it: TokenIterator
)(implicit fieldName: FieldName): NonEmptyChain[T] =
NonEmptyChain.fromChain(JsonReader[Chain[T]].read(it)) match {
case Some(value) => value
case None =>
ReaderError.wrongJson(
s"Chain is empty and can't be converted to NonEmptyChain"
)
}
}

}
21 changes: 21 additions & 0 deletions modules/cats/src/main/scala/tethys/cats/writers/CatsWriters.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package tethys.cats.writers

import cats.data._
import tethys.JsonWriter

trait CatsWriters {
implicit def writerForNev[T: JsonWriter]: JsonWriter[NonEmptyVector[T]] =
JsonWriter[Vector[T]].contramap(_.toVector)

implicit def writerForNel[T: JsonWriter]: JsonWriter[NonEmptyList[T]] =
JsonWriter[List[T]].contramap(_.toList)

implicit def writerForNes[T: JsonWriter]: JsonWriter[NonEmptySet[T]] =
JsonWriter[Set[T]].contramap(_.toSortedSet)

implicit def writerForChain[T: JsonWriter]: JsonWriter[Chain[T]] =
JsonWriter[List[T]].contramap(_.toList)

implicit def writerForNec[T: JsonWriter]: JsonWriter[NonEmptyChain[T]] =
JsonWriter[Chain[T]].contramap(_.toChain)
}
44 changes: 44 additions & 0 deletions modules/cats/src/test/scala/tethys/cats/CatsSupportTests.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package tethys.cats

import cats.data._
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import tethys.commons.TokenNode._
import instances._
import tethys.readers.ReaderError
import tethys.writers.tokens.SimpleTokenWriter._

class CatsSupportTests extends AnyFlatSpec with Matchers {
val nev: NonEmptyVector[String] = NonEmptyVector.of("a", "b")
val nel: NonEmptyList[Int] = NonEmptyList.of(1, 2)
val nes: NonEmptySet[Int] = NonEmptySet.of(1, 2, 3, 4)
val chain: Chain[Int] = Chain.fromIterableOnce(Seq(1, 2, 3))
val nec: NonEmptyChain[String] = NonEmptyChain.of("a", "b", "c")

behavior of "CatsWriters"
it should "write non-empty" in {
nev.asTokenList shouldBe arr("a", "b")
nel.asTokenList shouldBe arr(1, 2)
nes.asTokenList shouldBe arr(1, 2, 3, 4)
chain.asTokenList shouldBe arr(1, 2, 3)
nec.asTokenList shouldBe arr("a", "b", "c")
}

behavior of "CatsReaders"
it should "read non-empty" in {
nev shouldBe arr("a", "b").tokensAs[NonEmptyVector[String]]
assertThrows[ReaderError](Nil.tokensAs[NonEmptyVector[String]])

nel shouldBe arr(1, 2).tokensAs[NonEmptyList[Int]]
assertThrows[ReaderError](Nil.tokensAs[NonEmptyList[Int]])

nes shouldBe arr(1, 2, 3, 4).tokensAs[NonEmptySet[Int]]
nes shouldBe arr(4, 4, 1, 3, 3, 2).tokensAs[NonEmptySet[Int]]
assertThrows[ReaderError](Nil.tokensAs[NonEmptySet[Int]])

chain shouldBe arr(1, 2, 3).tokensAs[Chain[Int]]

nec shouldBe arr("a", "b", "c").tokensAs[NonEmptyChain[String]]
assertThrows[ReaderError](Nil.tokensAs[NonEmptyChain[String]])
}
}

0 comments on commit b2ecae3

Please sign in to comment.