Skip to content

Commit

Permalink
Fix nested routes issue #3243
Browse files Browse the repository at this point in the history
  • Loading branch information
deekshatomer committed Feb 12, 2025
1 parent 668eb91 commit 78f5a8e
Show file tree
Hide file tree
Showing 401 changed files with 78,693 additions and 0 deletions.
11 changes: 11 additions & 0 deletions js/src/main/scala/zio/http/DriverPlatformSpecific.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package zio.http

import scala.annotation.nowarn

import zio.ZLayer

@nowarn("msg=dead code")
trait DriverPlatformSpecific {
val default: ZLayer[Server.Config, Throwable, Driver] =
throw new UnsupportedOperationException("Not implemented for Scala.js")
}
10 changes: 10 additions & 0 deletions js/src/main/scala/zio/http/HandlerPlatformSpecific.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package zio.http

import zio.Trace

trait HandlerPlatformSpecific {
self: Handler.type =>

def fromResource(path: String)(implicit trace: Trace): Handler[Any, Throwable, Any, Response] =
throw new UnsupportedOperationException("Not supported on Scala.js")
}
16 changes: 16 additions & 0 deletions js/src/main/scala/zio/http/ServerPlatformSpecific.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package zio.http

import scala.annotation.nowarn

import zio._

import zio.http.Server.Config

@nowarn("msg=dead code")
trait ServerPlatformSpecific {
val customized: ZLayer[Config, Throwable, Server] =
throw new UnsupportedOperationException("Not implemented for Scala.js")

val live: ZLayer[Config, Throwable, Server] =
throw new UnsupportedOperationException("Not implemented for Scala.js")
}
5 changes: 5 additions & 0 deletions js/src/main/scala/zio/http/URLPlatformSpecific.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package zio.http

trait URLPlatformSpecific {
self: URL =>
}
41 changes: 41 additions & 0 deletions js/src/main/scala/zio/http/ZClientPlatformSpecific.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package zio.http

import zio._

import zio.http.ZClient.Config
import zio.http.internal.FetchDriver

trait ZClientPlatformSpecific {

def customized: ZLayer[Config with ZClient.Driver[Any, Scope, Throwable], Throwable, Client] = {
implicit val trace: Trace = Trace.empty
ZLayer.scoped {
for {
config <- ZIO.service[Config]
driver <- ZIO.service[ZClient.Driver[Any, Scope, Throwable]]
baseClient = ZClient.fromDriver(driver)
} yield
if (config.addUserAgentHeader)
baseClient.addHeader(ZClient.defaultUAHeader)
else
baseClient
}
}

def live: ZLayer[ZClient.Config, Throwable, Client] = {
implicit val trace: Trace = Trace.empty
FetchDriver.live >>> customized
}.fresh

def configured(
path: NonEmptyChunk[String] = NonEmptyChunk("zio", "http", "client"),
)(implicit trace: Trace): ZLayer[Any, Throwable, Client] =
ZLayer(ZIO.config(Config.config.nested(path.head, path.tail: _*)))
.mapError(error => new RuntimeException(s"Configuration error: $error")) >>> live

def default: ZLayer[Any, Throwable, Client] = {
implicit val trace: Trace = Trace.empty
ZLayer.succeed(Config.default) >>> live
}

}
97 changes: 97 additions & 0 deletions js/src/main/scala/zio/http/codec/PathCodecPlatformSpecific.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package zio.http.codec

trait PathCodecPlatformSpecific {
private[codec] def parseLong(s: CharSequence, beginIndex: Int, endIndex: Int, radix: Int): Long = {
require(s != null, "CharSequence cannot be null")
checkFromToIndex(beginIndex, endIndex, s.length)

if (radix < Character.MIN_RADIX)
throw new NumberFormatException("radix " + radix + " less than Character.MIN_RADIX")
if (radix > Character.MAX_RADIX)
throw new NumberFormatException("radix " + radix + " greater than Character.MAX_RADIX")
var negative = false
var i = beginIndex
var limit = -Long.MaxValue
if (i < endIndex) {
val firstChar = s.charAt(i)
if (firstChar < '0') { // Possible leading "+" or "-"
if (firstChar == '-') {
negative = true
limit = Long.MinValue
} else if (firstChar != '+') throw forCharSequence(s, beginIndex, endIndex, i)
i += 1
}
if (i >= endIndex) { // Cannot have lone "+", "-" or ""
throw forCharSequence(s, beginIndex, endIndex, i)
}
val multmin = limit / radix
var result = 0L
while (i < endIndex) {
// Accumulating negatively avoids surprises near MAX_VALUE
val digit = Character.digit(s.charAt(i), radix)
if (digit < 0 || result < multmin) throw forCharSequence(s, beginIndex, endIndex, i)
result *= radix
if (result < limit + digit) throw forCharSequence(s, beginIndex, endIndex, i)
i += 1
result -= digit
}
if (negative) result
else -result
} else throw new NumberFormatException("")
}

private[codec] def parseInt(s: CharSequence, beginIndex: Int, endIndex: Int, radix: Int): Int = {
require(s != null, "CharSequence cannot be null")
checkFromToIndex(beginIndex, endIndex, s.length)

if (radix < Character.MIN_RADIX)
throw new NumberFormatException("radix " + radix + " less than Character.MIN_RADIX")
if (radix > Character.MAX_RADIX)
throw new NumberFormatException("radix " + radix + " greater than Character.MAX_RADIX")
var negative = false
var i = beginIndex
var limit = -Int.MaxValue
if (i < endIndex) {
val firstChar = s.charAt(i)
if (firstChar < '0') { // Possible leading "+" or "-"
if (firstChar == '-') {
negative = true
limit = Int.MinValue
} else if (firstChar != '+') throw forCharSequence(s, beginIndex, endIndex, i)
i += 1
if (i == endIndex) { // Cannot have lone "+" or "-"
throw forCharSequence(s, beginIndex, endIndex, i)
}
}
val multmin = limit / radix
var result = 0
while (i < endIndex) {
// Accumulating negatively avoids surprises near MAX_VALUE
val digit = Character.digit(s.charAt(i), radix)
if (digit < 0 || result < multmin) throw forCharSequence(s, beginIndex, endIndex, i)
result *= radix
if (result < limit + digit) throw forCharSequence(s, beginIndex, endIndex, i)
i += 1
result -= digit
}
if (negative) result
else -result
} else throw forInputString("", radix)
}

private[codec] def forCharSequence(s: CharSequence, beginIndex: Int, endIndex: Int, errorIndex: Int) =
new NumberFormatException(
"Error at index " + (errorIndex - beginIndex) + " in: \"" + s.subSequence(beginIndex, endIndex) + "\"",
)

private[codec] def forInputString(s: String, radix: Int) = new NumberFormatException(
"For input string: \"" + s + "\"" + (if (radix == 10) ""
else " under radix " + radix),
)

private def checkFromToIndex(from: Int, to: Int, length: Int): Unit = {
if (from < 0 || to > length || from > to) {
throw new IndexOutOfBoundsException(s"Range [$from, $to) out of bounds for length $length")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package zio.http.internal

import scala.annotation.nowarn

@nowarn("msg=dead code")
trait BodyEncodingPlatformSpecific {
val default: BodyEncoding = throw new NotImplementedError("No version implemented for Scala.js yet.")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package zio.http.internal

import scala.annotation.nowarn

@nowarn("msg=dead code")
private[http] trait CookieEncodingPlatformSpecific {
val default: CookieEncoding = throw new NotImplementedError("No version implemented for Scala.js yet.")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package zio.http.internal

import scala.annotation.nowarn

@nowarn("msg=dead code")
private[http] trait DateEncodingPlatformSpecific {
val default: DateEncoding = throw new NotImplementedError("No version implemented for Scala.js yet.")
}
75 changes: 75 additions & 0 deletions js/src/main/scala/zio/http/internal/FetchBody.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package zio.http.internal

import scala.scalajs.js.typedarray.Uint8Array

import zio._

import zio.stream.ZStream

import zio.http._

import org.scalajs.dom.ReadableStream

case class FetchBody(
content: ReadableStream[Uint8Array],
contentType: Option[Body.ContentType],
) extends Body {

/**
* Returns an effect that decodes the content of the body as array of bytes.
* Note that attempting to decode a large stream of bytes into an array could
* result in an out of memory error.
*/
override def asArray(implicit trace: Trace): Task[Array[Byte]] =
ZIO.fromFuture { implicit ec =>
content.getReader().read().toFuture.map { value =>
value.value.map(_.toByte).toArray
}
}

/**
* Returns an effect that decodes the content of the body as a chunk of bytes.
* Note that attempting to decode a large stream of bytes into a chunk could
* result in an out of memory error.
*/
override def asChunk(implicit trace: Trace): Task[Chunk[Byte]] =
asArray.map(Chunk.fromArray)

/**
* Returns a stream that contains the bytes of the body. This method is safe
* to use with large bodies, because the elements of the returned stream are
* lazily produced from the body.
*/
override def asStream(implicit trace: Trace): ZStream[Any, Throwable, Byte] =
ZStream.fromIterableZIO(asChunk)

/**
* Returns whether or not the bytes of the body have been fully read.
*/
override def isComplete: Boolean =
false // Seems to not be possible to check this with Fetch API

/**
* Returns whether or not the content length is known
*/
override def knownContentLength: Option[Long] = None

/**
* Returns whether or not the body is known to be empty. Note that some bodies
* may not be known to be empty until an attempt is made to consume them.
*/
override def isEmpty: Boolean = false

/**
* Updates the media type attached to this body, returning a new Body with the
* updated media type
*/
override def contentType(newContentType: Body.ContentType): Body = copy(contentType = Some(newContentType))
}

object FetchBody {

def fromResponse(result: org.scalajs.dom.Response, contentType: Option[Body.ContentType]): Body = {
FetchBody(result.body, contentType)
}
}
89 changes: 89 additions & 0 deletions js/src/main/scala/zio/http/internal/FetchDriver.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package zio.http.internal

import scala.collection.compat.immutable.ArraySeq
import scala.scalajs.js
import scala.scalajs.js.typedarray.Uint8Array

import zio._

import zio.http._

import org.scalajs.dom
import org.scalajs.dom.BodyInit

final case class FetchDriver() extends ZClient.Driver[Any, Scope, Throwable] {
override def request(
version: Version,
requestMethod: Method,
url: URL,
requestHeaders: Headers,
requestBody: Body,
sslConfig: Option[ClientSSLConfig],
proxy: Option[Proxy],
)(implicit trace: Trace): ZIO[Scope, Throwable, Response] = {
for {
jsBody <- fromZBody(requestBody)
response <-
ZIO.fromFuture { implicit ec =>
val jsMethod = fromZMethod(requestMethod)
val jsHeaders = js.Dictionary(requestHeaders.map(h => h.headerName -> h.renderedValue).toSeq: _*)
for {
response <- dom
.fetch(
url.encode,
new dom.RequestInit {
method = jsMethod
headers = jsHeaders
body = jsBody
},
)
.toFuture
} yield {
val respHeaders = Headers.fromIterable(response.headers.map(h => Header.Custom(h(0), h(1))))
val ct = respHeaders.get(Header.ContentType)
Response(
status = Status.fromInt(response.status),
headers = respHeaders,
body = FetchBody.fromResponse(response, ct.map(Body.ContentType.fromHeader)),
)
}

}
} yield response
}

private def fromZMethod(method: Method): dom.HttpMethod = method match {
case Method.GET => dom.HttpMethod.GET
case Method.POST => dom.HttpMethod.POST
case Method.PUT => dom.HttpMethod.PUT
case Method.PATCH => dom.HttpMethod.PATCH
case Method.DELETE => dom.HttpMethod.DELETE
case Method.HEAD => dom.HttpMethod.HEAD
case Method.OPTIONS => dom.HttpMethod.OPTIONS
case Method.ANY => dom.HttpMethod.POST
case Method.CUSTOM(name) => throw new IllegalArgumentException(s"Custom method $name is not supported")
case Method.TRACE => throw new IllegalArgumentException("TRACE is not supported")
case Method.CONNECT => throw new IllegalArgumentException("CONNECT is not supported")
}

private def fromZBody(body: Body): ZIO[Any, Throwable, js.UndefOr[BodyInit]] =
if (body.isEmpty) {
ZIO.succeed(js.undefined)
} else {
body.asArray.map { ar => Uint8Array.of(ArraySeq.unsafeWrapArray(ar.map(_.toShort)): _*) }
}

override def socket[Env1](version: Version, url: URL, headers: Headers, app: WebSocketApp[Env1])(implicit
trace: Trace,
ev: Scope =:= Scope,
): ZIO[Env1 & Scope, Throwable, Response] =
throw new UnsupportedOperationException("WebSockets are not supported in the js client yet.")

}

object FetchDriver {

val live: ZLayer[Any, Nothing, FetchDriver] =
ZLayer.succeed(FetchDriver())

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package zio.http.internal

import scala.annotation.nowarn

@nowarn("msg=dead code")
private[http] trait HeaderEncodingPlatformSpecific {
val default: HeaderEncoding = throw new NotImplementedError("No version implemented for Scala.js yet.")
}
Loading

0 comments on commit 78f5a8e

Please sign in to comment.