Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
237 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,4 @@ | ||
*.class | ||
*.log | ||
target/ | ||
project/target |
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,11 @@ | ||
dist: trusty | ||
language: scala | ||
jdk: openjdk8 | ||
scala: | ||
- 2.12.3 | ||
|
||
script: | ||
- sbt ++$TRAVIS_SCALA_VERSION clean coverage test coverageReport | ||
|
||
after_success: | ||
- bash <(curl -s https://codecov.io/bash) |
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,70 @@ | ||
[![Build status](https://api.travis-ci.org/btlines/play-json-refined.svg?branch=master)](https://travis-ci.org/btlines/play-json-refined) | ||
[![codecov](https://codecov.io/gh/btlines/play-json-refined/branch/master/graph/badge.svg)](https://codecov.io/gh/btlines/play-json-refined) | ||
[![License](https://img.shields.io/:license-MIT-blue.svg)](https://opensource.org/licenses/MIT) | ||
[![Download](https://api.bintray.com/packages/beyondthelines/maven/play-json-refined/images/download.svg) ](https://bintray.com/beyondthelines/maven/play-json-refined/_latestVersion) | ||
|
||
# Play JSON Refined | ||
|
||
A tiny library providing Json formats for [refined](https://github.com/fthomas/refined) types. | ||
|
||
## Context | ||
|
||
Refined types come in handy to limit the valid values accepted in a function. However as values are often not available at compile time you need to validate them as soon as they enter your application. | ||
|
||
In case of JSON inputs we need a format to convert from JSON to a refined type. (e.g. We want to read a non empty list or a positive integer directly from JSON). | ||
|
||
## Setup | ||
|
||
In order to use play-json-refined you need to add the following lines to your `build.sbt`: | ||
|
||
```scala | ||
resolvers += Resolver.bintrayRepo("beyondthelines", "maven") | ||
|
||
libraryDependencies += "beyondthelines" %% "play-json-refined" % "0.0.1" | ||
``` | ||
|
||
## Dependencies | ||
|
||
Play JSON Refined has only 2 dependencies: [Play-json](https://github.com/playframework/play-json) and [Refined](https://github.com/fthomas/refined). | ||
|
||
## Usage | ||
|
||
In order to use Play JSON with refined types you need to import the following: | ||
|
||
```scala | ||
import eu.timepit.refined.api.Refined | ||
import eu.timepit.refined.auto._ | ||
import play.api.libs.json._ | ||
import play.json.refined._ | ||
``` | ||
|
||
Importing play.json.refined._ adds implicit definitions to derive Json formats for refined types. | ||
|
||
## Example | ||
|
||
Let's take a basic example to illustrate the usage: | ||
|
||
```scala | ||
// import the refined that you need | ||
// here we use numeric and collection | ||
import eu.timepit.refined.collection._ | ||
import eu.timepit.refined.numeric._ | ||
|
||
type PosInt = Int Refined Positive | ||
type NonEmptyString = String Refined NonEmpty | ||
|
||
final case class Data( | ||
i: PosInt, | ||
s: NonEmptyString | ||
) | ||
|
||
implicit val dataFormat: OFormat[Data] = | ||
Json.format[Data] | ||
|
||
val data = Data(1, "a") | ||
// convert to JSON | ||
val json = Json.toJson(data) | ||
// convert from JSON | ||
val parsed = json.as[Data] | ||
``` | ||
|
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 @@ | ||
scalaVersion := "2.12.3" | ||
|
||
name := "play-json-refined" | ||
|
||
version := "0.0.1" | ||
|
||
organization := "beyondthelines" | ||
|
||
resolvers += Resolver.bintrayRepo("beyondthelines", "maven") | ||
|
||
libraryDependencies ++= Seq( | ||
"com.typesafe.play" %% "play-json" % "2.6.6", | ||
"eu.timepit" %% "refined" % "0.8.4", | ||
"org.scalatest" %% "scalatest" % "3.0.4" % "test", | ||
"org.scalacheck" %% "scalacheck" % "1.13.4" % "test" | ||
) | ||
|
||
licenses := ("MIT", url("http://opensource.org/licenses/MIT")) :: Nil | ||
|
||
bintrayOrganization := Some("beyondthelines") | ||
|
||
bintrayPackageLabels := Seq("scala", "json", "play", "refined") |
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,2 @@ | ||
sbt.version=1.0.2 | ||
|
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 @@ | ||
addSbtPlugin("org.foundweekends" % "sbt-bintray" % "0.5.1") | ||
|
||
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.5.1") |
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,29 @@ | ||
package play.json | ||
|
||
import eu.timepit.refined.api.{Refined, Validate} | ||
import eu.timepit.refined.refineV | ||
import play.api.libs.json.{JsError, JsSuccess, Reads, Writes} | ||
import play.api.libs.functional.syntax._ | ||
|
||
package object refined { | ||
|
||
implicit def refinedReads[T, P]( | ||
implicit reads: Reads[T], validate: Validate[T, P] | ||
): Reads[T Refined P] = | ||
Reads[T Refined P] { json => | ||
reads | ||
.reads(json) | ||
.flatMap { t: T => | ||
refineV[P](t) match { | ||
case Left(error) => JsError(error) | ||
case Right(value) => JsSuccess(value) | ||
} | ||
} | ||
} | ||
|
||
implicit def refinedWrites[T, P]( | ||
implicit writes: Writes[T] | ||
): Writes[T Refined P] = | ||
writes.contramap(_.value) | ||
|
||
} |
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,21 @@ | ||
package play.json.refined | ||
|
||
import eu.timepit.refined.api.{Refined, Validate} | ||
import eu.timepit.refined.refineV | ||
import org.scalacheck.{Arbitrary, Gen} | ||
|
||
object ArbitraryRefined { | ||
|
||
implicit def refinedArbitrary[T, P]( | ||
implicit arbitrary: Arbitrary[T], validate: Validate[T, P] | ||
): Arbitrary[T Refined P] = | ||
Arbitrary( | ||
arbitrary.arbitrary.flatMap { t: T => | ||
refineV[P](t) match { | ||
case Right(value) => Gen.const(value) | ||
case _ => Gen.fail | ||
} | ||
} | ||
) | ||
|
||
} |
77 changes: 77 additions & 0 deletions
77
src/test/scala/play/json/refined/PlayJsonRefinedSpec.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,77 @@ | ||
package play.json.refined | ||
|
||
import eu.timepit.refined.api.Refined | ||
import eu.timepit.refined.auto._ | ||
import eu.timepit.refined.collection.NonEmpty | ||
import eu.timepit.refined.numeric.Positive | ||
import org.scalatest.{Matchers, WordSpec} | ||
import org.scalatest.prop.GeneratorDrivenPropertyChecks._ | ||
import play.api.libs.json.{JsError, JsSuccess, Json, OFormat} | ||
import org.scalacheck.{Arbitrary, Gen} | ||
|
||
import ArbitraryRefined._ | ||
|
||
object PlayJsonRefinedSpec { | ||
type NonEmptyList[A] = List[A] Refined NonEmpty | ||
type NonEmptyString = String Refined NonEmpty | ||
type PosInt = Int Refined Positive | ||
|
||
final case class Data( | ||
s: NonEmptyString, | ||
n: PosInt, | ||
xs: NonEmptyList[Int] | ||
) | ||
|
||
implicit val dataArbitrary: Arbitrary[Data] = | ||
Arbitrary(Gen.resultOf(Data)) | ||
|
||
implicit val dataFormat: OFormat[Data] = Json.format[Data] | ||
} | ||
|
||
class PlayJsonRefinedSpec extends WordSpec with Matchers { | ||
|
||
import PlayJsonRefinedSpec._ | ||
|
||
"play.json.refined" should { | ||
"read non empty list" in forAll { list: List[String] => | ||
val json = Json.toJson(list) | ||
json.validate[NonEmptyList[String]] match { | ||
case JsSuccess(nel, _) => (nel: List[String]) shouldBe list | ||
case JsError(_) => list shouldBe empty | ||
} | ||
} | ||
"read non empty string" in forAll { s: String => | ||
val json = Json.toJson(s) | ||
json.validate[NonEmptyString] match { | ||
case JsSuccess(t, _) => (t: String) shouldBe s | ||
case JsError(_) => s shouldBe empty | ||
} | ||
} | ||
"read positive ints" in forAll { n: Int => | ||
val json = Json.toJson(n) | ||
json.validate[PosInt] match { | ||
case JsSuccess(m, _) => (m: Int) shouldBe n | ||
case JsError(_) => n shouldBe <= (0) | ||
} | ||
} | ||
|
||
"write non empty list" in forAll { list: NonEmptyList[String] => | ||
val json = Json.toJson(list) | ||
json.as[List[String]] shouldBe (list: List[String]) | ||
} | ||
"write non empty string" in forAll { s: NonEmptyString => | ||
val json = Json.toJson(s) | ||
json.as[String] shouldBe (s: String) | ||
} | ||
"write positive ints" in forAll { n: PosInt => | ||
val json = Json.toJson(n) | ||
json.as[Int] shouldBe (n: Int) | ||
} | ||
|
||
"format case classes with refined members" in forAll { data: Data => | ||
val json = Json.toJson(data) | ||
json.as[Data] shouldBe data | ||
} | ||
} | ||
|
||
} |