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

[BUG] Enhance iron library support in Tapir #3830

Closed
dacr opened this issue Jun 7, 2024 · 1 comment · Fixed by #3858
Closed

[BUG] Enhance iron library support in Tapir #3830

dacr opened this issue Jun 7, 2024 · 1 comment · Fixed by #3858
Assignees
Labels
bug Something isn't working

Comments

@dacr
Copy link

dacr commented Jun 7, 2024

Tapir version: 1.10.8

Scala version: 3.4.2

Some examples of issues I had while trying to use Tapir with a data model which using iron type constraints :

  • Support iron constraint union and intersection
    • As soon as it is used we loose both the documentation and the validation
      • type Name = String :| (MinLength[3] & MaxLength[64])
  • Support MinLength, MaxLength in generated documentation for collection types
  • Support the DescribedAs (type Age = Int :| Positive DescribedAs "Age should be positive")

(not an exhaustive list as I've just started to use/learn iron and I'm aware that this support is experimental)

Maybe you can provide code to reproduce the problem?

the source code base I'm using to evaluate iron support in tapir

// ---------------------
//> using scala "3.4.2"
//> using dep "com.softwaremill.sttp.tapir::tapir-zio:1.10.8"
//> using dep "com.softwaremill.sttp.tapir::tapir-zio-http-server:1.10.8"
//> using dep "com.softwaremill.sttp.tapir::tapir-iron:1.10.8"
//> using dep "com.softwaremill.sttp.tapir::tapir-jsoniter-scala:1.10.8"
//> using dep "com.softwaremill.sttp.tapir::tapir-swagger-ui-bundle:1.10.8"
//> using dep "io.github.iltotore::iron-jsoniter:2.5.0"
//> using dep "com.github.plokhotnyuk.jsoniter-scala::jsoniter-scala-core:2.30.1"
//> using dep "com.github.plokhotnyuk.jsoniter-scala::jsoniter-scala-macros:2.30.1"
// ---------------------

import zio.*
import zio.http.Server

import com.github.plokhotnyuk.jsoniter_scala.macros.*
import com.github.plokhotnyuk.jsoniter_scala.core.*

import io.github.iltotore.iron.*
import io.github.iltotore.iron.constraint.all.*
import io.github.iltotore.iron.jsoniter.given

import sttp.tapir.ztapir.*
import sttp.tapir.server.ziohttp.ZioHttpInterpreter
import sttp.tapir.swagger.bundle.SwaggerInterpreter
import sttp.tapir.json.jsoniter.*
import sttp.tapir.Schema
import sttp.tapir.generic.auto.*
import sttp.tapir.codec.iron.given

import sttp.apispec.openapi.Info

object WebApp extends ZIOAppDefault {

  //type Name      = String :| (Not[Empty] & Alphanumeric) // //DescribedAs "name should not be empty and only made of alphanumeric characters"
  type Name      = String :| (MinLength[3] & MaxLength[64])
  type Age       = Int :| Positive // DescribedAs "Age should be positive"
  type NickName  = String :| (MinLength[3] & MaxLength[6])
  type NickNames = List[NickName] :| MinLength[1]
  //type NickNames = List[NickName] :| Not[Empty]

  enum Gender derives ConfiguredJsonValueCodec {
    case Female, Male, Other
  }

  case class Salute(
    name: Name,
    age: Int :| Positive, /*DescribedAs "Age should be positive"*/
    gender: Option[Gender],
    nicknames: NickNames
  ) derives ConfiguredJsonValueCodec

  case class Greeting(message: String) derives ConfiguredJsonValueCodec

  // --------------------------------------------------
  def helloLogic(salute: Salute): UIO[Greeting] = ZIO.succeed(Greeting("hello"))

  val helloEndPoint =
    endpoint
      .tag("Greetings")
      .name("hello")
      .description("Returns greeting")
      .post
      .in("hello")
      .in(jsonBody[Salute])
      .out(jsonBody[Greeting])

  val helloRoute = helloEndPoint.zServerLogic[Any](helloLogic)

  // --------------------------------------------------

  val apiDocRoute =
    SwaggerInterpreter()
      .fromServerEndpoints(
        List(helloRoute),
        Info(title = "Greeting API", version = "1.0", description = Some("Everything required to be polite"))
      )

  // --------------------------------------------------

  val routes = ZioHttpInterpreter().toHttp(List(helloRoute) ++ apiDocRoute)

  val server = for {
    _ <- ZIO.log("API documentation : http://127.0.0.1:8080/docs")
    _ <- Server.serve(routes)
  } yield ()

  override def run = server.provide(Server.default)
}

WebApp.main(Array.empty)
@kciesielski kciesielski self-assigned this Jun 12, 2024
@kciesielski
Copy link
Member

Thanks a lot for reporting @dacr
I added #3843 which should resolve the second point:

Support MinLength, MaxLength in generated documentation for collection types

will work on other cases soon

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants