Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add defaultValue to Schema #80

Merged
merged 11 commits into from
Nov 15, 2021
Merged

Conversation

IMax153
Copy link
Contributor

@IMax153 IMax153 commented Jun 18, 2021

This PR adds a defaultValue method to the Schema trait, which will return an appropriate value a given Schema and closes #74.

Disclaimer: I am incredibly new to both Scala and ZIO, so any and all constructive criticism and feedback is most welcome 🙏.

Additional Context

For StandardTypes, I defaulted the primitive values to the following:

Basic Primitives

  • UnitType -> ()
  • StringType -> ""
  • BoolType -> true
  • ShortType -> 0
  • IntType -> 0
  • LongType -> 0
  • FloatType -> 0.0
  • DoubleType -> 0.0
  • BinaryType -> Chunk.empty
  • CharType -> '\u0000' (the empty character '' does not compile, so I went with the NUL character code)
  • BigDecimalType -> BigDecimal.ZERO
  • BigIntegerType -> BigInteger.ZERO

java.time Primitives

  • DayOfWeekType -> java.time.temporal.WeekFields.of(java.util.Locale.getDefault).getFirstDayOfWeek
  • Month -> java.time.Month.JANUARY
  • MonthDay -> java.time.MonthDay.of(java.time.Month.JANUARY, 1)
  • Period -> java.time.Period.ZERO
  • Year -> java.time.Year.now
  • YearMonth -> java.time.YearMonth.now
  • ZoneId -> java.time.ZoneId.systemDefault
  • ZoneOffset -> java.time.ZoneOffset.UTC
  • Duration -> java.time.Duration.ZERO
  • Instant -> java.time.Instant.EPOCH
  • LocalDate -> java.time.LocalDate.now
  • LocalTime -> java.time.LocalTime.MIDNIGHT
  • LocalDateTime -> java.time.LocalDateTime.now
  • OffsetTime -> java.time.OffsetTime.now
  • OffsetDateTime -> java.time.OffsetDateTime.now
  • ZonedDateTime -> java.time.ZonedDateTime.now

I also added a new test suite (DefaultValueSpec) to the current array of test suites.

@IMax153 IMax153 requested a review from a team as a code owner June 18, 2021 15:11
@CLAassistant
Copy link

CLAassistant commented Jun 18, 2021

CLA assistant check
All committers have signed the CLA.

def structure: Chunk[Field[_]]
def annotations: Chunk[Any] = Chunk.empty
// TODO: make this much more robust
def defaultValue: Either[String, R] = self.rawConstruct(structure.map(_.schema.defaultValue.getOrElse(())))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure what the best method is for lifting a Chunk[Either[String, A]] to an Either[String, Chunk[A]].

In fp-ts, I would generally use Traverse to take care of this, but wasn't sure what the most semantic/idiomatic way to do this in ZIO was.

I also asked the question in Discord and was informed about zio-prelude's analogous methods, but I wanted to avoid adding additional dependencies to zio-schema.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding a dependency on zio-prelude is fine but if we wanted to do it the old fashioned way it would be

structure.map(_.schema.defaultValue()).foldRight[Either[String,Chunk[A]](Right(Chunk.empty) { 
    case (_, e @ Left(_)) => e
    case (Left(e), _) => Left[String,List[Unit]](e)
    case (Right(value), Right(values)) => Right(values :+ value)
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest avoiding to add ZIO Prelude just yet since it doesn't yet have a 1.0 release. This implementation looks good.

Copy link
Contributor

@thinkharderdev thinkharderdev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few comments but overall nicely done! Thanks for your work on this!

def structure: Chunk[Field[_]]
def annotations: Chunk[Any] = Chunk.empty
// TODO: make this much more robust
def defaultValue: Either[String, R] = self.rawConstruct(structure.map(_.schema.defaultValue.getOrElse(())))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding a dependency on zio-prelude is fine but if we wanted to do it the old fashioned way it would be

structure.map(_.schema.defaultValue()).foldRight[Either[String,Chunk[A]](Right(Chunk.empty) { 
    case (_, e @ Left(_)) => e
    case (Left(e), _) => Left[String,List[Unit]](e)
    case (Right(value), Right(values)) => Right(values :+ value)
}

Comment on lines 858 to 862
def defaultValue: Either[String, (String, _)] =
if (structure.isEmpty)
Left(s"cannot access default value for enumeration with no members")
else
structure.head._2.defaultValue.map((structure.head._1, _))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in this case we should require a default case be specified when the Enumeration is constructed. So maybe change to

final case class Enumeration(structure: ListMap[String,Schema[_]], defaultCase: Option[String] = None) extends Schema[(String,_)] {
   def defaultValue: Either[String,(String,_)] = 
      defaultCase match {
         case Some(id) => structure.get(id).map(_.defaultValue.map(id -> _)).getOrElse(Left("invalid default case"))
         case None => Left("no default case is defined")
      }

Comment on lines 922 to 937
extends Schema[Z] {
def defaultValue: Either[String, Z] = case1.codec.defaultValue
}

final case class CaseObject[Z](instance: Z) extends Schema[Z]
final case class EnumN[Z](cases: Seq[Case[_, Z]]) extends Schema[Z] {

def defaultValue: Either[String, Z] =
if (cases.isEmpty)
Left("cannot access default value for enum with no members")
else
cases.head.codec.defaultValue.asInstanceOf[Either[String, Z]]
}

final case class CaseObject[Z](instance: Z) extends Schema[Z] {
def defaultValue: Either[String, Z] = Right(instance)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. It feels a little wonky to just declare on subtype as the default but these are also constructed using macros so we can't really allow one to be declared by the user (we could, but it would require an annotation which hasn't been defined yet). Maybe for these we just have defaultValue return `Left("no default value can be determined"). If we want to allow a default to be specified in the future then we can do it with an annotation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest we just use the first subtype. Or we could do the "smallest" subtype, but first is more predictable.

Both Haskell and Scala type class derivation libraries choose to order the terms in a sum type (for Ord[A]) according to declaration order, so there's a precedent for this.

}

implicit object BoolType extends StandardType[Boolean] {
def defaultValue: Either[String, Boolean] = Right(true)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Bool should default to false to match the behavior of an uninitialized Boolean in Java and Scala.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@thinkharderdev
Copy link
Contributor

@jdegoes Is there any particular reason we want defaultValue to return Either[String,A]. My instinct would be to have it return Option[A] (i.e. there either is a default value or there isn't but there shouldn't really be any way an error can occur retrieving it)?

@jdegoes
Copy link
Member

jdegoes commented Jun 20, 2021

@thinkharderdev The only reason it has to return Either[String, A] is because of Transform nodes, which may fail for arbitrary reasons. In which case it's nice to preserve the error.

@IMax153
Copy link
Contributor Author

IMax153 commented Jun 24, 2021

Thank you @thinkharderdev and @jdegoes for your review and suggestions! Apologies for the delay - I have been on vacation.

I made the requested change to the BoolType default value (now defaults to false), and used the implementation suggested by @thinkharderdev for lifting a Chunk[Either[String, A]] to an Either[String, Chunk[A]].

I also left the defaultValue implementations for both Enumeration and Enum to default to the first declared subtype, but am happy to adjust if necessary.

Please let me know if you think any additional changes are necessary!

Also as an aside, I am happy to work on a @defaultValue annotation as well either as part of this PR or as a separate PR - if you have any good resources on how to create an annotation, I can get started on this.

@jdegoes
Copy link
Member

jdegoes commented Jun 25, 2021

@IMax153 Thank you for your work on this! Can you resolve conflicts so we can see if the automated build passes?

@IMax153
Copy link
Contributor Author

IMax153 commented Jun 26, 2021

@jdegoes - thank you! I believe I've resolved all conflicts.

Also, as I mentioned - if you guys would like me to try to add a @defaultValue annotation as part of this PR, I can try to work on it - would probably just need an example from the ZIO ecosystem to get me started.

@jdegoes
Copy link
Member

jdegoes commented Oct 23, 2021

@IMax153 @thinkharderdev Would be great to get this in by the ZIO Hackathon!!

@IMax153
Copy link
Contributor Author

IMax153 commented Oct 25, 2021

@jdegoes Thank you very much for the reminder on this PR!

I've gone ahead and merged main back into my branch and resolved all conflicts. One somewhat major change that I wanted to get feedback on before we go ahead with this PR is the implementation of defaultValue for enumerations.

As we discussed, I am using the first defined Case to determine the defaultValue for enumerations. However, I modified the implementation slightly to return just the value of the Case instead of a tuple of the key/value pair. You can observe what I mean by this in the test for enumerations here where it is now returning just the integer value 0 instead of the tuple ("myInt", 0).

Please let me know what you think! I am open to any and all feedback.

Copy link
Contributor

@thinkharderdev thinkharderdev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your work on this!

@thinkharderdev thinkharderdev merged commit 21228f5 into zio:main Nov 15, 2021
jdegoes pushed a commit that referenced this pull request Dec 14, 2021
* added basic documentation with example

* added more dependencies to docs

* incorporated feedback by @thinkharderdev

* fixed wording 'ZIO-Schema' -> 'ZIO Schema'

* fixed naming of other zio libraries

* fix sbt publish setting

* added examples subproject

* docs: fixed mdoc link and added version placeholder in overview/index.md

* docs: incorporated suggestions by @TobiasPfeifer

* scalafmt for build.sbt

* incorporated suggestions by @thinkharderdev

* Support for annotations on all types (#147)

* Add addonation to Schema[A] interface and add as field in Schema.Sequence

* Add annotation field to Schema.Transform

* Add annotation field to Schema.Primitive

* Add annotations field to Schema.Optional

* Add annotations field to Schema.Fail

* Add annotations field to Schema.Tuple

* Add annotations field to EitherSchema

* Add annotations field to Schema.Lazy

* Add annotations field to Schema.Meta

* Add remaining assertion equality checks to equalsSchema

* Remove annotations from Record[A] (since it exists in Schema[A] already) and add annotations field to Schema.GenericRecord

* Fix test case for schema generation on annotated ADT

* Fix handling of annotations for Lazy (and thus failing tests)

* Wherever we accept an annotations chunk in the constructor/method we default it to Chunk.empty

* Add `def annotate(annotation: Any): Schema[A]` to Schema

* Replace usage of Chunk.appended with :+ to fix 2.12.12 compilation

* Provide direct support for Map (#142)

* Provide direct support for Map
Unit tests for json serdes
Unit tests for protobuf serdes

* run scalafmt

* Rebased/reformatted

* Rebased/reformatted, and added a minimum contribution guide

* Run sbt `prepare test` instead of `fmt test`

* Add defaultValue to Schema  (#80)

* feat(schema): add first draft defaultValue implementation for Schema

* feat(schema): add missing test cases

* feat(schema): change default value for BoolType to false

* feat(schema): fold default values in Record type appropriately

* fix(zio-schema): fix failing test

* feat(zio-schema): add defaultValue implementation for MapSchema

* patch(zio-schema): remove orElse for Scala 2.12 compat

* first commit

* [zio-schema-examples] sbt setup

* [zio-schema-examples] readme init

* [zio-schema-examples] example 1

* [zio-schema-examples] example 2

* [zio-schema-examples] example 3 (WIP)

* [zio-schema-examples] example 3

* [zio-schema-examples] Example3: Don't share domain with other samples

* [zio-schema-examples] Example4 transforming DTOs

* [zio-schema-examples] Example5 diffing

* [zio-schema-examples] example 4: fixed map/flatmap in example

* [zio-schema-examples] example 4: another try to converting DTOs

* [zio-schema-examples] example 6 reified optics stub

* [zio-schema-examples] example 6 reified optics examples

* [zio-schema-examples] example 1 + 2: schema fixes for newest zio-schema

* [zio-schema-examples] Problem 7: Initial code

* [zio-schema-examples] Example 6: adding employee now works

* [zio-schema-examples] Example 7: added notes on performance requirements

* [zio-schema-examples] Example 7: added parameter list to methods, fixed names

* [zio-schema-examples] Example 7: added Person and Profile schema as implicits

* [zio-schema-examples] Example 7: renamed method for viewing, added Runner

* [zio-schema-examples] Example 7: sketched toDynamicValue method

* [zio-schema-examples] Example 7: implemented toDynamicValue method

* [zio-schema-examples] Example 7: primitive impl of decode using toDV

* [zio-schema-examples] Example 7: produce multiple solutions, pick working one

* [zio-schema-examples] Example 7: introduce QueryParam type alias and compile-function

* [zio-schema-examples] Example 7: return static function on error case

* [zio-schema-examples] Example 7: initial impl of Primitive(standardtype)

* [zio-schema-examples] Example 7: implemented fail

* [zio-schema-examples] Example 7: simple impl of lazy

* [zio-schema-examples] Example 7: better lazy impl with `lazy val`

* [zio-schema-examples] Example 7: impl of Meta

* [zio-schema-examples] Example 7: impl of CaseClass1

* [zio-schema-examples] Example 7: impl of CaseClass2

* [zio-schema-examples] Example 7: impl of CaseClass3

* [zio-schema-examples] Example 7: extracted compiler to builder

* [zio-schema-examples] Example 7: implemented Transform

* [zio-schema-examples] * Example for an Encoder and Decoder using the DynamicValue API
* Add scalafmt
* Update zio-schema 0.1.2
* Update examples to account for annotations

* revert local changes

* moved sources to appropriate place

* fixed example

* [zio-schema-examples] reverted changes to build.sbt

* [zio-schema-examples] removed additional build.sbt, fixed problems in example7, added explicit import in build.sbt

* [zio-schema-examples] scalaFmt applied

* Fix build for scala 2.12 and apply linter

* Fix compilation errors for scala 2.12

* Unused imports

Co-authored-by: Dominik Dorn <dominik@dominikdorn.com>
Co-authored-by: Alexander van Olst <alexvanolst@gmail.com>
Co-authored-by: Pierangelo Cecchetto <pierangeloc@gmail.com>
Co-authored-by: Maxwell Brown <maxwellbrown1990@gmail.com>
Co-authored-by: calvinlfer <calvin.l.fer@gmail.com>
landlockedsurfer pushed a commit to landlockedsurfer/zio-schema that referenced this pull request May 28, 2022
* feat(schema): add first draft defaultValue implementation for Schema

* feat(schema): add missing test cases

* feat(schema): change default value for BoolType to false

* feat(schema): fold default values in Record type appropriately

* fix(zio-schema): fix failing test

* feat(zio-schema): add defaultValue implementation for MapSchema

* patch(zio-schema): remove orElse for Scala 2.12 compat
landlockedsurfer pushed a commit to landlockedsurfer/zio-schema that referenced this pull request May 28, 2022
* added basic documentation with example

* added more dependencies to docs

* incorporated feedback by @thinkharderdev

* fixed wording 'ZIO-Schema' -> 'ZIO Schema'

* fixed naming of other zio libraries

* fix sbt publish setting

* added examples subproject

* docs: fixed mdoc link and added version placeholder in overview/index.md

* docs: incorporated suggestions by @TobiasPfeifer

* scalafmt for build.sbt

* incorporated suggestions by @thinkharderdev

* Support for annotations on all types (zio#147)

* Add addonation to Schema[A] interface and add as field in Schema.Sequence

* Add annotation field to Schema.Transform

* Add annotation field to Schema.Primitive

* Add annotations field to Schema.Optional

* Add annotations field to Schema.Fail

* Add annotations field to Schema.Tuple

* Add annotations field to EitherSchema

* Add annotations field to Schema.Lazy

* Add annotations field to Schema.Meta

* Add remaining assertion equality checks to equalsSchema

* Remove annotations from Record[A] (since it exists in Schema[A] already) and add annotations field to Schema.GenericRecord

* Fix test case for schema generation on annotated ADT

* Fix handling of annotations for Lazy (and thus failing tests)

* Wherever we accept an annotations chunk in the constructor/method we default it to Chunk.empty

* Add `def annotate(annotation: Any): Schema[A]` to Schema

* Replace usage of Chunk.appended with :+ to fix 2.12.12 compilation

* Provide direct support for Map (zio#142)

* Provide direct support for Map
Unit tests for json serdes
Unit tests for protobuf serdes

* run scalafmt

* Rebased/reformatted

* Rebased/reformatted, and added a minimum contribution guide

* Run sbt `prepare test` instead of `fmt test`

* Add defaultValue to Schema  (zio#80)

* feat(schema): add first draft defaultValue implementation for Schema

* feat(schema): add missing test cases

* feat(schema): change default value for BoolType to false

* feat(schema): fold default values in Record type appropriately

* fix(zio-schema): fix failing test

* feat(zio-schema): add defaultValue implementation for MapSchema

* patch(zio-schema): remove orElse for Scala 2.12 compat

* first commit

* [zio-schema-examples] sbt setup

* [zio-schema-examples] readme init

* [zio-schema-examples] example 1

* [zio-schema-examples] example 2

* [zio-schema-examples] example 3 (WIP)

* [zio-schema-examples] example 3

* [zio-schema-examples] Example3: Don't share domain with other samples

* [zio-schema-examples] Example4 transforming DTOs

* [zio-schema-examples] Example5 diffing

* [zio-schema-examples] example 4: fixed map/flatmap in example

* [zio-schema-examples] example 4: another try to converting DTOs

* [zio-schema-examples] example 6 reified optics stub

* [zio-schema-examples] example 6 reified optics examples

* [zio-schema-examples] example 1 + 2: schema fixes for newest zio-schema

* [zio-schema-examples] Problem 7: Initial code

* [zio-schema-examples] Example 6: adding employee now works

* [zio-schema-examples] Example 7: added notes on performance requirements

* [zio-schema-examples] Example 7: added parameter list to methods, fixed names

* [zio-schema-examples] Example 7: added Person and Profile schema as implicits

* [zio-schema-examples] Example 7: renamed method for viewing, added Runner

* [zio-schema-examples] Example 7: sketched toDynamicValue method

* [zio-schema-examples] Example 7: implemented toDynamicValue method

* [zio-schema-examples] Example 7: primitive impl of decode using toDV

* [zio-schema-examples] Example 7: produce multiple solutions, pick working one

* [zio-schema-examples] Example 7: introduce QueryParam type alias and compile-function

* [zio-schema-examples] Example 7: return static function on error case

* [zio-schema-examples] Example 7: initial impl of Primitive(standardtype)

* [zio-schema-examples] Example 7: implemented fail

* [zio-schema-examples] Example 7: simple impl of lazy

* [zio-schema-examples] Example 7: better lazy impl with `lazy val`

* [zio-schema-examples] Example 7: impl of Meta

* [zio-schema-examples] Example 7: impl of CaseClass1

* [zio-schema-examples] Example 7: impl of CaseClass2

* [zio-schema-examples] Example 7: impl of CaseClass3

* [zio-schema-examples] Example 7: extracted compiler to builder

* [zio-schema-examples] Example 7: implemented Transform

* [zio-schema-examples] * Example for an Encoder and Decoder using the DynamicValue API
* Add scalafmt
* Update zio-schema 0.1.2
* Update examples to account for annotations

* revert local changes

* moved sources to appropriate place

* fixed example

* [zio-schema-examples] reverted changes to build.sbt

* [zio-schema-examples] removed additional build.sbt, fixed problems in example7, added explicit import in build.sbt

* [zio-schema-examples] scalaFmt applied

* Fix build for scala 2.12 and apply linter

* Fix compilation errors for scala 2.12

* Unused imports

Co-authored-by: Dominik Dorn <dominik@dominikdorn.com>
Co-authored-by: Alexander van Olst <alexvanolst@gmail.com>
Co-authored-by: Pierangelo Cecchetto <pierangeloc@gmail.com>
Co-authored-by: Maxwell Brown <maxwellbrown1990@gmail.com>
Co-authored-by: calvinlfer <calvin.l.fer@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add defaultValue to Schema trait
4 participants