diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8016dc52fd..d25ad260bb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,6 +77,8 @@ jobs: java-version: 21 cache: sbt + - uses: coursier/setup-action@v1 + - name: Check formatting if: matrix.scala == '2.13.14' run: sbt ++2.13.14 fmtCheck @@ -87,6 +89,8 @@ jobs: - name: Build project run: sbt '++ ${{ matrix.scala }}' test + - uses: coursier/setup-action@v1 + - name: Check doc generation if: ${{ github.event_name == 'pull_request' }} run: sbt ++2.13.14 doc @@ -229,11 +233,12 @@ jobs: java: [temurin@21] runs-on: ${{ matrix.os }} steps: - - name: Checkout current branch (full) - uses: actions/checkout@v4 + - uses: actions/checkout@v4 with: fetch-depth: 0 + - uses: coursier/setup-action@v1 + - name: Setup Java (temurin@21) if: matrix.java == 'temurin@21' uses: actions/setup-java@v4 @@ -253,11 +258,14 @@ jobs: java: [temurin@8] runs-on: ${{ matrix.os }} steps: - - name: Checkout current branch (full) - uses: actions/checkout@v4 + - uses: actions/checkout@v4 with: fetch-depth: 0 + - uses: coursier/setup-action@v1 + with: + apps: sbt + - name: Add Scoverage id: add_plugin run: sed -i -e '$aaddSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.12")' project/plugins.sbt @@ -286,11 +294,15 @@ jobs: java: [temurin@8] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 + - uses: coursier/setup-action@v1 + with: + apps: sbt + + - uses: actions/checkout@v4 with: path: zio-http - - uses: actions/setup-java@v2 + - uses: actions/setup-java@v4 with: distribution: temurin java-version: 11 @@ -305,7 +317,7 @@ jobs: cat > Main_CachedDateHeaderBenchmark.txt sbt -no-colors -v "zioHttpBenchmarks/jmh:run -i 3 -wi 3 -f1 -t1 CachedDateHeaderBenchmark" | grep -e "thrpt" -e "avgt" >> ../Main_CachedDateHeaderBenchmark.txt - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: Jmh_Main_CachedDateHeaderBenchmark path: Main_CachedDateHeaderBenchmark.txt @@ -320,11 +332,15 @@ jobs: java: [temurin@8] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 + - uses: coursier/setup-action@v1 + with: + apps: sbt + + - uses: actions/checkout@v4 with: path: zio-http - - uses: actions/setup-java@v2 + - uses: actions/setup-java@v4 with: distribution: temurin java-version: 11 @@ -339,7 +355,7 @@ jobs: cat > Main_ClientBenchmark.txt sbt -no-colors -v "zioHttpBenchmarks/jmh:run -i 3 -wi 3 -f1 -t1 ClientBenchmark" | grep -e "thrpt" -e "avgt" >> ../Main_ClientBenchmark.txt - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: Jmh_Main_ClientBenchmark path: Main_ClientBenchmark.txt @@ -354,11 +370,15 @@ jobs: java: [temurin@8] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 + - uses: coursier/setup-action@v1 + with: + apps: sbt + + - uses: actions/checkout@v4 with: path: zio-http - - uses: actions/setup-java@v2 + - uses: actions/setup-java@v4 with: distribution: temurin java-version: 11 @@ -373,7 +393,7 @@ jobs: cat > Main_CookieDecodeBenchmark.txt sbt -no-colors -v "zioHttpBenchmarks/jmh:run -i 3 -wi 3 -f1 -t1 CookieDecodeBenchmark" | grep -e "thrpt" -e "avgt" >> ../Main_CookieDecodeBenchmark.txt - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: Jmh_Main_CookieDecodeBenchmark path: Main_CookieDecodeBenchmark.txt @@ -388,11 +408,15 @@ jobs: java: [temurin@8] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 + - uses: coursier/setup-action@v1 + with: + apps: sbt + + - uses: actions/checkout@v4 with: path: zio-http - - uses: actions/setup-java@v2 + - uses: actions/setup-java@v4 with: distribution: temurin java-version: 11 @@ -407,7 +431,7 @@ jobs: cat > Main_EndpointBenchmark.txt sbt -no-colors -v "zioHttpBenchmarks/jmh:run -i 3 -wi 3 -f1 -t1 EndpointBenchmark" | grep -e "thrpt" -e "avgt" >> ../Main_EndpointBenchmark.txt - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: Jmh_Main_EndpointBenchmark path: Main_EndpointBenchmark.txt @@ -422,11 +446,15 @@ jobs: java: [temurin@8] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 + - uses: coursier/setup-action@v1 + with: + apps: sbt + + - uses: actions/checkout@v4 with: path: zio-http - - uses: actions/setup-java@v2 + - uses: actions/setup-java@v4 with: distribution: temurin java-version: 11 @@ -441,7 +469,7 @@ jobs: cat > Main_HttpCollectEval.txt sbt -no-colors -v "zioHttpBenchmarks/jmh:run -i 3 -wi 3 -f1 -t1 HttpCollectEval" | grep -e "thrpt" -e "avgt" >> ../Main_HttpCollectEval.txt - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: Jmh_Main_HttpCollectEval path: Main_HttpCollectEval.txt @@ -456,11 +484,15 @@ jobs: java: [temurin@8] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 + - uses: coursier/setup-action@v1 + with: + apps: sbt + + - uses: actions/checkout@v4 with: path: zio-http - - uses: actions/setup-java@v2 + - uses: actions/setup-java@v4 with: distribution: temurin java-version: 11 @@ -475,7 +507,7 @@ jobs: cat > Main_HttpCombineEval.txt sbt -no-colors -v "zioHttpBenchmarks/jmh:run -i 3 -wi 3 -f1 -t1 HttpCombineEval" | grep -e "thrpt" -e "avgt" >> ../Main_HttpCombineEval.txt - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: Jmh_Main_HttpCombineEval path: Main_HttpCombineEval.txt @@ -490,11 +522,15 @@ jobs: java: [temurin@8] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 + - uses: coursier/setup-action@v1 + with: + apps: sbt + + - uses: actions/checkout@v4 with: path: zio-http - - uses: actions/setup-java@v2 + - uses: actions/setup-java@v4 with: distribution: temurin java-version: 11 @@ -509,7 +545,7 @@ jobs: cat > Main_HttpNestedFlatMapEval.txt sbt -no-colors -v "zioHttpBenchmarks/jmh:run -i 3 -wi 3 -f1 -t1 HttpNestedFlatMapEval" | grep -e "thrpt" -e "avgt" >> ../Main_HttpNestedFlatMapEval.txt - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: Jmh_Main_HttpNestedFlatMapEval path: Main_HttpNestedFlatMapEval.txt @@ -524,11 +560,15 @@ jobs: java: [temurin@8] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 + - uses: coursier/setup-action@v1 + with: + apps: sbt + + - uses: actions/checkout@v4 with: path: zio-http - - uses: actions/setup-java@v2 + - uses: actions/setup-java@v4 with: distribution: temurin java-version: 11 @@ -543,7 +583,7 @@ jobs: cat > Main_HttpRouteTextPerf.txt sbt -no-colors -v "zioHttpBenchmarks/jmh:run -i 3 -wi 3 -f1 -t1 HttpRouteTextPerf" | grep -e "thrpt" -e "avgt" >> ../Main_HttpRouteTextPerf.txt - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: Jmh_Main_HttpRouteTextPerf path: Main_HttpRouteTextPerf.txt @@ -558,11 +598,15 @@ jobs: java: [temurin@8] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 + - uses: coursier/setup-action@v1 + with: + apps: sbt + + - uses: actions/checkout@v4 with: path: zio-http - - uses: actions/setup-java@v2 + - uses: actions/setup-java@v4 with: distribution: temurin java-version: 11 @@ -577,7 +621,7 @@ jobs: cat > Main_ProbeContentTypeBenchmark.txt sbt -no-colors -v "zioHttpBenchmarks/jmh:run -i 3 -wi 3 -f1 -t1 ProbeContentTypeBenchmark" | grep -e "thrpt" -e "avgt" >> ../Main_ProbeContentTypeBenchmark.txt - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: Jmh_Main_ProbeContentTypeBenchmark path: Main_ProbeContentTypeBenchmark.txt @@ -592,11 +636,15 @@ jobs: java: [temurin@8] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 + - uses: coursier/setup-action@v1 + with: + apps: sbt + + - uses: actions/checkout@v4 with: path: zio-http - - uses: actions/setup-java@v2 + - uses: actions/setup-java@v4 with: distribution: temurin java-version: 11 @@ -611,7 +659,7 @@ jobs: cat > Main_SchemeDecodeBenchmark.txt sbt -no-colors -v "zioHttpBenchmarks/jmh:run -i 3 -wi 3 -f1 -t1 SchemeDecodeBenchmark" | grep -e "thrpt" -e "avgt" >> ../Main_SchemeDecodeBenchmark.txt - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: Jmh_Main_SchemeDecodeBenchmark path: Main_SchemeDecodeBenchmark.txt @@ -626,11 +674,15 @@ jobs: java: [temurin@8] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 + - uses: coursier/setup-action@v1 + with: + apps: sbt + + - uses: actions/checkout@v4 with: path: zio-http - - uses: actions/setup-java@v2 + - uses: actions/setup-java@v4 with: distribution: temurin java-version: 11 @@ -645,7 +697,7 @@ jobs: cat > Main_ServerInboundHandlerBenchmark.txt sbt -no-colors -v "zioHttpBenchmarks/jmh:run -i 3 -wi 3 -f1 -t1 ServerInboundHandlerBenchmark" | grep -e "thrpt" -e "avgt" >> ../Main_ServerInboundHandlerBenchmark.txt - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: Jmh_Main_ServerInboundHandlerBenchmark path: Main_ServerInboundHandlerBenchmark.txt @@ -660,11 +712,15 @@ jobs: java: [temurin@8] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 + - uses: coursier/setup-action@v1 + with: + apps: sbt + + - uses: actions/checkout@v4 with: path: zio-http - - uses: actions/setup-java@v2 + - uses: actions/setup-java@v4 with: distribution: temurin java-version: 11 @@ -679,7 +735,7 @@ jobs: cat > Main_UtilBenchmark.txt sbt -no-colors -v "zioHttpBenchmarks/jmh:run -i 3 -wi 3 -f1 -t1 UtilBenchmark" | grep -e "thrpt" -e "avgt" >> ../Main_UtilBenchmark.txt - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: Jmh_Main_UtilBenchmark path: Main_UtilBenchmark.txt @@ -709,91 +765,91 @@ jobs: java: [temurin@8] runs-on: ${{ matrix.os }} steps: - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: Jmh_Main_CachedDateHeaderBenchmark - name: Format_Main_CachedDateHeaderBenchmark run: cat Main_CachedDateHeaderBenchmark.txt >> Main_benchmarks.txt - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: Jmh_Main_ClientBenchmark - name: Format_Main_ClientBenchmark run: cat Main_ClientBenchmark.txt >> Main_benchmarks.txt - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: Jmh_Main_CookieDecodeBenchmark - name: Format_Main_CookieDecodeBenchmark run: cat Main_CookieDecodeBenchmark.txt >> Main_benchmarks.txt - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: Jmh_Main_EndpointBenchmark - name: Format_Main_EndpointBenchmark run: cat Main_EndpointBenchmark.txt >> Main_benchmarks.txt - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: Jmh_Main_HttpCollectEval - name: Format_Main_HttpCollectEval run: cat Main_HttpCollectEval.txt >> Main_benchmarks.txt - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: Jmh_Main_HttpCombineEval - name: Format_Main_HttpCombineEval run: cat Main_HttpCombineEval.txt >> Main_benchmarks.txt - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: Jmh_Main_HttpNestedFlatMapEval - name: Format_Main_HttpNestedFlatMapEval run: cat Main_HttpNestedFlatMapEval.txt >> Main_benchmarks.txt - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: Jmh_Main_HttpRouteTextPerf - name: Format_Main_HttpRouteTextPerf run: cat Main_HttpRouteTextPerf.txt >> Main_benchmarks.txt - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: Jmh_Main_ProbeContentTypeBenchmark - name: Format_Main_ProbeContentTypeBenchmark run: cat Main_ProbeContentTypeBenchmark.txt >> Main_benchmarks.txt - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: Jmh_Main_SchemeDecodeBenchmark - name: Format_Main_SchemeDecodeBenchmark run: cat Main_SchemeDecodeBenchmark.txt >> Main_benchmarks.txt - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: Jmh_Main_ServerInboundHandlerBenchmark - name: Format_Main_ServerInboundHandlerBenchmark run: cat Main_ServerInboundHandlerBenchmark.txt >> Main_benchmarks.txt - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: Jmh_Main_UtilBenchmark - name: Format_Main_UtilBenchmark run: cat Main_UtilBenchmark.txt >> Main_benchmarks.txt - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: path: zio-http @@ -828,11 +884,11 @@ jobs: GITHUB_TOKEN: ${{secrets.ACTIONS_PAT}} run: sudo rm -rf * - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: path: zio-http - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: repository: khajavi/FrameworkBenchmarks path: FrameworkBenchMarks @@ -885,7 +941,7 @@ jobs: if (( REQUESTS_PER_SECOND > PERFORMANCE_FLOOR )); then echo "Woohoo! Performance is good! $REQUESTS_PER_SECOND requests/sec exceeds the performance floor of $PERFORMANCE_FLOOR requests/sec." - else + else echo "Performance benchmark failed with $REQUESTS_PER_SECOND req/sec! Performance must exceed $PERFORMANCE_FLOOR req/sec." exit 1 fi @@ -906,11 +962,11 @@ jobs: GITHUB_TOKEN: ${{secrets.ACTIONS_PAT}} run: sudo rm -rf * - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: path: zio-http - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: repository: khajavi/FrameworkBenchmarks path: FrameworkBenchMarks @@ -963,7 +1019,7 @@ jobs: if (( REQUESTS_PER_SECOND > PERFORMANCE_FLOOR )); then echo "Woohoo! Performance is good! $REQUESTS_PER_SECOND requests/sec exceeds the performance floor of $PERFORMANCE_FLOOR requests/sec." - else + else echo "Performance benchmark failed with $REQUESTS_PER_SECOND req/sec! Performance must exceed $PERFORMANCE_FLOOR req/sec." exit 1 fi diff --git a/.github/workflows/site.yml b/.github/workflows/site.yml index 027da47ab4..f8bc4e9ef6 100644 --- a/.github/workflows/site.yml +++ b/.github/workflows/site.yml @@ -1,4 +1,4 @@ -# This file was autogenerated using `zio-sbt-website` via `sbt generateGithubWorkflow` +# This file was autogenerated using `zio-sbt-website` via `sbt generateGithubWorkflow` # task and should be included in the git repository. Please do not edit it manually. name: Website @@ -23,6 +23,7 @@ jobs: uses: actions/checkout@v3.3.0 with: fetch-depth: '0' + - uses: coursier/setup-action@v1 - name: Setup Scala uses: actions/setup-java@v3.9.0 with: @@ -48,6 +49,7 @@ jobs: distribution: temurin java-version: 17 check-latest: true + - uses: coursier/setup-action@v1 - name: Setup NodeJs uses: actions/setup-node@v3 with: @@ -67,6 +69,7 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: '0' + - uses: coursier/setup-action@v1 - name: Install libuv run: sudo apt-get update && sudo apt-get install -y libuv1-dev - name: Setup Scala diff --git a/README.md b/README.md index 5c8b92ec0a..73dfc6c7ea 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Some of the key features of ZIO HTTP are: Setup via `build.sbt`: ```scala -libraryDependencies += "dev.zio" %% "zio-http" % "3.0.0" +libraryDependencies += "dev.zio" %% "zio-http" % "3.0.1" ``` **NOTES ON VERSIONING:** diff --git a/build.sbt b/build.sbt index e2ad47a064..c35a4dd9fb 100644 --- a/build.sbt +++ b/build.sbt @@ -38,7 +38,10 @@ ThisBuild / githubWorkflowAddedJobs := WorkflowJob( id = "mima_check", name = "Mima Check", - steps = WorkflowStep.CheckoutFull +: WorkflowStep.SetupJava(List(JavaSpec.temurin("21"))) :+ WorkflowStep.Sbt(List("mimaChecks")), + steps = List( + WorkflowStep.Use(UseRef.Public("actions", "checkout", "v4"), Map("fetch-depth" -> "0")), + WorkflowStep.Use(UseRef.Public("coursier", "setup-action", "v1")), + ) ++ WorkflowStep.SetupJava(List(JavaSpec.temurin("21"))) :+ WorkflowStep.Sbt(List("mimaChecks")), cond = Option("${{ github.event_name == 'pull_request' }}"), javas = List(JavaSpec.temurin("21")), ), @@ -75,6 +78,7 @@ ThisBuild / githubWorkflowPublish := //scala fix isn't available for scala 3 so ensure we only run the fmt check //using the latest scala 2.13 ThisBuild / githubWorkflowBuildPreamble := Seq( + WorkflowStep.Use(UseRef.Public("coursier", "setup-action", "v1")), WorkflowStep.Run( name = Some("Check formatting"), commands = List(s"sbt ++${Scala213} fmtCheck"), @@ -87,6 +91,7 @@ ThisBuild / githubWorkflowBuildPostamble := "checkDocGeneration", "Check doc generation", List( + WorkflowStep.Use(UseRef.Public("coursier", "setup-action", "v1")), WorkflowStep.Run( commands = List(s"sbt ++${Scala213} doc"), name = Some("Check doc generation"), diff --git a/docs/guides/testing-http-apps.md b/docs/guides/testing-http-apps.md index 578d958ee2..5d462bcf36 100644 --- a/docs/guides/testing-http-apps.md +++ b/docs/guides/testing-http-apps.md @@ -117,12 +117,10 @@ object TestServerExampleSpec extends ZIOSpecDefault { fallbackResponse <- client.batched(Request.get(testRequest.url / "any")) fallbackBody <- fallbackResponse.body.asString } yield assertTrue(helloBody == "Hey there!", fallbackBody == "fallback") - }.provideSome[Client with Driver](TestServer.layer) + } }.provide( - ZLayer.succeed(Server.Config.default.onAnyOpenPort), + TestServer.default, Client.default, - NettyDriver.customized, - ZLayer.succeed(NettyConfig.defaultWithFastShutdown), ) } ``` diff --git a/docs/reference/handler.md b/docs/reference/handler.md index 52bddff4e4..f4b43aedf5 100644 --- a/docs/reference/handler.md +++ b/docs/reference/handler.md @@ -612,6 +612,17 @@ In this example, the type of the handler before applying the `sandbox` operator Without the `sandbox` operator, the compiler would complain about the unhandled `Throwable` error. +By default, sandboxed errors will result in a `500 Internal Server Error` response without a body. If you want to have all information about the error in the response body you can use a different (`ErrorResponseConfig`)[response/response.md#failure-responses-with-details] like `ErrorResponseConfig.debug`: + +```scala mdoc:compile-only +import zio.http._ +import java.nio.file._ + +Routes( + Method.GET / "file" -> + Handler.fromFile(Paths.get("file.txt").toFile).sandbox, + ) @@ ErrorResponseConfig.debug +``` ### Converting a `Handler` to an `Routes` The `Handler#toRoutes` operator, converts a handler to an `Routes` to be served by the `Server`. The following example, shows an HTTP application that serves a simple "Hello, World!" response for all types of incoming requests: diff --git a/docs/reference/response/response.md b/docs/reference/response/response.md index cb6a3ce842..f5d0dec5dc 100644 --- a/docs/reference/response/response.md +++ b/docs/reference/response/response.md @@ -188,6 +188,56 @@ val failedHandler = Handler.fail(new IOException()) failedHandler.mapErrorCause(Response.fromCause) ``` +#### Failure Responses with Details + +By default, the `Response.fromThrowable` and `Response.fromCause` methods create a response with a status code only. If we want to include additional details in the response, we have to hand over a `ErrorResponseConfig`. + +```scala +/** + * Configuration for the response generation + * + * @param withErrorBody + * if true, includes the error message in the response body + * @param withStackTrace + * if true, includes the stack trace in the response body + * @param maxStackTraceDepth + * maximum number of stack trace lines to include in the response body. Set to + * 0 to include all lines. + * @param errorFormat + * the preferred format for the error response. + * If the context in which the response is created has access to an Accept header, + * the header will be used preferably to determine the format. + */ +final case class ErrorResponseConfig( + withErrorBody: Boolean = false, + withStackTrace: Boolean = false, + maxStackTraceDepth: Int = 10, + errorFormat: ErrorResponseConfig.ErrorFormat = ErrorResponseConfig.ErrorFormat.Html, +) +``` + +This config can not only be used directly, but can also configure how ZIO-HTTP internally converts a `Cause` or `Throwable` to a `Response`. +You can configure error responses globally by providing a custom `ErrorResponseConfig` via layer for example in the bootstrap of your application. +Or you can apply the config locally to some routes via middleware. + +```scala mdoc +import zio.http._ + +object MyHttpApp extends ZIOAppDefault { + // Provide a custom ErrorResponseConfig via layer + // Equivalent to: val bootstrap = ErrorResponseConfig.configLayer(ErrorResponseConfig.debugConfig) + override val bootstrap: ZLayer[ZIOAppArgs, Any, Any] = ErrorResponseConfig.debugLayer + + // Apply the ErrorResponseConfig.debug middleware to routes + // Equivalent to: val myRoutes = Handler.ok.toRoutes @@ ErrorResponseConfig.withConfig(ErrorResponseConfig.debugConfig) + val myRoutes = Handler.ok.toRoutes @@ ErrorResponseConfig.debug + + override def run = ??? +} +``` + +The debug config will include the error message and full stack trace in the response body. + :::note In many cases, it is more convenient to use the `sandbox` method to automatically convert all failures into a corresponding `Response`. But in some cases, to have more granular control over the error handling, we may want to use `Response.fromCause` and `Response.fromThrowable` directly. ::: diff --git a/project/BenchmarkWorkFlow.scala b/project/BenchmarkWorkFlow.scala index dd2f01bde5..25cf7edaeb 100644 --- a/project/BenchmarkWorkFlow.scala +++ b/project/BenchmarkWorkFlow.scala @@ -35,13 +35,13 @@ object BenchmarkWorkFlow { commands = List("sudo rm -rf *"), ), WorkflowStep.Use( - UseRef.Public("actions", "checkout", s"v2"), + UseRef.Public("actions", "checkout", s"v4"), Map( "path" -> "zio-http", ), ), WorkflowStep.Use( - UseRef.Public("actions", "checkout", s"v2"), + UseRef.Public("actions", "checkout", s"v4"), Map( "repository" -> "khajavi/FrameworkBenchmarks", "path" -> "FrameworkBenchMarks", @@ -103,7 +103,7 @@ object BenchmarkWorkFlow { |if (( REQUESTS_PER_SECOND > PERFORMANCE_FLOOR )); then | echo "Woohoo! Performance is good! $REQUESTS_PER_SECOND requests/sec exceeds the performance floor of $PERFORMANCE_FLOOR requests/sec." - |else + |else | echo "Performance benchmark failed with $REQUESTS_PER_SECOND req/sec! Performance must exceed $PERFORMANCE_FLOOR req/sec." | exit 1 |fi""".stripMargin, diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 65007e599b..9bd09b8f72 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -5,7 +5,7 @@ object Dependencies { val NettyVersion = "4.1.112.Final" val NettyIncubatorVersion = "0.0.25.Final" val ScalaCompactCollectionVersion = "2.12.0" - val ZioVersion = "2.1.9" + val ZioVersion = "2.1.11" val ZioCliVersion = "0.5.0" val ZioJsonVersion = "0.7.1" val ZioParserVersion = "0.1.10" diff --git a/project/JmhBenchmarkWorkflow.scala b/project/JmhBenchmarkWorkflow.scala index abb3a1b580..c49c17c5ea 100644 --- a/project/JmhBenchmarkWorkflow.scala +++ b/project/JmhBenchmarkWorkflow.scala @@ -11,7 +11,7 @@ object JmhBenchmarkWorkflow { Glob("zio-http-benchmarks/src/main/scala-2.13/**"), Glob("zio-http-benchmarks/src/main/scala/**")),scalaSources ) - + /** * Get zioHttpBenchmark file names */ @@ -45,7 +45,7 @@ object JmhBenchmarkWorkflow { def downloadArtifacts(branch: String, batchSize: Int) = groupedBenchmarks(batchSize).flatMap(l => { Seq( WorkflowStep.Use( - ref = UseRef.Public("actions", "download-artifact", "v3"), + ref = UseRef.Public("actions", "download-artifact", "v4"), Map( "name" -> s"Jmh_${branch}_${l.head}", ), @@ -84,7 +84,7 @@ object JmhBenchmarkWorkflow { steps = downloadArtifacts("Main", batchSize) ++ Seq( WorkflowStep.Use( - UseRef.Public("actions", "checkout", "v2"), + UseRef.Public("actions", "checkout", "v4"), Map( "path" -> "zio-http" ) @@ -113,14 +113,15 @@ object JmhBenchmarkWorkflow { ), scalas = List(Scala213), steps = List( + WorkflowStep.Use(UseRef.Public("coursier", "setup-action","v1"), Map("apps" -> "sbt")), WorkflowStep.Use( - UseRef.Public("actions", "checkout", "v2"), + UseRef.Public("actions", "checkout", "v4"), Map( "path" -> "zio-http", ), ), WorkflowStep.Use( - UseRef.Public("actions", "setup-java", "v2"), + UseRef.Public("actions", "setup-java", "v4"), Map( "distribution" -> "temurin", "java-version" -> "11", @@ -137,7 +138,7 @@ object JmhBenchmarkWorkflow { name = Some("Benchmark_Main"), ), WorkflowStep.Use( - UseRef.Public("actions", "upload-artifact", "v3"), + UseRef.Public("actions", "upload-artifact", "v4"), Map( "name" -> s"Jmh_Main_${l.head}", "path" -> s"Main_${l.head}.txt", diff --git a/project/ScoverageWorkFlow.scala b/project/ScoverageWorkFlow.scala index 54d677a727..e623d94a44 100644 --- a/project/ScoverageWorkFlow.scala +++ b/project/ScoverageWorkFlow.scala @@ -1,5 +1,5 @@ import BuildHelper.{Scala213, ScoverageVersion} -import sbtghactions.GenerativePlugin.autoImport.{WorkflowJob, WorkflowStep} +import sbtghactions.GenerativePlugin.autoImport.{UseRef, WorkflowJob, WorkflowStep} object ScoverageWorkFlow { // TODO move plugins to plugins.sbt after scoverage's support for Scala 3 @@ -15,7 +15,8 @@ object ScoverageWorkFlow { name = "Unsafe Scoverage", scalas = List(Scala213), steps = List( - WorkflowStep.CheckoutFull, + WorkflowStep.Use(UseRef.Public("actions", "checkout", "v4"), Map("fetch-depth" -> "0")), + WorkflowStep.Use(UseRef.Public("coursier", "setup-action","v1"), Map("apps" -> "sbt")), WorkflowStep.Run( commands = List(s"sed -i -e '$$a${scoveragePlugin}' project/plugins.sbt"), id = Some("add_plugin"), diff --git a/zio-http-gen/src/test/resources/ComponentAliasKey.scala b/zio-http-gen/src/test/resources/ComponentAliasKey.scala new file mode 100644 index 0000000000..a9e41fa76c --- /dev/null +++ b/zio-http-gen/src/test/resources/ComponentAliasKey.scala @@ -0,0 +1,9 @@ +package test.component + +import zio.prelude.Newtype +import zio.schema.Schema +import java.util.UUID + +object Key extends Newtype[UUID] { + implicit val schema: Schema[Key.Type] = Schema.primitive[UUID].transform(wrap, unwrap) +} \ No newline at end of file diff --git a/zio-http-gen/src/test/resources/inline_schema_alias_only_as_key_schema.yaml b/zio-http-gen/src/test/resources/inline_schema_alias_only_as_key_schema.yaml new file mode 100644 index 0000000000..6c23c0cf2e --- /dev/null +++ b/zio-http-gen/src/test/resources/inline_schema_alias_only_as_key_schema.yaml @@ -0,0 +1,36 @@ +info: + title: Dummy Service + version: 0.0.1 +servers: + - url: http://127.0.0.1:5000/ +tags: + - name: Dummy_API +paths: + /api/text_by_key: + get: + operationId: text_by_key + description: Get a dictionary mapping keys to text + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/ObjectWithDictionary' + description: OK +openapi: 3.0.3 +components: + schemas: + Key: + type: string + format: uuid + ObjectWithDictionary: + type: object + required: + - dict + properties: + dict: + type: object + additionalProperties: + type: string + x-string-key-schema: + $ref: '#/components/schemas/Key' diff --git a/zio-http-gen/src/test/scala/zio/http/gen/scala/CodeGenSpec.scala b/zio-http-gen/src/test/scala/zio/http/gen/scala/CodeGenSpec.scala index e65ca03d81..16ba55d5d4 100644 --- a/zio-http-gen/src/test/scala/zio/http/gen/scala/CodeGenSpec.scala +++ b/zio-http-gen/src/test/scala/zio/http/gen/scala/CodeGenSpec.scala @@ -979,6 +979,29 @@ object CodeGenSpec extends ZIOSpecDefault { } } } @@ TestAspect.exceptScala3, + test("Schema with newtype only referenced as dictionary key") { + val openAPIString = stringFromResource("/inline_schema_alias_only_as_key_schema.yaml") + + openApiFromYamlString(openAPIString) { oapi => + codeGenFromOpenAPI( + oapi, + Config.default.copy(generateSafeTypeAliases = true), + ) { testDir => + allFilesShouldBe( + testDir.toFile, + List( + "api/Text_by_key.scala", + "component/Key.scala", + "component/ObjectWithDictionary.scala", + ), + ) && fileShouldBe( + testDir, + "component/Key.scala", + "/ComponentAliasKey.scala", + ) + } + } + } @@ TestAspect.exceptScala3, test("Generate all responses") { val oapi = OpenAPI( diff --git a/zio-http-testkit/src/main/scala/zio/http/TestServer.scala b/zio-http-testkit/src/main/scala/zio/http/TestServer.scala index 0bdd25dad9..d551f693dd 100644 --- a/zio-http-testkit/src/main/scala/zio/http/TestServer.scala +++ b/zio-http-testkit/src/main/scala/zio/http/TestServer.scala @@ -2,6 +2,9 @@ package zio.http import zio._ +import zio.http.netty.NettyConfig +import zio.http.netty.server.NettyDriver + /** * Enables tests that make calls against "localhost" with user-specified * Behavior/Responses. @@ -136,4 +139,11 @@ object TestServer { } yield TestServer(driver, result.port) } + val default: ZLayer[Any, Nothing, Server with TestServer] = ZLayer.make[Server with TestServer][Nothing]( + TestServer.layer.orDie, + ZLayer.succeed(Server.Config.default.onAnyOpenPort), + NettyDriver.customized.orDie, + ZLayer.succeed(NettyConfig.defaultWithFastShutdown), + ) + } diff --git a/zio-http-testkit/src/test/scala/zio/http/TestServerSpec.scala b/zio-http-testkit/src/test/scala/zio/http/TestServerSpec.scala index 084d9197e2..a6b90871bb 100644 --- a/zio-http-testkit/src/test/scala/zio/http/TestServerSpec.scala +++ b/zio-http-testkit/src/test/scala/zio/http/TestServerSpec.scala @@ -37,8 +37,8 @@ object TestServerSpec extends ZIOHttpSpec { ) } yield assertTrue(status(response1) == Status.Ok) && assertTrue(status(response2) == Status.InternalServerError) - }.provideSome[Client with Driver]( - TestServer.layer, + }.provideSome[Client]( + TestServer.default, Scope.default, ), suite("Exact Request=>Response version")( @@ -83,8 +83,8 @@ object TestServerSpec extends ZIOHttpSpec { } yield assertTrue(status(finalResponse) == Status.NotFound) }, ) - .provideSome[Client with Driver]( - TestServer.layer, + .provideSome[Client]( + TestServer.default, Scope.default, ), test("add routes to the server") { @@ -102,15 +102,12 @@ object TestServerSpec extends ZIOHttpSpec { fallbackResponse <- client(Request.get(testRequest.url / "any")) fallbackBody <- fallbackResponse.body.asString } yield assertTrue(helloBody == "Hey there!", fallbackBody == "fallback") - }.provideSome[Client with Driver]( - TestServer.layer, + }.provideSome[Client]( + TestServer.default, Scope.default, ), ).provide( - ZLayer.succeed(Server.Config.default.onAnyOpenPort), Client.default, - NettyDriver.customized, - ZLayer.succeed(NettyConfig.defaultWithFastShutdown), ) private def requestToCorrectPort = diff --git a/zio-http/jvm/src/main/scala/zio/http/netty/client/ClientSSLConverter.scala b/zio-http/jvm/src/main/scala/zio/http/netty/client/ClientSSLConverter.scala index 6df29da1db..7a3b3e0c94 100644 --- a/zio-http/jvm/src/main/scala/zio/http/netty/client/ClientSSLConverter.scala +++ b/zio-http/jvm/src/main/scala/zio/http/netty/client/ClientSSLConverter.scala @@ -18,7 +18,7 @@ package zio.http.netty.client import java.io.{File, FileInputStream, InputStream} import java.security.KeyStore -import javax.net.ssl.TrustManagerFactory +import javax.net.ssl.{KeyManagerFactory, TrustManagerFactory} import scala.util.Using @@ -31,6 +31,39 @@ import zio.http.ClientSSLConfig import io.netty.handler.ssl.util.InsecureTrustManagerFactory import io.netty.handler.ssl.{SslContext, SslContextBuilder} private[netty] object ClientSSLConverter { + private def keyManagerTrustManagerToSslContext( + keyManagerInfo: Option[(String, InputStream, Option[Secret])], + trustManagerInfo: Option[(String, InputStream, Option[Secret])], + sslContextBuilder: SslContextBuilder, + ): SslContextBuilder = { + val mkeyManagerFactory = + keyManagerInfo.map { case (keyStoreType, inputStream, maybePassword) => + val keyStore = KeyStore.getInstance(keyStoreType) + val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm) + val password = maybePassword.map(_.value.toArray).orNull + + keyStore.load(inputStream, password) + keyManagerFactory.init(keyStore, password) + keyManagerFactory + } + + val mtrustManagerFactory = + trustManagerInfo.map { case (keyStoreType, inputStream, maybePassword) => + val keyStore = KeyStore.getInstance(keyStoreType) + val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm) + val password = maybePassword.map(_.value.toArray).orNull + + keyStore.load(inputStream, password) + trustManagerFactory.init(keyStore) + trustManagerFactory + } + + var bldr = SslContextBuilder.forClient() + mkeyManagerFactory.foreach(kmf => bldr = bldr.keyManager(kmf)) + mtrustManagerFactory.foreach(tmf => bldr = bldr.trustManager(tmf)) + bldr + } + private def trustStoreToSslContext( trustStoreStream: InputStream, trustStorePassword: Secret, @@ -78,6 +111,41 @@ private[netty] object ClientSSLConverter { case ClientSSLConfig.FromTrustStoreFile(trustStorePath, trustStorePassword) => val trustStoreStream = new FileInputStream(trustStorePath) trustStoreToSslContext(trustStoreStream, trustStorePassword, sslContextBuilder) + case ClientSSLConfig.FromJavaxNetSsl( + keyManagerKeyStoreType, + keyManagerSource, + keyManagerPassword, + trustManagerKeyStoreType, + trustManagerSource, + trustManagerPassword, + ) => + val keyManagerInfo = + keyManagerSource match { + case ClientSSLConfig.FromJavaxNetSsl.File(path) => + Option(new FileInputStream(path)).map(inputStream => + (keyManagerKeyStoreType, inputStream, keyManagerPassword), + ) + case ClientSSLConfig.FromJavaxNetSsl.Resource(path) => + Option(getClass.getClassLoader.getResourceAsStream(path)).map(inputStream => + (keyManagerKeyStoreType, inputStream, keyManagerPassword), + ) + case ClientSSLConfig.FromJavaxNetSsl.Empty => None + } + + val trustManagerInfo = + trustManagerSource match { + case ClientSSLConfig.FromJavaxNetSsl.File(path) => + Option(new FileInputStream(path)).map(inputStream => + (trustManagerKeyStoreType, inputStream, trustManagerPassword), + ) + case ClientSSLConfig.FromJavaxNetSsl.Resource(path) => + Option(getClass.getClassLoader.getResourceAsStream(path)).map(inputStream => + (trustManagerKeyStoreType, inputStream, trustManagerPassword), + ) + case ClientSSLConfig.FromJavaxNetSsl.Empty => None + } + + keyManagerTrustManagerToSslContext(keyManagerInfo, trustManagerInfo, sslContextBuilder) } def toNettySSLContext(sslConfig: ClientSSLConfig): SslContext = { diff --git a/zio-http/jvm/src/test/scala/zio/http/ClientHttpsSpec.scala b/zio-http/jvm/src/test/scala/zio/http/ClientHttpsSpec.scala index 97d5810d7c..fcdb1842e2 100644 --- a/zio-http/jvm/src/test/scala/zio/http/ClientHttpsSpec.scala +++ b/zio-http/jvm/src/test/scala/zio/http/ClientHttpsSpec.scala @@ -18,18 +18,14 @@ package zio.http import zio._ import zio.test.Assertion._ -import zio.test.TestAspect.{ignore, nonFlaky} +import zio.test.TestAspect.nonFlaky import zio.test.{TestAspect, assertZIO} import zio.http.netty.NettyConfig import zio.http.netty.client.NettyClientDriver -object ClientHttpsSpec extends ZIOHttpSpec { - - val sslConfig = ClientSSLConfig.FromTrustStoreResource( - trustStorePath = "truststore.jks", - trustStorePassword = "changeit", - ) +abstract class ClientHttpsSpecBase extends ZIOHttpSpec { + val sslConfig: ClientSSLConfig val zioDev = URL.decode("https://zio.dev").toOption.get @@ -37,7 +33,7 @@ object ClientHttpsSpec extends ZIOHttpSpec { val badRequest = URL .decode( - "https://www.whatissslcertificate.com/google-has-made-the-list-of-untrusted-providers-of-digital-certificates/", + "https://httpbin.org/status/400", ) .toOption .get @@ -57,7 +53,7 @@ object ClientHttpsSpec extends ZIOHttpSpec { test("should respond as Bad Request") { val actual = Client.batched(Request.get(badRequest)).map(_.status) assertZIO(actual)(equalTo(Status.BadRequest)) - } @@ ignore, + }, test("should throw DecoderException for handshake failure") { val actual = Client.batched(Request.get(untrusted)).exit assertZIO(actual)( @@ -69,7 +65,7 @@ object ClientHttpsSpec extends ZIOHttpSpec { ), ), ) - } @@ nonFlaky(20) @@ ignore, + } @@ nonFlaky(20), ) .provideShared( ZLayer.succeed(ZClient.Config.default.ssl(sslConfig)), @@ -83,3 +79,20 @@ object ClientHttpsSpec extends ZIOHttpSpec { ZLayer.succeed(NettyConfig.defaultWithFastShutdown), ) } + +object ClientHttpsSpec extends ClientHttpsSpecBase { + + val sslConfig = ClientSSLConfig.FromTrustStoreResource( + trustStorePath = "truststore.jks", + trustStorePassword = "changeit", + ) +} + +object ClientHttpsFromJavaxNetSslSpec extends ClientHttpsSpecBase { + + val sslConfig = + ClientSSLConfig.FromJavaxNetSsl + .builderWithTrustManagerResource("trustStore.jks") + .trustManagerPassword("changeit") + .build() +} diff --git a/zio-http/jvm/src/test/scala/zio/http/RouteSpec.scala b/zio-http/jvm/src/test/scala/zio/http/RouteSpec.scala index a0fc0f8b0a..4d195e2f14 100644 --- a/zio-http/jvm/src/test/scala/zio/http/RouteSpec.scala +++ b/zio-http/jvm/src/test/scala/zio/http/RouteSpec.scala @@ -135,6 +135,12 @@ object RouteSpec extends ZIOHttpSpec { bodyString <- response.body.asString } yield assertTrue(extractStatus(response) == Status.InternalServerError, bodyString == "error") }, + test("handleErrorCause should pass through responses in error channel of handled routes") { + val route = Method.GET / "endpoint" -> handler { (_: Request) => ZIO.fail(Response.ok) } + val errorHandled = route.handleErrorCause(_ => Response.text("error").status(Status.InternalServerError)) + val request = Request.get(URL.decode("/endpoint").toOption.get) + errorHandled.toRoutes.runZIO(request).map(response => assertTrue(extractStatus(response) == Status.Ok)) + }, test("handleErrorCauseZIO should handle defects") { val route = Method.GET / "endpoint" -> handler { (_: Request) => ZIO.dieMessage("hmm...") } val errorHandled = @@ -145,6 +151,18 @@ object RouteSpec extends ZIOHttpSpec { bodyString <- response.body.asString } yield assertTrue(extractStatus(response) == Status.InternalServerError, bodyString == "error") }, + test("handleErrorCauseZIO should pass through responses in error channel of handled routes") { + val route = Method.GET / "endpoint" -> handler { (_: Request) => ZIO.fail(Response.ok) } + val request = Request.get(URL.decode("/endpoint").toOption.get) + for { + ref <- Ref.make(false) + errorHandled = route.handleErrorCauseZIO(_ => + ref.set(true) *> ZIO.succeed(Response.text("error").status(Status.InternalServerError)), + ) + response <- errorHandled.toRoutes.runZIO(request) + refValue <- ref.get + } yield assertTrue(extractStatus(response) == Status.Ok, !refValue) + }, test("handleErrorRequestCause should handle defects") { val route = Method.GET / "endpoint" -> handler { (_: Request) => ZIO.dieMessage("hmm...") } val errorHandled = @@ -155,6 +173,13 @@ object RouteSpec extends ZIOHttpSpec { bodyString <- response.body.asString } yield assertTrue(extractStatus(response) == Status.InternalServerError, bodyString == "error") }, + test("handleErrorRequestCause should pass through responses in error channel of handled routes") { + val route = Method.GET / "endpoint" -> handler { (_: Request) => ZIO.fail(Response.ok) } + val errorHandled = + route.handleErrorRequestCause((_, _) => Response.text("error").status(Status.InternalServerError)) + val request = Request.get(URL.decode("/endpoint").toOption.get) + errorHandled.toRoutes.runZIO(request).map(response => assertTrue(extractStatus(response) == Status.Ok)) + }, test("handleErrorRequestCauseZIO should handle defects") { val route = Method.GET / "endpoint" -> handler { (_: Request) => ZIO.dieMessage("hmm...") } val errorHandled = route.handleErrorRequestCauseZIO((_, _) => @@ -166,6 +191,18 @@ object RouteSpec extends ZIOHttpSpec { bodyString <- response.body.asString } yield assertTrue(extractStatus(response) == Status.InternalServerError, bodyString == "error") }, + test("handleErrorRequestCauseZIO should pass through responses in error channel of handled routes") { + val route = Method.GET / "endpoint" -> handler { (_: Request) => ZIO.fail(Response.ok) } + val request = Request.get(URL.decode("/endpoint").toOption.get) + for { + ref <- Ref.make(false) + errorHandled = route.handleErrorRequestCauseZIO((_, _) => + ref.set(true) *> ZIO.succeed(Response.text("error").status(Status.InternalServerError)), + ) + response <- errorHandled.toRoutes.runZIO(request) + refValue <- ref.get + } yield assertTrue(extractStatus(response) == Status.Ok, !refValue) + }, test( "Routes with context can eliminate environment type partially when elimination produces intersection type environment", ) { diff --git a/zio-http/jvm/src/test/scala/zio/http/codec/PathCodecSpec.scala b/zio-http/jvm/src/test/scala/zio/http/codec/PathCodecSpec.scala index 8c00e783ba..fc5026061b 100644 --- a/zio-http/jvm/src/test/scala/zio/http/codec/PathCodecSpec.scala +++ b/zio-http/jvm/src/test/scala/zio/http/codec/PathCodecSpec.scala @@ -194,17 +194,14 @@ object PathCodecSpec extends ZIOHttpSpec { test("/users") { val codec = PathCodec.empty / PathCodec.literal("users") - assertTrue( - codec.segments == - Chunk(SegmentCodec.empty, SegmentCodec.literal("users")), - ) + assertTrue(codec.segments == Chunk(SegmentCodec.literal("users"))) }, ), suite("render")( test("empty") { val codec = PathCodec.empty - assertTrue(codec.render == "") + assertTrue(codec.render == "/") }, test("/users") { val codec = PathCodec.empty / PathCodec.literal("users") diff --git a/zio-http/jvm/src/test/scala/zio/http/endpoint/AuthSpec.scala b/zio-http/jvm/src/test/scala/zio/http/endpoint/AuthSpec.scala index 6b60f20c0a..4d93dff8cf 100644 --- a/zio-http/jvm/src/test/scala/zio/http/endpoint/AuthSpec.scala +++ b/zio-http/jvm/src/test/scala/zio/http/endpoint/AuthSpec.scala @@ -154,7 +154,7 @@ object AuthSpec extends ZIOSpecDefault { }, test("Auth basic or bearer with context and endpoint client") { val endpoint = - Endpoint(Method.GET / "test" / "multiAuth") + Endpoint(Method.GET / "multiAuth") .out[String](MediaType.text.`plain`) .auth(AuthType.Basic | AuthType.Bearer) val routes = @@ -188,7 +188,7 @@ object AuthSpec extends ZIOSpecDefault { } yield assertTrue(responseBasic == "admin" && responseBearer == "bearer-admin") }, test("Auth from query parameter with context and endpoint client") { - val endpoint = Endpoint(Method.GET / "test" / "query") + val endpoint = Endpoint(Method.GET / "query") .out[String](MediaType.text.`plain`) .auth(AuthType.Custom(HttpCodec.query[String]("token"))) val routes = @@ -214,7 +214,7 @@ object AuthSpec extends ZIOSpecDefault { }, test("Auth with context and endpoint client with path parameter") { val endpoint = - Endpoint(Method.GET / "test" / int("a")).out[String](MediaType.text.`plain`).auth(AuthType.Basic) + Endpoint(Method.GET / int("a")).out[String](MediaType.text.`plain`).auth(AuthType.Basic) val routes = Routes( endpoint.implementHandler(handler((_: Int) => withContext((ctx: AuthContext) => ctx.value))), diff --git a/zio-http/jvm/src/test/scala/zio/http/endpoint/ServerSentEventEndpointSpec.scala b/zio-http/jvm/src/test/scala/zio/http/endpoint/ServerSentEventEndpointSpec.scala index 77a14a4f4e..e5816e22e8 100644 --- a/zio-http/jvm/src/test/scala/zio/http/endpoint/ServerSentEventEndpointSpec.scala +++ b/zio-http/jvm/src/test/scala/zio/http/endpoint/ServerSentEventEndpointSpec.scala @@ -14,6 +14,7 @@ import zio.schema.{DeriveSchema, Schema} import zio.http._ import zio.http.codec.HttpCodec +import zio.http.netty.NettyConfig object ServerSentEventEndpointSpec extends ZIOHttpSpec { @@ -40,13 +41,14 @@ object ServerSentEventEndpointSpec extends ZIOHttpSpec { : Invocation[Unit, Unit, ZNothing, ZStream[Any, Nothing, ServerSentEvent[String]], AuthType.None] = sseEndpoint(()) - def client(port: Int): ZIO[Scope, Throwable, Chunk[ServerSentEvent[String]]] = - (for { + def client(port: Int): ZIO[Client, Throwable, Chunk[ServerSentEvent[String]]] = ZIO.scoped { + for { client <- ZIO.service[Client] executor = EndpointExecutor(client, locator(port)) stream <- executor(invocation) events <- stream.take(5).runCollect - } yield events).provideSome[Scope](ZClient.default) + } yield events + } } object JsonPayload { @@ -77,13 +79,14 @@ object ServerSentEventEndpointSpec extends ZIOHttpSpec { : Invocation[Unit, Unit, ZNothing, ZStream[Any, Nothing, ServerSentEvent[Payload]], AuthType.None] = sseEndpoint(()) - def client(port: Int): ZIO[Scope, Throwable, Chunk[ServerSentEvent[Payload]]] = - (for { + def client(port: Int): ZIO[Client, Throwable, Chunk[ServerSentEvent[Payload]]] = ZIO.scoped { + for { client <- ZIO.service[Client] executor = EndpointExecutor(client, locator(port)) stream <- executor(invocation) events <- stream.take(5).runCollect - } yield events).provideSome[Scope](ZClient.default) + } yield events + } } override def spec: Spec[TestEnvironment with Scope, Any] = @@ -95,7 +98,7 @@ object ServerSentEventEndpointSpec extends ZIOHttpSpec { port <- ZIO.serviceWithZIO[Server](_.port) events <- client(port) } yield assertTrue(events.size == 5 && events.forall(event => Try(ISO_LOCAL_TIME.parse(event.data)).isSuccess)) - }.provideSome[Scope](Server.defaultWithPort(0)), + }, test("Send and receive ServerSentEvent with json payload") { import JsonPayload._ for { @@ -103,6 +106,14 @@ object ServerSentEventEndpointSpec extends ZIOHttpSpec { port <- ZIO.serviceWithZIO[Server](_.port) events <- client(port) } yield assertTrue(events.size == 5 && events.forall(event => Try(event.data.timeStamp).isSuccess)) - }.provideSome[Scope](Server.defaultWithPort(0)), - ) @@ TestAspect.withLiveClock + }, + ) + .provideSomeLayer[Client & Server.Config & NettyConfig](Server.customized) + .provideShared( + Client.live, + ZLayer.succeed(Server.Config.default.port(0)), + ZLayer.succeed(NettyConfig.defaultWithFastShutdown), + ZLayer.succeed(ZClient.Config.default), + DnsResolver.default, + ) @@ TestAspect.withLiveClock } diff --git a/zio-http/jvm/src/test/scala/zio/http/endpoint/openapi/OpenAPIGenSpec.scala b/zio-http/jvm/src/test/scala/zio/http/endpoint/openapi/OpenAPIGenSpec.scala index 06e00af622..73ceede3a0 100644 --- a/zio-http/jvm/src/test/scala/zio/http/endpoint/openapi/OpenAPIGenSpec.scala +++ b/zio-http/jvm/src/test/scala/zio/http/endpoint/openapi/OpenAPIGenSpec.scala @@ -10,8 +10,8 @@ import zio.schema.{DeriveSchema, Schema} import zio.http.Method.{GET, POST} import zio.http._ -import zio.http.codec.PathCodec.string -import zio.http.codec.{ContentCodec, Doc, HttpCodec} +import zio.http.codec.PathCodec.{empty, string} +import zio.http.codec._ import zio.http.endpoint._ object OpenAPIGenSpec extends ZIOSpecDefault { @@ -180,6 +180,13 @@ object OpenAPIGenSpec extends ZIOSpecDefault { .out[SimpleOutputBody] .outError[NotFoundError](Status.NotFound) + private val queryParamOptEndpoint = + Endpoint(GET / "withQuery") + .in[SimpleInputBody] + .query(HttpCodec.query[String]("query").optional) + .out[SimpleOutputBody] + .outError[NotFoundError](Status.NotFound) + private val queryParamCollectionEndpoint = Endpoint(GET / "withQuery") .in[SimpleInputBody] @@ -194,6 +201,19 @@ object OpenAPIGenSpec extends ZIOSpecDefault { .out[SimpleOutputBody] .outError[NotFoundError](Status.NotFound) + private val optionalHeaderEndpoint = + Endpoint(GET / "withHeader") + .in[SimpleInputBody] + .header(HttpCodec.contentType.optional) + .out[SimpleOutputBody] + .outError[NotFoundError](Status.NotFound) + + private val optionalPayloadEndpoint = + Endpoint(GET / "withPayload") + .inCodec(HttpCodec.content[Payload].optional) + .out[SimpleOutputBody] + .outError[NotFoundError](Status.NotFound) + private val alternativeInputEndpoint = Endpoint(GET / "inputAlternative") .inCodec( @@ -211,8 +231,31 @@ object OpenAPIGenSpec extends ZIOSpecDefault { override def spec: Spec[TestEnvironment with Scope, Any] = suite("OpenAPIGenSpec")( + test("root endpoint to OpenAPI") { + val rootEndpoint = Endpoint(Method.GET / empty) + val generated = OpenAPIGen.fromEndpoints("Root Endpoint", "1.0", rootEndpoint) + val json = toJsonAst(generated) + val expectedJson = """|{ + | "openapi" : "3.1.0", + | "info" : { + | "title" : "Root Endpoint", + | "version" : "1.0" + | }, + | "paths" : { + | "/" : { + | "get" : {} + | } + | }, + | "components" : {} + |}""".stripMargin + assertTrue(json == toJsonAst(expectedJson)) + }, test("simple endpoint to OpenAPI") { - val generated = OpenAPIGen.fromEndpoints("Simple Endpoint", "1.0", simpleEndpoint.tag("simple", "endpoint")) + val generated = OpenAPIGen.fromEndpoints( + "Simple Endpoint", + "1.0", + simpleEndpoint.tag("simple", "endpoint") ?? Doc.p("some extra doc"), + ) val json = toJsonAst(generated) val expectedJson = """{ | "openapi" : "3.1.0", @@ -222,7 +265,7 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | }, | "paths" : { | "/static/{id}/{uuid}/{name}" : { - | "description" : "- simple\n- endpoint\n", + | "description" : "some extra doc\n\n- simple\n- endpoint\n", | "get" : { | "tags" : [ | "simple", @@ -474,6 +517,134 @@ object OpenAPIGenSpec extends ZIOSpecDefault { |}""".stripMargin assertTrue(json == toJsonAst(expectedJson)) }, + test("with optional query parameter") { + val generated = OpenAPIGen.fromEndpoints("Simple Endpoint", "1.0", queryParamOptEndpoint) + val json = toJsonAst(generated) + val expectedJson = """{ + | "openapi" : "3.1.0", + | "info" : { + | "title" : "Simple Endpoint", + | "version" : "1.0" + | }, + | "paths" : { + | "/withQuery" : { + | "get" : { + | "parameters" : [ + | + | { + | "name" : "query", + | "in" : "query", + | "schema" : + | { + | "type" :[ + | "string", + | "null" + | ] + | }, + | "allowReserved" : false, + | "style" : "form" + | } + | ], + | "requestBody" : + | { + | "content" : { + | "application/json" : { + | "schema" : + | { + | "$ref" : "#/components/schemas/SimpleInputBody" + | } + | } + | }, + | "required" : true + | }, + | "responses" : { + | "200" : + | { + | "content" : { + | "application/json" : { + | "schema" : + | { + | "$ref" : "#/components/schemas/SimpleOutputBody" + | } + | } + | } + | }, + | "404" : + | { + | "content" : { + | "application/json" : { + | "schema" : + | { + | "$ref" : "#/components/schemas/NotFoundError" + | } + | } + | } + | } + | } + | } + | } + | }, + | "components" : { + | "schemas" : { + | "NotFoundError" : + | { + | "type" : + | "object", + | "properties" : { + | "message" : { + | "type" : + | "string" + | } + | }, + | "required" : [ + | "message" + | ] + | }, + | "SimpleInputBody" : + | { + | "type" : + | "object", + | "properties" : { + | "name" : { + | "type" : + | "string" + | }, + | "age" : { + | "type" : + | "integer", + | "format" : "int32" + | } + | }, + | "required" : [ + | "name", + | "age" + | ] + | }, + | "SimpleOutputBody" : + | { + | "type" : + | "object", + | "properties" : { + | "userName" : { + | "type" : + | "string" + | }, + | "score" : { + | "type" : + | "integer", + | "format" : "int32" + | } + | }, + | "required" : [ + | "userName", + | "score" + | ] + | } + | } + | } + |}""".stripMargin + assertTrue(json == toJsonAst(expectedJson)) + }, test("with query parameter with multiple values") { val generated = OpenAPIGen.fromEndpoints("Simple Endpoint", "1.0", queryParamCollectionEndpoint) val json = toJsonAst(generated) @@ -730,6 +901,213 @@ object OpenAPIGenSpec extends ZIOSpecDefault { |}""".stripMargin assertTrue(json == toJsonAst(expectedJson)) }, + test("optional header") { + val generated = OpenAPIGen.fromEndpoints("Simple Endpoint", "1.0", optionalHeaderEndpoint) + val json = toJsonAst(generated) + val expectedJson = """{ + | "openapi" : "3.1.0", + | "info" : { + | "title" : "Simple Endpoint", + | "version" : "1.0" + | }, + | "paths" : { + | "/withHeader" : { + | "get" : { + | "parameters" : [ + | { + | "name" : "content-type", + | "in" : "header", + | "schema" : { + | "type" : [ + | "string", + | "null" + | ] + | }, + | "style" : "simple" + | } + | ], + | "requestBody" : { + | "content" : { + | "application/json" : { + | "schema" : { + | "$ref" : "#/components/schemas/SimpleInputBody", + | "description" : "" + | } + | } + | }, + | "required" : true + | }, + | "responses" : { + | "200" : { + | "content" : { + | "application/json" : { + | "schema" : { + | "$ref" : "#/components/schemas/SimpleOutputBody" + | } + | } + | } + | }, + | "404" : { + | "content" : { + | "application/json" : { + | "schema" : { + | "$ref" : "#/components/schemas/NotFoundError" + | } + | } + | } + | } + | } + | } + | } + | }, + | "components" : { + | "schemas" : { + | "NotFoundError" : { + | "type" : "object", + | "properties" : { + | "message" : { + | "type" : "string" + | } + | }, + | "required" : [ + | "message" + | ] + | }, + | "SimpleInputBody" : { + | "type" : "object", + | "properties" : { + | "name" : { + | "type" : "string" + | }, + | "age" : { + | "type" : "integer", + | "format" : "int32" + | } + | }, + | "required" : [ + | "name", + | "age" + | ] + | }, + | "SimpleOutputBody" : { + | "type" : "object", + | "properties" : { + | "userName" : { + | "type" : "string" + | }, + | "score" : { + | "type" : "integer", + | "format" : "int32" + | } + | }, + | "required" : [ + | "userName", + | "score" + | ] + | } + | } + | } + |}""".stripMargin + assertTrue(json == toJsonAst(expectedJson)) + }, + test("optional payload") { + val generated = OpenAPIGen.fromEndpoints("Simple Endpoint", "1.0", optionalPayloadEndpoint) + val json = toJsonAst(generated) + val expected = """{ + | "openapi" : "3.1.0", + | "info" : { + | "title" : "Simple Endpoint", + | "version" : "1.0" + | }, + | "paths" : { + | "/withPayload" : { + | "get" : { + | "requestBody" : { + | "content" : { + | "application/json" : { + | "schema" : { + | "anyOf" : [ + | { + | "type" : "null" + | }, + | { + | "$ref" : "#/components/schemas/Payload" + | } + | ] + | } + | } + | }, + | "required" : false + | }, + | "responses" : { + | "200" : { + | "content" : { + | "application/json" : { + | "schema" : { + | "$ref" : "#/components/schemas/SimpleOutputBody" + | } + | } + | } + | }, + | "404" : { + | "content" : { + | "application/json" : { + | "schema" : { + | "$ref" : "#/components/schemas/NotFoundError" + | } + | } + | } + | } + | } + | } + | } + | }, + | "components" : { + | "schemas" : { + | "NotFoundError" : { + | "type" : "object", + | "properties" : { + | "message" : { + | "type" : "string" + | } + | }, + | "required" : [ + | "message" + | ] + | }, + | "Payload" : { + | "type" : "object", + | "properties" : { + | "content" : { + | "type" : "string" + | } + | }, + | "required" : [ + | "content" + | ], + | "description" : "A simple payload" + | }, + | "SimpleOutputBody" : { + | "type" : "object", + | "properties" : { + | "userName" : { + | "type" : "string" + | }, + | "score" : { + | "type" : "integer", + | "format" : "int32" + | } + | }, + | "required" : [ + | "userName", + | "score" + | ] + | } + | } + | } + |}""".stripMargin + assertTrue(json == toJsonAst(expected)) + }, test("alternative input") { val generated = OpenAPIGen.fromEndpoints("Simple Endpoint", "1.0", alternativeInputEndpoint) val json = toJsonAst(generated) @@ -1379,8 +1757,7 @@ object OpenAPIGenSpec extends ZIOSpecDefault { | "width", | "height", | "metadata" - | ], - | "description" : "Test doc\n\n" + | ] | } | } | } diff --git a/zio-http/jvm/src/test/scala/zio/http/headers/ForwardedSpec.scala b/zio-http/jvm/src/test/scala/zio/http/headers/ForwardedSpec.scala index c979b79095..61726d5459 100644 --- a/zio-http/jvm/src/test/scala/zio/http/headers/ForwardedSpec.scala +++ b/zio-http/jvm/src/test/scala/zio/http/headers/ForwardedSpec.scala @@ -19,7 +19,7 @@ package zio.http.headers import zio.Scope import zio.test._ -import zio.http.{Header, ZIOHttpSpec} +import zio.http.{Header, Headers, Request, ZIOHttpSpec} object ForwardedSpec extends ZIOHttpSpec { override def spec: Spec[TestEnvironment with Scope, Any] = suite("Forwarded suite")( @@ -58,5 +58,22 @@ object ForwardedSpec extends ZIOHttpSpec { ) assertTrue(Header.Forwarded.parse(headerValue) == Right(header)) }, + test("render Forwarded produces a valid raw header value") { + val gen = for { + by <- Gen.option(Gen.const("by.host")) + forv <- Gen.listOf( + Gen.elements("127.0.0.1", "localhost", "0.0.0.0"), + ) + host <- Gen.option(Gen.const("host.com")) + proto <- Gen.option(Gen.const("http")) + } yield (by, forv, host, proto) + + check(gen) { case (by, forv, host, proto) => + val expected = Header.Forwarded(by = by, forValues = forv, host = host, proto = proto) + val raw = Header.Forwarded.render(expected) + val actual = Header.Forwarded.parse(raw) + assertTrue(actual.is(_.right) == expected).label(s"Rendering result > '${raw}'") + } + } @@ TestAspect.shrinks(0), ) } diff --git a/zio-http/jvm/src/test/scala/zio/http/security/SizeLimitsSpec.scala b/zio-http/jvm/src/test/scala/zio/http/security/SizeLimitsSpec.scala index 2926182a08..8e950a5692 100644 --- a/zio-http/jvm/src/test/scala/zio/http/security/SizeLimitsSpec.scala +++ b/zio-http/jvm/src/test/scala/zio/http/security/SizeLimitsSpec.scala @@ -35,6 +35,7 @@ object SizeLimitsSpec extends ZIOHttpSpec { val DEFAULT_URL_SIZE = 4096 val DEFAULT_HEADER_SIZE = 8192 val DEFAULT_CONTENT_SIZE = 1024 * 100 + val ERROR_MARGIN = 3 /* Checks that for `A` with size until `maxSize`, server responds with `Status.Ok` and `badStatus` after it. @@ -63,7 +64,7 @@ object SizeLimitsSpec extends ZIOHttpSpec { } info <- if (expected == status) loop(size + 1, lstTestSize, inc(size)(content), f, expected) - else if (size >= lstTestSize - 2) // adding margin for differences in scala 2 and scala 3 + else if (size >= lstTestSize - ERROR_MARGIN) // adding margin for differences in scala 2 and scala 3 ZIO.succeed(((size, expected), Some(content))) else ZIO.succeed(((size, status), None)) } yield info @@ -81,7 +82,7 @@ object SizeLimitsSpec extends ZIOHttpSpec { (lstWorkingSize1, lstStatus1) = info1 (lstWorkingSize2, lstStatus2) = info2 } yield assertTrue( - maxSize - lstWorkingSize1 <= 2, + maxSize - lstWorkingSize1 <= ERROR_MARGIN, maxSize - lstWorkingSize1 >= 0, lstStatus1 == Status.Ok, lstWorkingSize2 == lstTestSize, diff --git a/zio-http/shared/src/main/scala/zio/http/ClientSSLConfig.scala b/zio-http/shared/src/main/scala/zio/http/ClientSSLConfig.scala index caed30430d..a7a4ab8f7f 100644 --- a/zio-http/shared/src/main/scala/zio/http/ClientSSLConfig.scala +++ b/zio-http/shared/src/main/scala/zio/http/ClientSSLConfig.scala @@ -28,6 +28,15 @@ object ClientSSLConfig { val trustStorePath = Config.string("trust-store-path") val trustStorePassword = Config.secret("trust-store-password") + val keyManagerKeyStoreType = Config.string("keyManagerKeyStoreType") + val keyManagerFile = Config.string("keyManagerFile") + val keyManagerResource = Config.string("keyManagerResource") + val keyManagerPassword = Config.secret("keyManagerPassword") + val trustManagerKeyStoreType = Config.string("trustManagerKeyStoreType") + val trustManagerFile = Config.string("trustManagerFile") + val trustManagerResource = Config.string("trustManagerResource") + val trustManagerPassword = Config.secret("trustManagerPassword") + val default = Config.succeed(Default) val fromCertFile = certPath.map(FromCertFile(_)) val fromCertResource = certPath.map(FromCertResource(_)) @@ -39,6 +48,45 @@ object ClientSSLConfig { serverCertConfig.zipWith(clientCertConfig)(FromClientAndServerCert(_, _)) } + val fromJavaxNetSsl = { + keyManagerKeyStoreType.optional + .zip(keyManagerFile.optional) + .zip(keyManagerResource.optional) + .zip(keyManagerPassword.optional) + .zip(trustManagerKeyStoreType.optional) + .zip( + trustManagerFile.optional + .zip(trustManagerResource.optional) + .validate("must supply trustManagerFile or trustManagerResource")(pair => + pair._1.isDefined || pair._2.isDefined, + ), + ) + .zip(trustManagerPassword.optional) + .map { case (kmkst, kmf, kmr, kmpass, tmkst, (tmf, tmr), tmpass) => + val bldr0 = + List[(Option[String], FromJavaxNetSsl => String => FromJavaxNetSsl)]( + (kmkst, b => b.keyManagerKeyStoreType(_)), + (kmf, b => b.keyManagerFile), + (kmr, b => b.keyManagerResource), + (tmkst, b => b.trustManagerKeyStoreType(_)), + (tmf, b => b.trustManagerFile), + (tmr, b => b.trustManagerResource), + ) + .foldLeft(FromJavaxNetSsl()) { case (bldr, (maybe, lens)) => + maybe.fold(bldr)(s => lens(bldr)(s)) + } + + List[(Option[Secret], FromJavaxNetSsl => Secret => FromJavaxNetSsl)]( + (kmpass, b => b.keyManagerPassword(_)), + (tmpass, b => b.trustManagerPassword(_)), + ) + .foldLeft(bldr0) { case (bldr, (maybe, lens)) => + maybe.fold(bldr)(s => lens(bldr)(s)) + } + .build() + } + } + tpe.switch( "Default" -> default, "FromCertFile" -> fromCertFile, @@ -58,6 +106,55 @@ object ClientSSLConfig { clientCertConfig: ClientSSLCertConfig, ) extends ClientSSLConfig + final case class FromJavaxNetSsl( + keyManagerKeyStoreType: String = "JKS", + keyManagerSource: FromJavaxNetSsl.Source = FromJavaxNetSsl.Empty, + keyManagerPassword: Option[Secret] = None, + trustManagerKeyStoreType: String = "JKS", + trustManagerSource: FromJavaxNetSsl.Source = FromJavaxNetSsl.Empty, + trustManagerPassword: Option[Secret] = None, + ) extends ClientSSLConfig { self => + + def isValidBuild: Boolean = trustManagerSource != FromJavaxNetSsl.Empty + def isInvalidBuild: Boolean = !isValidBuild + def build(): FromJavaxNetSsl = this + + def keyManagerKeyStoreType(tpe: String): FromJavaxNetSsl = self.copy(keyManagerKeyStoreType = tpe) + def keyManagerFile(file: String): FromJavaxNetSsl = + keyManagerSource match { + case FromJavaxNetSsl.Resource(_) => this + case _ => self.copy(keyManagerSource = FromJavaxNetSsl.File(file)) + } + def keyManagerResource(path: String): FromJavaxNetSsl = self.copy(keyManagerSource = FromJavaxNetSsl.Resource(path)) + def keyManagerPassword(password: Secret): FromJavaxNetSsl = self.copy(keyManagerPassword = Some(password)) + def keyManagerPassword(password: String): FromJavaxNetSsl = keyManagerPassword(Secret(password)) + + def trustManagerKeyStoreType(tpe: String): FromJavaxNetSsl = self.copy(trustManagerKeyStoreType = tpe) + def trustManagerFile(file: String): FromJavaxNetSsl = + trustManagerSource match { + case FromJavaxNetSsl.Resource(_) => this + case _ => self.copy(trustManagerSource = FromJavaxNetSsl.File(file)) + } + def trustManagerResource(path: String): FromJavaxNetSsl = + self.copy(trustManagerSource = FromJavaxNetSsl.Resource(path)) + def trustManagerPassword(password: Secret): FromJavaxNetSsl = self.copy(trustManagerPassword = Some(password)) + def trustManagerPassword(password: String): FromJavaxNetSsl = trustManagerPassword(Secret(password)) + } + + object FromJavaxNetSsl { + + sealed trait Source extends Product with Serializable + case object Empty extends Source + final case class File(file: String) extends Source + final case class Resource(resource: String) extends Source + + def builderWithTrustManagerFile(file: String): FromJavaxNetSsl = + FromJavaxNetSsl().trustManagerFile(file) + + def builderWithTrustManagerResource(resource: String): FromJavaxNetSsl = + FromJavaxNetSsl().trustManagerResource(resource) + } + object FromTrustStoreResource { def apply(trustStorePath: String, trustStorePassword: String): FromTrustStoreResource = FromTrustStoreResource(trustStorePath, Secret(trustStorePassword)) diff --git a/zio-http/shared/src/main/scala/zio/http/ErrorResponseConfig.scala b/zio-http/shared/src/main/scala/zio/http/ErrorResponseConfig.scala index bc9a3fa8d5..10237403a8 100644 --- a/zio-http/shared/src/main/scala/zio/http/ErrorResponseConfig.scala +++ b/zio-http/shared/src/main/scala/zio/http/ErrorResponseConfig.scala @@ -12,6 +12,10 @@ import zio._ * @param maxStackTraceDepth * maximum number of stack trace lines to include in the response body. Set to * 0 to include all lines. + * @param errorFormat + * the preferred format for the error response. If the context in which the + * response is created has access to an Accept header, the header will be used + * preferably to determine the format. */ final case class ErrorResponseConfig( withErrorBody: Boolean = false, @@ -38,6 +42,9 @@ object ErrorResponseConfig { val debug: HandlerAspect[Any, Unit] = Middleware.runBefore(setConfig(debugConfig)) + val debugLayer: ULayer[Unit] = + ZLayer(setConfig(debugConfig)) + def withConfig(config: ErrorResponseConfig): HandlerAspect[Any, Unit] = Middleware.runBefore(setConfig(config)) diff --git a/zio-http/shared/src/main/scala/zio/http/Header.scala b/zio-http/shared/src/main/scala/zio/http/Header.scala index fca8e9d09f..48a87bb8ad 100644 --- a/zio-http/shared/src/main/scala/zio/http/Header.scala +++ b/zio-http/shared/src/main/scala/zio/http/Header.scala @@ -2856,8 +2856,14 @@ object Header { Right(Forwarded(by, forValue, host, proto)) } - def render(forwarded: Forwarded): String = - s"${forwarded.by}; ${forwarded.forValues.map(v => s"for=$v").mkString(",")}; ${forwarded.host}; ${forwarded.proto}" + def render(forwarded: Forwarded): String = { + def formatDirective(directiveName: String, directiveValue: Option[String]) = + directiveValue.map(v => s"${directiveName}=${v};").getOrElse("") + + val forValues = if (forwarded.forValues.nonEmpty) forwarded.forValues.mkString("for=", ",for=", ";") else "" + + s"${formatDirective("by", forwarded.by)}${forValues}${formatDirective("host", forwarded.host)}${formatDirective("proto", forwarded.proto)}" + } } /** From header value. */ diff --git a/zio-http/shared/src/main/scala/zio/http/Method.scala b/zio-http/shared/src/main/scala/zio/http/Method.scala index 0e308c402e..8c80ea934e 100644 --- a/zio-http/shared/src/main/scala/zio/http/Method.scala +++ b/zio-http/shared/src/main/scala/zio/http/Method.scala @@ -31,7 +31,9 @@ sealed trait Method { self => if (that == Method.ANY) self else that - def /[A](that: PathCodec[A]): RoutePattern[A] = RoutePattern.fromMethod(self) / that + def /[A](that: PathCodec[A]): RoutePattern[A] = + if (that == PathCodec.empty) RoutePattern.fromMethod(self).asInstanceOf[RoutePattern[A]] + else RoutePattern.fromMethod(self) / that def matches(that: Method): Boolean = if (self == Method.ANY) true diff --git a/zio-http/shared/src/main/scala/zio/http/Route.scala b/zio-http/shared/src/main/scala/zio/http/Route.scala index 9db2d28d13..617a0f3586 100644 --- a/zio-http/shared/src/main/scala/zio/http/Route.scala +++ b/zio-http/shared/src/main/scala/zio/http/Route.scala @@ -49,10 +49,21 @@ sealed trait Route[-Env, +Err] { self => * Handles all typed errors in the route by converting them into responses. * This method can be used to convert a route that does not handle its errors * into one that does handle its errors. + * + * If the underlying handler uses the error channel to send responses, this + * method will not pass the responses to the provided function. */ final def handleError(f: Err => Response)(implicit trace: Trace): Route[Env, Nothing] = self.handleErrorCauseZIO(c => ErrorResponseConfig.configRef.get.map(Response.fromCauseWith(c, _)(f))) + /** + * Handles all typed errors in the route by converting them into a zio effect + * that produces a response. This method can be used to convert a route that + * does not handle its errors into one that does handle its errors. + * + * If the underlying handler uses the error channel to send responses, this + * method will not pass the responses to the provided function. + */ final def handleErrorZIO[Env1 <: Env]( f: Err => ZIO[Env1, Nothing, Response], )(implicit trace: Trace): Route[Env1, Nothing] = @@ -67,13 +78,16 @@ sealed trait Route[-Env, +Err] { self => * Handles all typed errors, as well as all non-recoverable errors, by * converting them into responses. This method can be used to convert a route * that does not handle its errors into one that does handle its errors. + * + * If the underlying handler uses the error channel to send responses, this + * method will not pass the responses to the provided function. */ final def handleErrorCause(f: Cause[Err] => Response)(implicit trace: Trace): Route[Env, Nothing] = self match { case Provided(route, env) => Provided(route.handleErrorCause(f), env) case Augmented(route, aspect) => Augmented(route.handleErrorCause(f), aspect) case Handled(routePattern, handler, location) => - Handled(routePattern, handler.map(_.mapErrorCause(c => f(c.asInstanceOf[Cause[Nothing]]))), location) + Handled(routePattern, handler.map(_.mapErrorCause(_.failureOrCause.fold(identity, f))), location) case Unhandled(pattern, handler0, zippable, location) => val handler2: Handler[Any, Nothing, RoutePattern[_], Handler[Env, Response, Request, Response]] = @@ -89,7 +103,6 @@ sealed trait Route[-Env, +Err] { self => } } - // Sandbox before applying aspect: paramHandler.mapErrorCause(f) } @@ -101,6 +114,9 @@ sealed trait Route[-Env, +Err] { self => * converting them into a ZIO effect that produces the response. This method * can be used to convert a route that does not handle its errors into one * that does handle its errors. + * + * If the underlying handler uses the error channel to send responses, this + * method will not pass the responses to the provided function. */ final def handleErrorCauseZIO[Env1 <: Env]( f: Cause[Err] => ZIO[Env1, Nothing, Response], @@ -120,7 +136,7 @@ sealed trait Route[-Env, +Err] { self => case Augmented(route, aspect) => Augmented(route.handleErrorCauseZIO(f).asInstanceOf[Route[Any, Nothing]], aspect) case Handled(routePattern, handler, location) => - Handled(routePattern, handler.map(_.mapErrorCauseZIO(c => f(c.asInstanceOf[Cause[Nothing]]))), location) + Handled(routePattern, handler.map(_.mapErrorCauseZIO(_.failureOrCause.fold(ZIO.succeed(_), f))), location) case Unhandled(pattern, handler0, zippable, location) => val handler2: Handler[Any, Nothing, RoutePattern[_], Handler[Env1, Response, Request, Response]] = { @@ -174,6 +190,9 @@ sealed trait Route[-Env, +Err] { self => * taking into account the request that caused the error. This method can be * used to convert a route that does not handle its errors into one that does * handle its errors. + * + * If the underlying handler uses the error channel to send responses, this + * method will not pass the responses to the provided function. */ final def handleErrorRequest(f: (Err, Request) => Response)(implicit trace: Trace): Route[Env, Nothing] = self.handleErrorRequestCauseZIO((request, cause) => @@ -185,6 +204,9 @@ sealed trait Route[-Env, +Err] { self => * converting them into responses, taking into account the request that caused * the error. This method can be used to convert a route that does not handle * its errors into one that does handle its errors. + * + * If the underlying handler uses the error channel to send responses, this + * method will not pass the responses to the provided function. */ final def handleErrorRequestCause(f: (Request, Cause[Err]) => Response)(implicit trace: Trace): Route[Env, Nothing] = self match { @@ -195,7 +217,7 @@ sealed trait Route[-Env, +Err] { self => routePattern, handler0.map { h => Handler.fromFunctionHandler { (req: Request) => - h.mapErrorCause(c => f(req, c.asInstanceOf[Cause[Nothing]])) + h.mapErrorCause(_.failureOrCause.fold(identity, f(req, _))) } }, location, @@ -229,6 +251,9 @@ sealed trait Route[-Env, +Err] { self => * account the request that caused the error. This method can be used to * convert a route that does not handle its errors into one that does handle * its errors. + * + * If the underlying handler uses the error channel to send responses, this + * method will not pass the responses to the provided function. */ final def handleErrorRequestCauseZIO[Env1 <: Env]( f: (Request, Cause[Err]) => ZIO[Env1, Nothing, Response], @@ -252,7 +277,7 @@ sealed trait Route[-Env, +Err] { self => routePattern, handler.map { handler => Handler.fromFunctionHandler { (req: Request) => - handler.mapErrorCauseZIO(c => f(req, c.asInstanceOf[Cause[Nothing]])) + handler.mapErrorCauseZIO(_.failureOrCause.fold(ZIO.succeed(_), f(req, _))) } }, location, @@ -336,10 +361,8 @@ object Route { def handledIgnoreParams[Env]( routePattern: RoutePattern[_], - )(handler0: Handler[Env, Response, Request, Response])(implicit trace: Trace): Route[Env, Nothing] = { - // Sandbox before constructing: - Route.Handled(routePattern, handler((_: RoutePattern[_]) => handler0.sandbox), Trace.empty) - } + )(handler0: Handler[Env, Response, Request, Response])(implicit trace: Trace): Route[Env, Nothing] = + Route.Handled(routePattern, handler((_: RoutePattern[_]) => handler0), Trace.empty) def handled[Params, Env](rpm: RoutePattern[Params]): HandledConstructor[Env, Params] = new HandledConstructor[Env, Params](rpm) @@ -365,7 +388,7 @@ object Route { } // Sandbox before applying aspect: - paramHandler.sandbox + paramHandler } } @@ -414,7 +437,7 @@ object Route { location: Trace, ) extends Route[Env, Nothing] { override def toHandler(implicit ev: Nothing <:< Response, trace: Trace): Handler[Env, Response, Request, Response] = - Handler.fromZIO(handler(routePattern)).flatten + Handler.fromZIO(handler(routePattern).map(_.sandbox)).flatten override def toString = s"Route.Handled(${routePattern}, ${location})" } diff --git a/zio-http/shared/src/main/scala/zio/http/RoutePattern.scala b/zio-http/shared/src/main/scala/zio/http/RoutePattern.scala index 52c475f16d..7bcdff3505 100644 --- a/zio-http/shared/src/main/scala/zio/http/RoutePattern.scala +++ b/zio-http/shared/src/main/scala/zio/http/RoutePattern.scala @@ -53,7 +53,9 @@ final case class RoutePattern[A](method: Method, pathCodec: PathCodec[A]) { self * Returns a new pattern that is extended with the specified segment pattern. */ def /[B](that: PathCodec[B])(implicit combiner: Combiner[A, B]): RoutePattern[combiner.Out] = - copy(pathCodec = pathCodec ++ that) + if (that == PathCodec.empty) self.asInstanceOf[RoutePattern[combiner.Out]] + else if (pathCodec == PathCodec.empty) copy(pathCodec = that.asInstanceOf[PathCodec[combiner.Out]]) + else copy(pathCodec = pathCodec ++ that) /** * Creates a route from this pattern and the specified handler. diff --git a/zio-http/shared/src/main/scala/zio/http/codec/Doc.scala b/zio-http/shared/src/main/scala/zio/http/codec/Doc.scala index f6e7f3d221..e10dbc90ae 100644 --- a/zio-http/shared/src/main/scala/zio/http/codec/Doc.scala +++ b/zio-http/shared/src/main/scala/zio/http/codec/Doc.scala @@ -31,9 +31,10 @@ sealed trait Doc { self => def +(that: Doc): Doc = (self, that) match { - case (self, that) if self.isEmpty => that - case (self, that) if that.isEmpty => self - case _ => Doc.Sequence(self, that) + case (self, that) if self.isEmpty => that + case (self, that) if that.isEmpty => self + case _ if tags.isEmpty && that.tags.isEmpty => Doc.Sequence(self, that) + case _ => Doc.Sequence(self, that).tag(self.tags ++ that.tags) } def isEmpty: Boolean = @@ -148,7 +149,8 @@ sealed trait Doc { self => case Doc.Raw(_, docType) => throw new IllegalArgumentException(s"Unsupported raw doc type: $docType") - case Doc.Tagged(_, _) => + case Doc.Tagged(doc, _) => + render(doc, indent) } } diff --git a/zio-http/shared/src/main/scala/zio/http/codec/HttpCodec.scala b/zio-http/shared/src/main/scala/zio/http/codec/HttpCodec.scala index df546bd0da..278f185366 100644 --- a/zio-http/shared/src/main/scala/zio/http/codec/HttpCodec.scala +++ b/zio-http/shared/src/main/scala/zio/http/codec/HttpCodec.scala @@ -530,6 +530,1680 @@ object HttpCodec extends ContentCodecs with HeaderCodecs with MethodCodecs with case _ => Left(s"Unexpected error type") }, ) + + def f9[ + AtomTypes, + Sub1 <: Value: ClassTag, + Sub2 <: Value: ClassTag, + Sub3 <: Value: ClassTag, + Sub4 <: Value: ClassTag, + Sub5 <: Value: ClassTag, + Sub6 <: Value: ClassTag, + Sub7 <: Value: ClassTag, + Sub8 <: Value: ClassTag, + Sub9 <: Value: ClassTag, + ]( + codec1: HttpCodec[AtomTypes, Sub1], + codec2: HttpCodec[AtomTypes, Sub2], + codec3: HttpCodec[AtomTypes, Sub3], + codec4: HttpCodec[AtomTypes, Sub4], + codec5: HttpCodec[AtomTypes, Sub5], + codec6: HttpCodec[AtomTypes, Sub6], + codec7: HttpCodec[AtomTypes, Sub7], + codec8: HttpCodec[AtomTypes, Sub8], + codec9: HttpCodec[AtomTypes, Sub9], + ): HttpCodec[AtomTypes, Value] = + (codec1 | codec2 | codec3 | codec4 | codec5 | codec6 | codec7 | codec8 | codec9).transformOrFail(either => + Right( + either.left + .map( + _.left + .map(_.left.map(_.left.map(_.left.map(_.left.map(_.left.map(_.merge).merge).merge).merge).merge).merge) + .merge, + ) + .merge, + ), + )((value: Value) => + value match { + case sub1: Sub1 => Right(Left(Left(Left(Left(Left(Left(Left(Left(sub1))))))))) + case sub2: Sub2 => Right(Left(Left(Left(Left(Left(Left(Left(Right(sub2))))))))) + case sub3: Sub3 => Right(Left(Left(Left(Left(Left(Left(Right(sub3)))))))) + case sub4: Sub4 => Right(Left(Left(Left(Left(Left(Right(sub4))))))) + case sub5: Sub5 => Right(Left(Left(Left(Left(Right(sub5)))))) + case sub6: Sub6 => Right(Left(Left(Left(Right(sub6))))) + case sub7: Sub7 => Right(Left(Left(Right(sub7)))) + case sub8: Sub8 => Right(Left(Right(sub8))) + case sub9: Sub9 => Right(Right(sub9)) + case _ => Left(s"Unexpected error type") + }, + ) + + def f10[ + AtomTypes, + Sub1 <: Value: ClassTag, + Sub2 <: Value: ClassTag, + Sub3 <: Value: ClassTag, + Sub4 <: Value: ClassTag, + Sub5 <: Value: ClassTag, + Sub6 <: Value: ClassTag, + Sub7 <: Value: ClassTag, + Sub8 <: Value: ClassTag, + Sub9 <: Value: ClassTag, + Sub10 <: Value: ClassTag, + ]( + codec1: HttpCodec[AtomTypes, Sub1], + codec2: HttpCodec[AtomTypes, Sub2], + codec3: HttpCodec[AtomTypes, Sub3], + codec4: HttpCodec[AtomTypes, Sub4], + codec5: HttpCodec[AtomTypes, Sub5], + codec6: HttpCodec[AtomTypes, Sub6], + codec7: HttpCodec[AtomTypes, Sub7], + codec8: HttpCodec[AtomTypes, Sub8], + codec9: HttpCodec[AtomTypes, Sub9], + codec10: HttpCodec[AtomTypes, Sub10], + ): HttpCodec[AtomTypes, Value] = + (codec1 | codec2 | codec3 | codec4 | codec5 | codec6 | codec7 | codec8 | codec9 | codec10).transformOrFail( + either => + Right( + either.left + .map( + _.left + .map( + _.left + .map( + _.left + .map(_.left.map(_.left.map(_.left.map(_.left.map(_.merge).merge).merge).merge).merge) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ), + )((value: Value) => + value match { + case sub1: Sub1 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(sub1)))))))))) + case sub2: Sub2 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub2)))))))))) + case sub3: Sub3 => Right(Left(Left(Left(Left(Left(Left(Left(Right(sub3))))))))) + case sub4: Sub4 => Right(Left(Left(Left(Left(Left(Left(Right(sub4)))))))) + case sub5: Sub5 => Right(Left(Left(Left(Left(Left(Right(sub5))))))) + case sub6: Sub6 => Right(Left(Left(Left(Left(Right(sub6)))))) + case sub7: Sub7 => Right(Left(Left(Left(Right(sub7))))) + case sub8: Sub8 => Right(Left(Left(Right(sub8)))) + case sub9: Sub9 => Right(Left(Right(sub9))) + case sub10: Sub10 => Right(Right(sub10)) + case _ => Left(s"Unexpected error type") + }, + ) + + def f11[ + AtomTypes, + Sub1 <: Value: ClassTag, + Sub2 <: Value: ClassTag, + Sub3 <: Value: ClassTag, + Sub4 <: Value: ClassTag, + Sub5 <: Value: ClassTag, + Sub6 <: Value: ClassTag, + Sub7 <: Value: ClassTag, + Sub8 <: Value: ClassTag, + Sub9 <: Value: ClassTag, + Sub10 <: Value: ClassTag, + Sub11 <: Value: ClassTag, + ]( + codec1: HttpCodec[AtomTypes, Sub1], + codec2: HttpCodec[AtomTypes, Sub2], + codec3: HttpCodec[AtomTypes, Sub3], + codec4: HttpCodec[AtomTypes, Sub4], + codec5: HttpCodec[AtomTypes, Sub5], + codec6: HttpCodec[AtomTypes, Sub6], + codec7: HttpCodec[AtomTypes, Sub7], + codec8: HttpCodec[AtomTypes, Sub8], + codec9: HttpCodec[AtomTypes, Sub9], + codec10: HttpCodec[AtomTypes, Sub10], + codec11: HttpCodec[AtomTypes, Sub11], + ): HttpCodec[AtomTypes, Value] = + (codec1 | codec2 | codec3 | codec4 | codec5 | codec6 | codec7 | codec8 | codec9 | codec10 | codec11) + .transformOrFail(either => + Right( + either.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map(_.left.map(_.left.map(_.left.map(_.left.map(_.merge).merge).merge).merge).merge) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ), + )((value: Value) => + value match { + case sub1: Sub1 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(sub1))))))))))) + case sub2: Sub2 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub2))))))))))) + case sub3: Sub3 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub3)))))))))) + case sub4: Sub4 => Right(Left(Left(Left(Left(Left(Left(Left(Right(sub4))))))))) + case sub5: Sub5 => Right(Left(Left(Left(Left(Left(Left(Right(sub5)))))))) + case sub6: Sub6 => Right(Left(Left(Left(Left(Left(Right(sub6))))))) + case sub7: Sub7 => Right(Left(Left(Left(Left(Right(sub7)))))) + case sub8: Sub8 => Right(Left(Left(Left(Right(sub8))))) + case sub9: Sub9 => Right(Left(Left(Right(sub9)))) + case sub10: Sub10 => Right(Left(Right(sub10))) + case sub11: Sub11 => Right(Right(sub11)) + case _ => Left(s"Unexpected error type") + }, + ) + def f12[ + AtomTypes, + Sub1 <: Value: ClassTag, + Sub2 <: Value: ClassTag, + Sub3 <: Value: ClassTag, + Sub4 <: Value: ClassTag, + Sub5 <: Value: ClassTag, + Sub6 <: Value: ClassTag, + Sub7 <: Value: ClassTag, + Sub8 <: Value: ClassTag, + Sub9 <: Value: ClassTag, + Sub10 <: Value: ClassTag, + Sub11 <: Value: ClassTag, + Sub12 <: Value: ClassTag, + ]( + codec1: HttpCodec[AtomTypes, Sub1], + codec2: HttpCodec[AtomTypes, Sub2], + codec3: HttpCodec[AtomTypes, Sub3], + codec4: HttpCodec[AtomTypes, Sub4], + codec5: HttpCodec[AtomTypes, Sub5], + codec6: HttpCodec[AtomTypes, Sub6], + codec7: HttpCodec[AtomTypes, Sub7], + codec8: HttpCodec[AtomTypes, Sub8], + codec9: HttpCodec[AtomTypes, Sub9], + codec10: HttpCodec[AtomTypes, Sub10], + codec11: HttpCodec[AtomTypes, Sub11], + codec12: HttpCodec[AtomTypes, Sub12], + ): HttpCodec[AtomTypes, Value] = + (codec1 | codec2 | codec3 | codec4 | codec5 | codec6 | codec7 | codec8 | codec9 | codec10 | codec11 | codec12) + .transformOrFail(either => + Right( + either.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map(_.left.map(_.left.map(_.left.map(_.left.map(_.merge).merge).merge).merge).merge) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ), + )((value: Value) => + value match { + case sub1: Sub1 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(sub1)))))))))))) + case sub2: Sub2 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub2)))))))))))) + case sub3: Sub3 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub3))))))))))) + case sub4: Sub4 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub4)))))))))) + case sub5: Sub5 => Right(Left(Left(Left(Left(Left(Left(Left(Right(sub5))))))))) + case sub6: Sub6 => Right(Left(Left(Left(Left(Left(Left(Right(sub6)))))))) + case sub7: Sub7 => Right(Left(Left(Left(Left(Left(Right(sub7))))))) + case sub8: Sub8 => Right(Left(Left(Left(Left(Right(sub8)))))) + case sub9: Sub9 => Right(Left(Left(Left(Right(sub9))))) + case sub10: Sub10 => Right(Left(Left(Right(sub10)))) + case sub11: Sub11 => Right(Left(Right(sub11))) + case sub12: Sub12 => Right(Right(sub12)) + case _ => Left(s"Unexpected error type") + }, + ) + def f13[ + AtomTypes, + Sub1 <: Value: ClassTag, + Sub2 <: Value: ClassTag, + Sub3 <: Value: ClassTag, + Sub4 <: Value: ClassTag, + Sub5 <: Value: ClassTag, + Sub6 <: Value: ClassTag, + Sub7 <: Value: ClassTag, + Sub8 <: Value: ClassTag, + Sub9 <: Value: ClassTag, + Sub10 <: Value: ClassTag, + Sub11 <: Value: ClassTag, + Sub12 <: Value: ClassTag, + Sub13 <: Value: ClassTag, + ]( + codec1: HttpCodec[AtomTypes, Sub1], + codec2: HttpCodec[AtomTypes, Sub2], + codec3: HttpCodec[AtomTypes, Sub3], + codec4: HttpCodec[AtomTypes, Sub4], + codec5: HttpCodec[AtomTypes, Sub5], + codec6: HttpCodec[AtomTypes, Sub6], + codec7: HttpCodec[AtomTypes, Sub7], + codec8: HttpCodec[AtomTypes, Sub8], + codec9: HttpCodec[AtomTypes, Sub9], + codec10: HttpCodec[AtomTypes, Sub10], + codec11: HttpCodec[AtomTypes, Sub11], + codec12: HttpCodec[AtomTypes, Sub12], + codec13: HttpCodec[AtomTypes, Sub13], + ): HttpCodec[AtomTypes, Value] = + (codec1 | codec2 | codec3 | codec4 | codec5 | codec6 | codec7 | codec8 | codec9 | codec10 | codec11 | codec12 | codec13) + .transformOrFail(either => + Right( + either.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left.map(_.left.map(_.left.map(_.left.map(_.merge).merge).merge).merge).merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ), + )((value: Value) => + value match { + case sub1: Sub1 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(sub1))))))))))))) + case sub2: Sub2 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub2))))))))))))) + case sub3: Sub3 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub3)))))))))))) + case sub4: Sub4 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub4))))))))))) + case sub5: Sub5 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub5)))))))))) + case sub6: Sub6 => Right(Left(Left(Left(Left(Left(Left(Left(Right(sub6))))))))) + case sub7: Sub7 => Right(Left(Left(Left(Left(Left(Left(Right(sub7)))))))) + case sub8: Sub8 => Right(Left(Left(Left(Left(Left(Right(sub8))))))) + case sub9: Sub9 => Right(Left(Left(Left(Left(Right(sub9)))))) + case sub10: Sub10 => Right(Left(Left(Left(Right(sub10))))) + case sub11: Sub11 => Right(Left(Left(Right(sub11)))) + case sub12: Sub12 => Right(Left(Right(sub12))) + case sub13: Sub13 => Right(Right(sub13)) + case _ => Left(s"Unexpected error type") + }, + ) + def f14[ + AtomTypes, + Sub1 <: Value: ClassTag, + Sub2 <: Value: ClassTag, + Sub3 <: Value: ClassTag, + Sub4 <: Value: ClassTag, + Sub5 <: Value: ClassTag, + Sub6 <: Value: ClassTag, + Sub7 <: Value: ClassTag, + Sub8 <: Value: ClassTag, + Sub9 <: Value: ClassTag, + Sub10 <: Value: ClassTag, + Sub11 <: Value: ClassTag, + Sub12 <: Value: ClassTag, + Sub13 <: Value: ClassTag, + Sub14 <: Value: ClassTag, + ]( + codec1: HttpCodec[AtomTypes, Sub1], + codec2: HttpCodec[AtomTypes, Sub2], + codec3: HttpCodec[AtomTypes, Sub3], + codec4: HttpCodec[AtomTypes, Sub4], + codec5: HttpCodec[AtomTypes, Sub5], + codec6: HttpCodec[AtomTypes, Sub6], + codec7: HttpCodec[AtomTypes, Sub7], + codec8: HttpCodec[AtomTypes, Sub8], + codec9: HttpCodec[AtomTypes, Sub9], + codec10: HttpCodec[AtomTypes, Sub10], + codec11: HttpCodec[AtomTypes, Sub11], + codec12: HttpCodec[AtomTypes, Sub12], + codec13: HttpCodec[AtomTypes, Sub13], + codec14: HttpCodec[AtomTypes, Sub14], + ): HttpCodec[AtomTypes, Value] = + (codec1 | codec2 | codec3 | codec4 | codec5 | codec6 | codec7 | codec8 | codec9 | codec10 | codec11 | codec12 | codec13 | codec14) + .transformOrFail(either => + Right( + either.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map(_.left.map(_.left.map(_.left.map(_.merge).merge).merge).merge) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ), + )(value => + value match { + case sub1: Sub1 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(sub1)))))))))))))) + case sub2: Sub2 => + Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub2)))))))))))))) + case sub3: Sub3 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub3))))))))))))) + case sub4: Sub4 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub4)))))))))))) + case sub5: Sub5 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub5))))))))))) + case sub6: Sub6 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub6)))))))))) + case sub7: Sub7 => Right(Left(Left(Left(Left(Left(Left(Left(Right(sub7))))))))) + case sub8: Sub8 => Right(Left(Left(Left(Left(Left(Left(Right(sub8)))))))) + case sub9: Sub9 => Right(Left(Left(Left(Left(Left(Right(sub9))))))) + case sub10: Sub10 => Right(Left(Left(Left(Left(Right(sub10)))))) + case sub11: Sub11 => Right(Left(Left(Left(Right(sub11))))) + case sub12: Sub12 => Right(Left(Left(Right(sub12)))) + case sub13: Sub13 => Right(Left(Right(sub13))) + case sub14: Sub14 => Right(Right(sub14)) + case _ => Left(s"Unexpected error type") + }, + ) + + def f15[ + AtomTypes, + Sub1 <: Value: ClassTag, + Sub2 <: Value: ClassTag, + Sub3 <: Value: ClassTag, + Sub4 <: Value: ClassTag, + Sub5 <: Value: ClassTag, + Sub6 <: Value: ClassTag, + Sub7 <: Value: ClassTag, + Sub8 <: Value: ClassTag, + Sub9 <: Value: ClassTag, + Sub10 <: Value: ClassTag, + Sub11 <: Value: ClassTag, + Sub12 <: Value: ClassTag, + Sub13 <: Value: ClassTag, + Sub14 <: Value: ClassTag, + Sub15 <: Value: ClassTag, + ]( + codec1: HttpCodec[AtomTypes, Sub1], + codec2: HttpCodec[AtomTypes, Sub2], + codec3: HttpCodec[AtomTypes, Sub3], + codec4: HttpCodec[AtomTypes, Sub4], + codec5: HttpCodec[AtomTypes, Sub5], + codec6: HttpCodec[AtomTypes, Sub6], + codec7: HttpCodec[AtomTypes, Sub7], + codec8: HttpCodec[AtomTypes, Sub8], + codec9: HttpCodec[AtomTypes, Sub9], + codec10: HttpCodec[AtomTypes, Sub10], + codec11: HttpCodec[AtomTypes, Sub11], + codec12: HttpCodec[AtomTypes, Sub12], + codec13: HttpCodec[AtomTypes, Sub13], + codec14: HttpCodec[AtomTypes, Sub14], + codec15: HttpCodec[AtomTypes, Sub15], + ): HttpCodec[AtomTypes, Value] = + (codec1 | codec2 | codec3 | codec4 | codec5 | codec6 | codec7 | codec8 | codec9 | codec10 | codec11 | codec12 | codec13 | codec14 | codec15) + .transformOrFail(either => + Right( + either.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map(_.left.map(_.left.map(_.left.map(_.merge).merge).merge).merge) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ), + )((value: Value) => + value match { + case sub1: Sub1 => + Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(sub1))))))))))))))) + case sub2: Sub2 => + Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub2))))))))))))))) + case sub3: Sub3 => + Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub3)))))))))))))) + case sub4: Sub4 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub4))))))))))))) + case sub5: Sub5 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub5)))))))))))) + case sub6: Sub6 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub6))))))))))) + case sub7: Sub7 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub7)))))))))) + case sub8: Sub8 => Right(Left(Left(Left(Left(Left(Left(Left(Right(sub8))))))))) + case sub9: Sub9 => Right(Left(Left(Left(Left(Left(Left(Right(sub9)))))))) + case sub10: Sub10 => Right(Left(Left(Left(Left(Left(Right(sub10))))))) + case sub11: Sub11 => Right(Left(Left(Left(Left(Right(sub11)))))) + case sub12: Sub12 => Right(Left(Left(Left(Right(sub12))))) + case sub13: Sub13 => Right(Left(Left(Right(sub13)))) + case sub14: Sub14 => Right(Left(Right(sub14))) + case sub15: Sub15 => Right(Right(sub15)) + case _ => Left(s"Unexpected error type") + + }, + ) + + def f16[ + AtomTypes, + Sub1 <: Value: ClassTag, + Sub2 <: Value: ClassTag, + Sub3 <: Value: ClassTag, + Sub4 <: Value: ClassTag, + Sub5 <: Value: ClassTag, + Sub6 <: Value: ClassTag, + Sub7 <: Value: ClassTag, + Sub8 <: Value: ClassTag, + Sub9 <: Value: ClassTag, + Sub10 <: Value: ClassTag, + Sub11 <: Value: ClassTag, + Sub12 <: Value: ClassTag, + Sub13 <: Value: ClassTag, + Sub14 <: Value: ClassTag, + Sub15 <: Value: ClassTag, + Sub16 <: Value: ClassTag, + ]( + codec1: HttpCodec[AtomTypes, Sub1], + codec2: HttpCodec[AtomTypes, Sub2], + codec3: HttpCodec[AtomTypes, Sub3], + codec4: HttpCodec[AtomTypes, Sub4], + codec5: HttpCodec[AtomTypes, Sub5], + codec6: HttpCodec[AtomTypes, Sub6], + codec7: HttpCodec[AtomTypes, Sub7], + codec8: HttpCodec[AtomTypes, Sub8], + codec9: HttpCodec[AtomTypes, Sub9], + codec10: HttpCodec[AtomTypes, Sub10], + codec11: HttpCodec[AtomTypes, Sub11], + codec12: HttpCodec[AtomTypes, Sub12], + codec13: HttpCodec[AtomTypes, Sub13], + codec14: HttpCodec[AtomTypes, Sub14], + codec15: HttpCodec[AtomTypes, Sub15], + codec16: HttpCodec[AtomTypes, Sub16], + ): HttpCodec[AtomTypes, Value] = + (codec1 | codec2 | codec3 | codec4 | codec5 | codec6 | codec7 | codec8 | codec9 | codec10 | codec11 | codec12 | codec13 | codec14 | codec15 | codec16) + .transformOrFail(either => + Right( + either.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left.map(_.left.map(_.left.map(_.merge).merge).merge).merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ), + )((value: Value) => + value match { + case sub1: Sub1 => + Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(sub1)))))))))))))))) + case sub2: Sub2 => + Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub2)))))))))))))))) + case sub3: Sub3 => + Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub3))))))))))))))) + case sub4: Sub4 => + Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub4)))))))))))))) + case sub5: Sub5 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub5))))))))))))) + case sub6: Sub6 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub6)))))))))))) + case sub7: Sub7 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub7))))))))))) + case sub8: Sub8 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub8)))))))))) + case sub9: Sub9 => Right(Left(Left(Left(Left(Left(Left(Left(Right(sub9))))))))) + case sub10: Sub10 => Right(Left(Left(Left(Left(Left(Left(Right(sub10)))))))) + case sub11: Sub11 => Right(Left(Left(Left(Left(Left(Right(sub11))))))) + case sub12: Sub12 => Right(Left(Left(Left(Left(Right(sub12)))))) + case sub13: Sub13 => Right(Left(Left(Left(Right(sub13))))) + case sub14: Sub14 => Right(Left(Left(Right(sub14)))) + case sub15: Sub15 => Right(Left(Right(sub15))) + case sub16: Sub16 => Right(Right(sub16)) + case _ => Left(s"Unexpected error type") + }, + ) + + def f17[ + AtomTypes, + Sub1 <: Value: ClassTag, + Sub2 <: Value: ClassTag, + Sub3 <: Value: ClassTag, + Sub4 <: Value: ClassTag, + Sub5 <: Value: ClassTag, + Sub6 <: Value: ClassTag, + Sub7 <: Value: ClassTag, + Sub8 <: Value: ClassTag, + Sub9 <: Value: ClassTag, + Sub10 <: Value: ClassTag, + Sub11 <: Value: ClassTag, + Sub12 <: Value: ClassTag, + Sub13 <: Value: ClassTag, + Sub14 <: Value: ClassTag, + Sub15 <: Value: ClassTag, + Sub16 <: Value: ClassTag, + Sub17 <: Value: ClassTag, + ]( + codec1: HttpCodec[AtomTypes, Sub1], + codec2: HttpCodec[AtomTypes, Sub2], + codec3: HttpCodec[AtomTypes, Sub3], + codec4: HttpCodec[AtomTypes, Sub4], + codec5: HttpCodec[AtomTypes, Sub5], + codec6: HttpCodec[AtomTypes, Sub6], + codec7: HttpCodec[AtomTypes, Sub7], + codec8: HttpCodec[AtomTypes, Sub8], + codec9: HttpCodec[AtomTypes, Sub9], + codec10: HttpCodec[AtomTypes, Sub10], + codec11: HttpCodec[AtomTypes, Sub11], + codec12: HttpCodec[AtomTypes, Sub12], + codec13: HttpCodec[AtomTypes, Sub13], + codec14: HttpCodec[AtomTypes, Sub14], + codec15: HttpCodec[AtomTypes, Sub15], + codec16: HttpCodec[AtomTypes, Sub16], + codec17: HttpCodec[AtomTypes, Sub17], + ): HttpCodec[AtomTypes, Value] = + (codec1 | codec2 | codec3 | codec4 | codec5 | codec6 | codec7 | codec8 | codec9 | codec10 | codec11 | codec12 | codec13 | codec14 | codec15 | codec16 | codec17) + .transformOrFail(either => + Right( + either.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map(_.left.map(_.left.map(_.merge).merge).merge) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ), + )((value: Value) => + value match { + case sub1: Sub1 => + Right( + Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(sub1)))))))))))))))), + ) + case sub2: Sub2 => + Right( + Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub2)))))))))))))))), + ) + case sub3: Sub3 => + Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub3)))))))))))))))) + case sub4: Sub4 => + Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub4))))))))))))))) + case sub5: Sub5 => + Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub5)))))))))))))) + case sub6: Sub6 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub6))))))))))))) + case sub7: Sub7 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub7)))))))))))) + case sub8: Sub8 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub8))))))))))) + case sub9: Sub9 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub9)))))))))) + case sub10: Sub10 => Right(Left(Left(Left(Left(Left(Left(Left(Right(sub10))))))))) + case sub11: Sub11 => Right(Left(Left(Left(Left(Left(Left(Right(sub11)))))))) + case sub12: Sub12 => Right(Left(Left(Left(Left(Left(Right(sub12))))))) + case sub13: Sub13 => Right(Left(Left(Left(Left(Right(sub13)))))) + case sub14: Sub14 => Right(Left(Left(Left(Right(sub14))))) + case sub15: Sub15 => Right(Left(Left(Right(sub15)))) + case sub16: Sub16 => Right(Left(Right(sub16))) + case sub17: Sub17 => Right(Right(sub17)) + case _ => Left(s"Unexpected error type") + }, + ) + + def f18[ + AtomTypes, + Sub1 <: Value: ClassTag, + Sub2 <: Value: ClassTag, + Sub3 <: Value: ClassTag, + Sub4 <: Value: ClassTag, + Sub5 <: Value: ClassTag, + Sub6 <: Value: ClassTag, + Sub7 <: Value: ClassTag, + Sub8 <: Value: ClassTag, + Sub9 <: Value: ClassTag, + Sub10 <: Value: ClassTag, + Sub11 <: Value: ClassTag, + Sub12 <: Value: ClassTag, + Sub13 <: Value: ClassTag, + Sub14 <: Value: ClassTag, + Sub15 <: Value: ClassTag, + Sub16 <: Value: ClassTag, + Sub17 <: Value: ClassTag, + Sub18 <: Value: ClassTag, + ]( + codec1: HttpCodec[AtomTypes, Sub1], + codec2: HttpCodec[AtomTypes, Sub2], + codec3: HttpCodec[AtomTypes, Sub3], + codec4: HttpCodec[AtomTypes, Sub4], + codec5: HttpCodec[AtomTypes, Sub5], + codec6: HttpCodec[AtomTypes, Sub6], + codec7: HttpCodec[AtomTypes, Sub7], + codec8: HttpCodec[AtomTypes, Sub8], + codec9: HttpCodec[AtomTypes, Sub9], + codec10: HttpCodec[AtomTypes, Sub10], + codec11: HttpCodec[AtomTypes, Sub11], + codec12: HttpCodec[AtomTypes, Sub12], + codec13: HttpCodec[AtomTypes, Sub13], + codec14: HttpCodec[AtomTypes, Sub14], + codec15: HttpCodec[AtomTypes, Sub15], + codec16: HttpCodec[AtomTypes, Sub16], + codec17: HttpCodec[AtomTypes, Sub17], + codec18: HttpCodec[AtomTypes, Sub18], + ): HttpCodec[AtomTypes, Value] = + (codec1 | codec2 | codec3 | codec4 | codec5 | codec6 | codec7 | codec8 | codec9 | codec10 | codec11 | codec12 | codec13 | codec14 | codec15 | codec16 | codec17 | codec18) + .transformOrFail(either => + Right( + either.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map(_.left.map(_.left.map(_.merge).merge).merge) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ), + )((value: Value) => + value match { + case sub1: Sub1 => + Right( + Left( + Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(sub1)))))))))))))))), + ), + ) + case sub2: Sub2 => + Right( + Left( + Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub2)))))))))))))))), + ), + ) + case sub3: Sub3 => + Right( + Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub3)))))))))))))))), + ) + case sub4: Sub4 => + Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub4)))))))))))))))) + case sub5: Sub5 => + Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub5))))))))))))))) + case sub6: Sub6 => + Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub6)))))))))))))) + case sub7: Sub7 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub7))))))))))))) + case sub8: Sub8 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub8)))))))))))) + case sub9: Sub9 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub9))))))))))) + case sub10: Sub10 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub10)))))))))) + case sub11: Sub11 => Right(Left(Left(Left(Left(Left(Left(Left(Right(sub11))))))))) + case sub12: Sub12 => Right(Left(Left(Left(Left(Left(Left(Right(sub12)))))))) + case sub13: Sub13 => Right(Left(Left(Left(Left(Left(Right(sub13))))))) + case sub14: Sub14 => Right(Left(Left(Left(Left(Right(sub14)))))) + case sub15: Sub15 => Right(Left(Left(Left(Right(sub15))))) + case sub16: Sub16 => Right(Left(Left(Right(sub16)))) + case sub17: Sub17 => Right(Left(Right(sub17))) + case sub18: Sub18 => Right(Right(sub18)) + case _ => Left(s"Unexpected error type") + }, + ) + + def f19[ + AtomTypes, + Sub1 <: Value: ClassTag, + Sub2 <: Value: ClassTag, + Sub3 <: Value: ClassTag, + Sub4 <: Value: ClassTag, + Sub5 <: Value: ClassTag, + Sub6 <: Value: ClassTag, + Sub7 <: Value: ClassTag, + Sub8 <: Value: ClassTag, + Sub9 <: Value: ClassTag, + Sub10 <: Value: ClassTag, + Sub11 <: Value: ClassTag, + Sub12 <: Value: ClassTag, + Sub13 <: Value: ClassTag, + Sub14 <: Value: ClassTag, + Sub15 <: Value: ClassTag, + Sub16 <: Value: ClassTag, + Sub17 <: Value: ClassTag, + Sub18 <: Value: ClassTag, + Sub19 <: Value: ClassTag, + ]( + codec1: HttpCodec[AtomTypes, Sub1], + codec2: HttpCodec[AtomTypes, Sub2], + codec3: HttpCodec[AtomTypes, Sub3], + codec4: HttpCodec[AtomTypes, Sub4], + codec5: HttpCodec[AtomTypes, Sub5], + codec6: HttpCodec[AtomTypes, Sub6], + codec7: HttpCodec[AtomTypes, Sub7], + codec8: HttpCodec[AtomTypes, Sub8], + codec9: HttpCodec[AtomTypes, Sub9], + codec10: HttpCodec[AtomTypes, Sub10], + codec11: HttpCodec[AtomTypes, Sub11], + codec12: HttpCodec[AtomTypes, Sub12], + codec13: HttpCodec[AtomTypes, Sub13], + codec14: HttpCodec[AtomTypes, Sub14], + codec15: HttpCodec[AtomTypes, Sub15], + codec16: HttpCodec[AtomTypes, Sub16], + codec17: HttpCodec[AtomTypes, Sub17], + codec18: HttpCodec[AtomTypes, Sub18], + codec19: HttpCodec[AtomTypes, Sub19], + ): HttpCodec[AtomTypes, Value] = + (codec1 | codec2 | codec3 | codec4 | codec5 | codec6 | codec7 | codec8 | codec9 | codec10 | codec11 | codec12 | codec13 | codec14 | codec15 | codec16 | codec17 | codec18 | codec19) + .transformOrFail(either => + Right( + either.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map(_.left.map(_.left.map(_.merge).merge).merge) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ), + )((value: Value) => + value match { + case sub1: Sub1 => + Right( + Left( + Left( + Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(sub1)))))))))))))))), + ), + ), + ) + case sub2: Sub2 => + Right( + Left( + Left( + Left( + Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub2))))))))))))))), + ), + ), + ), + ) + case sub3: Sub3 => + Right( + Left( + Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub3)))))))))))))))), + ), + ) + case sub4: Sub4 => + Right( + Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub4)))))))))))))))), + ) + case sub5: Sub5 => + Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub5)))))))))))))))) + case sub6: Sub6 => + Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub6))))))))))))))) + case sub7: Sub7 => + Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub7)))))))))))))) + case sub8: Sub8 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub8))))))))))))) + case sub9: Sub9 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub9)))))))))))) + case sub10: Sub10 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub10))))))))))) + case sub11: Sub11 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub11)))))))))) + case sub12: Sub12 => Right(Left(Left(Left(Left(Left(Left(Left(Right(sub12))))))))) + case sub13: Sub13 => Right(Left(Left(Left(Left(Left(Left(Right(sub13)))))))) + case sub14: Sub14 => Right(Left(Left(Left(Left(Left(Right(sub14))))))) + case sub15: Sub15 => Right(Left(Left(Left(Left(Right(sub15)))))) + case sub16: Sub16 => Right(Left(Left(Left(Right(sub16))))) + case sub17: Sub17 => Right(Left(Left(Right(sub17)))) + case sub18: Sub18 => Right(Left(Right(sub18))) + case sub19: Sub19 => Right(Right(sub19)) + case _ => Left(s"Unexpected error type") + }, + ) + + def f20[ + AtomTypes, + Sub1 <: Value: ClassTag, + Sub2 <: Value: ClassTag, + Sub3 <: Value: ClassTag, + Sub4 <: Value: ClassTag, + Sub5 <: Value: ClassTag, + Sub6 <: Value: ClassTag, + Sub7 <: Value: ClassTag, + Sub8 <: Value: ClassTag, + Sub9 <: Value: ClassTag, + Sub10 <: Value: ClassTag, + Sub11 <: Value: ClassTag, + Sub12 <: Value: ClassTag, + Sub13 <: Value: ClassTag, + Sub14 <: Value: ClassTag, + Sub15 <: Value: ClassTag, + Sub16 <: Value: ClassTag, + Sub17 <: Value: ClassTag, + Sub18 <: Value: ClassTag, + Sub19 <: Value: ClassTag, + Sub20 <: Value: ClassTag, + ]( + codec1: HttpCodec[AtomTypes, Sub1], + codec2: HttpCodec[AtomTypes, Sub2], + codec3: HttpCodec[AtomTypes, Sub3], + codec4: HttpCodec[AtomTypes, Sub4], + codec5: HttpCodec[AtomTypes, Sub5], + codec6: HttpCodec[AtomTypes, Sub6], + codec7: HttpCodec[AtomTypes, Sub7], + codec8: HttpCodec[AtomTypes, Sub8], + codec9: HttpCodec[AtomTypes, Sub9], + codec10: HttpCodec[AtomTypes, Sub10], + codec11: HttpCodec[AtomTypes, Sub11], + codec12: HttpCodec[AtomTypes, Sub12], + codec13: HttpCodec[AtomTypes, Sub13], + codec14: HttpCodec[AtomTypes, Sub14], + codec15: HttpCodec[AtomTypes, Sub15], + codec16: HttpCodec[AtomTypes, Sub16], + codec17: HttpCodec[AtomTypes, Sub17], + codec18: HttpCodec[AtomTypes, Sub18], + codec19: HttpCodec[AtomTypes, Sub19], + codec20: HttpCodec[AtomTypes, Sub20], + ): HttpCodec[AtomTypes, Value] = + (codec1 | codec2 | codec3 | codec4 | codec5 | codec6 | codec7 | codec8 | codec9 | codec10 | codec11 | codec12 | codec13 | codec14 | codec15 | codec16 | codec17 | codec18 | codec19 | codec20) + .transformOrFail(either => + Right( + either.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left.map(_.left.map(_.merge).merge).merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ), + )((value: Value) => + value match { + case sub1: Sub1 => + Right( + Left( + Left( + Left( + Left( + Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(sub1))))))))))))))), + ), + ), + ), + ), + ) + case sub2: Sub2 => + Right( + Left( + Left( + Left( + Left( + Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub2))))))))))))))), + ), + ), + ), + ), + ) + case sub3: Sub3 => + Right( + Left( + Left( + Left( + Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub3))))))))))))))), + ), + ), + ), + ) + case sub4: Sub4 => + Right( + Left( + Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub4)))))))))))))))), + ), + ) + case sub5: Sub5 => + Right( + Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub5)))))))))))))))), + ) + case sub6: Sub6 => + Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub6)))))))))))))))) + case sub7: Sub7 => + Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub7))))))))))))))) + case sub8: Sub8 => + Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub8)))))))))))))) + case sub9: Sub9 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub9))))))))))))) + case sub10: Sub10 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub10)))))))))))) + case sub11: Sub11 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub11))))))))))) + case sub12: Sub12 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub12)))))))))) + case sub13: Sub13 => Right(Left(Left(Left(Left(Left(Left(Left(Right(sub13))))))))) + case sub14: Sub14 => Right(Left(Left(Left(Left(Left(Left(Right(sub14)))))))) + case sub15: Sub15 => Right(Left(Left(Left(Left(Left(Right(sub15))))))) + case sub16: Sub16 => Right(Left(Left(Left(Left(Right(sub16)))))) + case sub17: Sub17 => Right(Left(Left(Left(Right(sub17))))) + case sub18: Sub18 => Right(Left(Left(Right(sub18)))) + case sub19: Sub19 => Right(Left(Right(sub19))) + case sub20: Sub20 => Right(Right(sub20)) + case _ => Left(s"Unexpected error type") + }, + ) + + def f21[ + AtomTypes, + Sub1 <: Value: ClassTag, + Sub2 <: Value: ClassTag, + Sub3 <: Value: ClassTag, + Sub4 <: Value: ClassTag, + Sub5 <: Value: ClassTag, + Sub6 <: Value: ClassTag, + Sub7 <: Value: ClassTag, + Sub8 <: Value: ClassTag, + Sub9 <: Value: ClassTag, + Sub10 <: Value: ClassTag, + Sub11 <: Value: ClassTag, + Sub12 <: Value: ClassTag, + Sub13 <: Value: ClassTag, + Sub14 <: Value: ClassTag, + Sub15 <: Value: ClassTag, + Sub16 <: Value: ClassTag, + Sub17 <: Value: ClassTag, + Sub18 <: Value: ClassTag, + Sub19 <: Value: ClassTag, + Sub20 <: Value: ClassTag, + Sub21 <: Value: ClassTag, + ]( + codec1: HttpCodec[AtomTypes, Sub1], + codec2: HttpCodec[AtomTypes, Sub2], + codec3: HttpCodec[AtomTypes, Sub3], + codec4: HttpCodec[AtomTypes, Sub4], + codec5: HttpCodec[AtomTypes, Sub5], + codec6: HttpCodec[AtomTypes, Sub6], + codec7: HttpCodec[AtomTypes, Sub7], + codec8: HttpCodec[AtomTypes, Sub8], + codec9: HttpCodec[AtomTypes, Sub9], + codec10: HttpCodec[AtomTypes, Sub10], + codec11: HttpCodec[AtomTypes, Sub11], + codec12: HttpCodec[AtomTypes, Sub12], + codec13: HttpCodec[AtomTypes, Sub13], + codec14: HttpCodec[AtomTypes, Sub14], + codec15: HttpCodec[AtomTypes, Sub15], + codec16: HttpCodec[AtomTypes, Sub16], + codec17: HttpCodec[AtomTypes, Sub17], + codec18: HttpCodec[AtomTypes, Sub18], + codec19: HttpCodec[AtomTypes, Sub19], + codec20: HttpCodec[AtomTypes, Sub20], + codec21: HttpCodec[AtomTypes, Sub21], + ): HttpCodec[AtomTypes, Value] = + (codec1 | codec2 | codec3 | codec4 | codec5 | codec6 | codec7 | codec8 | codec9 | codec10 | codec11 | codec12 | codec13 | codec14 | codec15 | codec16 | codec17 | codec18 | codec19 | codec20 | codec21) + .transformOrFail(either => + Right( + either.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map(_.left.map(_.merge).merge) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ), + )((value: Value) => + value match { + case sub1: Sub1 => + Right( + Left( + Left( + Left( + Left( + Left( + Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(sub1))))))))))))))), + ), + ), + ), + ), + ), + ) + case sub2: Sub2 => + Right( + Left( + Left( + Left( + Left( + Left( + Left( + Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub2)))))))))))))), + ), + ), + ), + ), + ), + ), + ) + case sub3: Sub3 => + Right( + Left( + Left( + Left( + Left( + Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub3))))))))))))))), + ), + ), + ), + ), + ) + case sub4: Sub4 => + Right( + Left( + Left( + Left( + Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub4))))))))))))))), + ), + ), + ), + ) + case sub5: Sub5 => + Right( + Left( + Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub5)))))))))))))))), + ), + ) + case sub6: Sub6 => + Right( + Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub6)))))))))))))))), + ) + case sub7: Sub7 => + Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub7)))))))))))))))) + case sub8: Sub8 => + Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub8))))))))))))))) + case sub9: Sub9 => + Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub9)))))))))))))) + case sub10: Sub10 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub10))))))))))))) + case sub11: Sub11 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub11)))))))))))) + case sub12: Sub12 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub12))))))))))) + case sub13: Sub13 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub13)))))))))) + case sub14: Sub14 => Right(Left(Left(Left(Left(Left(Left(Left(Right(sub14))))))))) + case sub15: Sub15 => Right(Left(Left(Left(Left(Left(Left(Right(sub15)))))))) + case sub16: Sub16 => Right(Left(Left(Left(Left(Left(Right(sub16))))))) + case sub17: Sub17 => Right(Left(Left(Left(Left(Right(sub17)))))) + case sub18: Sub18 => Right(Left(Left(Left(Right(sub18))))) + case sub19: Sub19 => Right(Left(Left(Right(sub19)))) + case sub20: Sub20 => Right(Left(Right(sub20))) + case sub21: Sub21 => Right(Right(sub21)) + case _ => Left(s"Unexpected error type") + }, + ) + + def f22[ + AtomTypes, + Sub1 <: Value: ClassTag, + Sub2 <: Value: ClassTag, + Sub3 <: Value: ClassTag, + Sub4 <: Value: ClassTag, + Sub5 <: Value: ClassTag, + Sub6 <: Value: ClassTag, + Sub7 <: Value: ClassTag, + Sub8 <: Value: ClassTag, + Sub9 <: Value: ClassTag, + Sub10 <: Value: ClassTag, + Sub11 <: Value: ClassTag, + Sub12 <: Value: ClassTag, + Sub13 <: Value: ClassTag, + Sub14 <: Value: ClassTag, + Sub15 <: Value: ClassTag, + Sub16 <: Value: ClassTag, + Sub17 <: Value: ClassTag, + Sub18 <: Value: ClassTag, + Sub19 <: Value: ClassTag, + Sub20 <: Value: ClassTag, + Sub21 <: Value: ClassTag, + Sub22 <: Value: ClassTag, + ]( + codec1: HttpCodec[AtomTypes, Sub1], + codec2: HttpCodec[AtomTypes, Sub2], + codec3: HttpCodec[AtomTypes, Sub3], + codec4: HttpCodec[AtomTypes, Sub4], + codec5: HttpCodec[AtomTypes, Sub5], + codec6: HttpCodec[AtomTypes, Sub6], + codec7: HttpCodec[AtomTypes, Sub7], + codec8: HttpCodec[AtomTypes, Sub8], + codec9: HttpCodec[AtomTypes, Sub9], + codec10: HttpCodec[AtomTypes, Sub10], + codec11: HttpCodec[AtomTypes, Sub11], + codec12: HttpCodec[AtomTypes, Sub12], + codec13: HttpCodec[AtomTypes, Sub13], + codec14: HttpCodec[AtomTypes, Sub14], + codec15: HttpCodec[AtomTypes, Sub15], + codec16: HttpCodec[AtomTypes, Sub16], + codec17: HttpCodec[AtomTypes, Sub17], + codec18: HttpCodec[AtomTypes, Sub18], + codec19: HttpCodec[AtomTypes, Sub19], + codec20: HttpCodec[AtomTypes, Sub20], + codec21: HttpCodec[AtomTypes, Sub21], + codec22: HttpCodec[AtomTypes, Sub22], + ): HttpCodec[AtomTypes, Value] = + (codec1 | codec2 | codec3 | codec4 | codec5 | codec6 | codec7 | codec8 | codec9 | codec10 | codec11 | codec12 | codec13 | codec14 | codec15 | codec16 | codec17 | codec18 | codec19 | codec20 | codec21 | codec22) + .transformOrFail(either => + Right( + either.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map( + _.left + .map(_.left.map(_.merge).merge) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ) + .merge, + ), + )((value: Value) => + value match { + case sub1: Sub1 => + Right( + Left( + Left( + Left( + Left( + Left( + Left( + Left( + Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(sub1)))))))))))))), + ), + ), + ), + ), + ), + ), + ), + ) + case sub2: Sub2 => + Right( + Left( + Left( + Left( + Left( + Left( + Left( + Left( + Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub2)))))))))))))), + ), + ), + ), + ), + ), + ), + ), + ) + case sub3: Sub3 => + Right( + Left( + Left( + Left( + Left( + Left( + Left( + Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub3)))))))))))))), + ), + ), + ), + ), + ), + ), + ) + case sub4: Sub4 => + Right( + Left( + Left( + Left( + Left( + Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub4))))))))))))))), + ), + ), + ), + ), + ) + case sub5: Sub5 => + Right( + Left( + Left( + Left( + Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub5))))))))))))))), + ), + ), + ), + ) + case sub6: Sub6 => + Right( + Left( + Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub6)))))))))))))))), + ), + ) + case sub7: Sub7 => + Right( + Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub7)))))))))))))))), + ) + case sub8: Sub8 => + Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub8)))))))))))))))) + case sub9: Sub9 => + Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub9))))))))))))))) + case sub10: Sub10 => + Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub10)))))))))))))) + case sub11: Sub11 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub11))))))))))))) + case sub12: Sub12 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub12)))))))))))) + case sub13: Sub13 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub13))))))))))) + case sub14: Sub14 => Right(Left(Left(Left(Left(Left(Left(Left(Left(Right(sub14)))))))))) + case sub15: Sub15 => Right(Left(Left(Left(Left(Left(Left(Left(Right(sub15))))))))) + case sub16: Sub16 => Right(Left(Left(Left(Left(Left(Left(Right(sub16)))))))) + case sub17: Sub17 => Right(Left(Left(Left(Left(Left(Right(sub17))))))) + case sub18: Sub18 => Right(Left(Left(Left(Left(Right(sub18)))))) + case sub19: Sub19 => Right(Left(Left(Left(Right(sub19))))) + case sub20: Sub20 => Right(Left(Left(Right(sub20)))) + case sub21: Sub21 => Right(Left(Right(sub21))) + case sub22: Sub22 => Right(Right(sub22)) + case _ => Left(s"Unexpected error type") + }, + ) + } private[http] sealed trait Atom[-AtomTypes, Value0] extends HttpCodec[AtomTypes, Value0] { @@ -566,6 +2240,19 @@ object HttpCodec extends ContentCodecs with HeaderCodecs with MethodCodecs with def tag: AtomTag = AtomTag.Content def index(index: Int): Content[A] = copy(index = index) + + /** + * Returns a new codec, where the value produced by this one is optional. + */ + override def optional: HttpCodec[HttpCodecType.Content, Option[A]] = + Annotated( + Content( + codec.optional, + name, + index, + ), + Metadata.Optional(), + ) } private[http] final case class ContentStream[A]( codec: HttpContentCodec[A], diff --git a/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala b/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala index c0223e4fe6..3f8a3f29a2 100644 --- a/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala +++ b/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala @@ -148,6 +148,36 @@ sealed trait HttpContentCodec[A] { self => choices.headOption.map(_._2).getOrElse { throw new IllegalArgumentException(s"No codec defined") } + + def optional: HttpContentCodec[Option[A]] = + self match { + case HttpContentCodec.Choices(choices) => + HttpContentCodec.Choices( + choices.map { case (mediaType, BinaryCodecWithSchema(fromConfig, schema)) => + mediaType -> BinaryCodecWithSchema(fromConfig.andThen(optBinaryCodec), schema.optional) + }, + ) + case HttpContentCodec.Filtered(codec, mediaType) => + HttpContentCodec.Filtered(codec.optional, mediaType) + } + + private def optBinaryCodec(bc: BinaryCodec[A]): BinaryCodec[Option[A]] = new BinaryCodec[Option[A]] { + override def encode(value: Option[A]): Chunk[Byte] = value match { + case Some(a) => bc.encode(a) + case None => Chunk.empty + } + + override def decode(bytes: Chunk[Byte]): Either[DecodeError, Option[A]] = + if (bytes.isEmpty) Right(None) + else bc.decode(bytes).map(Some(_)) + + override def streamDecoder: ZPipeline[Any, DecodeError, Byte, Option[A]] = + ZPipeline.chunks[Byte].map(bc.decode).map(_.toOption) + + override def streamEncoder: ZPipeline[Any, Nothing, Option[A], Byte] = + ZPipeline.identity[Option[A]].map(_.fold(Chunk.empty[Byte])(bc.encode)).flattenChunks + } + } object HttpContentCodec { diff --git a/zio-http/shared/src/main/scala/zio/http/codec/PathCodec.scala b/zio-http/shared/src/main/scala/zio/http/codec/PathCodec.scala index ead97b5da7..75c7e34140 100644 --- a/zio-http/shared/src/main/scala/zio/http/codec/PathCodec.scala +++ b/zio-http/shared/src/main/scala/zio/http/codec/PathCodec.scala @@ -48,8 +48,11 @@ sealed trait PathCodec[A] extends codec.PathCodecPlatformSpecific { self => final def ++[B](that: PathCodec[B])(implicit combiner: Combiner[A, B]): PathCodec[combiner.Out] = PathCodec.Concat(self, that, combiner) - final def /[B](that: PathCodec[B])(implicit combiner: Combiner[A, B]): PathCodec[combiner.Out] = - self ++ that + final def /[B](that: PathCodec[B])(implicit combiner: Combiner[A, B]): PathCodec[combiner.Out] = { + if (self == PathCodec.empty) that.asInstanceOf[PathCodec[combiner.Out]] + else if (that == PathCodec.empty) self.asInstanceOf[PathCodec[combiner.Out]] + else self ++ that + } final def /[Env, Err](routes: Routes[Env, Err])(implicit ev: PathCodec[A] <:< PathCodec[Unit], diff --git a/zio-http/shared/src/main/scala/zio/http/codec/SegmentCodec.scala b/zio-http/shared/src/main/scala/zio/http/codec/SegmentCodec.scala index a8077c6b64..e60ea41f8c 100644 --- a/zio-http/shared/src/main/scala/zio/http/codec/SegmentCodec.scala +++ b/zio-http/shared/src/main/scala/zio/http/codec/SegmentCodec.scala @@ -114,6 +114,7 @@ sealed trait SegmentCodec[A] { self => } if (self ne SegmentCodec.Empty) b.append('/') loop(self.asInstanceOf[SegmentCodec[_]]) + if (b.isEmpty) b.appendAll("/") b.result() } diff --git a/zio-http/shared/src/main/scala/zio/http/endpoint/Endpoint.scala b/zio-http/shared/src/main/scala/zio/http/endpoint/Endpoint.scala index 95a0222b9a..7d3f094265 100644 --- a/zio-http/shared/src/main/scala/zio/http/endpoint/Endpoint.scala +++ b/zio-http/shared/src/main/scala/zio/http/endpoint/Endpoint.scala @@ -979,6 +979,751 @@ object Endpoint { val codec = HttpCodec.enumeration.f8(codec1, codec2, codec3, codec4, codec5, codec6, codec7, codec8) self.copy[PathInput, Input, alt.Out, Output, Auth](error = codec | self.error) } + + // apply 9 + def apply[ + Sub1 <: Err2: ClassTag, + Sub2 <: Err2: ClassTag, + Sub3 <: Err2: ClassTag, + Sub4 <: Err2: ClassTag, + Sub5 <: Err2: ClassTag, + Sub6 <: Err2: ClassTag, + Sub7 <: Err2: ClassTag, + Sub8 <: Err2: ClassTag, + Sub9 <: Err2: ClassTag, + ]( + codec1: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub1], + codec2: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub2], + codec3: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub3], + codec4: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub4], + codec5: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub5], + codec6: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub6], + codec7: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub7], + codec8: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub8], + codec9: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub9], + )(implicit alt: Alternator[Err2, Err]): Endpoint[PathInput, Input, alt.Out, Output, Auth] = { + val codec = HttpCodec.enumeration.f9(codec1, codec2, codec3, codec4, codec5, codec6, codec7, codec8, codec9) + self.copy[PathInput, Input, alt.Out, Output, Auth](error = codec | self.error) + } + + def apply[ + Sub1 <: Err2: ClassTag, + Sub2 <: Err2: ClassTag, + Sub3 <: Err2: ClassTag, + Sub4 <: Err2: ClassTag, + Sub5 <: Err2: ClassTag, + Sub6 <: Err2: ClassTag, + Sub7 <: Err2: ClassTag, + Sub8 <: Err2: ClassTag, + Sub9 <: Err2: ClassTag, + Sub10 <: Err2: ClassTag, + ]( + codec1: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub1], + codec2: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub2], + codec3: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub3], + codec4: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub4], + codec5: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub5], + codec6: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub6], + codec7: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub7], + codec8: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub8], + codec9: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub9], + codec10: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub10], + )(implicit alt: Alternator[Err2, Err]): Endpoint[PathInput, Input, alt.Out, Output, Auth] = { + val codec = + HttpCodec.enumeration.f10(codec1, codec2, codec3, codec4, codec5, codec6, codec7, codec8, codec9, codec10) + self.copy[PathInput, Input, alt.Out, Output, Auth](error = codec | self.error) + } + + def apply[ + Sub1 <: Err2: ClassTag, + Sub2 <: Err2: ClassTag, + Sub3 <: Err2: ClassTag, + Sub4 <: Err2: ClassTag, + Sub5 <: Err2: ClassTag, + Sub6 <: Err2: ClassTag, + Sub7 <: Err2: ClassTag, + Sub8 <: Err2: ClassTag, + Sub9 <: Err2: ClassTag, + Sub10 <: Err2: ClassTag, + Sub11 <: Err2: ClassTag, + ]( + codec1: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub1], + codec2: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub2], + codec3: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub3], + codec4: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub4], + codec5: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub5], + codec6: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub6], + codec7: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub7], + codec8: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub8], + codec9: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub9], + codec10: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub10], + codec11: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub11], + )(implicit alt: Alternator[Err2, Err]): Endpoint[PathInput, Input, alt.Out, Output, Auth] = { + val codec = HttpCodec.enumeration.f11( + codec1, + codec2, + codec3, + codec4, + codec5, + codec6, + codec7, + codec8, + codec9, + codec10, + codec11, + ) + self.copy[PathInput, Input, alt.Out, Output, Auth](error = codec | self.error) + } + + def apply[ + Sub1 <: Err2: ClassTag, + Sub2 <: Err2: ClassTag, + Sub3 <: Err2: ClassTag, + Sub4 <: Err2: ClassTag, + Sub5 <: Err2: ClassTag, + Sub6 <: Err2: ClassTag, + Sub7 <: Err2: ClassTag, + Sub8 <: Err2: ClassTag, + Sub9 <: Err2: ClassTag, + Sub10 <: Err2: ClassTag, + Sub11 <: Err2: ClassTag, + Sub12 <: Err2: ClassTag, + ]( + codec1: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub1], + codec2: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub2], + codec3: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub3], + codec4: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub4], + codec5: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub5], + codec6: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub6], + codec7: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub7], + codec8: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub8], + codec9: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub9], + codec10: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub10], + codec11: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub11], + codec12: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub12], + )(implicit alt: Alternator[Err2, Err]): Endpoint[PathInput, Input, alt.Out, Output, Auth] = { + val codec = HttpCodec.enumeration.f12( + codec1, + codec2, + codec3, + codec4, + codec5, + codec6, + codec7, + codec8, + codec9, + codec10, + codec11, + codec12, + ) + self.copy[PathInput, Input, alt.Out, Output, Auth](error = codec | self.error) + } + + def apply[ + Sub1 <: Err2: ClassTag, + Sub2 <: Err2: ClassTag, + Sub3 <: Err2: ClassTag, + Sub4 <: Err2: ClassTag, + Sub5 <: Err2: ClassTag, + Sub6 <: Err2: ClassTag, + Sub7 <: Err2: ClassTag, + Sub8 <: Err2: ClassTag, + Sub9 <: Err2: ClassTag, + Sub10 <: Err2: ClassTag, + Sub11 <: Err2: ClassTag, + Sub12 <: Err2: ClassTag, + Sub13 <: Err2: ClassTag, + ]( + codec1: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub1], + codec2: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub2], + codec3: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub3], + codec4: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub4], + codec5: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub5], + codec6: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub6], + codec7: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub7], + codec8: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub8], + codec9: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub9], + codec10: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub10], + codec11: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub11], + codec12: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub12], + codec13: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub13], + )(implicit alt: Alternator[Err2, Err]): Endpoint[PathInput, Input, alt.Out, Output, Auth] = { + val codec = HttpCodec.enumeration.f13( + codec1, + codec2, + codec3, + codec4, + codec5, + codec6, + codec7, + codec8, + codec9, + codec10, + codec11, + codec12, + codec13, + ) + self.copy[PathInput, Input, alt.Out, Output, Auth](error = codec | self.error) + } + + def apply[ + Sub1 <: Err2: ClassTag, + Sub2 <: Err2: ClassTag, + Sub3 <: Err2: ClassTag, + Sub4 <: Err2: ClassTag, + Sub5 <: Err2: ClassTag, + Sub6 <: Err2: ClassTag, + Sub7 <: Err2: ClassTag, + Sub8 <: Err2: ClassTag, + Sub9 <: Err2: ClassTag, + Sub10 <: Err2: ClassTag, + Sub11 <: Err2: ClassTag, + Sub12 <: Err2: ClassTag, + Sub13 <: Err2: ClassTag, + Sub14 <: Err2: ClassTag, + ]( + codec1: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub1], + codec2: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub2], + codec3: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub3], + codec4: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub4], + codec5: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub5], + codec6: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub6], + codec7: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub7], + codec8: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub8], + codec9: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub9], + codec10: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub10], + codec11: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub11], + codec12: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub12], + codec13: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub13], + codec14: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub14], + )(implicit alt: Alternator[Err2, Err]): Endpoint[PathInput, Input, alt.Out, Output, Auth] = { + val codec = HttpCodec.enumeration.f14( + codec1, + codec2, + codec3, + codec4, + codec5, + codec6, + codec7, + codec8, + codec9, + codec10, + codec11, + codec12, + codec13, + codec14, + ) + self.copy[PathInput, Input, alt.Out, Output, Auth](error = codec | self.error) + } + + def apply[ + Sub1 <: Err2: ClassTag, + Sub2 <: Err2: ClassTag, + Sub3 <: Err2: ClassTag, + Sub4 <: Err2: ClassTag, + Sub5 <: Err2: ClassTag, + Sub6 <: Err2: ClassTag, + Sub7 <: Err2: ClassTag, + Sub8 <: Err2: ClassTag, + Sub9 <: Err2: ClassTag, + Sub10 <: Err2: ClassTag, + Sub11 <: Err2: ClassTag, + Sub12 <: Err2: ClassTag, + Sub13 <: Err2: ClassTag, + Sub14 <: Err2: ClassTag, + Sub15 <: Err2: ClassTag, + ]( + codec1: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub1], + codec2: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub2], + codec3: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub3], + codec4: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub4], + codec5: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub5], + codec6: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub6], + codec7: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub7], + codec8: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub8], + codec9: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub9], + codec10: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub10], + codec11: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub11], + codec12: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub12], + codec13: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub13], + codec14: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub14], + codec15: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub15], + )(implicit alt: Alternator[Err2, Err]): Endpoint[PathInput, Input, alt.Out, Output, Auth] = { + val codec = HttpCodec.enumeration.f15( + codec1, + codec2, + codec3, + codec4, + codec5, + codec6, + codec7, + codec8, + codec9, + codec10, + codec11, + codec12, + codec13, + codec14, + codec15, + ) + self.copy[PathInput, Input, alt.Out, Output, Auth](error = codec | self.error) + } + + def apply[ + Sub1 <: Err2: ClassTag, + Sub2 <: Err2: ClassTag, + Sub3 <: Err2: ClassTag, + Sub4 <: Err2: ClassTag, + Sub5 <: Err2: ClassTag, + Sub6 <: Err2: ClassTag, + Sub7 <: Err2: ClassTag, + Sub8 <: Err2: ClassTag, + Sub9 <: Err2: ClassTag, + Sub10 <: Err2: ClassTag, + Sub11 <: Err2: ClassTag, + Sub12 <: Err2: ClassTag, + Sub13 <: Err2: ClassTag, + Sub14 <: Err2: ClassTag, + Sub15 <: Err2: ClassTag, + Sub16 <: Err2: ClassTag, + ]( + codec1: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub1], + codec2: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub2], + codec3: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub3], + codec4: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub4], + codec5: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub5], + codec6: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub6], + codec7: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub7], + codec8: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub8], + codec9: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub9], + codec10: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub10], + codec11: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub11], + codec12: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub12], + codec13: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub13], + codec14: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub14], + codec15: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub15], + codec16: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub16], + )(implicit alt: Alternator[Err2, Err]): Endpoint[PathInput, Input, alt.Out, Output, Auth] = { + val codec = HttpCodec.enumeration.f16( + codec1, + codec2, + codec3, + codec4, + codec5, + codec6, + codec7, + codec8, + codec9, + codec10, + codec11, + codec12, + codec13, + codec14, + codec15, + codec16, + ) + self.copy[PathInput, Input, alt.Out, Output, Auth](error = codec | self.error) + } + + def apply[ + Sub1 <: Err2: ClassTag, + Sub2 <: Err2: ClassTag, + Sub3 <: Err2: ClassTag, + Sub4 <: Err2: ClassTag, + Sub5 <: Err2: ClassTag, + Sub6 <: Err2: ClassTag, + Sub7 <: Err2: ClassTag, + Sub8 <: Err2: ClassTag, + Sub9 <: Err2: ClassTag, + Sub10 <: Err2: ClassTag, + Sub11 <: Err2: ClassTag, + Sub12 <: Err2: ClassTag, + Sub13 <: Err2: ClassTag, + Sub14 <: Err2: ClassTag, + Sub15 <: Err2: ClassTag, + Sub16 <: Err2: ClassTag, + Sub17 <: Err2: ClassTag, + ]( + codec1: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub1], + codec2: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub2], + codec3: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub3], + codec4: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub4], + codec5: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub5], + codec6: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub6], + codec7: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub7], + codec8: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub8], + codec9: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub9], + codec10: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub10], + codec11: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub11], + codec12: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub12], + codec13: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub13], + codec14: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub14], + codec15: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub15], + codec16: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub16], + codec17: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub17], + )(implicit alt: Alternator[Err2, Err]): Endpoint[PathInput, Input, alt.Out, Output, Auth] = { + val codec = HttpCodec.enumeration.f17( + codec1, + codec2, + codec3, + codec4, + codec5, + codec6, + codec7, + codec8, + codec9, + codec10, + codec11, + codec12, + codec13, + codec14, + codec15, + codec16, + codec17, + ) + self.copy[PathInput, Input, alt.Out, Output, Auth](error = codec | self.error) + } + + def apply[ + Sub1 <: Err2: ClassTag, + Sub2 <: Err2: ClassTag, + Sub3 <: Err2: ClassTag, + Sub4 <: Err2: ClassTag, + Sub5 <: Err2: ClassTag, + Sub6 <: Err2: ClassTag, + Sub7 <: Err2: ClassTag, + Sub8 <: Err2: ClassTag, + Sub9 <: Err2: ClassTag, + Sub10 <: Err2: ClassTag, + Sub11 <: Err2: ClassTag, + Sub12 <: Err2: ClassTag, + Sub13 <: Err2: ClassTag, + Sub14 <: Err2: ClassTag, + Sub15 <: Err2: ClassTag, + Sub16 <: Err2: ClassTag, + Sub17 <: Err2: ClassTag, + Sub18 <: Err2: ClassTag, + ]( + codec1: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub1], + codec2: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub2], + codec3: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub3], + codec4: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub4], + codec5: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub5], + codec6: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub6], + codec7: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub7], + codec8: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub8], + codec9: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub9], + codec10: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub10], + codec11: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub11], + codec12: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub12], + codec13: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub13], + codec14: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub14], + codec15: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub15], + codec16: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub16], + codec17: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub17], + codec18: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub18], + )(implicit alt: Alternator[Err2, Err]): Endpoint[PathInput, Input, alt.Out, Output, Auth] = { + val codec = HttpCodec.enumeration.f18( + codec1, + codec2, + codec3, + codec4, + codec5, + codec6, + codec7, + codec8, + codec9, + codec10, + codec11, + codec12, + codec13, + codec14, + codec15, + codec16, + codec17, + codec18, + ) + self.copy[PathInput, Input, alt.Out, Output, Auth](error = codec | self.error) + } + + def apply[ + Sub1 <: Err2: ClassTag, + Sub2 <: Err2: ClassTag, + Sub3 <: Err2: ClassTag, + Sub4 <: Err2: ClassTag, + Sub5 <: Err2: ClassTag, + Sub6 <: Err2: ClassTag, + Sub7 <: Err2: ClassTag, + Sub8 <: Err2: ClassTag, + Sub9 <: Err2: ClassTag, + Sub10 <: Err2: ClassTag, + Sub11 <: Err2: ClassTag, + Sub12 <: Err2: ClassTag, + Sub13 <: Err2: ClassTag, + Sub14 <: Err2: ClassTag, + Sub15 <: Err2: ClassTag, + Sub16 <: Err2: ClassTag, + Sub17 <: Err2: ClassTag, + Sub18 <: Err2: ClassTag, + Sub19 <: Err2: ClassTag, + ]( + codec1: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub1], + codec2: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub2], + codec3: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub3], + codec4: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub4], + codec5: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub5], + codec6: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub6], + codec7: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub7], + codec8: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub8], + codec9: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub9], + codec10: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub10], + codec11: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub11], + codec12: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub12], + codec13: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub13], + codec14: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub14], + codec15: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub15], + codec16: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub16], + codec17: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub17], + codec18: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub18], + codec19: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub19], + )(implicit alt: Alternator[Err2, Err]): Endpoint[PathInput, Input, alt.Out, Output, Auth] = { + val codec = HttpCodec.enumeration.f19( + codec1, + codec2, + codec3, + codec4, + codec5, + codec6, + codec7, + codec8, + codec9, + codec10, + codec11, + codec12, + codec13, + codec14, + codec15, + codec16, + codec17, + codec18, + codec19, + ) + self.copy[PathInput, Input, alt.Out, Output, Auth](error = codec | self.error) + } + + def apply[ + Sub1 <: Err2: ClassTag, + Sub2 <: Err2: ClassTag, + Sub3 <: Err2: ClassTag, + Sub4 <: Err2: ClassTag, + Sub5 <: Err2: ClassTag, + Sub6 <: Err2: ClassTag, + Sub7 <: Err2: ClassTag, + Sub8 <: Err2: ClassTag, + Sub9 <: Err2: ClassTag, + Sub10 <: Err2: ClassTag, + Sub11 <: Err2: ClassTag, + Sub12 <: Err2: ClassTag, + Sub13 <: Err2: ClassTag, + Sub14 <: Err2: ClassTag, + Sub15 <: Err2: ClassTag, + Sub16 <: Err2: ClassTag, + Sub17 <: Err2: ClassTag, + Sub18 <: Err2: ClassTag, + Sub19 <: Err2: ClassTag, + Sub20 <: Err2: ClassTag, + ]( + codec1: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub1], + codec2: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub2], + codec3: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub3], + codec4: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub4], + codec5: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub5], + codec6: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub6], + codec7: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub7], + codec8: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub8], + codec9: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub9], + codec10: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub10], + codec11: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub11], + codec12: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub12], + codec13: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub13], + codec14: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub14], + codec15: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub15], + codec16: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub16], + codec17: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub17], + codec18: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub18], + codec19: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub19], + codec20: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub20], + )(implicit alt: Alternator[Err2, Err]): Endpoint[PathInput, Input, alt.Out, Output, Auth] = { + val codec = HttpCodec.enumeration.f20( + codec1, + codec2, + codec3, + codec4, + codec5, + codec6, + codec7, + codec8, + codec9, + codec10, + codec11, + codec12, + codec13, + codec14, + codec15, + codec16, + codec17, + codec18, + codec19, + codec20, + ) + self.copy[PathInput, Input, alt.Out, Output, Auth](error = codec | self.error) + } + + def apply[ + Sub1 <: Err2: ClassTag, + Sub2 <: Err2: ClassTag, + Sub3 <: Err2: ClassTag, + Sub4 <: Err2: ClassTag, + Sub5 <: Err2: ClassTag, + Sub6 <: Err2: ClassTag, + Sub7 <: Err2: ClassTag, + Sub8 <: Err2: ClassTag, + Sub9 <: Err2: ClassTag, + Sub10 <: Err2: ClassTag, + Sub11 <: Err2: ClassTag, + Sub12 <: Err2: ClassTag, + Sub13 <: Err2: ClassTag, + Sub14 <: Err2: ClassTag, + Sub15 <: Err2: ClassTag, + Sub16 <: Err2: ClassTag, + Sub17 <: Err2: ClassTag, + Sub18 <: Err2: ClassTag, + Sub19 <: Err2: ClassTag, + Sub20 <: Err2: ClassTag, + Sub21 <: Err2: ClassTag, + ]( + codec1: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub1], + codec2: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub2], + codec3: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub3], + codec4: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub4], + codec5: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub5], + codec6: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub6], + codec7: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub7], + codec8: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub8], + codec9: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub9], + codec10: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub10], + codec11: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub11], + codec12: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub12], + codec13: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub13], + codec14: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub14], + codec15: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub15], + codec16: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub16], + codec17: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub17], + codec18: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub18], + codec19: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub19], + codec20: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub20], + codec21: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub21], + )(implicit alt: Alternator[Err2, Err]): Endpoint[PathInput, Input, alt.Out, Output, Auth] = { + val codec = HttpCodec.enumeration.f21( + codec1, + codec2, + codec3, + codec4, + codec5, + codec6, + codec7, + codec8, + codec9, + codec10, + codec11, + codec12, + codec13, + codec14, + codec15, + codec16, + codec17, + codec18, + codec19, + codec20, + codec21, + ) + self.copy[PathInput, Input, alt.Out, Output, Auth](error = codec | self.error) + } + + def apply[ + Sub1 <: Err2: ClassTag, + Sub2 <: Err2: ClassTag, + Sub3 <: Err2: ClassTag, + Sub4 <: Err2: ClassTag, + Sub5 <: Err2: ClassTag, + Sub6 <: Err2: ClassTag, + Sub7 <: Err2: ClassTag, + Sub8 <: Err2: ClassTag, + Sub9 <: Err2: ClassTag, + Sub10 <: Err2: ClassTag, + Sub11 <: Err2: ClassTag, + Sub12 <: Err2: ClassTag, + Sub13 <: Err2: ClassTag, + Sub14 <: Err2: ClassTag, + Sub15 <: Err2: ClassTag, + Sub16 <: Err2: ClassTag, + Sub17 <: Err2: ClassTag, + Sub18 <: Err2: ClassTag, + Sub19 <: Err2: ClassTag, + Sub20 <: Err2: ClassTag, + Sub21 <: Err2: ClassTag, + Sub22 <: Err2: ClassTag, + ]( + codec1: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub1], + codec2: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub2], + codec3: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub3], + codec4: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub4], + codec5: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub5], + codec6: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub6], + codec7: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub7], + codec8: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub8], + codec9: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub9], + codec10: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub10], + codec11: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub11], + codec12: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub12], + codec13: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub13], + codec14: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub14], + codec15: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub15], + codec16: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub16], + codec17: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub17], + codec18: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub18], + codec19: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub19], + codec20: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub20], + codec21: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub21], + codec22: HttpCodec[HttpCodecType.Status & HttpCodecType.Content, Sub22], + )(implicit alt: Alternator[Err2, Err]): Endpoint[PathInput, Input, alt.Out, Output, Auth] = { + val codec = HttpCodec.enumeration.f22( + codec1, + codec2, + codec3, + codec4, + codec5, + codec6, + codec7, + codec8, + codec9, + codec10, + codec11, + codec12, + codec13, + codec14, + codec15, + codec16, + codec17, + codec18, + codec19, + codec20, + codec21, + codec22, + ) + self.copy[PathInput, Input, alt.Out, Output, Auth](error = codec | self.error) + } + } private[endpoint] val defaultMediaTypes = diff --git a/zio-http/shared/src/main/scala/zio/http/endpoint/openapi/JsonSchema.scala b/zio-http/shared/src/main/scala/zio/http/endpoint/openapi/JsonSchema.scala index abd59326c0..d2063c2bc8 100644 --- a/zio-http/shared/src/main/scala/zio/http/endpoint/openapi/JsonSchema.scala +++ b/zio-http/shared/src/main/scala/zio/http/endpoint/openapi/JsonSchema.scala @@ -55,11 +55,11 @@ private[openapi] case class SerializableJsonSchema( if (nullable && schemaType.isDefined) copy(schemaType = Some(schemaType.get.add("null"))) else if (nullable && oneOf.isDefined) - copy(oneOf = Some(oneOf.get :+ typeNull)) + copy(oneOf = Some((oneOf.get :+ typeNull).distinct)) else if (nullable && allOf.isDefined) - SerializableJsonSchema(allOf = Some(Chunk(this, typeNull))) + SerializableJsonSchema(allOf = Some((allOf.get :+ typeNull).distinct)) else if (nullable && anyOf.isDefined) - copy(anyOf = Some(anyOf.get :+ typeNull)) + copy(anyOf = Some((anyOf.get :+ typeNull).distinct)) else if (nullable && ref.isDefined) SerializableJsonSchema(anyOf = Some(Chunk(typeNull, this))) else @@ -117,8 +117,8 @@ private[openapi] object BoolOrSchema { private[openapi] sealed trait TypeOrTypes { self => def add(value: String): TypeOrTypes = self match { - case TypeOrTypes.Type(string) => TypeOrTypes.Types(Chunk(string, value)) - case TypeOrTypes.Types(chunk) => TypeOrTypes.Types(chunk :+ value) + case TypeOrTypes.Type(string) => TypeOrTypes.Types(Chunk(string, value).distinct) + case TypeOrTypes.Types(chunk) => TypeOrTypes.Types((chunk :+ value).distinct) } } @@ -253,7 +253,7 @@ object JsonSchema { .toOption .get - private def fromSerializableSchema(schema: SerializableJsonSchema): JsonSchema = { + private[openapi] def fromSerializableSchema(schema: SerializableJsonSchema): JsonSchema = { val additionalProperties = schema.additionalProperties match { case Some(BoolOrSchema.BooleanWrapper(bool)) => Left(bool) case Some(BoolOrSchema.SchemaWrapper(schema)) => diff --git a/zio-http/shared/src/main/scala/zio/http/endpoint/openapi/OpenAPI.scala b/zio-http/shared/src/main/scala/zio/http/endpoint/openapi/OpenAPI.scala index d9e57f5ef5..2dbf6f437e 100644 --- a/zio-http/shared/src/main/scala/zio/http/endpoint/openapi/OpenAPI.scala +++ b/zio-http/shared/src/main/scala/zio/http/endpoint/openapi/OpenAPI.scala @@ -97,15 +97,48 @@ final case class OpenAPI( .groupBy(_._1) .map { case (path, pathItems) => val pathItem = pathItems.map(_._2).reduce { (i, j) => + var docI = Doc.empty + var docJ = Doc.empty + var get = i.get + var put = i.put + var post = i.post + var delete = i.delete + var options = i.options + var head = i.head + var patch = i.patch + var trace = i.trace + + if ( + get.isDefined || put.isDefined || post.isDefined || delete.isDefined || options.isDefined || head.isDefined || patch.isDefined || trace.isDefined + ) { + docI = i.description.getOrElse(Doc.empty) + } + if ( + (get.isEmpty && j.get.isDefined) || (put.isEmpty && j.put.isDefined) || (post.isEmpty && j.post.isDefined) || (delete.isEmpty && j.delete.isDefined) || (options.isEmpty && j.options.isDefined) || (head.isEmpty && j.head.isDefined) || (patch.isEmpty && j.patch.isDefined) || (trace.isEmpty && j.trace.isDefined) + ) { + docJ = j.description.getOrElse(Doc.empty) + } + get = get.orElse(j.get) + put = put.orElse(j.put) + post = post.orElse(j.post) + delete = delete.orElse(j.delete) + options = options.orElse(j.options) + head = head.orElse(j.head) + patch = patch.orElse(j.patch) + trace = trace.orElse(j.trace) + i.copy( - get = i.get.orElse(j.get), - put = i.put.orElse(j.put), - post = i.post.orElse(j.post), - delete = i.delete.orElse(j.delete), - options = i.options.orElse(j.options), - head = i.head.orElse(j.head), - patch = i.patch.orElse(j.patch), - trace = i.trace.orElse(j.trace), + get = get, + put = put, + post = post, + delete = delete, + options = options, + head = head, + patch = patch, + trace = trace, + description = Some(docI + docJ).filter(!_.isEmpty), + servers = i.servers ++ j.servers, + parameters = i.parameters ++ j.parameters, ) } (path, pathItem) diff --git a/zio-http/shared/src/main/scala/zio/http/endpoint/openapi/OpenAPIGen.scala b/zio-http/shared/src/main/scala/zio/http/endpoint/openapi/OpenAPIGen.scala index 9b12066770..64ba057807 100644 --- a/zio-http/shared/src/main/scala/zio/http/endpoint/openapi/OpenAPIGen.scala +++ b/zio-http/shared/src/main/scala/zio/http/endpoint/openapi/OpenAPIGen.scala @@ -538,8 +538,7 @@ object OpenAPIGen { val path = buildPath(endpoint.input) val method0 = method(inAtoms.method) // Endpoint has only one doc. But open api has a summery and a description - val pathItem = OpenAPI.PathItem.empty - .copy(description = Some(endpoint.documentation + endpoint.input.doc.getOrElse(Doc.empty)).filter(!_.isEmpty)) + val pathItem = OpenAPI.PathItem.empty.copy(description = Some(endpoint.documentation).filter(!_.isEmpty)) val pathItemWithOp = method0 match { case Method.OPTIONS => pathItem.addOptions(operation(endpoint)) case Method.GET => pathItem.addGet(operation(endpoint)) @@ -581,7 +580,7 @@ object OpenAPIGen { } def operation(endpoint: Endpoint[_, _, _, _, _]): OpenAPI.Operation = { - val maybeDoc = Some(endpoint.documentation + pathDoc).filter(!_.isEmpty) + val maybeDoc = Some(pathDoc).filter(!_.isEmpty) OpenAPI.Operation( tags = endpoint.tags, summary = None, @@ -635,7 +634,7 @@ object OpenAPIGen { def queryParams: Set[OpenAPI.ReferenceOr[OpenAPI.Parameter]] = { inAtoms.query.collect { - case mc @ MetaCodec(HttpCodec.Query(HttpCodec.Query.QueryType.Primitive(name, codec), _), _) => + case mc @ MetaCodec(q @ HttpCodec.Query(HttpCodec.Query.QueryType.Primitive(name, codec), _), _) => OpenAPI.ReferenceOr.Or( OpenAPI.Parameter.queryParameter( name = name, @@ -648,10 +647,10 @@ object OpenAPIGen { examples = mc.examples.map { case (name, value) => name -> OpenAPI.ReferenceOr.Or(OpenAPI.Example(value = Json.Str(value.toString))) }, - required = mc.required, + required = mc.required && !q.isOptional, ), ) :: Nil - case mc @ MetaCodec(HttpCodec.Query(record @ HttpCodec.Query.QueryType.Record(schema), _), _) => + case mc @ MetaCodec(HttpCodec.Query(record @ HttpCodec.Query.QueryType.Record(schema), _), _) => val recordSchema = (schema match { case schema if schema.isInstanceOf[Schema.Optional[_]] => schema.asInstanceOf[Schema.Optional[_]].schema case _ => schema @@ -709,7 +708,7 @@ object OpenAPIGen { examples = mc.examples.map { case (exName, value) => exName -> OpenAPI.ReferenceOr.Or(OpenAPI.Example(value = Json.Str(value.toString))) }, - required = !optional, + required = mc.required && !optional, ), ) :: Nil } @@ -740,7 +739,8 @@ object OpenAPIGen { OpenAPI.Parameter.headerParameter( name = mc.name.getOrElse(codec.name), description = mc.docsOpt, - definition = Some(OpenAPI.ReferenceOr.Or(JsonSchema.fromTextCodec(codec.textCodec))), + definition = + Some(OpenAPI.ReferenceOr.Or(JsonSchema.fromTextCodec(codec.textCodec).nullable(!mc.required))), deprecated = mc.deprecated, examples = mc.examples.map { case (name, value) => name -> OpenAPI.ReferenceOr.Or(OpenAPI.Example(codec.textCodec.encode(value).toJsonAST.toOption.get)) @@ -940,9 +940,13 @@ object OpenAPIGen { case (mediaType, values) => val combinedAtomized: AtomizedMetaCodecs = values.map(_._1).reduce(_ ++ _) val combinedContentDoc = combinedAtomized.contentDocs.toCommonMark + val vals = + if (values.forall(v => v._2.isNullable || v._2 == JsonSchema.Null)) + values.map(_._2).filter(_ != JsonSchema.Null) + else values.map(_._2) val alternativesSchema = { JsonSchema - .AnyOfSchema(values.map { case (_, schema) => + .AnyOfSchema(vals.map { schema => schema.description match { case Some(value) => schema.description(value.replace(combinedContentDoc, "")) case None => schema @@ -979,6 +983,10 @@ object OpenAPIGen { } case t: Transform[_, _, _] => nominal(t.schema, referenceType) + case Schema.Optional(inner, _) => + nominal(inner, referenceType) + case Schema.Lazy(schema0) => + nominal(schema0(), referenceType) case _ => None }