Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Code generation fails when components are named identically to one of their enum properties #2050

Open
Jonnty opened this issue Nov 10, 2024 · 0 comments

Comments

@Jonnty
Copy link

Jonnty commented Nov 10, 2024

I've been getting errors when generating code for a YAML OpenAPI 3 spec (this one, incidentally). They appear to be happen when one of the enum properties of a component is the same as the component itself. The cause seems to be that attempts to reference the component class are taken to be references to the identically named property object within the class when such an object happens to exist. (This doesn't happen for standard, non-enum strings as no child object is generated.)

The conflict is not case-sensitive, as generated class/object names are automatically camel cased.

I think the suggested fix for #1147 - fully qualifying component class names within generated code - would probably fix this as well.

I've narrowed this down into a minimal failing case - I get the following error when attempting to generate code for the specification below it by running sbt compile on a project using sbt-guardrail 1.0.0-M1.

Error

[info] compiling 1 Scala source to C:\...\untitled\target\scala-2.13\classes ...
[error] C:\...\untitled\target\scala-2.13\src_managed\main\nhclosures\definitions\Test.scala:13:129: value test is not a member of nhclosures.definitions.Test.Test
[error]     _root_.io.circe.Encoder.AsObject.instance[Test](a => _root_.io.circe.JsonObject.fromIterable(_root_.scala.Vector(("test", a.test.asJson))))
[error]                                                                                                                                 ^
[error] C:\...\untitled\target\scala-2.13\src_managed\main\nhclosures\definitions\Test.scala:15:219: type Test is not a member of object nhclosures.definitions.Test.Test
[error]   implicit val decodeTest: _root_.io.circe.Decoder[Test] = new _root_.io.circe.Decoder[Test] { final def apply(c: _root_.io.circe.HCursor): _root_.io.circe.Decoder.Result[Test] = for (v0 <- c.downField("test").as[Test.Test]) yield Test(v0) }
[error]                                                                                                                                                                                                                           ^
[error] C:\...\untitled\target\scala-2.13\src_managed\main\nhclosures\definitions\Test.scala:15:236: nhclosures.definitions.Test.Test.type does not take parameters
[error]   implicit val decodeTest: _root_.io.circe.Decoder[Test] = new _root_.io.circe.Decoder[Test] { final def apply(c: _root_.io.circe.HCursor): _root_.io.circe.Decoder.Result[Test] = for (v0 <- c.downField("test").as[Test.Test]) yield Test(v0) }
[error]                                                                                                                                                                                                                                            ^
[error] C:\...\untitled\target\scala-2.13\src_managed\main\nhclosures\definitions\Test.scala:10:28: type Test is not a member of object nhclosures.definitions.Test.Test
[error] case class Test(test: Test.Test)

Spec used

openapi: 3.0.1
info:
  title: Minimal Error Case
  version: "1.0"
servers:
  - url: https://example.com
paths:
  /test:
    get:
      operationId: Test
      responses:
        '200':
          description: A test
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Test'
components:
  schemas:
    Test:
      title: Test
      required:
        - test
      type: object
      properties:
        test:
          title: test
          enum:
            - optionA
            - optionB
          type: string

Code generated (Test.scala)

/*
 * This file was generated by guardrail (https://github.com/guardrail-dev/guardrail).
 * Modifications will be overwritten; instead edit the OpenAPI/Swagger spec file.
 */
package nhclosures.definitions
import cats.syntax.either._
import io.circe.syntax._
import cats.instances.all._
import _root_.nhclosures.Implicits._
case class Test(test: Test.Test)
object Test {
  implicit val encodeTest: _root_.io.circe.Encoder.AsObject[Test] = {
    _root_.io.circe.Encoder.AsObject.instance[Test](a => _root_.io.circe.JsonObject.fromIterable(_root_.scala.Vector(("test", a.test.asJson))))
  }
  implicit val decodeTest: _root_.io.circe.Decoder[Test] = new _root_.io.circe.Decoder[Test] { final def apply(c: _root_.io.circe.HCursor): _root_.io.circe.Decoder.Result[Test] = for (v0 <- c.downField("test").as[Test.Test]) yield Test(v0) }
  sealed abstract class Test(val value: String) extends _root_.scala.Product with _root_.scala.Serializable { override def toString: String = value.toString }
  object Test {
    object members {
      case object OptionA extends Test("optionA")
      case object OptionB extends Test("optionB")
    }
    val OptionA: Test = members.OptionA
    val OptionB: Test = members.OptionB
    val values = _root_.scala.Vector(OptionA, OptionB)
    implicit val encodeTest: _root_.io.circe.Encoder[Test] = _root_.io.circe.Encoder[String].contramap(_.value)
    implicit val decodeTest: _root_.io.circe.Decoder[Test] = _root_.io.circe.Decoder[String].emap(value => from(value).toRight(s"$value not a member of Test"))
    implicit val showTest: Show[Test] = Show[String].contramap[Test](_.value)
    def from(value: String): _root_.scala.Option[Test] = values.find(_.value == value)
    implicit val order: cats.Order[Test] = cats.Order.by[Test, Int](values.indexOf)
  }
}
Jonnty added a commit to Jonnty/guardrail that referenced this issue Nov 11, 2024
…oid naming conflicts

Previously, when a property was named the same as its component, references to the component class were misinterpreted as references to the property class, causing errors. Fully qualifying the references to each should prevent this from happening.

A regression test covering a minimal failing case is included.

Fixes issue guardrail-dev#2050
Jonnty added a commit to Jonnty/guardrail that referenced this issue Nov 11, 2024
…oid naming conflicts

Previously, when a property was named the same as its component, references to the component class were misinterpreted as references to the property class, causing errors. Fully qualifying the references to each should prevent this from happening.

A regression test covering a minimal failing case is included.

Fixes issue guardrail-dev#2050
Jonnty added a commit to Jonnty/guardrail that referenced this issue Nov 11, 2024
Previously, when a property was named the same as its component, references to the component class and property class conflicted, causing errors. Fully qualifying the references to each should prevent this from happening.

A regression test covering a minimal failing case is included.

Fixes issue guardrail-dev#2050
Jonnty added a commit to Jonnty/guardrail that referenced this issue Nov 11, 2024
Previously, when a property was named the same as its component, references to the component class and property class conflicted, causing errors. Fully qualifying the references to each should prevent this from happening.

A regression test covering a minimal failing case is included.

Fixes issue guardrail-dev#2050
Jonnty added a commit to Jonnty/guardrail that referenced this issue Nov 11, 2024
Previously, when a property was named the same as its component, references to the component class and property class conflicted, causing errors. Fully qualifying the references to each should prevent this from happening.

A regression test covering a minimal failing case is included.

Fixes issue guardrail-dev#2050
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant