smithy4s icon indicating copy to clipboard operation
smithy4s copied to clipboard

Custom code generator support

Open schmeedy opened this issue 3 years ago • 7 comments

This is just a draft PR to discuss if something like this would be viable from your point of view. It'd need more refinement, but for the time being, I just wanted to show the interface & the sbt plugin hook that would work for us.

Our use case is to generate some extra Scala code (in separate source files) on top of what's already provided by smithy4s. This hook would allow us to do this based on the intermediate codegen model.

schmeedy avatar Sep 02 '22 16:09 schmeedy

@schmeedy we'll probably need to wait for @Baccata's input, but in the meantime I'm curious what your usecase is - what sort of code do you want to generate in these?

kubukoz avatar Sep 02 '22 18:09 kubukoz

Thanks for your comment @kubukoz!

We want to use a slightly different encoding for errors. We're only targeting Scala 3, so we'd like to use the built-in union type instead of the generated error union. Also, we want to have explicit errors in the type and still use the standard http4s interpreter.

It's entirely possible both of these would be achievable without any additional codegen, but we could not find a way to do it. One major complication is that there's no simple relation between the error union type (the one exposed in the signature of the generated service) and the types the individual cases are wrapping.

The solution involving additional codegen would look something like this:

trait ObjectApiGenWithErrors[B[_, _]]:
  self =>

  def removeObject(key: ObjectKey): B[ResourceNotFound | Unauthorized, Unit]

  // this returns the stock generated service instance
  def asGen[F[_]](
      // transforms the bifunctor encoding to a simple effect to be used with the http4s interpreter
      unlift: [e <: Throwable, a] => B[e, a] => F[a]
  ): ObjectApiGen[[_, _, O, _, _] =>> F[O]] =
  	new ObjectApiGen:
  	  override def removeObject(key: ObjectKey): F[Unit] = 
  	    unlift(self.removeObject(key))

Note that the errors in ResourceNotFound | Unauthorized are not wrapped in the error union.

schmeedy avatar Sep 02 '22 18:09 schmeedy

I'm sorry to say this is gonna be a strong no on my end. Although it's not currently clear, the intermediate representation of the code-generator is something that we treat as private, non-API detail. We need the flexibility to change it when the need arise, and giving you a way to piggyback on it would mean for us to have to make promises with regards to the stability of this intermediate representation, which we really don't want to do.

The original model of Smithy is simple enough that you shouldn't need to piggyback on our intermediate representation to come up with your own code generation pipeline. Feel free to copy/paste anything you want to fit your needs. You can even keep rendering instances of the abstractions from smithy4s-core and still benefit from the runtime libraries.

We'll probably make the intermediate representation package-private next version.

Baccata avatar Sep 02 '22 20:09 Baccata

The primary motivation was to access the same model used by the built-in code generation. I understand you want to keep the IR internal, and we can certainly use just the smithy Model to drive custom codegen. We wish to avoid independent loading and assembly of the model, which could produce a slightly different model than what the built-in smithy4s codegen is using.

My main question is, how do you feel about the idea of smithy4s users generating some limited amount of extra code on top of what's provided by the library? The basic level of support for this that would not require exposing the internals could be a variation of what's in this PR — dropping the IR model as an input and only passing the smithy model & a set of namespaces to process. But it could be done differently.

I'd prefer not to touch codegen, but I couldn't figure out how to achieve what I drafted above.

I appreciate all of the work you're putting into this library. Thanks!

schmeedy avatar Sep 04 '22 22:09 schmeedy

dropping the IR model as an input and only passing the smithy model & a set of namespaces to process.

That's certainly something I would consider. We'd need to check the state of the art first, check whether there are already existing interfaces in the Smithy core tooling that could help, and whether we could get closer to the whole ecosystem. The point being to reduce the amount of ad-hoc mechanisms we need to support.

But I'm cool with the idea in principle.

Baccata avatar Sep 05 '22 09:09 Baccata

@Baccata thanks! I've looked at the smithy codegen framework a bit. In terms of customization, there's the SmithyIntegration interface, which allows some customization of standard generator output (renaming symbols, amending individual code sections...). It also defines a hook for additional codegen in the form of the customize method . That gives access to the CodegenContext through which you can access the smithy model, codegen settings, symbol provider and FileManifest, which can be used to write additional files.

It's all pretty specific to the native smithy codegen framework. The SmithyIntegration.customize method IMO essentially boils down to Model => Seq[GeneratedFile].

schmeedy avatar Sep 12 '22 13:09 schmeedy

@schmeedy thanks for the links and the preliminary look. It's gonna take us a little bit of time to digest and decide on a direction, and I cannot promise a timeframe, so I'm gonna ask for your patience on this :) .

Baccata avatar Sep 13 '22 13:09 Baccata

Closing - discussed with @schmeedy and the usecase will be supported by #703.

kubukoz avatar Jan 04 '23 14:01 kubukoz