Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add initial changes (:_:) #11

Open
wants to merge 36 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
4ba8c92
add initial changes (:_:)
varshith257 Sep 20, 2024
2614674
update tests (:_:)
varshith257 Sep 20, 2024
51bcdde
fix (:_:)
varshith257 Sep 20, 2024
bd9856b
fix (:_:)
varshith257 Sep 20, 2024
16f36e3
fix (:_:)
varshith257 Sep 20, 2024
f0756c4
fix (:_:)
varshith257 Sep 20, 2024
de20440
fix (:_:)
varshith257 Sep 20, 2024
a5c7be1
fix (:_:)
varshith257 Sep 20, 2024
a3ca6e9
fix (:_:)
varshith257 Sep 20, 2024
12ca858
fix (:_:)
varshith257 Sep 20, 2024
6d25569
fix (:_:)
varshith257 Sep 20, 2024
1873927
fix (:_:)
varshith257 Sep 20, 2024
86e3351
fix (:_:)
varshith257 Sep 20, 2024
f98708e
fix (:_:)
varshith257 Sep 20, 2024
313d628
fix (:_:)
varshith257 Sep 20, 2024
da8b717
fix (:_:)
varshith257 Sep 20, 2024
6f19acf
fix (:_:)
varshith257 Sep 20, 2024
33555d6
fmt
varshith257 Sep 20, 2024
aac6c2f
fmt
varshith257 Sep 20, 2024
75ce05d
fmt
varshith257 Sep 21, 2024
a67db91
Update TestServer.scala
varshith257 Sep 21, 2024
d704efb
fmt
varshith257 Sep 21, 2024
8672f3f
fix (:_:)
varshith257 Sep 21, 2024
541fcf8
fix (:_:)
varshith257 Sep 21, 2024
a6568e8
fix (:_:)
varshith257 Sep 21, 2024
6fcb65b
fmt
varshith257 Sep 21, 2024
ff7a55b
fix (:_:)
varshith257 Sep 21, 2024
0c0e241
fmt
varshith257 Sep 21, 2024
3850e98
fmt
varshith257 Sep 21, 2024
0c155b3
fix (:_:)
varshith257 Sep 21, 2024
9ce6375
fmt
varshith257 Sep 21, 2024
4fd067a
fmt
varshith257 Sep 21, 2024
6bfac30
fmt
varshith257 Sep 21, 2024
ff0c11e
Update TestServer.scala
varshith257 Sep 23, 2024
1e3d0e6
Update NettyDriver.scala
varshith257 Sep 23, 2024
cbecc83
Update NettyDriver.scala
varshith257 Sep 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions zio-http-testkit/src/test/scala/zio/http/TestServerSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,42 @@ object TestServerSpec extends ZIOHttpSpec {
TestServer.layer,
Scope.default,
),
suite("Environment updates")(
test("should use updated environment for each request") {
check(Gen.fromIterable(List(1, 2, 3, 4, 5))) { code =>
for {
client <- ZIO.service[Client]
server <- ZIO.service[TestServer]
port <- server.port
url = URL.root.port(port) / "api"
request = Request
.get(url)
.addHeader(Header.Accept(MediaType.application.json))
_ <- TestServer
.addRoutes(
Routes(
Method.GET / "api" -> handler(ZIO.serviceWith[TestEnv](env => Response.text(env.code.toString))),
),
)
.provideSomeLayer[TestServer](ZLayer.succeed(TestEnv(code)))
response <- client.request(request)
body <- response.body.asString
} yield assertTrue(body == code.toString)
}
},
).provideSome[Scope](
ZLayer.succeed(Server.Config.default.onAnyOpenPort),
TestServer.layer,
Client.default,
NettyDriver.customized,
ZLayer.succeed(NettyConfig.defaultWithFastShutdown),
),
).provide(
ZLayer.succeed(Server.Config.default.onAnyOpenPort),
Client.default,
NettyDriver.customized,
ZLayer.succeed(NettyConfig.defaultWithFastShutdown),
Scope.default,
)

private def requestToCorrectPort =
Expand All @@ -120,4 +151,5 @@ object TestServerSpec extends ZIOHttpSpec {
.get(url = URL.root.port(port))
.addHeaders(Headers(Header.Accept(MediaType.text.`plain`)))

case class TestEnv(code: Int)
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ private[zio] final case class NettyDriver(
while (loop) {
val oldAppAndRt = appRef.get()
val (oldApp, oldRt) = oldAppAndRt
val updatedApp = (oldApp ++ newApp).asInstanceOf[Routes[Any, Response]]
val updatedApp = (newApp ++ oldApp).asInstanceOf[Routes[Any, Response]]
val updatedEnv = oldRt.environment.unionAll(env)
// Update the fiberRefs with the new environment to avoid doing this every time we run / fork a fiber
val updatedFibRefs = oldRt.fiberRefs.updatedAs(fiberId)(FiberRef.currentEnvironment, updatedEnv)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,14 @@ object HttpCodecSpec extends ZIOHttpSpec {
for {
result <- optional.decodeRequest(request).exit
} yield assertTrue(result.isFailure)
// } +
// test("fallback for empty body") {
// val codec = HttpCodec.content[String].optional
// val requestWithEmptyBody = Request.post(url = URL.root, body = Body.empty)

// for {
// result <- codec.decodeRequest(requestWithEmptyBody)
// } yield assertTrue(result.isEmpty)
}
} +
suite("HeaderCodec") {
Expand Down
51 changes: 42 additions & 9 deletions zio-http/shared/src/main/scala/zio/http/codec/HttpCodec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,22 @@ sealed trait HttpCodec[-AtomTypes, Value] {
def named(named: Metadata.Named[Value]): HttpCodec[AtomTypes, Value] =
HttpCodec.Annotated(self, Metadata.Named(named.name))

def optionalBody[A](implicit schema: Schema[A]): HttpCodec[HttpCodecType.Content, Option[A]] =
Annotated(
HttpCodec
.Fallback(
ContentCodec.content[A],
HttpCodec.empty.asInstanceOf[HttpCodec[HttpCodecType.Content, Option[A]]],
Alternator.either,
HttpCodec.Fallback.Condition.isBodyEmptyOrMissing,
)
.transform[Option[A]](either => either.fold(Some(_), _ => None)) {
case Some(value) => Right(Some(value))
case None => Right(None)
},
Metadata.Optional(),
)

/**
* Returns a new codec, where the value produced by this one is optional.
*/
Expand All @@ -275,7 +291,12 @@ sealed trait HttpCodec[-AtomTypes, Value] {
if (self eq HttpCodec.Halt) HttpCodec.empty.asInstanceOf[HttpCodec[AtomTypes, Option[Value]]]
else {
HttpCodec
.Fallback(self, HttpCodec.empty, Alternator.either, HttpCodec.Fallback.Condition.isMissingDataOnly)
.Fallback(
self,
HttpCodec.empty,
Alternator.either,
HttpCodec.Fallback.Condition.isMissingDataOnly.combine(HttpCodec.Fallback.Condition.isBodyEmptyOrMissing),
)
.transform[Option[Value]](either => either.fold(Some(_), _ => None))(_.toLeft(()))
},
Metadata.Optional(),
Expand Down Expand Up @@ -826,16 +847,20 @@ object HttpCodec extends ContentCodecs with HeaderCodecs with MethodCodecs with
* recover from `MissingHeader` or `MissingQueryParam` errors.
*/
sealed trait Condition { self =>
def apply(cause: Cause[Any]): Boolean =
def apply(cause: Cause[Any]): Boolean =
self match {
case Condition.IsHttpCodecError => HttpCodecError.isHttpCodecError(cause)
case Condition.isMissingDataOnly => HttpCodecError.isMissingDataOnly(cause)
case Condition.IsHttpCodecError => HttpCodecError.isHttpCodecError(cause)
case Condition.isMissingDataOnly => HttpCodecError.isMissingDataOnly(cause)
case Condition.isBodyEmptyOrMissing => HttpCodecError.isMissingBodyOrEmpty(cause) // New condition

}
def combine(that: Condition): Condition =
(self, that) match {
case (Condition.isMissingDataOnly, _) => Condition.isMissingDataOnly
case (_, Condition.isMissingDataOnly) => Condition.isMissingDataOnly
case _ => Condition.IsHttpCodecError
case (Condition.isMissingDataOnly, _) => Condition.isMissingDataOnly
case (_, Condition.isMissingDataOnly) => Condition.isMissingDataOnly
case (Condition.isBodyEmptyOrMissing, _) => Condition.isBodyEmptyOrMissing
case (_, Condition.isBodyEmptyOrMissing) => Condition.isBodyEmptyOrMissing
case _ => Condition.IsHttpCodecError
}
def isHttpCodecError: Boolean = self match {
case Condition.IsHttpCodecError => true
Expand All @@ -845,10 +870,18 @@ object HttpCodec extends ContentCodecs with HeaderCodecs with MethodCodecs with
case Condition.isMissingDataOnly => true
case _ => false
}
def isBodyEmptyOrMissing: Boolean = self match {
case Condition.isBodyEmptyOrMissing => true
case _ => false
}
}
object Condition {
case object IsHttpCodecError extends Condition
case object isMissingDataOnly extends Condition
case object IsHttpCodecError extends Condition
case object isMissingDataOnly extends Condition
case object isBodyEmptyOrMissing extends Condition {
override def apply(cause: Cause[Any]): Boolean =
HttpCodecError.isMissingBodyOrEmpty(cause)
}
}
}

Expand Down
16 changes: 16 additions & 0 deletions zio-http/shared/src/main/scala/zio/http/codec/HttpCodecError.scala
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ object HttpCodecError {
final case class InvalidEntity(details: String, cause: Chunk[ValidationError] = Chunk.empty) extends HttpCodecError {
def message = s"A well-formed entity failed validation: $details"
}
case object MissingBody extends HttpCodecError {
def message = "Request body is missing"
}
case object EmptyBody extends HttpCodecError {
def message: String = "Empty request body"
}
object InvalidEntity {
def wrap(errors: Chunk[ValidationError]): InvalidEntity =
InvalidEntity(
Expand Down Expand Up @@ -94,4 +100,14 @@ object HttpCodecError {
!cause.isFailure && cause.defects.forall(e =>
e.isInstanceOf[HttpCodecError.MissingHeader] || e.isInstanceOf[HttpCodecError.MissingQueryParam],
)

def isMissingBodyOrEmpty(cause: Cause[Any]): Boolean = {
!cause.isFailure && cause.defects.exists {
case HttpCodecError.MalformedBody(details, _) if details.contains("end of input") => true
case HttpCodecError.MissingBody => true
case HttpCodecError.EmptyBody => true
case _ => false
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ sealed trait HttpContentCodec[A] { self =>
lookup(contentType) match {
case Some((_, codec)) =>
request.body.asChunk.flatMap { bytes =>
ZIO.fromEither(codec.codec(config).decode(bytes))
if (bytes.isEmpty) {
ZIO.fail(HttpCodecError.EmptyBody)
} else {
ZIO.fromEither(codec.codec(config).decode(bytes))
}
}
case None =>
ZIO.fail(throw new IllegalArgumentException(s"No codec found for content type $contentType"))
Expand Down
Loading