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!