Skip to content

[gen] optional endpoint query params are not generated as optional #4196

Description

@jirihausner

Describe the bug
When generating endpoints from OpenAPI specification using EndpointGen, optional OpenAPI endpoint query params do not become optional QueryCodec[A].
This behavior alters the expected endpoint paths and may lead to discrepancies between the OpenAPI specification and the generated code.

To Reproduce

//> using scala 3.3.8
//> using dep dev.zio::zio-http-gen:3.11.3

import zio.http.endpoint.openapi.OpenAPI
import zio.http.gen.openapi.{Config, EndpointGen}
import zio.http.gen.scala.CodeGen

object OptionalQueryParamGen extends App {

  val spec: String =
    """|{
       |  "openapi": "3.0.3",
       |  "info": { "title": "Users API", "version": "1.0.0" },
       |  "paths": {
       |    "/users": {
       |      "get": {
       |        "parameters": [
       |          {
       |            "name": "limit",
       |            "in": "query",
       |            "required": false,
       |            "schema": { "type": "integer", "format": "int32" }
       |          },
       |          {
       |            "name": "name",
       |            "in": "query",
       |            "required": false,
       |            "schema": { "type": "string" }
       |          }
       |        ],
       |        "responses": {
       |          "200": {
       |            "description": "OK",
       |            "content": {
       |              "application/json": {
       |                "schema": {
       |                  "type": "array",
       |                  "items": {
       |                    "$ref": "#/components/schemas/User"
       |                  }
       |                }
       |              }
       |            }
       |          }
       |        }
       |      }
       |    }
       |  },
       |  "components": {
       |    "schemas": {
       |      "User": {
       |        "type": "object",
       |        "properties": {
       |          "id":   { "type": "integer" },
       |          "name": { "type": "string" }
       |        }
       |      }
       |    }
       |  }
       |}
       |""".stripMargin

  val openApi: OpenAPI =
    OpenAPI.fromJson(spec).fold(err => sys.error(s"Failed to parse OpenAPI spec: $err"), identity)

  val rendered: Map[String, String] =
    CodeGen.renderedFiles(
      EndpointGen.fromOpenAPI(openApi, Config.default),
      basePackage = "example",
    )

  rendered.foreach { case (path, content) =>
    println(s"=== generated: $path ===")
    println(content)
    println()
  }
}

Actual behaviour

val get = Endpoint(Method.GET / "users")
  .query(HttpCodec.query[Int]("limit"))
  .query(HttpCodec.query[String]("name"))
  .in[Unit]
  .out[Chunk[User]](status = Status.Ok)

Expected behaviour

val get = Endpoint(Method.GET / "users")
  .query(HttpCodec.query[Int]("limit").optional)
  .query(HttpCodec.query[String]("name").optional)
  .in[Unit]
  .out[Chunk[User]](status = Status.Ok)

so the endpoint query input is Option[Int] and Option[String] rather than Int and String which is currently the case.

Root cause

  • in zio.http.gen.openapi.EndpointGen, the query-parameter collection params.collect { case p if p.in == "query" => ... } builds Code.QueryParamCode(name, queryType) from the schema only; p.required is never consulted
  • Code.QueryParamCode has no optionality flag, and zio.http.gen.scala.CodeGen.renderQueryCode has a single rendering path that always emits .query(HttpCodec.query[$tpe]("$name")) — there is no code path that could emit .optional

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions