StackOverflow when try to create recursive schema on coproduct

I have a model like this

sealed trait Logic
final case class And(arr: Set[Logic]) extends Logic
final case class Expr(expr: String) extends Logic

I wrote schema like that

  implicit lazy val andSchema: Schema[And] = Schema.derived[And]
  implicit lazy val exprSchema: Schema[Expr] = Schema.derived[Expr]
  implicit lazy val logicSchema: Schema[Logic] = Schema(SchemaType.SCoproduct(List(andSchema, exprSchema), None) {
    case l: And => Some(SchemaWithValue(andSchema, l))
    case l: Expr => Some(SchemaWithValue(exprSchema, l))
  }, name = Some(SName("Logic")))

Everything compiles and I’m getting StackOverflow when running my specs for the endpoints.

I added a dummy schema for the And class for now. Is there a proper way to solve this issue?

  implicit def andSchema: Schema[And] = Schema[And](
    SchemaType.SProduct(List(
      SProductField.apply[And, Array[Logic]](FieldName("arr"), Schema.schemaForArray(Schema.string), a => Some(a.arr.toArray))
    )), name = Some(SName("And")))

In tapir, recursive schemes need to have a “cut-off” point, after which they are no longer recursively expanded. This is done using SRef - which points up in the schema tree, to a schema that is already defined. In your example, you want to have a SCoproduct schema for Logic, with two children: SProduct for Expr, and another SProduct for And. One of the fields of And should be a list of SRefs back to Logic.

The way it’s written now, there’s no SRef used anywhere. Because all schemas are defined as implicit values, the Schema.derived[And] simply uses logicSchema for the arr field: it’s not “aware” that any recursion takes place, so it doesn’t generate the SRef reference. Similarly, logicSchema doesn’t know about recursion, and it just reuses the andSchema.

The simplest solution is to derive the schema for Logic in one call, so that the derivation can be aware of the recursion and put the SRefs in teh right places:

implicit lazy val logicSchema: Schema[Logic] = Schema.derived[Logic]

Note that this works, as deriving coproduct hierarchies is explicitly supported in Magnolia to recurse one level deep into the structure (so that the typeclass - here Schema is derived for all implementations of the coproduct.

This gives you the desired schema. Alternatively, you can hand-craft an Schema[And] which uses SRef for the arr field.

Big thanks for that. And sorry for the stupid question. I didn’t assume that I can derive schema correctly for that complex structure and I even didn’t try this. Thanks again!

Definitely not a stupid question - recursive implicits and typeclasses for recursive structures are always tricky :slight_smile: