Skip to content

Commit

Permalink
Merge pull request #48 from GrigoriiBerezin/add_zio_prelude_quickstart
Browse files Browse the repository at this point in the history
feat: add zio-prelude-quickstart
  • Loading branch information
khajavi authored Apr 27, 2024
2 parents 3bbb14d + 3b2514b commit a412a92
Show file tree
Hide file tree
Showing 5 changed files with 238 additions and 1 deletion.
4 changes: 3 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ lazy val root =
`zio-quickstart-kafka`,
`zio-quickstart-graphql-webservice`,
`zio-quickstart-streams`,
`zio-quickstart-encode-decode-json`
`zio-quickstart-encode-decode-json`,
`zio-quickstart-prelude`
)

lazy val `zio-quickstart-hello-world` = project
Expand All @@ -48,3 +49,4 @@ lazy val `zio-quickstart-graphql-webservice` = project
lazy val `zio-quickstart-streams` = project
lazy val `zio-quickstart-encode-decode-json` = project
lazy val `zio-quickstart-reloadable-services` = project
lazy val `zio-quickstart-prelude` = project
8 changes: 8 additions & 0 deletions zio-quickstart-prelude/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
scalaVersion := "2.13.13"

libraryDependencies ++= Seq(
"dev.zio" %% "zio-prelude" % "1.0.0-RC23",
"dev.zio" %% "zio-test" % "2.0.22" % Test,
"dev.zio" %% "zio-test-sbt" % "2.0.22" % Test,
"dev.zio" %% "zio-test-junit" % "2.0.22" % Test
)
54 changes: 54 additions & 0 deletions zio-quickstart-prelude/src/test/scala/AssociativeSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import zio._
import zio.test.assertTrue
import zio.prelude._
import zio.prelude.newtypes.Max
import zio.test.{Spec, TestEnvironment}
import zio.test.junit.JUnitRunnableSpec

object AssociativeSpec extends JUnitRunnableSpec {
object Topic extends Newtype[String]
type Topic = Topic.Type

object Votes extends Subtype[Int] {
implicit val Associative: Associative[Votes] = new Associative[Votes] {
override def combine(l: => Votes, r: => Votes): Votes = Votes(l + r)
}
}
type Votes = Votes.Type

override def spec: Spec[TestEnvironment with Scope, Any] =
suite("Associative")(
test("combine custom class") {

case class VoteMap(wrapped: Map[Topic, Votes])
object VoteMap {
implicit val Associative: Associative[VoteMap] =
new Associative[VoteMap] {
override def combine(l: => VoteMap, r: => VoteMap): VoteMap =
VoteMap(l.wrapped <> r.wrapped)
}
}

val vm1 = VoteMap(
Map(Topic("newType") -> Votes(3), Topic("associative") -> Votes(1))
)
val vm2 = VoteMap(
Map(Topic("associative") -> Votes(6), Topic("prelude") -> Votes(2))
)
val resultVm =
VoteMap(
Map(
Topic("newType") -> Votes(3),
Topic("associative") -> Votes(7),
Topic("prelude") -> Votes(2)
)
)
assertTrue(vm1 <> vm2 == resultVm)
},
test("combine as max using Max newtype") {
val rawValues = Seq(100, 262, 131, 66)
val maxValues: Seq[Max[Int]] = Max.wrapAll(rawValues)
assertTrue(maxValues.reduce(_ <> _) === rawValues.max)
}
)
}
50 changes: 50 additions & 0 deletions zio-quickstart-prelude/src/test/scala/NewTypeSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import zio._
import zio.test.assertTrue
import zio.prelude._
import zio.prelude.newtypes._
import zio.test.junit.JUnitRunnableSpec

object NewTypeSpec extends JUnitRunnableSpec {
override def spec = suite("Newtype")(
test("natural") {
// new types to increase the type safety without compromising performance or ergonomics
// Natural new type is an Int type with assertion on greater or equal to 0
val five: Validation[String, Int] = Natural.make(5)
val notNatural: Validation[String, Natural] = Natural.make(-2)

assertTrue(five.toOption.contains(5))
assertTrue(
notNatural == Validation.failNonEmptyChunk(
NonEmptyChunk("-2 did not satisfy greaterThanOrEqualTo(0)")
)
)
assertTrue(Natural.zero - Natural.one == -1) // unsafe manipulations
assertTrue(
Natural.minus(Natural.zero, Natural.one) == Natural.zero
) // safe manipulations
},
test("custom assertion") {
// you can define your own value type with assertion
object MyType extends Subtype[String] {
override def assertion: QuotedAssertion[String] = (value: String) =>
Either.cond(
value.startsWith("!"),
value,
AssertionError.failure(s"must start with exclamation mark")
)
}

val valid: Validation[String, String] = MyType.make("!Hello!")
val notValid: Validation[String, String] = MyType.make("NotValidString")

assertTrue(valid.toOption.contains("!Hello!"))
assertTrue(
notValid == Validation.failNonEmptyChunk(
NonEmptyChunk.single(
"NotValidString did not satisfy must start with exclamation mark"
)
)
)
}
)
}
123 changes: 123 additions & 0 deletions zio-quickstart-prelude/src/test/scala/ValidationSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import zio._
import zio.prelude._
import zio.test.{Spec, TestEnvironment, assertTrue}
import zio.test.junit.JUnitRunnableSpec

object ValidationSpec extends JUnitRunnableSpec {
override def spec: Spec[TestEnvironment with Scope, Any] =
suite("Validation")(
test("validate class") {
case class Person(name: String, age: Int)

def validateName(name: String): Validation[String, String] =
if (name.isEmpty) Validation.fail("Name was empty")
else Validation.succeed(name)

def validateAge(age: Int): Validation[String, Int] =
Validation.fromPredicateWith(s"Age $age was less than zero")(age)(
_ >= 0
)

def validatePerson(name: String, age: Int): Validation[String, Person] =
Validation.validateWith(validateName(name), validateAge(age))(
Person.apply
)

assertTrue(
validatePerson("Grisha", 25).toOption.contains(Person("Grisha", 25))
)
assertTrue(
validatePerson("", -5) == Validation.failNonEmptyChunk(
NonEmptyChunk("Name was empty", "Age -5 was less than zero")
)
)
},
test("validate newtype") {
object Name extends Subtype[String] {
override def assertion = assert(!Assertion.isEmptyString)
}
type Name = Name.Type
object Age extends Subtype[Int] {
override def assertion = assert(Assertion.greaterThanOrEqualTo(0))
}
type Age = Age.Type

case class Person(name: Name, age: Age)

def validatePerson(name: String, age: Int) =
Validation.validateWith(Name.make(name), Age.make(age))(Person.apply)

assertTrue(
validatePerson("Grisha", 25).toOption.contains(
Person(Name("Grisha"), Age(25))
)
)
assertTrue(
validatePerson("", -5) == Validation.failNonEmptyChunk(
NonEmptyChunk(
" did not satisfy hasLength(notEqualTo(0))",
"-5 did not satisfy greaterThanOrEqualTo(0)"
)
)
)
},
test("chaining validations") {
object Age extends Subtype[Int] {
override def assertion = assert(Assertion.greaterThanOrEqualTo(0))
}
type Age = Age.Type

def validateNonEmpty(
s: String
): Validation[String, NonEmptyList[String]] =
Validation.fromOptionWith(
"String must contain at least one value divided by space character"
)(NonEmptyList.fromIterableOption(s.split(" ")))

def validateInt(s: String): Validation[String, Int] =
Validation.fromOptionWith(s"String must be int like, but got $s")(
s.toIntOption
)

def validateAge(i: Int): Validation[String, Age] = Age.make(i)

def calculateResult(
line: String
): Validation[String, NonEmptyList[Age]] =
for {
strAges <- validateNonEmpty(line)
intAges <- Validation.validateAll(strAges.map(validateInt))
ages <- Validation.validateAll(intAges.map(validateAge))
} yield ages

val result1 = calculateResult("12 10 5")
val result2 = calculateResult("12 -5 -2")
val result3 = calculateResult("")
val result4 = calculateResult("12 _f")

assertTrue(
result1.toOption.get === NonEmptyList(Age(12), Age(10), Age(5))
)
assertTrue(
result2 == Validation.failNonEmptyChunk(
NonEmptyChunk(
"-5 did not satisfy greaterThanOrEqualTo(0)",
"-2 did not satisfy greaterThanOrEqualTo(0)"
)
)
)
assertTrue(
result3 == Validation.failNonEmptyChunk(
NonEmptyChunk.single(
"String must contain at least one value divided by space character"
)
)
)
assertTrue(
result4 == Validation.failNonEmptyChunk(
NonEmptyChunk.single("String must be int like, but got _f")
)
)
}
)
}

0 comments on commit a412a92

Please sign in to comment.