Skip to content

Commit

Permalink
Merge branch 'main' into optimise/handlers
Browse files Browse the repository at this point in the history
  • Loading branch information
guizmaii authored Dec 16, 2024
2 parents f284275 + b643b2f commit e453ac9
Show file tree
Hide file tree
Showing 11 changed files with 271 additions and 53 deletions.
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
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
61 changes: 37 additions & 24 deletions zio-http-gen/src/main/scala/zio/http/gen/scala/CodeGen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import java.nio.charset.StandardCharsets
import java.nio.file.StandardOpenOption._
import java.nio.file._

import scala.util.matching.Regex

object CodeGen {

private val EndpointImports =
Expand Down Expand Up @@ -230,18 +232,21 @@ object CodeGen {
val multipleAnnotationsAboveContent = if (annotationValues.size > 1) "\n" + content else content
allImports -> annotationValues.mkString("", "\n", multipleAnnotationsAboveContent)

case Code.Primitive.ScalaBoolean => Nil -> "Boolean"
case Code.Primitive.ScalaByte => Nil -> "Byte"
case Code.Primitive.ScalaChar => Nil -> "Char"
case Code.Primitive.ScalaDouble => Nil -> "Double"
case Code.Primitive.ScalaFloat => Nil -> "Float"
case Code.Primitive.ScalaInt => Nil -> "Int"
case Code.Primitive.ScalaLong => Nil -> "Long"
case Code.Primitive.ScalaShort => Nil -> "Short"
case Code.Primitive.ScalaString => Nil -> "String"
case Code.Primitive.ScalaUnit => Nil -> "Unit"
case Code.Primitive.ScalaUUID => List(Code.Import("java.util.UUID")) -> "UUID"
case Code.ScalaType.Inferred => Nil -> ""
case Code.Primitive.ScalaBoolean => Nil -> "Boolean"
case Code.Primitive.ScalaByte => Nil -> "Byte"
case Code.Primitive.ScalaChar => Nil -> "Char"
case Code.Primitive.ScalaDouble => Nil -> "Double"
case Code.Primitive.ScalaFloat => Nil -> "Float"
case Code.Primitive.ScalaInt => Nil -> "Int"
case Code.Primitive.ScalaLong => Nil -> "Long"
case Code.Primitive.ScalaShort => Nil -> "Short"
case Code.Primitive.ScalaString => Nil -> "String"
case Code.Primitive.ScalaUnit => Nil -> "Unit"
case Code.Primitive.ScalaUUID => List(Code.Import("java.util.UUID")) -> "UUID"
case Code.Primitive.ScalaLocalDate => List(Code.Import("java.time.LocalDate")) -> "LocalDate"
case Code.Primitive.ScalaInstant => List(Code.Import("java.time.Instant")) -> "Instant"
case Code.Primitive.ScalaTime => List(Code.Import("java.time.LocalTime")) -> "LocalTime"
case Code.ScalaType.Inferred => Nil -> ""

case Code.EndpointCode(method, pathPatternCode, queryParamsCode, headersCode, inCode, outCodes, errorsCode) =>
val (queryImports, queryContent) = queryParamsCode.map(renderQueryCode).unzip
Expand All @@ -266,12 +271,16 @@ object CodeGen {

def renderSegmentType(name: String, segmentType: Code.CodecType): (String, List[Code.Import]) =
segmentType match {
case Code.CodecType.Boolean => s"""bool("$name")""" -> Nil
case Code.CodecType.Int => s"""int("$name")""" -> Nil
case Code.CodecType.Long => s"""long("$name")""" -> Nil
case Code.CodecType.String => s"""string("$name")""" -> Nil
case Code.CodecType.UUID => s"""uuid("$name")""" -> Nil
case Code.CodecType.Literal => s""""$name"""" -> Nil
case Code.CodecType.Boolean => s"""bool("$name")""" -> Nil
case Code.CodecType.Int => s"""int("$name")""" -> Nil
case Code.CodecType.Long => s"""long("$name")""" -> Nil
case Code.CodecType.String => s"""string("$name")""" -> Nil
case Code.CodecType.UUID => s"""uuid("$name")""" -> Nil
case Code.CodecType.LocalDate => s"""date("$name")""" -> Nil
case Code.CodecType.LocalTime => s"""time("$name")""" -> Nil
case Code.CodecType.Instant => s"""date-time("$name")""" -> Nil
case Code.CodecType.Duration => s"""duration("$name")""" -> Nil
case Code.CodecType.Literal => s""""$name"""" -> Nil
case Code.CodecType.Aliased(underlying, newtypeName) =>
val sb = new StringBuilder()
val (code, imports) = renderSegmentType(name, underlying)
Expand Down Expand Up @@ -379,12 +388,16 @@ object CodeGen {
def renderQueryCode(queryCode: Code.QueryParamCode): (List[Code.Import], String) = queryCode match {
case Code.QueryParamCode(name, queryType) =>
val (imports, tpe) = queryType match {
case Code.CodecType.Boolean => Nil -> "Boolean"
case Code.CodecType.Int => Nil -> "Int"
case Code.CodecType.Long => Nil -> "Long"
case Code.CodecType.String => Nil -> "String"
case Code.CodecType.UUID => List(Code.Import("java.util.UUID")) -> "UUID"
case Code.CodecType.Literal => throw new Exception("Literal query params are not supported")
case Code.CodecType.Boolean => Nil -> "Boolean"
case Code.CodecType.Int => Nil -> "Int"
case Code.CodecType.Long => Nil -> "Long"
case Code.CodecType.String => Nil -> "String"
case Code.CodecType.UUID => List(Code.Import("java.util.UUID")) -> "UUID"
case Code.CodecType.LocalDate => List(Code.Import("java.time.LocalDate")) -> "LocalDate"
case Code.CodecType.LocalTime => List(Code.Import("java.time.LocalTime")) -> "LocalTime"
case Code.CodecType.Instant => List(Code.Import("java.time.Instant")) -> "Instant"
case Code.CodecType.Duration => List(Code.Import("java.time.Duration")) -> "Duration"
case Code.CodecType.Literal => throw new Exception("Literal query params are not supported")
case Code.CodecType.Aliased(underlying, newtypeName) =>
val (imports, _) = renderQueryCode(Code.QueryParamCode(name, underlying))
(Code.Import.FromBase(s"components.$newtypeName") :: imports) -> (newtypeName + ".Type")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package test.api.v1

import test.component._
import zio.schema._

object Entries {
import zio.http._
import zio.http.endpoint._
import zio.http.codec._
val post = Endpoint(Method.POST / "api" / "v1" / "entries")
.in[POST.RequestBody]
.out[POST.ResponseBody](status = Status.Ok)

object POST {
import zio.schema.annotation.validate
import zio.schema.validation.Validation
import java.util.UUID
import java.time.Instant
import java.time.LocalTime
import java.time.LocalDate

case class RequestBody(
id: Int,
@validate[String](Validation.maxLength(255) && Validation.minLength(1)) name: String,
)
object RequestBody {
implicit val codec: Schema[RequestBody] = DeriveSchema.gen[RequestBody]
}
case class ResponseBody(
@validate[String](Validation.maxLength(255) && Validation.minLength(1)) name: String,
uuid: Option[UUID],
deadline: Option[Instant],
id: Int,
time: Option[LocalTime],
day: LocalDate,
)
object ResponseBody {
implicit val codec: Schema[ResponseBody] = DeriveSchema.gen[ResponseBody]
}
}
}
2 changes: 1 addition & 1 deletion zio-http-gen/src/test/resources/ValidatedData.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import zio.schema.annotation.validate
import zio.schema.validation.Validation

case class ValidatedData(
@validate[String](Validation.minLength(10)) name: String,
@validate[String](Validation.maxLength(10)) name: String,
@validate[Int](Validation.greaterThan(0) && Validation.lessThan(100)) age: Int,
)
object ValidatedData {
Expand Down
101 changes: 101 additions & 0 deletions zio-http-gen/src/test/resources/inline_schema_minmaxlength.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
{
"openapi" : "3.1.0",
"info" : {
"title" : "",
"version" : ""
},
"paths" : {
"/api/v1/entries" : {
"post" : {
"requestBody" :
{
"content" : {
"application/json" : {
"schema" :
{
"type" :
"object",
"properties" : {
"id" : {
"type" :
"integer",
"format" : "int32"
},
"name" : {
"type" :
"string",
"minLength" : 1,
"maxLength" : 255
}
},
"additionalProperties" :
true,
"required" : [
"id",
"name"
]
}

}
},
"required" : true
},
"responses" : {
"200" :
{
"description" : "",
"content" : {
"application/json" : {
"schema" :
{
"type" :
"object",
"properties" : {
"id" : {
"type" :
"integer",
"format" : "int32"
},
"name" : {
"type" :
"string",
"minLength" : 1,
"maxLength" : 255
},
"day" : {
"type" :
"string",
"format": "date"
},
"deadline": {
"type": "string",
"format": "date-time"
},
"time": {
"type": "string",
"format": "time"
},
"uuid" : {
"type" :
"string",
"format" : "uuid"
}
},
"additionalProperties" :
true,
"required" : [
"id",
"name",
"day"
]
}

}
}
}
},
"deprecated" : false
}
}
}
}
Loading

2 comments on commit e453ac9

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 : Performance Benchmarks (PlainTextBenchmarkServer)

concurrency: 256
requests/sec: 367625

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 : Performance Benchmarks (SimpleEffectBenchmarkServer)

concurrency: 256
requests/sec: 370952

Please sign in to comment.