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

Unable to set nonempty default for optional field #518

Open
Kazark opened this issue Apr 11, 2023 · 7 comments
Open

Unable to set nonempty default for optional field #518

Kazark opened this issue Apr 11, 2023 · 7 comments

Comments

@Kazark
Copy link

Kazark commented Apr 11, 2023

I am working on converting some contracts from using Avro4s to Vulcan. Because they are existing contracts I am constrained in what I can do---I am attempting to produce precisely equivalent Avro before and after.

I've hit a case that's giving me trouble. The following is a MWE.

import vulcan.*

final case class Foo(bar: Option[Int])

object Foo {
  implicit lazy val codec: Codec[Foo] =
    Codec.record(
      name = "Foo",
      namespace = "com.example",
    )(field =>
      field(
        "bar",
        _.bar,
        default = Some(Some(1337)),
      ).map(Foo.apply)
    )
}

This results in a Foo.codec.schema which is a Left containing

vulcan.AvroException$$anon$1: org.apache.avro.AvroTypeException: Invalid default for field bar: 1337 not a ["null","int"]

In my example, I was using an enum, not an integer, and got the same behavior. I noticed that Some(None) works as a default, but this does not match my existing contracts.

@Kazark
Copy link
Author

Kazark commented Apr 12, 2023

EDIT: the below is the original post, but a warning to those who find it hereafter that this did not work.


A coworker discovered a workaround which looks like it will suffice for our case: that is, everywhere we are using that enumeration, we are using the same default. Setting the default on the enumeration instead of on the field, works.

So we can probably move ahead; but the above still looks like a bug to me.

@Kazark
Copy link
Author

Kazark commented Apr 12, 2023

which looks like it will suffice for our case

Well, I hope. I have not fully validated this hope yet.

@soujiro32167
Copy link
Contributor

Got hit with this issue as well, and discovered an annoyance: https://avro.apache.org/docs/1.8.1/spec.html#Unions

(Note that when a default value is specified for a record field whose type is a union, the type of the default value must match the first element of the union. Thus, for unions containing "null", the "null" is usually listed first, since the default value of such unions is typically null.)

So the default value for an Option can only be None

@Kazark
Copy link
Author

Kazark commented May 24, 2023

@soujiro32167 unless you have the null second, which is the way that Avro4s has it IIRC.

@soujiro32167
Copy link
Contributor

Right, whatever the first is, is the one you can default to

@Kazark
Copy link
Author

Kazark commented May 31, 2023

Oh, Avro4s seems to change the way its option codecs work based on the default, sometimes putting null first, sometimes not.

@Kazark
Copy link
Author

Kazark commented May 31, 2023

This did not work. Looks like this will, however:

  private implicit lazy val optionAllowingDefaultSome
      : Codec[Option[Int]] =
    Codec.union(builder =>
      builder[Some[Int]] <+>
      builder[None.type]
    )

A locally-scoped monomorphic implicit that puts the null last, as Avro4s does.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

2 participants