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

Feature/client #32

Merged
merged 13 commits into from
Sep 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
19 changes: 9 additions & 10 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
name: Scala Build CI
on:
push:
branches:
- main
- develop
pull_request:
branches:
- main
- develop
name: Scala Build and Test CI
on: push

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: run compile
run: sbt compile
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: run tests
run: sbt test
lint:
runs-on: ubuntu-latest
steps:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
- name: Checkout code
uses: actions/checkout@v2
- name: publish to new Tag
run: sbt publish
run: sbt publishSmithy4Play
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ github.event.release.tag_name }}
35 changes: 29 additions & 6 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import sbt.Compile
import sbt.Keys.cleanFiles

val releaseVersion = sys.env.getOrElse("TAG", "0.2.2-1")

val releaseVersion = sys.env.getOrElse("TAG", "0.2.3-BETA")
addCommandAlias("publishSmithy4Play", "smithy4play/publish")
addCommandAlias("publishLocalSmithy4Play", "smithy4play/publishLocal")
addCommandAlias("generateCoverage", "clean; coverage; test; coverageReport")
val token = sys.env.getOrElse("GITHUB_TOKEN", "")
val githubSettings = Seq(
githubOwner := "innFactory",
Expand Down Expand Up @@ -30,14 +33,34 @@ val sharedSettings = defaultProjectSettings
lazy val smithy4play = project
.in(file("smithy4play"))
.settings(
sharedSettings
)
.settings(
sharedSettings,
scalaVersion := Dependencies.scalaVersion,
name := "smithy4play",
scalacOptions += "-Ymacro-annotations",
Compile / compile / wartremoverWarnings ++= Warts.unsafe,
libraryDependencies ++= Dependencies.list
)

lazy val root = project.in(file(".")).settings(sharedSettings).dependsOn(smithy4play).aggregate(smithy4play)
lazy val smithy4playTest = project
.in(file("smithy4playTest"))
.enablePlugins(Smithy4sCodegenPlugin, PlayScala)
.settings(
sharedSettings,
scalaVersion := Dependencies.scalaVersion,
name := "smithy4playTest",
scalacOptions += "-Ymacro-annotations",
Compile / compile / wartremoverWarnings ++= Warts.unsafe,
cleanKeepFiles += (ThisBuild / baseDirectory).value / "smithy4playTest" / "app",
cleanFiles += (ThisBuild / baseDirectory).value / "smithy4playTest" / "app" / "testDefinitions" / "test",
Compile / smithy4sInputDir := (ThisBuild / baseDirectory).value / "smithy4playTest" / "testSpecs",
Compile / smithy4sOutputDir := (ThisBuild / baseDirectory).value / "smithy4playTest" / "app",
libraryDependencies ++= Seq(
guice,
Dependencies.cats,
Dependencies.smithyCore,
Dependencies.scalatestPlus
)
)
.dependsOn(smithy4play)

lazy val root = project.in(file(".")).settings(sharedSettings).aggregate(smithy4play, smithy4playTest)
6 changes: 3 additions & 3 deletions project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ object Dependencies {

val scalaVersion = "2.13.8"

val smithyCore = "com.disneystreaming.smithy4s" %% "smithy4s-core" % "0.14.2"
val smithyJson = "com.disneystreaming.smithy4s" %% "smithy4s-json" % "0.14.2"
val smithyCore = "com.disneystreaming.smithy4s" %% "smithy4s-core" % "0.15.2"
val smithyJson = "com.disneystreaming.smithy4s" %% "smithy4s-json" % "0.15.2"
val classgraph = "io.github.classgraph" % "classgraph" % "4.8.149"

val scalatestPlus =
"org.scalatestplus.play" %% "scalatestplus-play" % "5.1.0" % Test
val cats = "org.typelevel" %% "cats-core" % "2.7.0"
val cats = "org.typelevel" %% "cats-core" % "2.8.0"

lazy val list = Seq(
smithyCore,
Expand Down
9 changes: 6 additions & 3 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
addSbtPlugin("com.codecommit" % "sbt-github-packages" % "0.5.3")
addSbtPlugin("org.wartremover" % "sbt-wartremover" % "3.0.5")
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6")
addSbtPlugin("com.codecommit" % "sbt-github-packages" % "0.5.3")
addSbtPlugin("org.wartremover" % "sbt-wartremover" % "3.0.5")
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6")
addSbtPlugin("com.disneystreaming.smithy4s" % "smithy4s-sbt-codegen" % "0.15.2")
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.8.15")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.9.3")
4 changes: 4 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ Autorouting
```scala
-> / de.innfactory.smithy4play.AutoRouter
```
- add package name to configuration
```scala
smithy4play.autoRoutePackage = "your.package.name"
```

Selfbinding

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ trait AutoRoutableController {
cc: ControllerComponents
): Routes =
new SmithyPlayRouter[Alg, Op, F](impl).routes()

val routes: Routes

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class AutoRouter @Inject(
val pkg = config.getString("smithy4play.autoRoutePackage")
val classGraphScanner: ScanResult = new ClassGraph().enableAllInfo().acceptPackages(pkg).scan()
val controllers = classGraphScanner.getClassesImplementing(classOf[AutoRoutableController])
logger.debug(s"[AutoRouter] found ${controllers.size()} Controllers")
logger.debug(s"[AutoRouter] found ${controllers.size().toString} Controllers")
val routes = controllers.asScala.map(_.loadClass(true)).map(clazz => createFromClass(clazz)).toSeq
classGraphScanner.close()
routes
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package de.innfactory.smithy4play

import akka.util.ByteString
import cats.data.EitherT
import play.api.mvc.{
AbstractController,
Expand Down Expand Up @@ -32,15 +33,15 @@ class SmithyPlayEndpoint[F[_] <: ContextRoute[_], Op[
)(implicit cc: ControllerComponents, ec: ExecutionContext)
extends AbstractController(cc) {

private val httpEndpoint = HttpEndpoint.cast(endpoint)
private val httpEndpoint: Option[HttpEndpoint[I]] = HttpEndpoint.cast(endpoint)

private val inputSchema: Schema[I] = endpoint.input
private val outputSchema: Schema[O] = endpoint.output

private val inputMetadataDecoder =
private val inputMetadataDecoder: Metadata.PartialDecoder[I] =
Metadata.PartialDecoder.fromSchema(inputSchema)

private val outputMetadataEncoder =
private val outputMetadataEncoder: Metadata.Encoder[O] =
Metadata.Encoder.fromSchema(outputSchema)

def handler(v1: RequestHeader): Handler =
Expand All @@ -67,13 +68,6 @@ class SmithyPlayEndpoint[F[_] <: ContextRoute[_], Op[
}
.getOrElse(Action(NotFound("404")))

def handleFailure(error: ContextRouteError): Result =
Results.Status(error.statusCode)(
Json.toJson(
RoutingErrorResponse(error.message, error.additionalInfoErrorCode)
)
)

private def getPathParams(
v1: RequestHeader,
httpEp: HttpEndpoint[I]
Expand Down Expand Up @@ -107,15 +101,17 @@ class SmithyPlayEndpoint[F[_] <: ContextRoute[_], Op[
)
}
case None =>
request.contentType.get match {
println(request.contentType.getOrElse("application/json"))
request.contentType.getOrElse("application/json") match {
case "application/json" => parseJson(request, metadata)
case _ => parseRaw(request, metadata)
}

})
)

private def parseJson(request: Request[RawBuffer], metadata: Metadata) =
private def parseJson(request: Request[RawBuffer], metadata: Metadata): Either[ContextRouteError, I] = {
val codec = codecs.compileCodec(inputSchema)
for {
metadataPartial <- inputMetadataDecoder
.decode(metadata)
Expand All @@ -126,18 +122,18 @@ class SmithyPlayEndpoint[F[_] <: ContextRoute[_], Op[
500
)
}
codec = codecs.compileCodec(inputSchema)
c <- codecs
.decodeFromByteBufferPartial(
codec,
request.body.asBytes().get.toByteBuffer
request.body.asBytes().getOrElse(ByteString.empty).toByteBuffer
)
.leftMap(e => Smithy4PlayError(s"expected: ${e.expected}", 400))
} yield metadataPartial.combine(c)
}

private def parseRaw(request: Request[RawBuffer], metadata: Metadata) = {
private def parseRaw(request: Request[RawBuffer], metadata: Metadata): Either[ContextRouteError, I] = {
val nativeCodec: CodecAPI = CodecAPI.nativeStringsAndBlob(codecs)
val input = ByteArray(request.body.asBytes().get.toArray)
val input = ByteArray(request.body.asBytes().getOrElse(ByteString.empty).toArray)
val codec = nativeCodec
.compileCodec(inputSchema)
for {
Expand All @@ -156,14 +152,21 @@ class SmithyPlayEndpoint[F[_] <: ContextRoute[_], Op[
} yield metadataPartial.combine(bodyPartial)
}

private def getMetadata(pathParams: PathParams, request: RequestHeader) =
private def getMetadata(pathParams: PathParams, request: RequestHeader): Metadata =
Metadata(
path = pathParams,
headers = getHeaders(request),
headers = getHeaders(request.headers),
query = request.queryString.map { case (k, v) => (k.trim, v) }
)

private def handleSuccess(output: O, code: Int) = {
def handleFailure(error: ContextRouteError): Result =
Results.Status(error.statusCode)(
Json.toJson(
RoutingErrorResponse(error.message, error.additionalInfoErrorCode)
)
)

private def handleSuccess(output: O, code: Int): Result = {
val outputMetadata = outputMetadataEncoder.encode(output)
val outputHeaders = outputMetadata.headers.map { case (k, v) =>
(k.toString, v.mkString(""))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package de.innfactory.smithy4play

import play.api.mvc.{ ControllerComponents, Handler, RequestHeader }
import cats.implicits.toTraverseOps
import play.api.mvc.{ AbstractController, ControllerComponents, Handler, RequestHeader }
import play.api.routing.Router.Routes
import smithy4s.http.{ matchPath, HttpEndpoint, HttpMethod, PathSegment }
import smithy4s.http.{ HttpEndpoint, PathSegment }
import smithy4s.{ Endpoint, GenLift, HintMask, Monadic, Service, Transformation }
import smithy4s.internals.InputOutput

Expand All @@ -12,7 +13,8 @@ class SmithyPlayRouter[Alg[_[_, _, _, _, _]], Op[_, _, _, _, _], F[
_
] <: ContextRoute[_]](
impl: Monadic[Alg, F]
)(implicit cc: ControllerComponents, ec: ExecutionContext) {
)(implicit cc: ControllerComponents, ec: ExecutionContext)
extends AbstractController(cc) {

def routes()(implicit
serviceProvider: smithy4s.Service.Provider[Alg, Op]
Expand All @@ -21,32 +23,27 @@ class SmithyPlayRouter[Alg[_[_, _, _, _, _]], Op[_, _, _, _, _], F[
val service: Service[Alg, Op] = serviceProvider.service
val interpreter: Transformation[Op, GenLift[F]#λ] = service.asTransformation[GenLift[F]#λ](impl)
val endpoints: Seq[Endpoint[Op, _, _, _, _, _]] = service.endpoints
val httpEndpoints = endpoints.map(HttpEndpoint.cast(_).get)
val httpEndpoints: Seq[Option[HttpEndpoint[_]]] = endpoints.map(HttpEndpoint.cast(_))

new PartialFunction[RequestHeader, Handler] {
override def isDefinedAt(x: RequestHeader): Boolean = {
logger.debug("[SmithyPlayRouter] calling isDefinedAt on service: " + service.id.name + "for path: " + x.path)
httpEndpoints.exists(ep => checkIfRequestHeaderMatchesEndpoint(x, ep))
logger.debug("[SmithyPlayRouter] calling isDefinedAt on service: " + service.id.name + " for path: " + x.path)
httpEndpoints.exists(ep => ep.exists(checkIfRequestHeaderMatchesEndpoint(x, _)))
}

override def apply(v1: RequestHeader): Handler = {
logger.debug("[SmithyPlayRouter] calling apply on: " + service.id.name)

val validEndpoint = endpoints.find(endpoint =>
checkIfRequestHeaderMatchesEndpoint(
v1,
HttpEndpoint.cast(endpoint).get
)
)
new SmithyPlayEndpoint(
for {
zippedEndpoints <- endpoints.map(ep => HttpEndpoint.cast(ep).map((ep, _))).sequence
endpointAndHttpEndpoint <- zippedEndpoints.find(ep => checkIfRequestHeaderMatchesEndpoint(v1, ep._2))
} yield new SmithyPlayEndpoint(
interpreter,
validEndpoint.get,
smithy4s.http.json.codecs(
smithy4s.api.SimpleRestJson.protocol.hintMask ++ HintMask(
InputOutput
)
)
endpointAndHttpEndpoint._1,
smithy4s.http.json.codecs(smithy4s.api.SimpleRestJson.protocol.hintMask ++ HintMask(InputOutput))
).handler(v1)
} match {
case Some(value) => value
case None => throw new Exception("Could not cast Endpoint to HttpEndpoint, likely a bug in smithy4s")
}

}
Expand All @@ -56,20 +53,13 @@ class SmithyPlayRouter[Alg[_[_, _, _, _, _]], Op[_, _, _, _, _], F[
x: RequestHeader,
ep: HttpEndpoint[_]
) = {
ep.path.foreach {
case PathSegment.StaticSegment(value) =>
if (value.contains(" "))
logger.info("following pathSegment contains a space: " + value)
case PathSegment.LabelSegment(value) =>
if (value.contains(" "))
logger.info("following pathSegment contains a space: " + value)
case PathSegment.GreedySegment(value) =>
if (value.contains(" "))
logger.info("following pathSegment contains a space: " + value)
ep.path.map {
case PathSegment.StaticSegment(value) => value
case PathSegment.LabelSegment(value) => value
case PathSegment.GreedySegment(value) => value
}
matchRequestPath(x, ep).isDefined && x.method
.equals(
ep.method.showUppercase
)
.filter(_.contains(" "))
.foreach(value => logger.info("following pathSegment contains a space: " + value))
matchRequestPath(x, ep).isDefined && x.method == ep.method.showUppercase
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package de.innfactory.smithy4play.client

import play.api.mvc.Headers

import scala.concurrent.Future

case class SmithyClientResponse(body: Option[Array[Byte]], headers: Map[String, Seq[String]], statusCode: Int)

trait RequestClient {
def send(
method: String,
path: String,
headers: Map[String, Seq[String]],
body: Option[Array[Byte]]
): Future[SmithyClientResponse]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package de.innfactory.smithy4play.client

import de.innfactory.smithy4play.ClientResponse
import smithy4s.http.HttpEndpoint

import scala.concurrent.ExecutionContext

class SmithyPlayClient[Alg[_[_, _, _, _, _]], Op[_, _, _, _, _], F[_]](
baseUri: String,
service: smithy4s.Service[Alg, Op]
)(implicit executionContext: ExecutionContext, client: RequestClient) {

def send[I, E, O, SI, SO](
op: Op[I, E, O, SI, SO],
additionalHeaders: Option[Map[String, Seq[String]]]
): ClientResponse[O] = {

val (input, endpoint) = service.endpoint(op)
HttpEndpoint
.cast(endpoint)
.map(httpEndpoint =>
new SmithyPlayClientEndpoint(endpoint, baseUri, additionalHeaders, httpEndpoint, input).send()
)
.get
}

}
Loading