disneystreaming / smithy4s

https://disneystreaming.github.io/smithy4s/
Other
348 stars 70 forks source link

Bincompat-friendly codegen #1500

Open kubukoz opened 6 months ago

kubukoz commented 6 months ago

Related: https://github.com/disneystreaming/smithy4s/pull/1485, https://github.com/smithy-lang/smithy/issues/2243, Discord discussion

In some situations, such as generating protocol shapes (not limited to, but in particular: traits), it would be beneficial to allow evolving models without breaking binary compatibility of generated code.

For example, adding a new field to a struct trait shouldn't break binary compatibility of the result.

We should come up with and agree on:

and implement these for the next minor version (0.19), if possible.

kubukoz commented 2 months ago

Made some progress with @Baccata.

I won't write down the exact encoding just yet (I changed my mind several times in the last couple hours), but it appears that we'll draw a lot of inspiration from Contraband, including a @since("1.0") trait (or similar) to allow marking new members as having been added in a particular version.

If there's a bunch of versions, we can generate a constructor / apply for each of them, e.g.

//assume everything is required

structure Foo {
  s1: String
  @since("1.0") s2: String = "test2"
  @since("1.0") s3: String = "test3"
  s4: String
}

// we can add these traits for external shapes too, with `apply`
apply Foo$s4 @since("2.0")
apply Foo$s4 @default("test4")
final case class Foo private(s1: String, s2: String, s3: String, s4: String) { ... }
object Foo {
  def apply(s1: String): Foo = new Foo(s1, "test2", "test3", "test4")
  def apply(s1: String, s2: String, s3: String): Foo = new Foo(s1, s2, s3, "test4")
  def apply(s1: String, s2: String, s3: String, s4: String) = new Foo(s1, s2, s3, s4)
}

I'll play with Contraband soon to see if it does this (and other interesting cases it handles, such as adding new values between existing ones) and get back with an update.