Skip to content

Commit

Permalink
Merge branch 'main' into fix-pathcodec
Browse files Browse the repository at this point in the history
  • Loading branch information
varshith257 authored Aug 6, 2024
2 parents c1ee3e5 + f73633f commit 0f36a05
Show file tree
Hide file tree
Showing 31 changed files with 746 additions and 162 deletions.
6 changes: 6 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@ lazy val sbtZioHttpGrpcTests = (project in file("sbt-zio-http-grpc-tests"))
.settings(publishSetting(false))
.settings(
Test / skip := (CrossVersion.partialVersion(scalaVersion.value) != Some((2, 12))),
ideSkipProject := (CrossVersion.partialVersion(scalaVersion.value) != Some((2, 12))),
libraryDependencies ++= Seq(
`zio-test-sbt`,
`zio-test`,
Expand Down Expand Up @@ -417,3 +418,8 @@ lazy val docs = project
.dependsOn(zioHttpJVM)
.enablePlugins(WebsitePlugin)
.dependsOn(zioHttpTestkit)

Global / excludeLintKeys ++= Set(
sbtZioHttpGrpcTests / autoAPIMappings,
ideSkipProject,
)
3 changes: 3 additions & 0 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,7 @@ addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.16.0")
addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.3.2")
addSbtPlugin("com.thesamet" % "sbt-protoc" % "1.0.7")
addSbtPlugin("com.thesamet" % "sbt-protoc-gen-project" % "0.1.8")

addSbtPlugin("org.jetbrains.scala" % "sbt-ide-settings" % "1.1.2")

libraryDependencies += "com.thesamet.scalapb" %% "compilerplugin" % "0.11.15"
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,8 @@ private[cli] object CliEndpoint {
case HttpCodec.Path(pathCodec, _) =>
CliEndpoint(url = HttpOptions.Path(pathCodec) :: List())

case HttpCodec.Query(name, textCodec, _, _) =>
textCodec.asInstanceOf[TextCodec[_]] match {
case TextCodec.Constant(value) => CliEndpoint(url = HttpOptions.QueryConstant(name, value) :: List())
case _ => CliEndpoint(url = HttpOptions.Query(name, textCodec) :: List())
}
case HttpCodec.Query(name, codec, _, _) =>
CliEndpoint(url = HttpOptions.Query(name, codec) :: List())

case HttpCodec.Status(_, _) => CliEndpoint.empty

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package zio.http.endpoint.cli
import scala.language.implicitConversions
import scala.util.Try

import zio.Chunk
import zio.cli._
import zio.json.ast._

Expand Down Expand Up @@ -261,11 +262,12 @@ private[cli] object HttpOptions {

}

final case class Query(override val name: String, textCodec: TextCodec[_], doc: Doc = Doc.empty) extends URLOptions {
final case class Query(override val name: String, codec: BinaryCodecWithSchema[_], doc: Doc = Doc.empty)
extends URLOptions {
self =>

override val tag = "?" + name
lazy val options: Options[_] = optionsFromTextCodec(textCodec)(name)
lazy val options: Options[_] = optionsFromSchema(codec)(name)

override def ??(doc: Doc): Query = self.copy(doc = self.doc + doc)

Expand All @@ -289,6 +291,76 @@ private[cli] object HttpOptions {

}

private[cli] def optionsFromSchema[A](codec: BinaryCodecWithSchema[A]): String => Options[A] =
codec.schema match {
case Schema.Primitive(standardType, _) =>
standardType match {
case StandardType.UnitType =>
_ => Options.Empty
case StandardType.StringType =>
Options.text
case StandardType.BoolType =>
Options.boolean(_)
case StandardType.ByteType =>
Options.integer(_).map(_.toByte)
case StandardType.ShortType =>
Options.integer(_).map(_.toShort)
case StandardType.IntType =>
Options.integer(_).map(_.toInt)
case StandardType.LongType =>
Options.integer(_).map(_.toLong)
case StandardType.FloatType =>
Options.decimal(_).map(_.toFloat)
case StandardType.DoubleType =>
Options.decimal(_).map(_.toDouble)
case StandardType.BinaryType =>
Options.text(_).map(_.getBytes).map(Chunk.fromArray)
case StandardType.CharType =>
Options.text(_).map(_.charAt(0))
case StandardType.UUIDType =>
Options.text(_).map(java.util.UUID.fromString)
case StandardType.CurrencyType =>
Options.text(_).map(java.util.Currency.getInstance)
case StandardType.BigDecimalType =>
Options.decimal(_).map(_.bigDecimal)
case StandardType.BigIntegerType =>
Options.integer(_).map(_.bigInteger)
case StandardType.DayOfWeekType =>
Options.integer(_).map(i => java.time.DayOfWeek.of(i.toInt))
case StandardType.MonthType =>
Options.text(_).map(java.time.Month.valueOf)
case StandardType.MonthDayType =>
Options.text(_).map(java.time.MonthDay.parse)
case StandardType.PeriodType =>
Options.text(_).map(java.time.Period.parse)
case StandardType.YearType =>
Options.integer(_).map(i => java.time.Year.of(i.toInt))
case StandardType.YearMonthType =>
Options.text(_).map(java.time.YearMonth.parse)
case StandardType.ZoneIdType =>
Options.text(_).map(java.time.ZoneId.of)
case StandardType.ZoneOffsetType =>
Options.text(_).map(java.time.ZoneOffset.of)
case StandardType.DurationType =>
Options.text(_).map(java.time.Duration.parse)
case StandardType.InstantType =>
Options.instant(_)
case StandardType.LocalDateType =>
Options.localDate(_)
case StandardType.LocalTimeType =>
Options.localTime(_)
case StandardType.LocalDateTimeType =>
Options.localDateTime(_)
case StandardType.OffsetTimeType =>
Options.text(_).map(java.time.OffsetTime.parse)
case StandardType.OffsetDateTimeType =>
Options.text(_).map(java.time.OffsetDateTime.parse)
case StandardType.ZonedDateTimeType =>
Options.text(_).map(java.time.ZonedDateTime.parse)
}
case schema => throw new NotImplementedError(s"Schema $schema not yet supported")
}

private[cli] def optionsFromTextCodec[A](textCodec: TextCodec[A]): (String => Options[A]) =
textCodec match {
case TextCodec.UUIDCodec =>
Expand Down
47 changes: 43 additions & 4 deletions zio-http-cli/src/test/scala/zio/http/endpoint/cli/CommandGen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -47,20 +47,20 @@ object CommandGen {
case _: HttpOptions.Constant => false
case _ => true
}.map {
case HttpOptions.Path(pathCodec, _) =>
case HttpOptions.Path(pathCodec, _) =>
pathCodec.segments.toList.flatMap { case segment =>
getSegment(segment) match {
case (_, "") => Nil
case (name, "boolean") => s"[${getName(name, "")}]" :: Nil
case (name, codec) => s"${getName(name, "")} $codec" :: Nil
}
}
case HttpOptions.Query(name, textCodec, _) =>
getType(textCodec) match {
case HttpOptions.Query(name, codec, _) =>
getType(codec) match {
case "" => s"[${getName(name, "")}]" :: Nil
case codec => s"${getName(name, "")} $codec" :: Nil
}
case _ => Nil
case _ => Nil
}.foldRight(List[String]())(_ ++ _)

val headersOptions = cliEndpoint.headers.filter {
Expand Down Expand Up @@ -121,6 +121,45 @@ object CommandGen {
case _ => ""
}

def getType[A](codec: BinaryCodecWithSchema[A]): String =
codec.schema match {
case Schema.Primitive(standardType, _) =>
standardType match {
case StandardType.UnitType => ""
case StandardType.StringType => "text"
case StandardType.BoolType => "bool"
case StandardType.ByteType => "integer"
case StandardType.ShortType => "integer"
case StandardType.IntType => "integer"
case StandardType.LongType => "integer"
case StandardType.FloatType => "decimal"
case StandardType.DoubleType => "decimal"
case StandardType.BinaryType => "binary"
case StandardType.CharType => "text"
case StandardType.UUIDType => "text"
case StandardType.CurrencyType => "currency"
case StandardType.BigDecimalType => "decimal"
case StandardType.BigIntegerType => "integer"
case StandardType.DayOfWeekType => "integer"
case StandardType.MonthType => "text"
case StandardType.MonthDayType => "text"
case StandardType.PeriodType => "text"
case StandardType.YearType => "integer"
case StandardType.YearMonthType => "text"
case StandardType.ZoneIdType => "text"
case StandardType.ZoneOffsetType => "text"
case StandardType.DurationType => "text"
case StandardType.InstantType => "instant"
case StandardType.LocalDateType => "date"
case StandardType.LocalTimeType => "time"
case StandardType.LocalDateTimeType => "datetime"
case StandardType.OffsetTimeType => "time"
case StandardType.OffsetDateTimeType => "datetime"
case StandardType.ZonedDateTimeType => "datetime"
}
case _ => ""
}

def getPrimitive(schema: Schema[_]): String =
schema match {
case Schema.Primitive(standardType, _) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ package zio.http.endpoint.cli
import zio.ZNothing
import zio.test._

import zio.schema.{Schema, StandardType}

import zio.http._
import zio.http.codec.HttpCodec.Query.QueryParamHint
import zio.http.codec._
import zio.http.codec.internal.TextBinaryCodec
import zio.http.endpoint._
import zio.http.endpoint.cli.AuxGen._
import zio.http.endpoint.cli.CliRepr.CliReprOf
Expand Down Expand Up @@ -99,13 +102,12 @@ object EndpointGen {
}

lazy val anyQuery: Gen[Any, CliReprOf[Codec[_]]] =
Gen.alphaNumericStringBounded(1, 30).zip(anyTextCodec).map { case (name, codec) =>
Gen.alphaNumericStringBounded(1, 30).zip(anyStandardType).map { case (name, schema0) =>
val schema = schema0.asInstanceOf[Schema[Any]]
val codec = BinaryCodecWithSchema(TextBinaryCodec.fromSchema(schema), schema)
CliRepr(
HttpCodec.Query(name, codec, QueryParamHint.Any),
codec match {
case TextCodec.Constant(value) => CliEndpoint(url = HttpOptions.QueryConstant(name, value) :: Nil)
case _ => CliEndpoint(url = HttpOptions.Query(name, codec) :: Nil)
},
CliEndpoint(url = HttpOptions.Query(name, codec) :: Nil),
)
}

Expand Down
35 changes: 20 additions & 15 deletions zio-http-cli/src/test/scala/zio/http/endpoint/cli/OptionsGen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ import zio._
import zio.cli._
import zio.test.Gen

import zio.schema.Schema

import zio.http._
import zio.http.codec._
import zio.http.codec.internal.TextBinaryCodec
import zio.http.endpoint.cli.AuxGen._
import zio.http.endpoint.cli.CliRepr._

Expand All @@ -30,6 +33,11 @@ object OptionsGen {
.optionsFromTextCodec(textCodec)(name)
.map(value => textCodec.encode(value))

def encodeOptions[A](name: String, codec: BinaryCodecWithSchema[A]): Options[String] =
HttpOptions
.optionsFromSchema(codec)(name)
.map(value => codec.codec.encode(value).asString)

lazy val anyBodyOption: Gen[Any, CliReprOf[Options[Retriever]]] =
Gen
.alphaNumericStringBounded(1, 30)
Expand Down Expand Up @@ -76,25 +84,22 @@ object OptionsGen {
},
Gen
.alphaNumericStringBounded(1, 30)
.zip(anyTextCodec)
.map {
case (name, TextCodec.Constant(value)) =>
CliRepr(
Options.Empty.map(_ => value),
CliEndpoint(url = HttpOptions.QueryConstant(name, value) :: Nil),
)
case (name, codec) =>
CliRepr(
encodeOptions(name, codec),
CliEndpoint(url = HttpOptions.Query(name, codec) :: Nil),
)
.zip(anyStandardType.map { s =>
val schema = s.asInstanceOf[Schema[Any]]
BinaryCodecWithSchema(TextBinaryCodec.fromSchema(schema), schema)
})
.map { case (name, codec) =>
CliRepr(
encodeOptions(name, codec),
CliEndpoint(url = HttpOptions.Query(name, codec) :: Nil),
)
},
)

lazy val anyMethod: Gen[Any, CliReprOf[Method]] =
Gen.fromIterable(List(Method.GET, Method.DELETE, Method.POST, Method.PUT)).map { case method =>
CliRepr(method, CliEndpoint(methods = method))
}
Gen
.fromIterable(List(Method.GET, Method.DELETE, Method.POST, Method.PUT))
.map(method => CliRepr(method, CliEndpoint(methods = method)))

lazy val anyCliEndpoint: Gen[Any, CliReprOf[Options[CliRequest]]] =
Gen
Expand Down
21 changes: 20 additions & 1 deletion zio-http-example/src/main/scala/example/SSEServer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package example
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter.ISO_LOCAL_TIME

import zio.{ExitCode, Schedule, URIO, ZIOAppDefault, durationInt}
import zio._

import zio.stream.ZStream

Expand All @@ -24,3 +24,22 @@ object SSEServer extends ZIOAppDefault {
Server.serve(app).provide(Server.default).exitCode
}
}

object SSEClient extends ZIOAppDefault {

override def run: ZIO[Any with ZIOAppArgs with Scope, Any, Any] =
ZIO
.scoped(for {
client <- ZIO.service[Client]
response <- client
.url(url"http://localhost:8080")
.request(
Request(method = Method.GET, url = url"http://localhost:8080/sse", body = Body.empty)
.addHeader(Header.Accept(MediaType.text.`event-stream`)),
)
_ <- response.body.asServerSentEvents[String].foreach { event =>
ZIO.logInfo(event.data)
}
} yield ())
.provide(ZClient.default)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package example

import java.time.{Instant, LocalDateTime}
import java.time.Instant

import zio._

Expand All @@ -10,11 +10,10 @@ import zio.schema.{DeriveSchema, Schema}

import zio.http._
import zio.http.codec.HttpCodec
import zio.http.endpoint.Endpoint
import zio.http.endpoint.EndpointMiddleware.None
import zio.http.endpoint.{Endpoint, EndpointExecutor, EndpointLocator}

object ServerSentEventAsJsonEndpoint extends ZIOAppDefault {
import HttpCodec._

case class Payload(timeStamp: Instant, message: String)

Expand All @@ -26,7 +25,9 @@ object ServerSentEventAsJsonEndpoint extends ZIOAppDefault {
ZStream.repeatWithSchedule(ServerSentEvent(Payload(Instant.now(), "message")), Schedule.spaced(1.second))

val sseEndpoint: Endpoint[Unit, Unit, ZNothing, ZStream[Any, Nothing, ServerSentEvent[Payload]], None] =
Endpoint(Method.GET / "sse").outStream[ServerSentEvent[Payload]]
Endpoint(Method.GET / "sse")
.outStream[ServerSentEvent[Payload]]
.inCodec(HttpCodec.header(Header.Accept).const(Header.Accept(MediaType.text.`event-stream`)))

val sseRoute = sseEndpoint.implementHandler(Handler.succeed(stream))

Expand All @@ -37,3 +38,17 @@ object ServerSentEventAsJsonEndpoint extends ZIOAppDefault {
}

}

object ServerSentEventAsJsonEndpointClient extends ZIOAppDefault {
val locator: EndpointLocator = EndpointLocator.fromURL(url"http://localhost:8080")

private val invocation = ServerSentEventAsJsonEndpoint.sseEndpoint(())

override def run: ZIO[Any with ZIOAppArgs with Scope, Any, Any] =
(for {
client <- ZIO.service[Client]
executor = EndpointExecutor(client, locator, ZIO.unit)
stream <- executor(invocation)
_ <- stream.foreach(event => ZIO.logInfo(event.data.toString))
} yield ()).provideSome[Scope](ZClient.default)
}
Loading

0 comments on commit 0f36a05

Please sign in to comment.