Skip to content

Instantly share code, notes, and snippets.

@dorsev dorsev/CodecLaws.scala
Last active May 29, 2019

Embed
What would you like to do?
import $ivy.`org.reactivemongo::reactivemongo:0.16.0`
import $ivy.`com.typesafe.play::play-json:2.6.10`
import $ivy.`org.typelevel::cats:0.9.0`
import $ivy.`com.github.alexarchambault::scalacheck-shapeless_1.13:1.1.6`
import $ivy.`org.scalatest::scalatest:3.0.5`
import $ivy.`org.scalacheck::scalacheck:1.14.0`
import cats.{Applicative, Functor, Id}
import play.api.libs.json._
import reactivemongo.bson.{BSONDocument, BSONReader, BSONWriter}
import cats.laws.IsEq
import cats.kernel.Eq
import scala.language.higherKinds
import cats.laws._
import cats.laws.discipline._
import org.scalacheck.{Arbitrary, Gen, Prop}
import org.scalatest.FunSuiteLike
import org.scalatest.prop.Checkers
import org.typelevel.discipline.scalatest.Discipline
import play.api.libs.json.JsResult.applicativeJsResult
import org.scalacheck.ScalacheckShapeless._
trait CodecLaws[F[_], A, B] {
def serialize: A => B
def deserialize: B => F[A]
def codecRoundTrip(a: A)(implicit applicative: Applicative[F]): IsEq[F[A]] =
serialize.andThen(deserialize)(a) <-> Applicative[F].pure(a)
}
object CodecLaws {
def apply[F[_], A, B](implicit read: B => F[A], write: A => B): CodecLaws[F, A, B] =
new CodecLaws[F, A, B] {
override def serialize: A => B = write
override def deserialize: B => F[A] = read
}
}
trait CodecTests[F[_], A, B] extends org.typelevel.discipline.Laws {
def laws: CodecLaws[F, A, B]
def tests(implicit arbitrary: Arbitrary[A], eqA: Eq[F[A]], applicative: Applicative[F]): RuleSet =
new DefaultRuleSet(
name = "codec tests",
parent = None,
"roundTrip" -> Prop.forAll { a: A =>
laws.codecRoundTrip(a)
}
)
}
object BSONCodecTests {
def apply[A: Arbitrary](
implicit
reader: BSONReader[BSONDocument, A],
writer: BSONWriter[A, BSONDocument]
): CodecTests[Id, A, BSONDocument] =
new CodecTests[Id, A, BSONDocument] {
override def laws: CodecLaws[Id, A, BSONDocument] =
CodecLaws[Id, A, BSONDocument](reader.read, writer.write)
}
}
object JsonCodecTests {
def apply[A: Arbitrary](implicit format: OFormat[A]): CodecTests[JsResult, A, JsValue] =
new CodecTests[JsResult, A, JsValue] {
override def laws: CodecLaws[JsResult, A, JsValue] =
CodecLaws[JsResult, A, JsValue](format.reads, format.writes)
}
}
trait CodecImplicits {
import cats.Eq
implicit val jsResultApplicative: Applicative[JsResult] = new Applicative[JsResult] {
override def pure[A](x: A): JsResult[A] = applicativeJsResult.pure(x)
override def ap[A, B](ff: JsResult[A => B])(fa: JsResult[A]): JsResult[B] =
applicativeJsResult.apply(ff, fa)
}
implicit def eq[T]: Eq[T] = Eq.fromUniversalEquals
implicit def arb[T](implicit gen: Gen[T]): Arbitrary[T] = Arbitrary(gen)
}
trait CodecSpec extends Checkers with CodecImplicits with FunSuiteLike with Discipline
case class Person(name: String, age: Int)
object BsonCodecSpec {
implicit val personBsonFormat
: BSONReader[BSONDocument, Person] with BSONWriter[Person, BSONDocument] =
new BSONReader[BSONDocument, Person] with BSONWriter[Person, BSONDocument] {
override def read(bson: BSONDocument): Person =
Person(bson.getAs[String]("name").get, bson.getAs[Int]("age").get)
override def write(t: Person): BSONDocument =
BSONDocument("name" -> t.name, "age" -> t.age)
}
}
class BsonCodecSpec extends CodecSpec {
import BsonCodecSpec.personBsonFormat
checkAll("PersonBSONCodecTests", BSONCodecTests[Person].tests)
}
object JsonCodecSpec {
implicit val expressionFormatter =
new OFormat[Person] {
override def reads(json: JsValue): JsResult[Person] = {
val name = (json \ "name").as[String]
val age = (json \ "age").as[Int]
JsSuccess(Person(name, age))
}
override def writes(o: Person): JsObject =
JsObject(Map("name" -> JsString(o.name), "age" -> JsNumber(o.age)))
}
}
class JsonCodecSpec extends CodecSpec {
import JsonCodecSpec._
checkAll("PersonJSONCodecTests", JsonCodecTests[Person].tests)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.