-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #74 from ulanzetz/master
Add tethys-refined module
- Loading branch information
Showing
5 changed files
with
191 additions
and
1 deletion.
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 |
---|---|---|
|
@@ -10,6 +10,7 @@ | |
.cache | ||
.history | ||
.lib | ||
.bsp | ||
|
||
# Intellij Idea | ||
*.iml | ||
|
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
33 changes: 33 additions & 0 deletions
33
modules/refined/src/main/scala/tethys/refined/TethysRefinedInstances.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,33 @@ | ||
package tethys.refined | ||
|
||
import eu.timepit.refined.api.{RefType, Validate} | ||
import tethys.readers.{FieldName, KeyReader, ReaderError} | ||
import tethys.readers.tokens.TokenIterator | ||
import tethys.{JsonReader, JsonWriter} | ||
|
||
trait TethysRefinedInstances { | ||
implicit final def RefinedJsonWriter[T: JsonWriter, P, F[_, _]: RefType]: JsonWriter[F[T, P]] = | ||
JsonWriter[T].contramap(RefType[F].unwrap) | ||
|
||
implicit final def RefinedJsonReader[T: JsonReader, P, F[_, _]: RefType]( | ||
implicit validate: Validate[T, P] | ||
): JsonReader[F[T, P]] = | ||
new JsonReader[F[T, P]] { | ||
override def read(it: TokenIterator)(implicit fieldName: FieldName): F[T, P] = | ||
fromEither(RefType[F].refine(JsonReader[T].read(it))) | ||
} | ||
|
||
implicit final def RefinedKeyReader[T, P, F[_, _]: RefType]( | ||
implicit reader: KeyReader[T], | ||
validate: Validate[T, P] | ||
): KeyReader[F[T, P]] = new KeyReader[F[T, P]] { | ||
override def read(s: String)(implicit fieldName: FieldName): F[T, P] = | ||
fromEither(RefType[F].refine(reader.read(s))) | ||
} | ||
|
||
private def fromEither[A](either: Either[String, A])(implicit fieldName: FieldName): A = | ||
either match { | ||
case Right(value) => value | ||
case Left(err) => ReaderError.wrongJson(s"Refined error: $err") | ||
} | ||
} |
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 @@ | ||
package tethys | ||
|
||
package object refined extends TethysRefinedInstances |
142 changes: 142 additions & 0 deletions
142
modules/refined/src/test/scala/tethys/refined/RefinedSupportTest.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,142 @@ | ||
package tethys.refined | ||
|
||
import eu.timepit.refined._ | ||
import eu.timepit.refined.api.Refined | ||
import eu.timepit.refined.collection.NonEmpty | ||
import eu.timepit.refined.numeric._ | ||
import eu.timepit.refined.string.IPv4 | ||
import eu.timepit.refined.types.numeric._ | ||
import org.scalatest.flatspec.AnyFlatSpec | ||
import org.scalatest.matchers.should.Matchers | ||
import tethys.commons.TokenNode.{value => token, _} | ||
import tethys.readers.ReaderError | ||
import tethys.writers.tokens.SimpleTokenWriter._ | ||
|
||
class RefinedSupportTest extends AnyFlatSpec with Matchers { | ||
val posInt: PosInt = refineMV[Positive](5) | ||
val nonNegInt: NonNegInt = refineMV[NonNegative](0) | ||
val negInt: NegInt = refineMV[Negative](-5) | ||
|
||
val posLong: PosLong = refineMV[Positive](5L) | ||
val nonNegLong: NonNegLong = refineMV[NonNegative](0L) | ||
val negLong: NegLong = refineMV[Negative](-5L) | ||
|
||
val posDouble: PosDouble = refineMV[Positive](5.0) | ||
val nonNegDouble: NonNegDouble = refineMV[NonNegative](0.0) | ||
val negDouble: NegDouble = refineMV[Negative](-5.0) | ||
val nonNaNDouble: NonNaNDouble = refineMV[NonNaN](5.0) | ||
|
||
val posFloat: PosFloat = refineMV[Positive](5.0f) | ||
val nonNegFloat: NonNegFloat = refineMV[NonNegative](0.0f) | ||
val negFloat: NegFloat = refineMV[Negative](-5.0f) | ||
val nonNaNFloat: NonNaNFloat = refineMV[NonNaN](5.0f) | ||
|
||
val ipV4: String Refined IPv4 = refineMV[IPv4]("192.168.0.1") | ||
|
||
val nel: List[String] Refined NonEmpty = | ||
refineV[NonEmpty](List("a", "b")).getOrElse(throw new RuntimeException("Empty list")) | ||
|
||
behavior of "RefinedJsonWriter" | ||
|
||
it should "work with numerics" in { | ||
posInt.asTokenList shouldBe token(5) | ||
nonNegInt.asTokenList shouldBe token(0) | ||
negInt.asTokenList shouldBe token(-5) | ||
|
||
posLong.asTokenList shouldBe token(5L) | ||
nonNegLong.asTokenList shouldBe token(0L) | ||
negLong.asTokenList shouldBe token(-5L) | ||
|
||
posDouble.asTokenList shouldBe token(5.0) | ||
nonNegDouble.asTokenList shouldBe token(0.0) | ||
negDouble.asTokenList shouldBe token(-5.0) | ||
nonNaNDouble.asTokenList shouldBe token(5.0) | ||
|
||
posFloat.asTokenList shouldBe token(5.0f) | ||
nonNegFloat.asTokenList shouldBe token(0.0f) | ||
negFloat.asTokenList shouldBe token(-5.0f) | ||
nonNaNFloat.asTokenList shouldBe token(5.0f) | ||
} | ||
|
||
it should "work with strings" in { | ||
ipV4.asTokenList shouldBe token("192.168.0.1") | ||
} | ||
|
||
it should "work with collections" in { | ||
nel.asTokenList shouldBe arr("a", "b") | ||
} | ||
|
||
behavior of "RefinedJsonReader" | ||
|
||
it should "work with numerics" in { | ||
// Int | ||
token(5).tokensAs[PosInt] shouldBe posInt | ||
assertThrows[ReaderError](token(0).tokensAs[PosInt]) | ||
|
||
token(0).tokensAs[NonNegInt] shouldBe nonNegInt | ||
assertThrows[ReaderError](token(-5).tokensAs[NonNegInt]) | ||
|
||
token(-5).tokensAs[NegInt] shouldBe negInt | ||
assertThrows[ReaderError](token(5).tokensAs[NegInt]) | ||
|
||
// Long | ||
token(5L).tokensAs[PosLong] shouldBe posLong | ||
assertThrows[ReaderError](token(0L).tokensAs[PosLong]) | ||
|
||
token(0L).tokensAs[NonNegLong] shouldBe nonNegLong | ||
assertThrows[ReaderError](token(-5L).tokensAs[NonNegLong]) | ||
|
||
token(-5L).tokensAs[NegLong] shouldBe negLong | ||
assertThrows[ReaderError](token(5L).tokensAs[NegLong]) | ||
|
||
// Double | ||
token(5.0).tokensAs[PosDouble] shouldBe posDouble | ||
assertThrows[ReaderError](token(0.0).tokensAs[PosDouble]) | ||
|
||
token(0.0).tokensAs[NonNegDouble] shouldBe nonNegDouble | ||
assertThrows[ReaderError](token(-5.0).tokensAs[NonNegDouble]) | ||
|
||
token(-5.0).tokensAs[NegDouble] shouldBe negDouble | ||
assertThrows[ReaderError](token(5.0).tokensAs[NegDouble]) | ||
|
||
token(5.0).tokensAs[NonNaNDouble] shouldBe nonNaNDouble | ||
assertThrows[ReaderError](token(Double.NaN).tokensAs[NonNaNDouble]) | ||
|
||
// Float | ||
token(5.0f).tokensAs[PosFloat] shouldBe posFloat | ||
assertThrows[ReaderError](token(0.0f).tokensAs[PosDouble]) | ||
|
||
token(0.0f).tokensAs[NonNegFloat] shouldBe nonNegFloat | ||
assertThrows[ReaderError](token(-5.0f).tokensAs[NonNegFloat]) | ||
|
||
token(-5.0f).tokensAs[NegFloat] shouldBe negFloat | ||
assertThrows[ReaderError](token(5.0f).tokensAs[NegDouble]) | ||
|
||
token(5.0f).tokensAs[NonNaNFloat] shouldBe nonNaNFloat | ||
assertThrows[ReaderError](token(Float.NaN).tokensAs[NonNaNDouble]) | ||
} | ||
|
||
it should "work with strings" in { | ||
token("192.168.0.1").tokensAs[String Refined IPv4] shouldBe ipV4 | ||
|
||
assertThrows[ReaderError](token("aaa").tokensAs[String Refined IPv4]) | ||
} | ||
|
||
it should "work with collections" in { | ||
arr("a", "b").tokensAs[List[String] Refined NonEmpty] shouldBe nel | ||
|
||
assertThrows[ReaderError](arr().tokensAs[List[String] Refined NonEmpty]) | ||
} | ||
|
||
behavior of "RefinedKeyReader" | ||
|
||
it should "work with refined strings" in { | ||
type Limits = Map[String Refined IPv4, Int] | ||
|
||
val limits: Limits = Map(refineMV[IPv4]("192.168.0.1") -> 1, refineMV[IPv4]("192.168.1.1") -> 2) | ||
|
||
obj("192.168.0.1" -> 1, "192.168.1.1" -> 2).tokensAs[Limits] shouldBe limits | ||
|
||
assertThrows[ReaderError](obj("192.168.0.1" -> 1, "192.168.256.1" -> 2).tokensAs[Limits]) | ||
} | ||
} |