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

Inline requestBody generates incorrect resource code #1103

Open
codejudas opened this issue May 16, 2021 · 3 comments
Open

Inline requestBody generates incorrect resource code #1103

codejudas opened this issue May 16, 2021 · 3 comments
Labels
bug Unexpected behaviour for functionality that either was intended to work, or has worked in the past scala Broadly concerning Scala code generation or the generated Scala code scala-http4s Pertains to guardrail-scala-http4s

Comments

@codejudas
Copy link
Contributor

codejudas commented May 16, 2021

This affects Guardrail version 0.64.1 and scala http server generation.
It affects the following scala frameworks:

  • http4s

When you have an API endpoint which takes an application/json payload, and you define the payload schema inside the endpoint definition, invalid code is generated which attempts to read the fields from the payload from a urlForm variable which does not exist.

This can be fixed by instead making the endpoint payload refer to a schema defined in the components section.

From my understanding, there should be no difference between these two definitions in the open-api spec.

Example spec which generates valid code:

openapi: 3.0.3

info:
  title: Example
  version: 0.0.1
  description: Example API

paths:
  "/users":
    post:
      description: Create a User
      operationId: createUser
      x-jvm-package: users
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateUserRequest"
      responses:
        201:
          description: Successfully created user
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"

components:
  schemas:
    User:
      type: object
      required:
        - username
        - date_created
      properties:
        username:
          type: string
        date_created:
          type: string
          x-jvm-type: java.time.OffsetDateTime

    CreateUserRequest:
      type: object
      required:
        - username
        - password
      properties:
        username:
          type: string
        password:
          type: string

Valid handler generated for this spec:

/* In Routes.scala */
/* ... Clip rest of file ... */
private[this] val createUserDecoder: EntityDecoder[F, _root_.com.example.server.definitions.CreateUserRequest] = jsonOf[F, _root_.com.example.server.definitions.CreateUserRequest]
def routes(handler: UsersHandler[F]): HttpRoutes[F] = HttpRoutes.of {
    {
      case req @ POST -> Root / "users" =>
        mapRoute("createUser", req, {
          req.decodeWith(createUserDecoder, strict = false) { body => 
            handler.createUser(CreateUserResponse)(body) flatMap ({
              case resp: CreateUserResponse.Created =>
                createUserCreatedEntityResponseGenerator(resp.value)(F, createUserCreatedEncoder)
            })
          }
        })
    }
  }

Example spec which produces invalid code:

openapi: 3.0.3

info:
  title: Example
  version: 0.0.1
  description: Example API

paths:
  "/users":
    post:
      description: Create a User
      operationId: createUser
      x-jvm-package: users
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - username
                - date_created
              properties:
                username:
                  type: string
                date_created:
                  type: string
                  x-jvm-type: java.time.OffsetDateTime
      responses:
        201:
          description: Successfully created user
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"

components:
  schemas:
    User:
      type: object
      required:
        - username
        - date_created
      properties:
        username:
          type: string
        date_created:
          type: string
          x-jvm-type: java.time.OffsetDateTime

Example generated invalid code:

/* In Routes.scala */
/* ... Clip rest of file ... */
  private[this] val createUserDecoder: EntityDecoder[F, io.circe.Json] = jsonOf[F, io.circe.Json]
  def routes(handler: UsersHandler[F]): HttpRoutes[F] = HttpRoutes.of {
    {
      case req @ POST -> Root / "users" =>
        mapRoute("createUser", req, {
          req.decodeWith(createUserDecoder, strict = false) { body => 
            (urlForm.values.get("username").flatMap(_.headOption), urlForm.values.get("date_created").flatMap(_.headOption).map(Json.fromString(_).as[java.time.OffsetDateTime])) match {
              case (Some(username), Some(Right(dateCreated))) =>
                handler.createUser(CreateUserResponse)(body, username, dateCreated) flatMap ({
                  case resp: CreateUserResponse.Created =>
                    createUserCreatedEntityResponseGenerator(resp.value)(F, createUserCreatedEncoder)
                  case CreateUserResponse.BadRequest =>
                    F.pure(Response[F](status = org.http4s.Status.BadRequest))
                })
              case _ =>
                BadRequest("Invalid data")
            }
          }
        })
    }
  }

Notice that it still decodes the payload but as just raw JSON, and then it tries to extract the individual fields from urlForm rather than the json body it just parsed.

@kelnos kelnos changed the title Scala: Inline requestBody generates incorrect resource code Inline requestBody generates incorrect resource code Jun 3, 2021
@kelnos kelnos added bug Unexpected behaviour for functionality that either was intended to work, or has worked in the past scala-http4s Pertains to guardrail-scala-http4s scala Broadly concerning Scala code generation or the generated Scala code labels Jun 3, 2021
@blast-hardcheese
Copy link
Member

#1407 threads through components into the various generators, it's now possible to dereference the $ref here

@andreaturli
Copy link

I'm using 1.0.0-M1 and I still can see the same error. Any suggestion?

@blast-hardcheese
Copy link
Member

@andreaturli The workaround is to define discrete, named type: object's in the specification's components section. Hope this helps!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Unexpected behaviour for functionality that either was intended to work, or has worked in the past scala Broadly concerning Scala code generation or the generated Scala code scala-http4s Pertains to guardrail-scala-http4s
Projects
None yet
Development

No branches or pull requests

4 participants