Skip to content

Commit

Permalink
feat: adding currency support (#680)
Browse files Browse the repository at this point in the history
* initial version

* initial test modifications

* fixing tests

* adding more tests and revising docs

* fixing formatting

* updating readme

* Revert "updating readme"

This reverts commit 77832e2.

* fixing tests

* fixing json codec test again

* removing json currency tests

* staging

* removing zone offset

* fixing null locale

* adding ci

* Revert "adding ci"

This reverts commit 197010c.

* removing comparison in currency standard type

* adding platform specific tests

* Revert "removing comparison in currency standard type"

This reverts commit de36381.

* fixing formatting and error handling

* fixing linting

* fixing currency tests

* adding docs

* fixing bug in patch law and adding tests

* revising doc generator and adding test types

* updating readme

* fixing default currency value

* revising docs

* finalizing after self-review

* fixing broken default currency test
  • Loading branch information
Andrapyre authored May 12, 2024
1 parent ad0275d commit 249058a
Show file tree
Hide file tree
Showing 36 changed files with 561 additions and 208 deletions.
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,17 @@ _ZIO Schema_ is used by a growing number of ZIO libraries, including [ZIO Flow](
In order to use this library, we need to add the following lines in our `build.sbt` file:

```scala
libraryDependencies += "dev.zio" %% "zio-schema" % "1.1.0"
libraryDependencies += "dev.zio" %% "zio-schema-avro" % "1.1.0"
libraryDependencies += "dev.zio" %% "zio-schema-bson" % "1.1.0"
libraryDependencies += "dev.zio" %% "zio-schema-json" % "1.1.0"
libraryDependencies += "dev.zio" %% "zio-schema-msg-pack" % "1.1.0"
libraryDependencies += "dev.zio" %% "zio-schema-protobuf" % "1.1.0"
libraryDependencies += "dev.zio" %% "zio-schema-thrift" % "1.1.0"
libraryDependencies += "dev.zio" %% "zio-schema-zio-test" % "1.1.0"
libraryDependencies += "dev.zio" %% "zio-schema" % "1.1.1"
libraryDependencies += "dev.zio" %% "zio-schema-avro" % "1.1.1"
libraryDependencies += "dev.zio" %% "zio-schema-bson" % "1.1.1"
libraryDependencies += "dev.zio" %% "zio-schema-json" % "1.1.1"
libraryDependencies += "dev.zio" %% "zio-schema-msg-pack" % "1.1.1"
libraryDependencies += "dev.zio" %% "zio-schema-protobuf" % "1.1.1"
libraryDependencies += "dev.zio" %% "zio-schema-thrift" % "1.1.1"
libraryDependencies += "dev.zio" %% "zio-schema-zio-test" % "1.1.1"

// Required for the automatic generic derivation of schemas
libraryDependencies += "dev.zio" %% "zio-schema-derivation" % "1.1.0"
libraryDependencies += "dev.zio" %% "zio-schema-derivation" % "1.1.1"
libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value % "provided"
```

Expand Down
4 changes: 4 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,10 @@ lazy val docs = project
mainModuleName := (zioSchemaJVM / moduleName).value,
projectStage := ProjectStage.Development,
ScalaUnidoc / unidoc / unidocProjectFilter := inProjects(),
mdoc := {
(Compile / run).evaluated
mdoc.evaluated
},
readmeContribution +=
"""|
|#### TL;DR
Expand Down
13 changes: 2 additions & 11 deletions docs/basic-building-blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,10 @@ To describe scalar data type `A`, we use the `Primitive[A]` data type which basi
case class Primitive[A](standardType: StandardType[A]) extends Schema[A]
```

Primitive values are represented using the `Primitive[A]` type class and represent the elements, that we cannot further define through other means. If we visualize our data structure as a tree, primitives are the leaves.
Primitive values are represented using the `Primitive[A]` type class and represent the elements that we cannot further define through other means. If we visualize our data structure as a tree, primitives are the leaves.

ZIO Schema provides a number of built-in primitive types, that we can use to represent our data. These can be found in the [`StandardType`](https://github.com/zio/zio-schema/blob/main/zio-schema/shared/src/main/scala/zio/schema/StandardType.scala) companion-object:
For a list of all standard types (and therefore primitive types) with built-in support, please see the [standard type reference](standard-type-reference.md)

```scala
sealed trait StandardType[A]
object StandardType {
implicit object UnitType extends StandardType[Unit]
implicit object StringType extends StandardType[String]
implicit object BoolType extends StandardType[Boolean]
// ...
}
```

Inside `Schema`'s companion object, we have an implicit conversion from `StandardType[A]` to `Schema[A]`:

Expand Down
1 change: 1 addition & 0 deletions docs/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const sidebars = {
"motivation",
"use-cases",
"basic-building-blocks",
"standard-type-reference",
{
type: "category",
label: "Writing Schema",
Expand Down
41 changes: 41 additions & 0 deletions docs/standard-type-reference.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
id: standard-type-reference
title: "Standard Type Reference"
---
# Standard Type Reference

ZIO Schema provides a number of built-in primitive types, that we can use to represent our data. These can be seen in the following table:

|Standard Type|JVM Support|ScalaJS Support|Scala Native Support|
|--------------|:--------------:|:--------------:|:--------------:|
|`Boolean`||||
|`Byte`||||
|`Char`||||
|`Chunk[Byte]`||||
|`Double`||||
|`Float`||||
|`Int`||||
|`Long`||||
|`Short`||||
|`String`||||
|`Unit`||||
|`java.math.BigDecimal`||||
|`java.math.BigInteger`||||
|`java.time.DayOfWeek`||||
|`java.time.Duration`||||
|`java.time.Instant`||||
|`java.time.LocalDate`||||
|`java.time.LocalDateTime`||||
|`java.time.LocalTime`||||
|`java.time.Month`||||
|`java.time.MonthDay`||||
|`java.time.OffsetDateTime`||||
|`java.time.OffsetTime`||||
|`java.time.Period`||||
|`java.time.Year`||||
|`java.time.YearMonth`||||
|`java.time.ZoneId`||||
|`java.time.ZoneOffset`||||
|`java.time.ZonedDateTime`||||
|`java.util.Currency`||||
|`java.util.UUID`||||
19 changes: 19 additions & 0 deletions tests/js/src/test/scala-2/zio/schema/PlatformSpecificGen.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package zio.schema

import zio.schema.SchemaGen.SchemaTest
import zio.schema.StandardTypeGen.StandardTypeAndGen
import zio.test.Gen

object PlatformSpecificGen {

val platformSpecificStandardTypes: Gen[Any, StandardType[_]] = Gen.fromIterable(
List.empty
)

def platformSpecificStandardTypeAndGen(standardTypeGen: StandardType[_]): StandardTypeAndGen[_] =
standardTypeGen match {
case _ => StandardType.UnitType -> Gen.unit: StandardTypeAndGen[_]
}

val platformSpecificSchemasAndGens: List[SchemaTest[_]] = List.empty
}
24 changes: 24 additions & 0 deletions tests/jvm/src/test/scala-2/zio/schema/PlatformSpecificGen.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package zio.schema

import zio.schema.SchemaGen.SchemaTest
import zio.schema.StandardTypeGen.StandardTypeAndGen
import zio.test.Gen

object PlatformSpecificGen {

val platformSpecificStandardTypes: Gen[Any, StandardType[_]] = Gen.fromIterable(
List(
StandardType.CurrencyType
)
)

def platformSpecificStandardTypeAndGen(standardTypeGen: StandardType[_]): StandardTypeAndGen[_] =
standardTypeGen match {
case typ: StandardType.CurrencyType.type => typ -> Gen.currency
case _ => StandardType.UnitType -> Gen.unit: StandardTypeAndGen[_]
}

val platformSpecificSchemasAndGens: List[SchemaTest[_]] = List(
SchemaTest("Currency", StandardType.CurrencyType, Gen.currency)
)
}
19 changes: 19 additions & 0 deletions tests/native/src/test/scala-2/zio/schema/PlatformSpecificGen.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package zio.schema

import zio.schema.SchemaGen.SchemaTest
import zio.schema.StandardTypeGen.StandardTypeAndGen
import zio.test.Gen

object PlatformSpecificGen {

val platformSpecificStandardTypes: Gen[Any, StandardType[_]] = Gen.fromIterable(
List.empty
)

def platformSpecificStandardTypeAndGen(standardTypeGen: StandardType[_]): StandardTypeAndGen[_] =
standardTypeGen match {
case _ => StandardType.UnitType -> Gen.unit: StandardTypeAndGen[_]
}

val platformSpecificSchemasAndGens: List[SchemaTest[_]] = List.empty
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ object DynamicValueGen {
case typ: StandardType.ZoneOffsetType.type => gen(typ, JavaTimeGen.anyZoneOffset)
case typ: StandardType.UnitType.type => Gen.const(DynamicValue.Primitive((), typ))
case typ: StandardType.UUIDType.type => gen(typ, Gen.uuid)
case typ: StandardType.CurrencyType.type => gen(typ, Gen.currency)
}
}

Expand Down
158 changes: 72 additions & 86 deletions tests/shared/src/test/scala-2/zio/schema/PatchSpec.scala
Original file line number Diff line number Diff line change
@@ -1,29 +1,25 @@
package zio.schema

import zio.schema.StandardType._
import zio.schema.DeriveSchema.gen
import zio.schema.SchemaGen.{ SchemaTest, schemasAndGens }
import zio.schema.types.Arities._
import zio.schema.types.{ Arities, Recursive }
import zio.test.Assertion._
import zio.test._
import zio.{ Chunk, Scope, URIO }
import zio.{ Scope, URIO }

object PatchSpec extends ZIOSpecDefault {

def spec: Spec[TestEnvironment with Scope, Any] = suite("PatchSpec")(
suite("identity law")(
suite("standard types")(
test("Int")(patchIdentityLaw[Int]),
test("Long")(patchIdentityLaw[Long]),
test("Float")(patchIdentityLaw[Float]),
test("Double")(patchIdentityLaw[Double]),
test("Boolean")(patchIdentityLaw[Boolean]),
test("Bytes")(patchIdentityLaw[Chunk[Byte]]),
suite("Either") {
test("primitive")(patchIdentityLaw[Either[String, String]])
},
suite("Option") {
test("primitive")(patchIdentityLaw[Option[String]])
}
schemaPatchIdentityLawTests()
),
suite("option")(
schemaPatchIdentityLawTests(Some(schema => Schema.option(schema)))
),
suite("either")(
schemaPatchIdentityLawTests(Some(schema => Schema.either(schema, schema)), Some(name => s"Either[$name,$name]"))
),
suite("records")(
test("singleton")(patchIdentityLaw[Singleton.type]),
Expand All @@ -39,56 +35,17 @@ object PatchSpec extends ZIOSpecDefault {
),
suite("patch law")(
suite("standard types")(
test("Int")(patchLaw[Int]),
test("Long")(patchLaw[Long]),
test("Float")(patchLaw[Float]),
test("Double")(patchLaw[Double]),
test("Boolean")(patchLaw[Boolean]),
test("String")(patchLaw[String]),
test("ZonedDateTime")(patchLaw[java.time.ZonedDateTime]),
test("OffsetDateTime")(patchLaw[java.time.OffsetDateTime]),
test("OffsetTime")(patchLaw[java.time.OffsetTime]),
test("LocalTime")(patchLaw[java.time.LocalTime]),
test("LocalDate")(patchLaw[java.time.LocalDate]),
test("Instant")(patchLaw[java.time.Instant]),
test("Duration")(patchLaw[java.time.Duration]),
test("ZoneOffset")(patchLaw[java.time.ZoneOffset]),
test("ZoneId")(patchLaw[java.time.ZoneId]),
test("YearMonth")(patchLaw[java.time.YearMonth]),
test("Year")(patchLaw[java.time.Year]),
test("Period")(patchLaw[java.time.Period]),
test("MonthDay")(patchLaw[java.time.MonthDay]) @@ TestAspect.ignore, // TODO Leap years!
test("Month")(patchLaw[java.time.Month]),
test("DayOfWeek")(patchLaw[java.time.DayOfWeek]),
test("BigInteger")(patchLaw[java.math.BigInteger]),
test("BigDecimal")(patchLaw[java.math.BigDecimal]),
test("Bytes")(patchLaw[Chunk[Byte]])
schemaPatchLawTests()
),
suite("option")(
schemaPatchLawTests(Some(schema => Schema.option(schema)))
),
suite("sequences")(
suite("either")(
schemaPatchLawTests(Some(schema => Schema.either(schema, schema)), Some(name => s"Either[$name,$name]"))
),
suite("lists")(
suite("of standard types")(
test("Int")(patchLaw[List[Int]]),
test("Long")(patchLaw[List[Long]]),
test("Float")(patchLaw[List[Float]]),
test("Double")(patchLaw[List[Double]]),
test("Boolean")(patchLaw[List[Boolean]]),
test("String")(patchLaw[List[String]]),
test("ZonedDateTime")(patchLaw[List[java.time.ZonedDateTime]]),
test("OffsetDateTime")(patchLaw[List[java.time.OffsetDateTime]]),
test("OffsetTime")(patchLaw[List[java.time.OffsetTime]]),
test("LocalTime")(patchLaw[List[java.time.LocalTime]]),
test("LocalDate")(patchLaw[List[java.time.LocalDate]]),
test("Instant")(patchLaw[List[java.time.Instant]]),
test("Duration")(patchLaw[List[java.time.Duration]]),
test("ZoneOffset")(patchLaw[List[java.time.ZoneOffset]]),
test("ZoneId")(patchLaw[List[java.time.ZoneId]]),
test("YearMonth")(patchLaw[List[java.time.YearMonth]]),
test("Year")(patchLaw[List[java.time.Year]]),
test("Period")(patchLaw[List[java.time.Period]]),
test("MonthDay")(patchLaw[List[java.time.MonthDay]]) @@ TestAspect.ignore, // TODO Leap years!
test("Month")(patchLaw[List[java.time.Month]]),
test("DayOfWeek")(patchLaw[List[java.time.DayOfWeek]]),
test("BigInteger")(patchLaw[List[java.math.BigInteger]]),
test("BigDecimal")(patchLaw[List[java.math.BigDecimal]])
schemaPatchLawTests(Some((primitiveSchema => Schema.list(primitiveSchema))))
),
suite("of records")(
test("Dog")(patchLaw[List[Pet.Dog]])
Expand All @@ -98,31 +55,21 @@ object PatchSpec extends ZIOSpecDefault {
test("recursive")(patchLaw[List[Recursive]])
)
),
suite("vectors")(
suite("of standard types")(
schemaPatchLawTests(Some((primitiveSchema => Schema.vector(primitiveSchema))))
),
suite("of records")(
test("Dog")(patchLaw[Vector[Pet.Dog]])
),
suite("of enumerations")(
test("Pet")(patchLaw[Vector[Pet]]),
test("recursive")(patchLaw[Vector[Recursive]])
)
),
suite("sets")(
suite("of standard types")(
test("Int")(patchLaw[Set[Int]]),
test("Long")(patchLaw[Set[Long]]),
test("Float")(patchLaw[Set[Float]]),
test("Double")(patchLaw[Set[Double]]),
test("Boolean")(patchLaw[Set[Boolean]]),
test("String")(patchLaw[Set[String]]),
test("ZonedDateTime")(patchLaw[Set[java.time.ZonedDateTime]]),
test("OffsetDateTime")(patchLaw[Set[java.time.OffsetDateTime]]),
test("OffsetTime")(patchLaw[Set[java.time.OffsetTime]]),
test("LocalTime")(patchLaw[Set[java.time.LocalTime]]),
test("LocalDate")(patchLaw[Set[java.time.LocalDate]]),
test("Instant")(patchLaw[Set[java.time.Instant]]),
test("Duration")(patchLaw[Set[java.time.Duration]]),
test("ZoneOffset")(patchLaw[Set[java.time.ZoneOffset]]),
test("ZoneId")(patchLaw[Set[java.time.ZoneId]]),
test("YearMonth")(patchLaw[Set[java.time.YearMonth]]),
test("Year")(patchLaw[Set[java.time.Year]]),
test("Period")(patchLaw[Set[java.time.Period]]),
test("MonthDay")(patchLaw[Set[java.time.MonthDay]]) @@ TestAspect.ignore, // TODO Leap years!
test("Month")(patchLaw[Set[java.time.Month]]),
test("DayOfWeek")(patchLaw[Set[java.time.DayOfWeek]]),
test("BigInteger")(patchLaw[Set[java.math.BigInteger]]),
test("BigDecimal")(patchLaw[Set[java.math.BigDecimal]])
schemaPatchLawTests(Some((primitiveSchema => Schema.set(primitiveSchema))))
),
suite("of records")(
test("Dog")(patchLaw[Set[Pet.Dog]])
Expand All @@ -134,7 +81,10 @@ object PatchSpec extends ZIOSpecDefault {
),
suite("maps")(
suite("of standard types")(
test("Int -> Int")(patchLaw[Map[Int, Int]])
schemaPatchLawTests(
Some((primitiveSchema => Schema.map(primitiveSchema, primitiveSchema))),
Some(name => (s"$name -> $name"))
)
),
suite("of records")(
test("Int -> Dog")(patchLaw[Map[Int, Pet.Dog]]),
Expand Down Expand Up @@ -173,6 +123,42 @@ object PatchSpec extends ZIOSpecDefault {
assertTrue(schema.diff(a, a).isIdentical)
}

private def schemaPatchIdentityLawTests(
schemaConversionFuncOption: Option[Schema[_] => Schema[_]] = None,
renamingFuncOption: Option[String => String] = None
): List[Spec[Sized with TestConfig, Nothing]] =
schemaPatchTests(schema => patchIdentityLaw(schema), schemaConversionFuncOption, renamingFuncOption)

private def schemaPatchLawTests(
schemaConversionFuncOption: Option[Schema[_] => Schema[_]] = None,
renamingFuncOption: Option[String => String] = None
): List[Spec[Sized with TestConfig, Nothing]] =
schemaPatchTests(schema => patchLaw(schema), schemaConversionFuncOption, renamingFuncOption)

private def schemaPatchTests(
patchingFunc: Schema[_] => URIO[Sized with TestConfig, TestResult],
schemaConversionFuncOption: Option[Schema[_] => Schema[_]],
renamingFuncOption: Option[String => String]
): List[Spec[Sized with TestConfig, Nothing]] =
schemasAndGens.map {
case SchemaTest(name, standardType, _) =>
val primitiveSchema = Schema.primitive(standardType)
val finalSchema = schemaConversionFuncOption.fold[Schema[_]](primitiveSchema) { schemaConversionFunc =>
schemaConversionFunc(primitiveSchema)
}
val finalTestName = renamingFuncOption.fold(name)(renamingFunc => renamingFunc(name))
standardType match {
case _ @StandardType.MonthDayType =>
test(finalTestName) {
patchingFunc(finalSchema)
} @@ TestAspect.ignore // TODO Leap years!
case _ =>
test(finalTestName) {
patchingFunc(finalSchema)
}
}
}

private def patchLaw[A](implicit schema: Schema[A]): URIO[Sized with TestConfig, TestResult] = {
val gen = DeriveGen.gen[A]
check(gen <*> gen) {
Expand Down
Loading

0 comments on commit 249058a

Please sign in to comment.