From 5553b33e32f9e187f7794276e84199c62c0b4783 Mon Sep 17 00:00:00 2001 From: Jules Ivanic Date: Sun, 15 Dec 2024 17:19:14 +1100 Subject: [PATCH 1/3] Clean `zio.http.codec.internal.AtomizedCodecs` code (#3212) * Clean `zio.http.codec.internal.AtomizedCodecs` code * fmt --------- Co-authored-by: Nabil Abdel-Hafeez <7283535+987Nabil@users.noreply.github.com> --- .../http/codec/internal/AtomizedCodecs.scala | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/zio-http/shared/src/main/scala/zio/http/codec/internal/AtomizedCodecs.scala b/zio-http/shared/src/main/scala/zio/http/codec/internal/AtomizedCodecs.scala index e8dfb1130..52cce2c84 100644 --- a/zio-http/shared/src/main/scala/zio/http/codec/internal/AtomizedCodecs.scala +++ b/zio-http/shared/src/main/scala/zio/http/codec/internal/AtomizedCodecs.scala @@ -44,28 +44,36 @@ private[http] final case class AtomizedCodecs( def makeInputsBuilder(): Mechanic.InputsBuilder = { Atomized( - Array.ofDim(method.length), - Array.ofDim(status.length), - Array.ofDim(path.length), - Array.ofDim(query.length), - Array.ofDim(header.length), - Array.ofDim(content.length), + method = Array.ofDim(method.length), + path = Array.ofDim(path.length), + query = Array.ofDim(query.length), + header = Array.ofDim(header.length), + content = Array.ofDim(content.length), + status = Array.ofDim(status.length), ) } def optimize: AtomizedCodecs = AtomizedCodecs( - method.materialize, - path.materialize, - query.materialize, - header.materialize, - content.materialize, - status.materialize, + method = method.materialize, + path = path.materialize, + query = query.materialize, + header = header.materialize, + content = content.materialize, + status = status.materialize, ) } private[http] object AtomizedCodecs { - val empty = AtomizedCodecs(Chunk.empty, Chunk.empty, Chunk.empty, Chunk.empty, Chunk.empty, Chunk.empty) + val empty: AtomizedCodecs = + AtomizedCodecs( + method = Chunk.empty, + path = Chunk.empty, + query = Chunk.empty, + header = Chunk.empty, + content = Chunk.empty, + status = Chunk.empty, + ) def flatten[R, A](in: HttpCodec[R, A]): AtomizedCodecs = { val atoms = flattenedAtoms(in) @@ -80,7 +88,7 @@ private[http] object AtomizedCodecs { private def flattenedAtoms[R, A](in: HttpCodec[R, A]): Chunk[Atom[_, _]] = in match { case Combine(left, right, _) => flattenedAtoms(left) ++ flattenedAtoms(right) - case atom: Atom[_, _] => Chunk(atom) + case atom: Atom[_, _] => Chunk.single(atom) case map: TransformOrFail[_, _, _] => flattenedAtoms(map.api) case Annotated(api, _) => flattenedAtoms(api) case Empty => Chunk.empty From 0fbf21b15eb7d4fc47bb21cb7dcda67a22981355 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20Ra=C3=BDman?= Date: Mon, 16 Dec 2024 06:24:31 +0100 Subject: [PATCH 2/3] Endpoint gen additional types (#3249) * Fixes #3246 - swapped validation annotations minLength and maxLength in code generated from OpenAPI * In the code generation from OpenAPI schema, added the handling for the string formats `date`, `date-time`, `time` and `duration` to be generated as `LocalDate`, `Instant`, `LocalTime` and `Duration`, and also to configure own formart to type mapping. --------- Co-authored-by: Nabil Abdel-Hafeez <7283535+987Nabil@users.noreply.github.com> --- .../scala/zio/http/gen/openapi/Config.scala | 7 +- .../zio/http/gen/openapi/EndpointGen.scala | 26 +++++ .../main/scala/zio/http/gen/scala/Code.scala | 30 ++++-- .../scala/zio/http/gen/scala/CodeGen.scala | 61 ++++++----- ...equestResponseBodyInlineMinMaxLength.scala | 41 +++++++ .../src/test/resources/ValidatedData.scala | 2 +- .../resources/inline_schema_minmaxlength.json | 101 ++++++++++++++++++ .../zio/http/gen/scala/CodeGenSpec.scala | 10 ++ .../http/endpoint/openapi/JsonSchema.scala | 2 +- 9 files changed, 242 insertions(+), 38 deletions(-) create mode 100644 zio-http-gen/src/test/resources/EndpointWithRequestResponseBodyInlineMinMaxLength.scala create mode 100644 zio-http-gen/src/test/resources/inline_schema_minmaxlength.json diff --git a/zio-http-gen/src/main/scala/zio/http/gen/openapi/Config.scala b/zio-http-gen/src/main/scala/zio/http/gen/openapi/Config.scala index e4d90db78..f9e62eda0 100644 --- a/zio-http-gen/src/main/scala/zio/http/gen/openapi/Config.scala +++ b/zio-http-gen/src/main/scala/zio/http/gen/openapi/Config.scala @@ -33,6 +33,7 @@ final case class Config( commonFieldsOnSuperType: Boolean, generateSafeTypeAliases: Boolean, fieldNamesNormalization: NormalizeFields, + stringFormatTypes: Map[String, String], ) object Config { @@ -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] } diff --git a/zio-http-gen/src/main/scala/zio/http/gen/openapi/EndpointGen.scala b/zio-http-gen/src/main/scala/zio/http/gen/openapi/EndpointGen.scala index f92bdb144..d1cb7a85a 100644 --- a/zio-http-gen/src/main/scala/zio/http/gen/openapi/EndpointGen.scala +++ b/zio-http-gen/src/main/scala/zio/http/gen/openapi/EndpointGen.scala @@ -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 => @@ -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(_, _, _, _) => @@ -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)) diff --git a/zio-http-gen/src/main/scala/zio/http/gen/scala/Code.scala b/zio-http-gen/src/main/scala/zio/http/gen/scala/Code.scala index 1777c4b44..aa8827d8e 100644 --- a/zio-http-gen/src/main/scala/zio/http/gen/scala/Code.scala +++ b/zio-http-gen/src/main/scala/zio/http/gen/scala/Code.scala @@ -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( @@ -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) diff --git a/zio-http-gen/src/main/scala/zio/http/gen/scala/CodeGen.scala b/zio-http-gen/src/main/scala/zio/http/gen/scala/CodeGen.scala index 4a797b83f..6b582ca7d 100644 --- a/zio-http-gen/src/main/scala/zio/http/gen/scala/CodeGen.scala +++ b/zio-http-gen/src/main/scala/zio/http/gen/scala/CodeGen.scala @@ -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 = @@ -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 @@ -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) @@ -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") diff --git a/zio-http-gen/src/test/resources/EndpointWithRequestResponseBodyInlineMinMaxLength.scala b/zio-http-gen/src/test/resources/EndpointWithRequestResponseBodyInlineMinMaxLength.scala new file mode 100644 index 000000000..2d69fa2a4 --- /dev/null +++ b/zio-http-gen/src/test/resources/EndpointWithRequestResponseBodyInlineMinMaxLength.scala @@ -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] + } + } +} diff --git a/zio-http-gen/src/test/resources/ValidatedData.scala b/zio-http-gen/src/test/resources/ValidatedData.scala index 99a34b258..95d28f3e2 100644 --- a/zio-http-gen/src/test/resources/ValidatedData.scala +++ b/zio-http-gen/src/test/resources/ValidatedData.scala @@ -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 { diff --git a/zio-http-gen/src/test/resources/inline_schema_minmaxlength.json b/zio-http-gen/src/test/resources/inline_schema_minmaxlength.json new file mode 100644 index 000000000..9f5970818 --- /dev/null +++ b/zio-http-gen/src/test/resources/inline_schema_minmaxlength.json @@ -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 + } + } + } +} 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 16ba55d5d..fe0aa3f37 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 @@ -198,6 +198,16 @@ object CodeGenSpec extends ZIOSpecDefault { } } } @@ TestAspect.exceptScala3, // for some reason, the temp dir is empty in Scala 3 + test("OpenAPI spec with inline schema request and response body with minLength and maxLength") { + val openAPIString = stringFromResource("/inline_schema_minmaxlength.json") + + openApiFromJsonString(openAPIString) { openAPI => + codeGenFromOpenAPI(openAPI) { testDir => + fileShouldBe(testDir, "api/v1/Entries.scala", "/EndpointWithRequestResponseBodyInlineMinMaxLength.scala") + } + } + } @@ TestAspect.exceptScala3, // for some reason, the temp dir is empty in Scala 3 + test("OpenAPI spec with inline schema request and response body, with nested object schema") { val openAPIString = stringFromResource("/inline_schema_nested.json") 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 0dc9d674c..3ccac01aa 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 @@ -293,8 +293,8 @@ object JsonSchema { JsonSchema.String( schema.format.map(StringFormat.fromString), schema.pattern.map(Pattern.apply), - schema.minLength, schema.maxLength, + schema.minLength, ) case schema if schema.schemaType.contains(TypeOrTypes.Type("boolean")) => JsonSchema.Boolean From b643b2f26314cd48fbca91591714b30c42eff492 Mon Sep 17 00:00:00 2001 From: Milad Khajavi Date: Mon, 16 Dec 2024 08:59:17 +0330 Subject: [PATCH 3/3] SBT and SIGINT Signal Using CTL+C (#3250) * graceful shutdown and sbt. * title --------- Co-authored-by: Nabil Abdel-Hafeez <7283535+987Nabil@users.noreply.github.com> --- docs/reference/server.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/reference/server.md b/docs/reference/server.md index e65abc445..61b619b64 100644 --- a/docs/reference/server.md +++ b/docs/reference/server.md @@ -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. ::: @@ -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.