Skip to content

Commit

Permalink
Merge branch 'main' into deprecate-3.0
Browse files Browse the repository at this point in the history
  • Loading branch information
987Nabil authored Dec 24, 2024
2 parents 2482a0a + 267c6ce commit 759cdec
Show file tree
Hide file tree
Showing 30 changed files with 947 additions and 86 deletions.
3 changes: 2 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,8 @@ lazy val zioHttpGen = (project in file("zio-http-gen"))
scalafmt.cross(CrossVersion.for3Use2_13),
scalametaParsers
.cross(CrossVersion.for3Use2_13)
.exclude("org.scala-lang.modules", "scala-collection-compat_2.13"),
.exclude("org.scala-lang.modules", "scala-collection-compat_2.13")
.exclude("com.lihaoyi", "sourcecode_2.13"),
`zio-json-yaml` % Test,
),
)
Expand Down
36 changes: 36 additions & 0 deletions docs/examples/endpoint-scala3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
id: endpoint-scala3
title: "Endpoint Scala 3 Syntax"
sidebar_label: "Endpoint Scala 3 Syntax"
---

```scala
import zio.http.*
import zio.http.codec.*
import zio.http.endpoint.*

import java.util.UUID

type NotFound[EntityId] = EntityId
type EntityId = UUID

val union: ContentCodec[String | UUID | Boolean] =
HttpCodec.content[String] || HttpCodec.content[UUID] || HttpCodec.content[Boolean]

val unionEndpoint =
Endpoint(Method.GET / "api" / "complex-union")
.outCodec(union)

val unionWithErrorEndpoint
: Endpoint[Unit, Unit, NotFound[EntityId] | String, UUID | Unit, AuthType.None] =
Endpoint(Method.GET / "api" / "union-with-error")
.out[UUID]
.orOut[Unit](Status.NoContent)
.outError[NotFound[EntityId]](Status.NotFound)
.orOutError[String](Status.BadRequest)

val impl = unionWithErrorEndpoint.implementEither { _ =>
val result: Either[NotFound[EntityId] | String, UUID | Unit] = Left("error")
result
}
```
33 changes: 33 additions & 0 deletions docs/reference/endpoint.md
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,15 @@ object EndpointWithMultipleOutputTypes extends ZIOAppDefault {
}
```

For Scala 3, we can use a union type instead of an `Either` by calling `Endpoint#orOut` for more than one output:

```scala
val endpoint: Endpoint[Unit, Unit, ZNothing, Course | Quiz, AuthType.None] =
Endpoint(RoutePattern.GET / "resources")
.out[Course]
.orOut[Quiz]
```

In the above example, we defined an endpoint that describes a path parameter `id` as input and returns either a `Book` or an `Article` as output.

With multiple outputs, we can define if all of them or just some should add an output header, by the order of calling `out` and `outHeader` methods:
Expand Down Expand Up @@ -472,6 +481,30 @@ utils.printSource("zio-http-example/src/main/scala/example/endpoint/EndpointWith
```
</details>

### Multiple Failure Outputs Using Union Types

The `Endpoint#orOutError` method can be used to describe multiple failure outputs using union types:

```scala
import zio.schema.DeriveSchema

case class Book(title: String, authors: List[String])
implicit val bookSchema = DeriveSchema.gen[Book]

case class BookNotFound(message: String, bookId: Int)
case class AuthenticationError(message: String, userId: Int)

implicit val notFoundSchema = DeriveSchema.gen[BookNotFound]
implicit val authSchema = DeriveSchema.gen[AuthenticationError]

val endpoint: Endpoint[Int, (Int, Header.Authorization), BookNotFound | AuthenticationError, Book, AuthType.None] =
Endpoint(RoutePattern.GET / "books" / PathCodec.int("id"))
.header(HeaderCodec.authorization)
.out[Book]
.outError[BookNotFound](Status.NotFound)
.orOutError[AuthenticationError](Status.Unauthorized)
```

## Transforming Endpoint Input/Output and Error Types

To transform the input, output, and error types of an endpoint, we can use the `Endpoint#transformIn`, `Endpoint#transformOut`, and `Endpoint#transformError` methods, respectively. Let's see an example:
Expand Down
13 changes: 11 additions & 2 deletions docs/reference/http-codec.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ There is also a `|` operator that allows us to create a codec that can decode ei
```scala mdoc:silent
import zio.http.codec._

val eitherQueryCodec: QueryCodec[String] = HttpCodec.query[String]("q") | HttpCodec.query[String]("query")
val eitherQueryCodec: QueryCodec[Either[Boolean, String]] = HttpCodec.query[Boolean]("q") | HttpCodec.query[String]("query")
```

Assume we have a request
Expand All @@ -229,7 +229,16 @@ We can decode the query parameter using the `decodeRequest` method:
```scala mdoc:silent
import zio._

val result: Task[String] = eitherQueryCodec.decodeRequest(request)
val result: Task[Either[Boolean, String]] = eitherQueryCodec.decodeRequest(request)
```

#### Scala 3 Union Type Syntax
For Scala 3 the `||` operator is available will return a union type instead of an `Either`.

```scala
import zio.http.codec._

val unionQueryCodec: QueryCodec[Boolean | String] = HttpCodec.query[Boolean]("q") || HttpCodec.query[String]("query")
```

```scala mdoc:invisible:reset
Expand Down
8 changes: 7 additions & 1 deletion docs/reference/server.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Server.serve(routes).provide(
)
```

:::note
:::note[Handling Interrupt Signals (Ctrl+C) in SBT]
Sometimes we may want to have more control over installation of the http application into the server. In such cases, we may want to use the `Server.install` method. This method only installs the `Routes` into the server, and the lifecycle of the server can be managed separately.
:::

Expand Down Expand Up @@ -566,6 +566,12 @@ printSource("zio-http-example/src/main/scala/example/GracefulShutdown.scala")

This approach ensures that clients receive appropriate responses for their requests, rather than encountering errors or abrupt disconnections. It helps maintain the integrity of the communication between clients and the server, providing a smoother experience for users and preventing potential data loss or corruption.

:::Note
When running a server through SBT, pressing Ctrl+C doesn't cleanly shut down the application. Instead, SBT intercepts the signal and throws a `java.lang.InterruptedException`, bypassing any custom shutdown handlers you may have implemented.

However, if you run the same server directly from a packaged JAR file using `java -jar`, Ctrl+C will trigger the expected graceful shutdown sequence, allowing your application to clean up resources properly before terminating.
:::

## Idle Timeout Configuration

The idle timeout is a mechanism by which the server automatically terminates an inactive connection after a certain period of inactivity. When a client connects to the server, it establishes a connection to request and receive responses. However, there may be instances where the client becomes slow, inactive, or unresponsive, and the server needs to reclaim resources associated with idle connections to optimize server performance and resource utilization.
Expand Down
1 change: 1 addition & 0 deletions docs/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ const sidebars = {
"examples/websocket",
"examples/streaming",
"examples/endpoint",
"examples/endpoint-scala3",
"examples/middleware-cors-handling",
"examples/authentication",
"examples/graceful-shutdown",
Expand Down
2 changes: 1 addition & 1 deletion project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import sbt.*

object Dependencies {
val JwtCoreVersion = "10.0.1"
val NettyVersion = "4.1.112.Final"
val NettyVersion = "4.1.116.Final"
val NettyIncubatorVersion = "0.0.25.Final"
val ScalaCompactCollectionVersion = "2.12.0"
val ZioVersion = "2.1.11"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ final case class Config(
commonFieldsOnSuperType: Boolean,
generateSafeTypeAliases: Boolean,
fieldNamesNormalization: NormalizeFields,
stringFormatTypes: Map[String, String],
)
object Config {

Expand Down Expand Up @@ -73,11 +74,15 @@ object Config {
enableAutomatic = false,
manualOverrides = Map.empty,
),
stringFormatTypes = Map.empty,
)

def config: zio.Config[Config] = (
zio.Config.boolean("common-fields-on-super-type").withDefault(Config.default.commonFieldsOnSuperType) ++
zio.Config.boolean("generate-safe-type-aliases").withDefault(Config.default.generateSafeTypeAliases) ++
NormalizeFields.config.nested("fields-normalization")
NormalizeFields.config.nested("fields-normalization") ++ zio.Config.table(
"string-format-types",
zio.Config.string,
)
).to[Config]
}
26 changes: 26 additions & 0 deletions zio-http-gen/src/main/scala/zio/http/gen/openapi/EndpointGen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,14 @@ final case class EndpointGen(config: Config) {
Code.PathSegmentCode(name = name, segmentType = Code.CodecType.Long)
case JsonSchema.String(Some(JsonSchema.StringFormat.UUID), _, _, _) =>
Code.PathSegmentCode(name = name, segmentType = Code.CodecType.UUID)
case JsonSchema.String(Some(JsonSchema.StringFormat.Date), _, _, _) =>
Code.PathSegmentCode(name = name, segmentType = Code.CodecType.LocalDate)
case JsonSchema.String(Some(JsonSchema.StringFormat.DateTime), _, _, _) =>
Code.PathSegmentCode(name = name, segmentType = Code.CodecType.Instant)
case JsonSchema.String(Some(JsonSchema.StringFormat.Time), _, _, _) =>
Code.PathSegmentCode(name = name, segmentType = Code.CodecType.LocalTime)
case JsonSchema.String(Some(JsonSchema.StringFormat.Duration), _, _, _) =>
Code.PathSegmentCode(name = name, segmentType = Code.CodecType.Duration)
case JsonSchema.String(_, _, _, _) =>
Code.PathSegmentCode(name = name, segmentType = Code.CodecType.String)
case JsonSchema.Boolean =>
Expand Down Expand Up @@ -719,6 +727,14 @@ final case class EndpointGen(config: Config) {
Code.QueryParamCode(name = name, queryType = Code.CodecType.Long)
case JsonSchema.Integer(JsonSchema.IntegerFormat.Timestamp, _, _, _, _, _) =>
Code.QueryParamCode(name = name, queryType = Code.CodecType.Long)
case JsonSchema.String(Some(JsonSchema.StringFormat.Date), _, _, _) =>
Code.QueryParamCode(name = name, queryType = Code.CodecType.LocalDate)
case JsonSchema.String(Some(JsonSchema.StringFormat.DateTime), _, _, _) =>
Code.QueryParamCode(name = name, queryType = Code.CodecType.Instant)
case JsonSchema.String(Some(JsonSchema.StringFormat.Duration), _, _, _) =>
Code.QueryParamCode(name = name, queryType = Code.CodecType.Duration)
case JsonSchema.String(Some(JsonSchema.StringFormat.Time), _, _, _) =>
Code.QueryParamCode(name = name, queryType = Code.CodecType.LocalTime)
case JsonSchema.String(Some(JsonSchema.StringFormat.UUID), _, _, _) =>
Code.QueryParamCode(name = name, queryType = Code.CodecType.UUID)
case JsonSchema.String(_, _, _, _) =>
Expand Down Expand Up @@ -1237,9 +1253,19 @@ final case class EndpointGen(config: Config) {
val annotations = addNumericValidations[Long](exclusiveMin, exclusiveMax)
Some(Code.Field(name, Code.Primitive.ScalaLong, annotations, config.fieldNamesNormalization))

case JsonSchema.String(Some(format), _, _, _) if config.stringFormatTypes.contains(format.value) =>
Some(Code.Field(name, Code.TypeRef(config.stringFormatTypes(format.value)), config.fieldNamesNormalization))
case JsonSchema.String(Some(JsonSchema.StringFormat.UUID), _, maxLength, minLength) =>
val annotations = addStringValidations(minLength, maxLength)
Some(Code.Field(name, Code.Primitive.ScalaUUID, annotations, config.fieldNamesNormalization))
case JsonSchema.String(Some(JsonSchema.StringFormat.Date), _, _, _) =>
Some(Code.Field(name, Code.Primitive.ScalaLocalDate, config.fieldNamesNormalization))
case JsonSchema.String(Some(JsonSchema.StringFormat.DateTime), _, _, _) =>
Some(Code.Field(name, Code.Primitive.ScalaInstant, config.fieldNamesNormalization))
case JsonSchema.String(Some(JsonSchema.StringFormat.Time), _, _, _) =>
Some(Code.Field(name, Code.Primitive.ScalaTime, config.fieldNamesNormalization))
case JsonSchema.String(Some(JsonSchema.StringFormat.Duration), _, _, _) =>
Some(Code.Field(name, Code.Primitive.ScalaDuration, config.fieldNamesNormalization))
case JsonSchema.String(_, _, maxLength, minLength) =>
val annotations = addStringValidations(minLength, maxLength)
Some(Code.Field(name, Code.Primitive.ScalaString, annotations, config.fieldNamesNormalization))
Expand Down
30 changes: 19 additions & 11 deletions zio-http-gen/src/main/scala/zio/http/gen/scala/Code.scala
Original file line number Diff line number Diff line change
Expand Up @@ -217,17 +217,21 @@ object Code {
sealed trait Primitive extends ScalaType

object Primitive {
case object ScalaInt extends Primitive
case object ScalaLong extends Primitive
case object ScalaDouble extends Primitive
case object ScalaFloat extends Primitive
case object ScalaChar extends Primitive
case object ScalaByte extends Primitive
case object ScalaShort extends Primitive
case object ScalaBoolean extends Primitive
case object ScalaUnit extends Primitive
case object ScalaUUID extends Primitive
case object ScalaString extends Primitive
case object ScalaInt extends Primitive
case object ScalaLong extends Primitive
case object ScalaDouble extends Primitive
case object ScalaFloat extends Primitive
case object ScalaChar extends Primitive
case object ScalaByte extends Primitive
case object ScalaShort extends Primitive
case object ScalaBoolean extends Primitive
case object ScalaUnit extends Primitive
case object ScalaUUID extends Primitive
case object ScalaLocalDate extends Primitive
case object ScalaInstant extends Primitive
case object ScalaTime extends Primitive
case object ScalaDuration extends Primitive
case object ScalaString extends Primitive
}

final case class EndpointCode(
Expand All @@ -253,6 +257,10 @@ object Code {
case object Long extends CodecType
case object String extends CodecType
case object UUID extends CodecType
case object LocalDate extends CodecType
case object LocalTime extends CodecType
case object Duration extends CodecType
case object Instant extends CodecType
case class Aliased(underlying: CodecType, newtypeName: String) extends CodecType
}
final case class QueryParamCode(name: String, queryType: CodecType)
Expand Down
Loading

0 comments on commit 759cdec

Please sign in to comment.