From 9a8e69f1424fe55d409e5c022bc4e2435e5a4d82 Mon Sep 17 00:00:00 2001 From: Yummy-Yums <77977520+Yummy-Yums@users.noreply.github.com> Date: Tue, 17 Sep 2024 15:46:01 +0000 Subject: [PATCH 1/4] zio-schema initial push --- zio-quickstart-schema/build.sbt | 16 +++ .../src/main/resources/incoming_data.txt | 32 +++++ .../scala/dev/zio/quickstart/Person.scala | 46 ++++++++ .../dev/zio/quickstart/PrepareDataUtils.scala | 47 ++++++++ .../dev/zio/quickstart/SchemaValidator.scala | 44 +++++++ .../zio/quickstart/SchemaValidatorSpec.scala | 110 ++++++++++++++++++ 6 files changed, 295 insertions(+) create mode 100644 zio-quickstart-schema/build.sbt create mode 100644 zio-quickstart-schema/src/main/resources/incoming_data.txt create mode 100644 zio-quickstart-schema/src/main/scala/dev/zio/quickstart/Person.scala create mode 100644 zio-quickstart-schema/src/main/scala/dev/zio/quickstart/PrepareDataUtils.scala create mode 100644 zio-quickstart-schema/src/main/scala/dev/zio/quickstart/SchemaValidator.scala create mode 100644 zio-quickstart-schema/src/test/scala/dev/zio/quickstart/SchemaValidatorSpec.scala diff --git a/zio-quickstart-schema/build.sbt b/zio-quickstart-schema/build.sbt new file mode 100644 index 0000000..07a3e43 --- /dev/null +++ b/zio-quickstart-schema/build.sbt @@ -0,0 +1,16 @@ +scalaVersion := "2.13.12" +organization := "dev.zio" +name := "zio-quickstart-restful-schema" + + +libraryDependencies ++= Seq( + "dev.zio" %% "zio-schema" % "1.4.1", + "dev.zio" %% "zio-schema-zio-test" % "1.4.1", + "dev.zio" %% "zio-schema-derivation" % "1.4.1", + "org.scala-lang" % "scala-reflect" % scalaVersion.value % "provided", + "dev.zio" %% "zio-test" % "2.1.9" % Test, + "dev.zio" %% "zio-test-sbt" % "2.1.9" % Test, + "dev.zio" %% "zio-test-magnolia" % "2.1.9" % Test +) + +resolvers ++= Resolver.sonatypeOssRepos("snapshots") \ No newline at end of file diff --git a/zio-quickstart-schema/src/main/resources/incoming_data.txt b/zio-quickstart-schema/src/main/resources/incoming_data.txt new file mode 100644 index 0000000..db9a23d --- /dev/null +++ b/zio-quickstart-schema/src/main/resources/incoming_data.txt @@ -0,0 +1,32 @@ +Name,Email,Date of birth +Rosalia Marini,annamaria26@example.org,2021-10-29 +Michela Rizzo-Traetta,pisaroniantonietta@example.com,1955-07-05 +Gianpaolo Nibali,donatogagliardi@example.com,1995-06-08 +Orlando Gradenigo,rcatenazzi@example.org,1970-09-07 +Pasqual Disdero-Verri,rosaria88@example.com,1929-11-25 +Alphons Amato,stefano58@example.net,1963-04-30 +Annetta Balbi,federica81@example.org,1986-09-23 +Sig. Ricciotti Gigli,bianchiflavia@example.net,1930-03-16 +Isabella Petrocelli,francesca93@example.org,1972-09-27 +Carolina Veneziano-Giovine,giulio81@example.net,1916-03-27 +Melina Respighi,ippaziodisdero@example.org,2013-11-04 +Dott. Mario Crespi,giampieroravaglioli@example.com,1920-02-23 +Sig.ra Annunziata Sforza,ftomei@example.org,1952-08-27 +Dott. Greca Paganini,eleanora72@example.org,1995-08-10 +Virginia Draghi,krinaldi@example.org,1960-06-30 +Ivo Magrassi-Ginese,qvarano@example.com,1961-07-13 +Ferdinando Prodi,ybompiani@example.org,2019-02-17 +Lucrezia Lucciano,tcarullo@example.com,1968-07-20 +Germana Favata,marazzililiana@example.org,1983-10-12 +Gioffre Sagnelli,ucontarini@example.org,2010-02-05 +Elladio Garibaldi-Iannuzzi,farnesevirgilio@example.net,2002-06-02 +Leopoldo Donarelli-Pagliaro,bettinaparri@example.net,1934-08-31 +Dott. Cirillo Caetani,antoniorosselli@example.net,2001-08-27 +Rosalia Roncalli-Gangemi,turatieugenia@example.com,1986-06-15 +Rembrandt Briccialdi,imelda80@example.net,1965-07-18 +Gianmarco Fanucci,ermenegildoprati@example.net,1962-09-12 +Francesca Callegari,ferruccicostanzo@example.org,1917-09-09 +Sig.ra Daria Nordio,donatozarlino@example.org,1911-01-24 +Sig.ra Vanessa Cremonesi,virginia96@example.net,2008-05-20 +Dott. Durante Treccani,borghesegionata@example.com,1933-07-12 +Adelasia Satriani,rubertoarnaldo@example.com,1970-07-29 \ No newline at end of file diff --git a/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/Person.scala b/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/Person.scala new file mode 100644 index 0000000..794c9cb --- /dev/null +++ b/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/Person.scala @@ -0,0 +1,46 @@ +package dev.zio.quickstart + +import zio._ +import zio.schema._ +import zio.schema.annotation.validate +import zio.schema.validation.Validation +import zio.stream.ZStream + +// one can choose the detailed way of validation or use annotations + +case class Person( + name: String, + @validate(Validation.greaterThan(18)) + age: Int) + +object Person { + implicit val schema : Schema[Person] = DeriveSchema.gen +} + + +// one can choose the detailed way of validation or use annotations + +//case class Person(name: String, age: Int) + +// object Person { +// implicit val schema: Schema[Person] = CaseClass2( +// id0 = TypeId.fromTypeName("Person"), +// field01 = Schema.Field( +// name0 = "name", +// schema0 = Schema[String], +// validation0 = Validation.minLength(15), +// get0 = (p: Person) => p.name, +// set0 = { (p: Person, s: String) => p.copy(name = s) } +// ), +// field02 = Schema.Field( +// name0 = "age", +// schema0 = Schema[Int], +// validation0 = Validation.greaterThan(18), +// get0 = (p: Person) => p.age, +// set0 = { (p: Person, age: Int) => p.copy(age = age) } +// ), +// construct0 = (name, age) => Person(name, age), +// ) + +// } + diff --git a/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/PrepareDataUtils.scala b/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/PrepareDataUtils.scala new file mode 100644 index 0000000..fce02da --- /dev/null +++ b/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/PrepareDataUtils.scala @@ -0,0 +1,47 @@ +package dev.zio.quickstart + +import zio._ + +import java.time.LocalDate +import java.io.{File, FileNotFoundException, IOException} +import scala.io.{BufferedSource, Source} +import zio.schema + +object PrepareDataUtils { + + val homeDirectory = java.lang.System.getProperty("user.home") + val fileName = homeDirectory + "/Desktop/centre/scala/zio-quickstarts/zio-quickstart-schema/src/main/resources/incoming_data.txt" + + + def openFile(name: String): IO[IOException, BufferedSource] = + ZIO.attemptBlockingIO(Source.fromFile(name)) + + def closeFile(bufferedSourceFile: BufferedSource): ZIO[Any, Nothing, Unit] = + ZIO.succeedBlocking(bufferedSourceFile.close()) + + def withFile[A](name: String)(useFile: BufferedSource => Task[A]): Task[A] = + ZIO.acquireReleaseWith(openFile(name))(closeFile)(useFile) + + def prepareData(bufferedSourceFile: BufferedSource): List[Person] = { + + def getPerson: Iterator[Person] = for { + line <- bufferedSourceFile.getLines().filter(incomingString => !incomingString.contains("Name")) + arr = line.split(",") + dob = arr.reverse.head + name = arr.head + age = getAge(dob) + person = Person(name, age) + } yield person + + getPerson.toList + + } + + def getAge(dob: String): Int = { + val currYear = LocalDate.now().getYear + currYear - LocalDate.parse(dob).getYear + } + +} + + diff --git a/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/SchemaValidator.scala b/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/SchemaValidator.scala new file mode 100644 index 0000000..fd74eac --- /dev/null +++ b/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/SchemaValidator.scala @@ -0,0 +1,44 @@ +package dev.zio.quickstart + +import zio._ +import zio.schema._ +import zio.schema.annotation.validate +import zio.schema.validation.Validation +import zio.stream.ZStream + + +object SchemaValidator extends ZIOAppDefault { + + import PrepareDataUtils._ + + val res = withFile(fileName)(file => ZIO.attempt(prepareData(file))) + + val runStream = ZStream + .fromIterableZIO(res) + .map{ person => + Person.schema.validate(person) match { + case Chunk() => Right(person) + case Chunk(_) | Chunk(_, _) => Left(person) + } + + } + + val b = runStream + .runFold((List.empty[Person], List.empty[String])) { + case ((valid, invalid), Right(person)) => (valid :+ person, invalid) // Collect valid persons + case ((valid, invalid), Left(error)) => (valid, invalid :+ error.toString) // Collect errors + } + + + def run= + program + + + val program = + for { + count <- runStream.runFold(0)((accum, _) => accum + 1) + c <- b + _ <- Console.printLine(s"Total count: ${c._2.size}") + } yield () + +} diff --git a/zio-quickstart-schema/src/test/scala/dev/zio/quickstart/SchemaValidatorSpec.scala b/zio-quickstart-schema/src/test/scala/dev/zio/quickstart/SchemaValidatorSpec.scala new file mode 100644 index 0000000..ea5d208 --- /dev/null +++ b/zio-quickstart-schema/src/test/scala/dev/zio/quickstart/SchemaValidatorSpec.scala @@ -0,0 +1,110 @@ +package dev.zio.quickstart + +import PrepareDataUtils._ +import zio.stream.ZStream +import zio.test._ +import zio.{Chunk, ZIO} + + +object StreamWithSchemaValidation { + private val res = withFile(fileName)(file => ZIO.attempt(prepareData(file))) + + private val runStream = ZStream + .fromIterableZIO(res) + .map{ person => + val validatedRecord = Person.schema.validate(person) + + validatedRecord match { + case Chunk() => Right(person) + case Chunk(_) | Chunk(_,_) => Left(person) + } + } + + def getTotalNumberofPeopleAbove18: ZIO[Any, Throwable, Int] = + runStream + .runFold(0)((acc, x) => + x match { + case Right(_) => acc + 1 + case Left(_) => acc + } + ) + + def getTotalNumberofRecords: ZIO[Any, Throwable, Int] = + runStream + .runCount + .map(_.toInt) + + def getPeopleBelow18: ZIO[Any, Throwable, Int] = + for { + peopleAbove18 <- getTotalNumberofPeopleAbove18 + totalNumber <- getTotalNumberofRecords + } yield totalNumber - peopleAbove18 + + + def getFirstPersonAbove18: ZIO[Any, Throwable, List[Person]] = + runStream + .runFold(List.empty[Person]){ + case (resList, Right(person)) => resList :+ person + case (resList, Left(_)) => resList + } + + def getFirstPersonbelow18: ZIO[Any, Throwable, List[Person]] = + runStream + .runFold(List.empty[Person]){ + case (resList, Left(person)) => resList :+ person + case (resList, Right(_)) => resList + } + +} + + +object SchemaValidatorSpec extends ZIOSpecDefault { + + object Answers { + val firstFiveNamesOfPeopleAbove18: List[Person] = List( + Person("Michela Rizzo-Traetta", 69), + Person("Gianpaolo Nibali", 29), + Person("Orlando Gradenigo", 54), + Person("Pasqual Disdero-Verri", 95), + Person("Alphons Amato", 61) + ) + + val firstFiveNamesOfPeoplebelow18 = List( + Person("Rosalia Marini", 3), + Person("Melina Respighi", 11), + Person("Ferdinando Prodi", 5), + Person("Gioffre Sagnelli", 14), + Person("Sig.ra Vanessa Cremonesi", 16) + ) + + } + def spec: Spec[Any, Throwable] = suite("ExampleSpec")( + test("Get total number of people"){ + assertZIO(StreamWithSchemaValidation.getTotalNumberofRecords)(Assertion.equalTo(31)) + }, + test("Get people above the age of 18"){ + for { + count <- StreamWithSchemaValidation.getTotalNumberofPeopleAbove18 + } yield assertTrue(count == 26) + }, + test("First name in the stream"){ + for { + persons <- StreamWithSchemaValidation.getFirstPersonAbove18 + } yield assertTrue(persons.head == Answers.firstFiveNamesOfPeopleAbove18.head) + }, + test("First five names of persons above 18"){ + for { + persons <- StreamWithSchemaValidation.getFirstPersonAbove18 + } yield assertTrue(persons.take(5) == Answers.firstFiveNamesOfPeopleAbove18) + }, + test("First name of person below 18 is Rosalia Marina"){ + for { + persons <- StreamWithSchemaValidation.getFirstPersonbelow18 + nameOfFirstPerson = persons.head.name + mustBeRosalia = Answers.firstFiveNamesOfPeoplebelow18.head.name + + } yield assertTrue(nameOfFirstPerson == mustBeRosalia) + } + + ) +} From a4c26c6d43258b8c1caa96a8fc74cb3b527811fc Mon Sep 17 00:00:00 2001 From: Yummy-Yums <77977520+Yummy-Yums@users.noreply.github.com> Date: Wed, 18 Sep 2024 13:01:51 +0000 Subject: [PATCH 2/4] fixed comments on PR --- .../scala/dev/zio/quickstart/Person.scala | 37 +---- .../dev/zio/quickstart/PersonWithEmail.scala | 30 ++++ .../dev/zio/quickstart/PrepareDataUtils.scala | 36 ++-- .../dev/zio/quickstart/SchemaValidator.scala | 8 +- .../zio/quickstart/SchemaValidatorSpec.scala | 155 +++++++++++++----- 5 files changed, 177 insertions(+), 89 deletions(-) create mode 100644 zio-quickstart-schema/src/main/scala/dev/zio/quickstart/PersonWithEmail.scala diff --git a/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/Person.scala b/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/Person.scala index 794c9cb..786218d 100644 --- a/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/Person.scala +++ b/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/Person.scala @@ -1,46 +1,15 @@ package dev.zio.quickstart -import zio._ import zio.schema._ import zio.schema.annotation.validate import zio.schema.validation.Validation -import zio.stream.ZStream // one can choose the detailed way of validation or use annotations -case class Person( - name: String, +case class Person(name: String, @validate(Validation.greaterThan(18)) age: Int) object Person { - implicit val schema : Schema[Person] = DeriveSchema.gen -} - - -// one can choose the detailed way of validation or use annotations - -//case class Person(name: String, age: Int) - -// object Person { -// implicit val schema: Schema[Person] = CaseClass2( -// id0 = TypeId.fromTypeName("Person"), -// field01 = Schema.Field( -// name0 = "name", -// schema0 = Schema[String], -// validation0 = Validation.minLength(15), -// get0 = (p: Person) => p.name, -// set0 = { (p: Person, s: String) => p.copy(name = s) } -// ), -// field02 = Schema.Field( -// name0 = "age", -// schema0 = Schema[Int], -// validation0 = Validation.greaterThan(18), -// get0 = (p: Person) => p.age, -// set0 = { (p: Person, age: Int) => p.copy(age = age) } -// ), -// construct0 = (name, age) => Person(name, age), -// ) - -// } - + implicit val schema: Schema[Person] = DeriveSchema.gen +} \ No newline at end of file diff --git a/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/PersonWithEmail.scala b/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/PersonWithEmail.scala new file mode 100644 index 0000000..9df4683 --- /dev/null +++ b/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/PersonWithEmail.scala @@ -0,0 +1,30 @@ +package dev.zio.quickstart + +import zio._ +import zio.schema.Schema.CaseClass1 +import zio.schema._ +import zio.schema.annotation.validate +import zio.schema.validation._ +import zio.stream.ZStream + +case class PersonWithEmail(email: String) + +object PersonWithEmail { + + val localPart = Regex.letter.atLeast(3) + val tld = (Regex.literal("org") | Regex.literal("net") | Regex.literal("com")) // Matches top-level domains (2 or more letters) + val regexValidator = localPart ~ Regex.digit.atLeast(1) ~ Regex.literal("@") ~ Regex.letter.atLeast(3) ~ Regex.literal(".") ~ tld + + implicit val schema: Schema[PersonWithEmail] = CaseClass1( + id0 = TypeId.fromTypeName("PersonWithEmail"), + field0 = Schema.Field( + name0 = "email", + schema0 = Schema[String], + validation0 = Validation.regex(regexValidator), + get0 = (p: PersonWithEmail) => p.email, + set0 = { (p: PersonWithEmail, s: String) => p.copy(email = s) } + ), + defaultConstruct0 = email => PersonWithEmail(email), + ) + +} \ No newline at end of file diff --git a/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/PrepareDataUtils.scala b/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/PrepareDataUtils.scala index fce02da..a8d6753 100644 --- a/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/PrepareDataUtils.scala +++ b/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/PrepareDataUtils.scala @@ -9,34 +9,46 @@ import zio.schema object PrepareDataUtils { - val homeDirectory = java.lang.System.getProperty("user.home") - val fileName = homeDirectory + "/Desktop/centre/scala/zio-quickstarts/zio-quickstart-schema/src/main/resources/incoming_data.txt" - + val resourceName = "incoming_data.txt" def openFile(name: String): IO[IOException, BufferedSource] = - ZIO.attemptBlockingIO(Source.fromFile(name)) + ZIO.attemptBlockingIO(Source.fromResource(name)) def closeFile(bufferedSourceFile: BufferedSource): ZIO[Any, Nothing, Unit] = ZIO.succeedBlocking(bufferedSourceFile.close()) def withFile[A](name: String)(useFile: BufferedSource => Task[A]): Task[A] = ZIO.acquireReleaseWith(openFile(name))(closeFile)(useFile) - - def prepareData(bufferedSourceFile: BufferedSource): List[Person] = { + def getNameAndAgeOfPerson(bufferedSourceFile: BufferedSource): List[Person] = { def getPerson: Iterator[Person] = for { - line <- bufferedSourceFile.getLines().filter(incomingString => !incomingString.contains("Name")) - arr = line.split(",") - dob = arr.reverse.head - name = arr.head - age = getAge(dob) - person = Person(name, age) + line <- bufferedSourceFile.getLines().filter(incomingString => !incomingString.contains("Name")) + list = line.split(",") + dob = list.reverse.head + name = list.head + age = getAge(dob) + person = Person(name, age) } yield person getPerson.toList } + def getEmailOfPerson(bufferedSourceFile: BufferedSource): List[PersonWithEmail] = { + + def getPersonWithEmail: Iterator[PersonWithEmail] = for { + line <- bufferedSourceFile.getLines().filter(incomingString => !incomingString.contains("Name")) + arr = line.split(",") + emaii = arr(1) + + + personWithEmail = PersonWithEmail(emaii) + } yield personWithEmail + + getPersonWithEmail.toList + + } + def getAge(dob: String): Int = { val currYear = LocalDate.now().getYear currYear - LocalDate.parse(dob).getYear diff --git a/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/SchemaValidator.scala b/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/SchemaValidator.scala index fd74eac..1cd256c 100644 --- a/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/SchemaValidator.scala +++ b/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/SchemaValidator.scala @@ -11,7 +11,7 @@ object SchemaValidator extends ZIOAppDefault { import PrepareDataUtils._ - val res = withFile(fileName)(file => ZIO.attempt(prepareData(file))) + val res = withFile(resourceName)(file => ZIO.attempt(getNameAndAgeOfPerson(file))) val runStream = ZStream .fromIterableZIO(res) @@ -23,21 +23,21 @@ object SchemaValidator extends ZIOAppDefault { } - val b = runStream + val listOfValidPersons = runStream .runFold((List.empty[Person], List.empty[String])) { case ((valid, invalid), Right(person)) => (valid :+ person, invalid) // Collect valid persons case ((valid, invalid), Left(error)) => (valid, invalid :+ error.toString) // Collect errors } - def run= + def run= program val program = for { count <- runStream.runFold(0)((accum, _) => accum + 1) - c <- b + c <- listOfValidPersons _ <- Console.printLine(s"Total count: ${c._2.size}") } yield () diff --git a/zio-quickstart-schema/src/test/scala/dev/zio/quickstart/SchemaValidatorSpec.scala b/zio-quickstart-schema/src/test/scala/dev/zio/quickstart/SchemaValidatorSpec.scala index ea5d208..74b8c78 100644 --- a/zio-quickstart-schema/src/test/scala/dev/zio/quickstart/SchemaValidatorSpec.scala +++ b/zio-quickstart-schema/src/test/scala/dev/zio/quickstart/SchemaValidatorSpec.scala @@ -7,10 +7,11 @@ import zio.{Chunk, ZIO} object StreamWithSchemaValidation { - private val res = withFile(fileName)(file => ZIO.attempt(prepareData(file))) + private val listOfNameAndAgeOfPersons = withFile(resourceName)(file => ZIO.attempt(getNameAndAgeOfPerson(file))) + private val listOfEmailOfPersons = withFile(resourceName)(file => ZIO.attempt(getEmailOfPerson(file))) - private val runStream = ZStream - .fromIterableZIO(res) + private val streamOfPersonWithNameAndAge = ZStream + .fromIterableZIO(listOfNameAndAgeOfPersons) .map{ person => val validatedRecord = Person.schema.validate(person) @@ -20,8 +21,19 @@ object StreamWithSchemaValidation { } } + private val streamOfPersonWithEmail = ZStream + .fromIterableZIO(listOfEmailOfPersons) + .map{ person => + val validatedRecord = PersonWithEmail.schema.validate(person) + + validatedRecord match { + case Chunk() => Right(person) + case Chunk(_) | Chunk(_,_) => Left(person) + } + } + def getTotalNumberofPeopleAbove18: ZIO[Any, Throwable, Int] = - runStream + streamOfPersonWithNameAndAge .runFold(0)((acc, x) => x match { case Right(_) => acc + 1 @@ -30,7 +42,7 @@ object StreamWithSchemaValidation { ) def getTotalNumberofRecords: ZIO[Any, Throwable, Int] = - runStream + streamOfPersonWithNameAndAge .runCount .map(_.toInt) @@ -40,27 +52,53 @@ object StreamWithSchemaValidation { totalNumber <- getTotalNumberofRecords } yield totalNumber - peopleAbove18 - def getFirstPersonAbove18: ZIO[Any, Throwable, List[Person]] = - runStream + streamOfPersonWithNameAndAge .runFold(List.empty[Person]){ case (resList, Right(person)) => resList :+ person case (resList, Left(_)) => resList } - def getFirstPersonbelow18: ZIO[Any, Throwable, List[Person]] = - runStream + def getFirstPersonBelow18: ZIO[Any, Throwable, List[Person]] = + streamOfPersonWithNameAndAge .runFold(List.empty[Person]){ case (resList, Left(person)) => resList :+ person case (resList, Right(_)) => resList } -} + def validEmailsTotal: ZIO[Any, Throwable, Int] = + streamOfPersonWithEmail + .runFold(0)((accum, res) => { + res match { + case Left(_) => accum + case Right(_) => accum + 1 + } + }) + def invalidEmailsTotal: ZIO[Any, Throwable, Int] = { + for { + res <- streamOfPersonWithEmail.runCollect + total = res.size + validEmailsTotal <- validEmailsTotal + } yield total - validEmailsTotal + } + + def listOfValidEmail = { + for { + list <- streamOfPersonWithEmail.runFold(List.empty[String])((list, res) => { + res match { + case Left(_) => list + case Right(elem) => list :+ elem.email + } + }) + } yield list + } -object SchemaValidatorSpec extends ZIOSpecDefault { +} - object Answers { + +object ExamplesSpec extends ZIOSpecDefault { + object PersonsData { val firstFiveNamesOfPeopleAbove18: List[Person] = List( Person("Michela Rizzo-Traetta", 69), Person("Gianpaolo Nibali", 29), @@ -77,34 +115,73 @@ object SchemaValidatorSpec extends ZIOSpecDefault { Person("Sig.ra Vanessa Cremonesi", 16) ) + val listOfValidEmails = List( + "annamaria26@example.org", + "rosaria88@example.com", + "stefano58@example.net", + "federica81@example.org", + "francesca93@example.org", + "giulio81@example.net", + "eleanora72@example.org", + "imelda80@example.net", + "virginia96@example.net" + ) + } - def spec: Spec[Any, Throwable] = suite("ExampleSpec")( - test("Get total number of people"){ - assertZIO(StreamWithSchemaValidation.getTotalNumberofRecords)(Assertion.equalTo(31)) - }, - test("Get people above the age of 18"){ - for { - count <- StreamWithSchemaValidation.getTotalNumberofPeopleAbove18 - } yield assertTrue(count == 26) - }, - test("First name in the stream"){ - for { - persons <- StreamWithSchemaValidation.getFirstPersonAbove18 - } yield assertTrue(persons.head == Answers.firstFiveNamesOfPeopleAbove18.head) - }, - test("First five names of persons above 18"){ - for { - persons <- StreamWithSchemaValidation.getFirstPersonAbove18 - } yield assertTrue(persons.take(5) == Answers.firstFiveNamesOfPeopleAbove18) - }, - test("First name of person below 18 is Rosalia Marina"){ - for { - persons <- StreamWithSchemaValidation.getFirstPersonbelow18 - nameOfFirstPerson = persons.head.name - mustBeRosalia = Answers.firstFiveNamesOfPeoplebelow18.head.name - - } yield assertTrue(nameOfFirstPerson == mustBeRosalia) - } + def spec: Spec[Any, Throwable] = suite("ExampleSpec")( + test("Total number of people is 31"){ + assertZIO(StreamWithSchemaValidation.getTotalNumberofRecords)(Assertion.equalTo(31)) + }, + test("Number of persons above the age 18 is 26"){ + for { + count <- StreamWithSchemaValidation.getTotalNumberofPeopleAbove18 + } yield assertTrue(count == 26) + }, + test("Number of persons below the age 18 is 5"){ + for { + count <- StreamWithSchemaValidation.getPeopleBelow18 + } yield assertTrue(count == 5) + }, + test("First name in the stream of persons Above 18 is Michela Rizzo-Traetta"){ + for { + persons <- StreamWithSchemaValidation.getFirstPersonAbove18 + } yield assertTrue(persons.head == PersonsData.firstFiveNamesOfPeopleAbove18.head) + }, + test("First five names of persons above 18"){ + for { + persons <- StreamWithSchemaValidation.getFirstPersonAbove18 + } yield assertTrue(persons.take(5) == PersonsData.firstFiveNamesOfPeopleAbove18) + }, + test("First name of person below 18 is Rosalia Marina"){ + for { + persons <- StreamWithSchemaValidation.getFirstPersonBelow18 + nameOfFirstPerson = persons.head.name + mustBeRosalia = PersonsData.firstFiveNamesOfPeoplebelow18.head.name + + } yield assertTrue(nameOfFirstPerson == mustBeRosalia) + }, + test("Total number of valid emails ( valid emails must have numbers before the '@'"){ + for { + total <- StreamWithSchemaValidation.validEmailsTotal + } yield assertTrue(total == 9) + }, + test("Total number of invalid emails"){ + for { + total <- StreamWithSchemaValidation.invalidEmailsTotal + } yield assertTrue(total == 22) + }, + test("first valid email is annamaria26@example.org"){ + for { + emails <- StreamWithSchemaValidation.listOfValidEmail + firstEmail = emails.head + } yield assertTrue(firstEmail == PersonsData.listOfValidEmails.head) + }, + test("valid emails list must match the one provided"){ + for { + emails <- StreamWithSchemaValidation.listOfValidEmail + + } yield assertTrue(emails == PersonsData.listOfValidEmails, emails.size == 9) + } ) } From 51a7cf02570f068da9c2ff5a75be988bcb538a83 Mon Sep 17 00:00:00 2001 From: Yummy-Yums <77977520+Yummy-Yums@users.noreply.github.com> Date: Fri, 27 Sep 2024 16:24:14 +0000 Subject: [PATCH 3/4] updated readme and build.sbt --- README.md | 1 + build.sbt | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c7b2538..2990290 100644 --- a/README.md +++ b/README.md @@ -38,3 +38,4 @@ $ sbt run - [ZIO STM](zio-quickstart-stm) - many thanks to [@jorge-vasquez-2301](https://github.com/jorge-vasquez-2301) and his [article](https://scalac.io/blog/how-to-write-a-completely-lock-free-concurrent-lru-cache-with-zio-stm/) for this example - [ZIO SQL](zio-quickstart-sql) - [ZIO Streams](zio-quickstart-streams) +- [ZIO Schema](zio-quickstart-schema) diff --git a/build.sbt b/build.sbt index 7c02c9e..95e2cb4 100644 --- a/build.sbt +++ b/build.sbt @@ -39,7 +39,8 @@ lazy val root = `zio-quickstart-cache`, `zio-quickstart-prelude`, `zio-quickstart-stm`, - `zio-quickstart-sql` + `zio-quickstart-sql`, + `zio-quickstart-schema`, ) .settings( testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") @@ -62,3 +63,4 @@ lazy val `zio-quickstart-cache` = project lazy val `zio-quickstart-prelude` = project lazy val `zio-quickstart-stm` = project lazy val `zio-quickstart-sql` = project +lazy val `zio-quickstart-schema` = project \ No newline at end of file From 268141ef78cc1557cef75163fbef7e1f9feaa9e8 Mon Sep 17 00:00:00 2001 From: Yummy-Yums <77977520+Yummy-Yums@users.noreply.github.com> Date: Fri, 27 Sep 2024 17:12:12 +0000 Subject: [PATCH 4/4] run scalafmt and fixed ci errors --- build.sbt | 4 +- zio-quickstart-schema/build.sbt | 19 ++-- .../scala/dev/zio/quickstart/Person.scala | 12 ++- .../dev/zio/quickstart/PersonWithEmail.scala | 13 ++- .../dev/zio/quickstart/PrepareDataUtils.scala | 31 +++--- .../dev/zio/quickstart/SchemaValidator.scala | 26 ++--- .../zio/quickstart/SchemaValidatorSpec.scala | 96 +++++++++++-------- 7 files changed, 113 insertions(+), 88 deletions(-) diff --git a/build.sbt b/build.sbt index 95e2cb4..55f0d18 100644 --- a/build.sbt +++ b/build.sbt @@ -40,7 +40,7 @@ lazy val root = `zio-quickstart-prelude`, `zio-quickstart-stm`, `zio-quickstart-sql`, - `zio-quickstart-schema`, + `zio-quickstart-schema` ) .settings( testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") @@ -63,4 +63,4 @@ lazy val `zio-quickstart-cache` = project lazy val `zio-quickstart-prelude` = project lazy val `zio-quickstart-stm` = project lazy val `zio-quickstart-sql` = project -lazy val `zio-quickstart-schema` = project \ No newline at end of file +lazy val `zio-quickstart-schema` = project diff --git a/zio-quickstart-schema/build.sbt b/zio-quickstart-schema/build.sbt index 07a3e43..b4e6c36 100644 --- a/zio-quickstart-schema/build.sbt +++ b/zio-quickstart-schema/build.sbt @@ -1,16 +1,15 @@ scalaVersion := "2.13.12" organization := "dev.zio" -name := "zio-quickstart-restful-schema" - +name := "zio-quickstart-schema" libraryDependencies ++= Seq( - "dev.zio" %% "zio-schema" % "1.4.1", - "dev.zio" %% "zio-schema-zio-test" % "1.4.1", - "dev.zio" %% "zio-schema-derivation" % "1.4.1", - "org.scala-lang" % "scala-reflect" % scalaVersion.value % "provided", - "dev.zio" %% "zio-test" % "2.1.9" % Test, - "dev.zio" %% "zio-test-sbt" % "2.1.9" % Test, - "dev.zio" %% "zio-test-magnolia" % "2.1.9" % Test + "dev.zio" %% "zio-schema" % "1.4.1", + "dev.zio" %% "zio-schema-zio-test" % "1.4.1", + "dev.zio" %% "zio-schema-derivation" % "1.4.1", + "org.scala-lang" % "scala-reflect" % scalaVersion.value % "provided", + "dev.zio" %% "zio-test" % "2.1.9" % Test, + "dev.zio" %% "zio-test-sbt" % "2.1.9" % Test, + "dev.zio" %% "zio-test-magnolia" % "2.1.9" % Test ) -resolvers ++= Resolver.sonatypeOssRepos("snapshots") \ No newline at end of file +resolvers ++= Resolver.sonatypeOssRepos("snapshots") diff --git a/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/Person.scala b/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/Person.scala index 786218d..094c730 100644 --- a/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/Person.scala +++ b/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/Person.scala @@ -6,10 +6,12 @@ import zio.schema.validation.Validation // one can choose the detailed way of validation or use annotations -case class Person(name: String, - @validate(Validation.greaterThan(18)) - age: Int) +case class Person( + name: String, + @validate(Validation.greaterThan(18)) + age: Int +) object Person { - implicit val schema: Schema[Person] = DeriveSchema.gen -} \ No newline at end of file + implicit val schema: Schema[Person] = DeriveSchema.gen +} diff --git a/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/PersonWithEmail.scala b/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/PersonWithEmail.scala index 9df4683..7e690ba 100644 --- a/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/PersonWithEmail.scala +++ b/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/PersonWithEmail.scala @@ -12,8 +12,13 @@ case class PersonWithEmail(email: String) object PersonWithEmail { val localPart = Regex.letter.atLeast(3) - val tld = (Regex.literal("org") | Regex.literal("net") | Regex.literal("com")) // Matches top-level domains (2 or more letters) - val regexValidator = localPart ~ Regex.digit.atLeast(1) ~ Regex.literal("@") ~ Regex.letter.atLeast(3) ~ Regex.literal(".") ~ tld + val tld = + (Regex.literal("org") | Regex.literal("net") | Regex.literal( + "com" + )) // Matches top-level domains (2 or more letters) + val regexValidator = localPart ~ Regex.digit.atLeast(1) ~ Regex.literal( + "@" + ) ~ Regex.letter.atLeast(3) ~ Regex.literal(".") ~ tld implicit val schema: Schema[PersonWithEmail] = CaseClass1( id0 = TypeId.fromTypeName("PersonWithEmail"), @@ -24,7 +29,7 @@ object PersonWithEmail { get0 = (p: PersonWithEmail) => p.email, set0 = { (p: PersonWithEmail, s: String) => p.copy(email = s) } ), - defaultConstruct0 = email => PersonWithEmail(email), + defaultConstruct0 = email => PersonWithEmail(email) ) -} \ No newline at end of file +} diff --git a/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/PrepareDataUtils.scala b/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/PrepareDataUtils.scala index a8d6753..26bb38c 100644 --- a/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/PrepareDataUtils.scala +++ b/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/PrepareDataUtils.scala @@ -19,14 +19,18 @@ object PrepareDataUtils { def withFile[A](name: String)(useFile: BufferedSource => Task[A]): Task[A] = ZIO.acquireReleaseWith(openFile(name))(closeFile)(useFile) - def getNameAndAgeOfPerson(bufferedSourceFile: BufferedSource): List[Person] = { + def getNameAndAgeOfPerson( + bufferedSourceFile: BufferedSource + ): List[Person] = { def getPerson: Iterator[Person] = for { - line <- bufferedSourceFile.getLines().filter(incomingString => !incomingString.contains("Name")) - list = line.split(",") - dob = list.reverse.head - name = list.head - age = getAge(dob) + line <- bufferedSourceFile + .getLines() + .filter(incomingString => !incomingString.contains("Name")) + list = line.split(",") + dob = list.reverse.head + name = list.head + age = getAge(dob) person = Person(name, age) } yield person @@ -34,14 +38,17 @@ object PrepareDataUtils { } - def getEmailOfPerson(bufferedSourceFile: BufferedSource): List[PersonWithEmail] = { + def getEmailOfPerson( + bufferedSourceFile: BufferedSource + ): List[PersonWithEmail] = { def getPersonWithEmail: Iterator[PersonWithEmail] = for { - line <- bufferedSourceFile.getLines().filter(incomingString => !incomingString.contains("Name")) - arr = line.split(",") + line <- bufferedSourceFile + .getLines() + .filter(incomingString => !incomingString.contains("Name")) + arr = line.split(",") emaii = arr(1) - personWithEmail = PersonWithEmail(emaii) } yield personWithEmail @@ -54,6 +61,4 @@ object PrepareDataUtils { currYear - LocalDate.parse(dob).getYear } -} - - +} diff --git a/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/SchemaValidator.scala b/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/SchemaValidator.scala index 1cd256c..ac39357 100644 --- a/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/SchemaValidator.scala +++ b/zio-quickstart-schema/src/main/scala/dev/zio/quickstart/SchemaValidator.scala @@ -6,38 +6,38 @@ import zio.schema.annotation.validate import zio.schema.validation.Validation import zio.stream.ZStream - object SchemaValidator extends ZIOAppDefault { import PrepareDataUtils._ - val res = withFile(resourceName)(file => ZIO.attempt(getNameAndAgeOfPerson(file))) + val res = + withFile(resourceName)(file => ZIO.attempt(getNameAndAgeOfPerson(file))) val runStream = ZStream .fromIterableZIO(res) - .map{ person => + .map { person => Person.schema.validate(person) match { - case Chunk() => Right(person) + case Chunk() => Right(person) case Chunk(_) | Chunk(_, _) => Left(person) } } - val listOfValidPersons = runStream + val listOfValidPersons = runStream .runFold((List.empty[Person], List.empty[String])) { - case ((valid, invalid), Right(person)) => (valid :+ person, invalid) // Collect valid persons - case ((valid, invalid), Left(error)) => (valid, invalid :+ error.toString) // Collect errors + case ((valid, invalid), Right(person)) => + (valid :+ person, invalid) // Collect valid persons + case ((valid, invalid), Left(error)) => + (valid, invalid :+ error.toString) // Collect errors } - - def run= - program - + def run = + program val program = for { - count <- runStream.runFold(0)((accum, _) => accum + 1) - c <- listOfValidPersons + count <- runStream.runFold(0)((accum, _) => accum + 1) + c <- listOfValidPersons _ <- Console.printLine(s"Total count: ${c._2.size}") } yield () diff --git a/zio-quickstart-schema/src/test/scala/dev/zio/quickstart/SchemaValidatorSpec.scala b/zio-quickstart-schema/src/test/scala/dev/zio/quickstart/SchemaValidatorSpec.scala index 74b8c78..0620543 100644 --- a/zio-quickstart-schema/src/test/scala/dev/zio/quickstart/SchemaValidatorSpec.scala +++ b/zio-quickstart-schema/src/test/scala/dev/zio/quickstart/SchemaValidatorSpec.scala @@ -5,30 +5,31 @@ import zio.stream.ZStream import zio.test._ import zio.{Chunk, ZIO} - object StreamWithSchemaValidation { - private val listOfNameAndAgeOfPersons = withFile(resourceName)(file => ZIO.attempt(getNameAndAgeOfPerson(file))) - private val listOfEmailOfPersons = withFile(resourceName)(file => ZIO.attempt(getEmailOfPerson(file))) + private val listOfNameAndAgeOfPersons = + withFile(resourceName)(file => ZIO.attempt(getNameAndAgeOfPerson(file))) + private val listOfEmailOfPersons = + withFile(resourceName)(file => ZIO.attempt(getEmailOfPerson(file))) private val streamOfPersonWithNameAndAge = ZStream .fromIterableZIO(listOfNameAndAgeOfPersons) - .map{ person => + .map { person => val validatedRecord = Person.schema.validate(person) validatedRecord match { - case Chunk() => Right(person) - case Chunk(_) | Chunk(_,_) => Left(person) + case Chunk() => Right(person) + case Chunk(_) | Chunk(_, _) => Left(person) } } private val streamOfPersonWithEmail = ZStream .fromIterableZIO(listOfEmailOfPersons) - .map{ person => + .map { person => val validatedRecord = PersonWithEmail.schema.validate(person) validatedRecord match { - case Chunk() => Right(person) - case Chunk(_) | Chunk(_,_) => Left(person) + case Chunk() => Right(person) + case Chunk(_) | Chunk(_, _) => Left(person) } } @@ -37,40 +38,39 @@ object StreamWithSchemaValidation { .runFold(0)((acc, x) => x match { case Right(_) => acc + 1 - case Left(_) => acc + case Left(_) => acc } ) def getTotalNumberofRecords: ZIO[Any, Throwable, Int] = - streamOfPersonWithNameAndAge - .runCount + streamOfPersonWithNameAndAge.runCount .map(_.toInt) def getPeopleBelow18: ZIO[Any, Throwable, Int] = for { peopleAbove18 <- getTotalNumberofPeopleAbove18 - totalNumber <- getTotalNumberofRecords + totalNumber <- getTotalNumberofRecords } yield totalNumber - peopleAbove18 def getFirstPersonAbove18: ZIO[Any, Throwable, List[Person]] = streamOfPersonWithNameAndAge - .runFold(List.empty[Person]){ + .runFold(List.empty[Person]) { case (resList, Right(person)) => resList :+ person - case (resList, Left(_)) => resList + case (resList, Left(_)) => resList } def getFirstPersonBelow18: ZIO[Any, Throwable, List[Person]] = streamOfPersonWithNameAndAge - .runFold(List.empty[Person]){ + .runFold(List.empty[Person]) { case (resList, Left(person)) => resList :+ person - case (resList, Right(_)) => resList + case (resList, Right(_)) => resList } def validEmailsTotal: ZIO[Any, Throwable, Int] = streamOfPersonWithEmail .runFold(0)((accum, res) => { res match { - case Left(_) => accum + case Left(_) => accum case Right(_) => accum + 1 } }) @@ -78,25 +78,26 @@ object StreamWithSchemaValidation { def invalidEmailsTotal: ZIO[Any, Throwable, Int] = { for { res <- streamOfPersonWithEmail.runCollect - total = res.size + total = res.size validEmailsTotal <- validEmailsTotal } yield total - validEmailsTotal } def listOfValidEmail = { for { - list <- streamOfPersonWithEmail.runFold(List.empty[String])((list, res) => { - res match { - case Left(_) => list - case Right(elem) => list :+ elem.email + list <- streamOfPersonWithEmail.runFold(List.empty[String])( + (list, res) => { + res match { + case Left(_) => list + case Right(elem) => list :+ elem.email + } } - }) + ) } yield list } } - object ExamplesSpec extends ZIOSpecDefault { object PersonsData { val firstFiveNamesOfPeopleAbove18: List[Person] = List( @@ -115,7 +116,7 @@ object ExamplesSpec extends ZIOSpecDefault { Person("Sig.ra Vanessa Cremonesi", 16) ) - val listOfValidEmails = List( + val listOfValidEmails = List( "annamaria26@example.org", "rosaria88@example.com", "stefano58@example.net", @@ -130,58 +131,71 @@ object ExamplesSpec extends ZIOSpecDefault { } def spec: Spec[Any, Throwable] = suite("ExampleSpec")( - test("Total number of people is 31"){ - assertZIO(StreamWithSchemaValidation.getTotalNumberofRecords)(Assertion.equalTo(31)) + test("Total number of people is 31") { + assertZIO(StreamWithSchemaValidation.getTotalNumberofRecords)( + Assertion.equalTo(31) + ) }, - test("Number of persons above the age 18 is 26"){ + test("Number of persons above the age 18 is 26") { for { count <- StreamWithSchemaValidation.getTotalNumberofPeopleAbove18 } yield assertTrue(count == 26) }, - test("Number of persons below the age 18 is 5"){ + test("Number of persons below the age 18 is 5") { for { count <- StreamWithSchemaValidation.getPeopleBelow18 } yield assertTrue(count == 5) }, - test("First name in the stream of persons Above 18 is Michela Rizzo-Traetta"){ + test( + "First name in the stream of persons Above 18 is Michela Rizzo-Traetta" + ) { for { persons <- StreamWithSchemaValidation.getFirstPersonAbove18 - } yield assertTrue(persons.head == PersonsData.firstFiveNamesOfPeopleAbove18.head) + } yield assertTrue( + persons.head == PersonsData.firstFiveNamesOfPeopleAbove18.head + ) }, - test("First five names of persons above 18"){ + test("First five names of persons above 18") { for { persons <- StreamWithSchemaValidation.getFirstPersonAbove18 - } yield assertTrue(persons.take(5) == PersonsData.firstFiveNamesOfPeopleAbove18) + } yield assertTrue( + persons.take(5) == PersonsData.firstFiveNamesOfPeopleAbove18 + ) }, - test("First name of person below 18 is Rosalia Marina"){ + test("First name of person below 18 is Rosalia Marina") { for { persons <- StreamWithSchemaValidation.getFirstPersonBelow18 nameOfFirstPerson = persons.head.name - mustBeRosalia = PersonsData.firstFiveNamesOfPeoplebelow18.head.name + mustBeRosalia = PersonsData.firstFiveNamesOfPeoplebelow18.head.name } yield assertTrue(nameOfFirstPerson == mustBeRosalia) }, - test("Total number of valid emails ( valid emails must have numbers before the '@'"){ + test( + "Total number of valid emails ( valid emails must have numbers before the '@'" + ) { for { total <- StreamWithSchemaValidation.validEmailsTotal } yield assertTrue(total == 9) }, - test("Total number of invalid emails"){ + test("Total number of invalid emails") { for { total <- StreamWithSchemaValidation.invalidEmailsTotal } yield assertTrue(total == 22) }, - test("first valid email is annamaria26@example.org"){ + test("first valid email is annamaria26@example.org") { for { emails <- StreamWithSchemaValidation.listOfValidEmail firstEmail = emails.head } yield assertTrue(firstEmail == PersonsData.listOfValidEmails.head) }, - test("valid emails list must match the one provided"){ + test("valid emails list must match the one provided") { for { emails <- StreamWithSchemaValidation.listOfValidEmail - } yield assertTrue(emails == PersonsData.listOfValidEmails, emails.size == 9) + } yield assertTrue( + emails == PersonsData.listOfValidEmails, + emails.size == 9 + ) } ) }