Why `subtypeNameToSchemaName` in Schema derivation always recalculates Snames

I was wondering why subtypeNameToSchemaName in SchemaMagnoliaDerivation always recalculates names based on TypeName, instead eg. attempting to get those names from known Schema.name and if it doesn’t exist then fallback to TypeName.

IN MY HEAD that would/could resolve issue of semiauto derivation loosing type info of params.

That’s a good question - I don’t know off the top of my head. Maybe you can try changing and seeing what breaks? There’s a lot of tests in openApiDocs which should help to quickly verify the behavior.

I did change it, tests passed, but it seems there is no value in it, that change or not semiauto derivation for anything with generic params is completly broken.

Is that semiauto issue magnolia bug? or some scala type system corner case?

Wonder if we could have typeclass for each generic argument to hack around it :smile:

Which semiauto issue? I would rather bet on a problem in tapir, than in magnolia.

eg:
scala 2.13

package com.test

import io.circe.generic.auto._
import sttp.apispec.openapi.circe.yaml.RichOpenAPI
import sttp.tapir._
import sttp.tapir.docs.openapi.OpenAPIDocsInterpreter
import sttp.tapir.json.circe.jsonBody

object Test extends App {

  lazy implicit val someValueStringSchema: Schema[SomeValueString] = Schema.derived
  lazy implicit val someValueIntSchema: Schema[SomeValueInt] = Schema.derived
  lazy implicit val hasStringTreeSchema: Schema[HasStringTree] = Schema.derived
  lazy implicit val hasIntTreeSchema: Schema[HasIntTree] = Schema.derived

  @inline
  implicit def adtTreeSchema[A: Schema]: Schema[AdtTree[A]] = Schema.derived[AdtTree[A]]

  val endpoint1 = endpoint
    .put
    .name("test1")
    .in("test" / "1")
    .in(jsonBody[AdtTree[SomeValueString]])

  val endpoint2 = endpoint
    .put
    .name("test2")
    .in("test" / "2")
    .in(jsonBody[AdtTree[SomeValueInt]])


  val endpoints = List(endpoint1, endpoint2)

  val yamlString: String = OpenAPIDocsInterpreter()
    .toOpenAPI(endpoints, "test", "v1")
    .toYaml

  println(yamlString)

}

sealed trait AdtTree[A]
object AdtTree {
  final case class AdtLeaf[A](a: A) extends AdtTree[A]
  final case class AdtNode[A](trees: List[AdtTree[A]]) extends AdtTree[A]
}

final case class SomeValueString(value: String)
final case class SomeValueInt(value: Int)
final case class HasStringTree(value: String, tree: AdtTree[SomeValueString])
final case class HasIntTree(value: String, tree: AdtTree[SomeValueInt])


generates:

openapi: 3.1.0
info:
  title: test
  version: v1
paths:
  /test/1:
    put:
      operationId: test1
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AdtTree_A'
        required: true
      responses:
        '200':
          description: ''
        '400':
          description: 'Invalid value for: body'
          content:
            text/plain:
              schema:
                type: string
  /test/2:
    put:
      operationId: test2
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AdtTree_A'
        required: true
      responses:
        '200':
          description: ''
        '400':
          description: 'Invalid value for: body'
          content:
            text/plain:
              schema:
                type: string
components:
  schemas:
    AdtLeaf_A:
      title: AdtLeaf_A
      type: object
      required:
      - a
      properties:
        a:
          $ref: '#/components/schemas/SomeValueString'
    AdtNode_A:
      title: AdtNode_A
      type: object
      properties:
        trees:
          type: array
          items:
            $ref: '#/components/schemas/AdtTree_A'
    AdtTree_A:
      title: AdtTree_A
      oneOf:
      - $ref: '#/components/schemas/AdtLeaf_A'
      - $ref: '#/components/schemas/AdtNode_A'
    SomeValueInt:
      title: SomeValueInt
      type: object
      required:
      - value
      properties:
        value:
          type: integer
          format: int32
    SomeValueString:
      title: SomeValueString
      type: object
      required:
      - value
      properties:
        value:
          type: string

where test 1 endpoint should take string tree, and test 2 int tree.

and generated docs result in it taking string on both.

auto - SchemaDerivation works as expected

Oh … that’s “interesting”. I’d have to take a look. As a work-around, you should be able to customise the semi-auto-derived schemas with correct names, as described here.

lazy implicit val hasIntTreeSchema: Schema[HasIntTree] = Schema.derived

yea I know that, just it is quite error prone and I hoped for some better solution

Ok great, yeah I agree, but that needs some dev-time, we’ll get there eventually :slight_smile:

Is the above reported as an issue in tapir?

no, sorry I’ve missed that response