Skip to content

Commit

Permalink
Merge pull request #186 from Dwolla/macro-improvements
Browse files Browse the repository at this point in the history
be smarter about how the Service is passed to the UriFrom macros
  • Loading branch information
bpholt authored Aug 28, 2024
2 parents c0a92b0 + 43629cf commit e346da0
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class ConsulMiddlewareSpec
expected: GreetOutput) =>
SimpleRestJsonBuilder(HelloService)
.client(Client.fromHttpApp(new TestServiceImpl[IO](consulAuthority, expected).routes.orNotFound))
.uri(UriFromService[HelloService])
.uri(UriFromService(HelloService))
.middleware(new ConsulMiddleware(new FakeConsuleUriResolver[IO](consulAuthority)))
.resource
.use(_.greet(input))
Expand All @@ -69,7 +69,7 @@ class ConsulMiddlewareSpec
}

class FakeConsuleUriResolver[F[_] : Applicative : Console](consulAuthority: Uri.Authority) extends ConsulUriResolver[F] {
private val baseAuthority: Uri.Authority = UriAuthorityFromService[HelloService]
private val baseAuthority: Uri.Authority = UriAuthorityFromService(HelloService)
private val consul = scheme"consul"

override def resolve(uri: Uri): F[Uri] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,37 +8,23 @@ class UriFromServiceSpec extends FunSuite {
test("Service URI is derived from Smithy hint at compile time") {
import org.http4s.syntax.all._

val uri = UriFromService[com.dwolla.test.HelloService]
val uri = UriFromService(com.dwolla.test.HelloService)
assertEquals(uri, uri"consul://hello-world")
}

test("Service Host is derived from Smithy hint at compile time") {
val uri = HostFromService[com.dwolla.test.HelloService]
val uri = HostFromService(com.dwolla.test.HelloService)
assertEquals(uri, Uri.Host.fromIp4sHost(host"hello-world"))
}

test("Service URI.Authority is derived from Smithy hint at compile time") {
val uri = UriAuthorityFromService[com.dwolla.test.HelloService]
val uri = UriAuthorityFromService(com.dwolla.test.HelloService)
assertEquals(uri, Uri.Authority(host = Uri.Host.fromIp4sHost(host"hello-world")))
}

test("traits of the right shape are rejected at compile time if they aren't smithy4s Services") {
assert {
compileErrors("UriFromService[NotASmithy4sServiceAndHasNoCompanionObject]")
.contains("com.dwolla.consul.smithy4s.NotASmithy4sServiceAndHasNoCompanionObject is not a Smithy4s Service")
}
}

test("traits of the right shape are rejected at compile time if they aren't smithy4s Services, even if they have a companion object") {
assert {
compileErrors("UriFromService[NotASmithy4sService]")
.contains("value hints is not a member of object com.dwolla.consul.smithy4s.NotASmithy4sService (is com.dwolla.consul.smithy4s.NotASmithy4sService a Smithy4s Service?)")
}
}

test("unannotated Smithy services are rejected at compile time") {
assert {
compileErrors("UriFromService[com.dwolla.test.UnannotatedService]")
compileErrors("UriFromService(com.dwolla.test.UnannotatedService)")
.contains("could not find Discoverable hint for com.dwolla.test.UnannotatedService")
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,74 +2,62 @@ package com.dwolla.consul.smithy4s

import com.dwolla.consul.smithy.{Discoverable, ServiceName}
import org.http4s.Uri
import smithy4s.Hints
import smithy4s.{Hints, Service}
import cats.syntax.all._
import org.http4s.Uri.{Host, Scheme}
import org.http4s.syntax.all._

import scala.reflect.macros.blackbox
import scala.util.Try

object DiscoveryMacros {
private val consulScheme: Scheme = scheme"consul"

def makeUri(c: blackbox.Context): c.Expr[Uri] = {
def makeUri[Alg[_[_, _, _, _, _]], Op[_, _, _, _, _]](c: blackbox.Context)
(service: c.Expr[smithy4s.Service.Mixin[Alg, Op]]): c.Expr[Uri] = {
import c.universe.{Try => _, _}

c.Expr[Uri](q"org.http4s.Uri(scheme = scala.Option(org.http4s.Uri.Scheme.unsafeFromString(${consulScheme.value})), authority = scala.Option(org.http4s.Uri.Authority(None, ${makeHost(c)}, None)))")
c.Expr[Uri](q"org.http4s.Uri(scheme = scala.Option(org.http4s.Uri.Scheme.unsafeFromString(${consulScheme.value})), authority = scala.Option(org.http4s.Uri.Authority(None, ${makeHost[Alg, Op](c)(service)}, None)))")
}

def makeUriAuthority(c: blackbox.Context): c.Expr[Uri.Authority] = {
def makeUriAuthority[Alg[_[_, _, _, _, _]], Op[_, _, _, _, _]](c: blackbox.Context)
(service: c.Expr[smithy4s.Service.Mixin[Alg, Op]]): c.Expr[Uri.Authority] = {
import c.universe.{Try => _, _}

c.Expr[Uri.Authority](q"org.http4s.Uri.Authority(host =${makeHost(c)})")
c.Expr[Uri.Authority](q"org.http4s.Uri.Authority(host =${makeHost[Alg, Op](c)(service)})")
}

def makeHost(c: blackbox.Context): c.Expr[Host] = {
def makeHost[Alg[_[_, _, _, _, _]], Op[_, _, _, _, _]](c: blackbox.Context)
(service: c.Expr[smithy4s.Service.Mixin[Alg, Op]]): c.Expr[Host] = {
import c.universe.{Try => _, _}

c.macroApplication match {
case TypeApply(_, List(tpe)) if tpe.symbol.companion != NoSymbol =>
Try {
tpe
.collect {
case x: TypTree =>
c.eval(c.Expr[Hints](q"${x.symbol.companion}.hints"))
}
.headOption
.toRight(s"could not find hints for $tpe")
}
.toEither
.leftMap(_.toString ++ s" (is $tpe a Smithy4s Service?)")
.flatten
.flatMap {
_.get(Discoverable.tagInstance)
.toRight(s"could not find Discoverable hint for $tpe")
}
.flatMap {
case Discoverable(ServiceName(serviceName)) =>
Host.fromString(serviceName)
.leftMap(_.message)
}
.map { host =>
c.Expr[Host](q"org.http4s.Uri.Host.unsafeFromString(${host.value})")
}
.fold(c.abort(c.enclosingPosition, _), identity)
case TypeApply(_, List(tpe)) if tpe.symbol.companion == NoSymbol =>
c.abort(c.enclosingPosition, s"$tpe is not a Smithy4s Service")
case other => c.abort(c.enclosingPosition, s"found $other, which is not a Smithy4s Service")
}
val cleanService = c.untypecheck(service.tree.duplicate)

c.eval(c.Expr[Hints](q"$cleanService.hints"))
.get(Discoverable.tagInstance)
.toRight(s"could not find Discoverable hint for ${cleanService.symbol.fullName}")
.flatMap {
case Discoverable(ServiceName(serviceName)) =>
Host.fromString(serviceName)
.leftMap(_.message)
}
.map { host =>
c.Expr[Host](q"org.http4s.Uri.Host.unsafeFromString(${host.value})")
}
.fold(c.abort(c.enclosingPosition, _), identity)
}
}

object UriAuthorityFromService {
def apply[Alg[_[_]]]: Uri.Authority = macro DiscoveryMacros.makeUriAuthority
def apply[Alg[_[_, _, _, _, _]], Op[_, _, _, _, _]](service: Service.Mixin[Alg, Op]): Uri.Authority =
macro DiscoveryMacros.makeUriAuthority[Alg, Op]
}

object HostFromService {
def apply[Alg[_[_]]]: Host = macro DiscoveryMacros.makeHost
def apply[Alg[_[_, _, _, _, _]], Op[_, _, _, _, _]](service: Service.Mixin[Alg, Op]): Host =
macro DiscoveryMacros.makeHost[Alg, Op]
}

object UriFromService {
def apply[Alg[_[_]]]: Uri = macro DiscoveryMacros.makeUri
def apply[Alg[_[_, _, _, _, _]], Op[_, _, _, _, _]](service: Service.Mixin[Alg, Op]): Uri =
macro DiscoveryMacros.makeUri[Alg, Op]
}

0 comments on commit e346da0

Please sign in to comment.