Skip to content
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
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package some_other_package

import com.dwolla.consul.ServiceName
import com.dwolla.consul.smithy4s._
import com.dwolla.test.HelloService
import munit.FunSuite
Expand All @@ -10,11 +11,13 @@ trait ConsulDiscoverableSpecPerScalaVersion { self: FunSuite =>
val serviceUri: Uri = UriFromService(HelloService)
private val serviceHost: Host = HostFromService(HelloService)
private val serviceUriAuthority: Uri.Authority = UriAuthorityFromService(HelloService)
private val serviceName: ServiceName = ServiceNameFromService(HelloService)

test("ConsulDiscoverable typeclass macro constructs a working instance of the typeclass") {
assertEquals(ConsulDiscoverable[HelloService].host, serviceHost)
assertEquals(ConsulDiscoverable[HelloService].uriAuthority, serviceUriAuthority)
assertEquals(ConsulDiscoverable[HelloService].uri, serviceUri)
assertEquals(ConsulDiscoverable[HelloService].serviceName, serviceName)
}

test("ConsulDiscoverable typeclass macro returns no instance when the type parameter isn't a Smithy Service") {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
package com.dwolla.consul.smithy4s

import com.comcast.ip4s._
import cats.syntax.all._
import com.dwolla.consul.ServiceName
import com.dwolla.consul.smithy.Discoverable
import com.dwolla.test.HelloService
import munit.FunSuite
import org.http4s.Uri
import org.http4s.syntax.all._

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)
assertEquals(uri, uri"consul://hello-world")
assertEquals(uri.some, HelloService.hints.get[Discoverable].map(d => Uri(scheme = scheme"consul".some, authority = Uri.Authority(host = Uri.RegName(d.serviceName.value)).some)))
}

test("Service Host is derived from Smithy hint at compile time") {
val uri = HostFromService(com.dwolla.test.HelloService)
assertEquals(uri, Uri.Host.fromIp4sHost(host"hello-world"))
assertEquals(uri.some, HelloService.hints.get[Discoverable].map(d => Uri.RegName(d.serviceName.value)))
}

test("Service URI.Authority is derived from Smithy hint at compile time") {
val uri = UriAuthorityFromService(com.dwolla.test.HelloService)
assertEquals(uri, Uri.Authority(host = Uri.Host.fromIp4sHost(host"hello-world")))
assertEquals(uri.some, HelloService.hints.get[Discoverable].map(d => Uri.Authority(host = Uri.RegName(d.serviceName.value))))
}

test("ServiceName is derived from Smithy hint at compile time") {
val serviceName = ServiceNameFromService(com.dwolla.test.HelloService)
assertEquals(serviceName.some, HelloService.hints.get[Discoverable].map(d => ServiceName(d.serviceName.value)))
}

test("unannotated Smithy services are rejected at compile time") {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.dwolla.consul.smithy4s

import cats.syntax.all._
import com.dwolla.consul.smithy._
import org.http4s.Uri.{Host, Scheme}
import com.dwolla.consul.ServiceName
import com.dwolla.consul.smithy
import com.dwolla.consul.smithy.{ServiceName => _, _}
import org.http4s.Uri
import org.typelevel.scalaccompat.annotation.nowarn212
import smithy4s.Hints

Expand All @@ -15,18 +17,16 @@ trait ConsulDiscoverablePlatform {
}

object ConsulDiscoverableMacros {
@nowarn212("msg=local val (liftableScheme|liftableHost) in method makeInstance is never used")
@nowarn212("msg=local val (liftableServiceName|liftableHost) in method makeInstance is never used")
def makeInstance[Alg[_[_]]](c: whitebox.Context): c.Expr[ConsulDiscoverable[Alg]] = {
import c.universe.{Try => _, _}

implicit val liftableScheme: Liftable[Scheme] = Liftable { scheme: Scheme =>
q"""_root_.org.http4s.Uri.Scheme.unsafeFromString(${scheme.value})"""
}
implicit val liftableHost: Liftable[Host] = Liftable { host: Host =>
implicit val liftableHost: Liftable[Uri.Host] = Liftable { host: Uri.Host =>
q"""_root_.org.http4s.Uri.Host.unsafeFromString(${host.value})"""
}

val consulScheme = DiscoveryMacros.consulScheme.some
implicit val liftableServiceName: Liftable[ServiceName] = Liftable { serviceName: ServiceName =>
q"""_root_.com.dwolla.consul.ServiceName(${serviceName.value})"""
}

def findHintsInTree(tpe: Tree): Either[String, Hints] =
Try {
Expand All @@ -45,28 +45,25 @@ object ConsulDiscoverableMacros {
_.get(Discoverable.tagInstance)
.toRight(s"could not find Discoverable hint for $tpe")

val getHostFromDiscoverable: PartialFunction[Discoverable, Either[String, Host]] = {
case Discoverable(ServiceName(serviceName)) =>
Host.fromString(serviceName)
val getHostFromDiscoverable: PartialFunction[Discoverable, Either[String, (ServiceName, Uri.Host)]] = {
case Discoverable(smithy.ServiceName(serviceName)) =>
Uri.Host.fromString(serviceName)
.leftMap(_.message)
.tupleLeft(ServiceName(serviceName))
}

def hostToConsulDiscoverableExpr(tpe: Tree): Host => c.Expr[ConsulDiscoverable[Alg]] = host =>
def hostToConsulDiscoverableExpr(tpe: Tree): (ServiceName, Uri.Host) => c.Expr[ConsulDiscoverable[Alg]] = (serviceName, host) =>
c.Expr[ConsulDiscoverable[Alg]](
q"""
new _root_.com.dwolla.consul.smithy4s.ConsulDiscoverable[$tpe] {
override def host: _root_.org.http4s.Uri.Host = $host
override def uriAuthority: _root_.org.http4s.Uri.Authority = _root_.org.http4s.Uri.Authority(host = host)
override def uri: _root_.org.http4s.Uri = _root_.org.http4s.Uri(scheme = $consulScheme, authority = _root_.scala.Option(uriAuthority))
}
_root_.com.dwolla.consul.smithy4s.ConsulDiscoverable.make[$tpe]($serviceName, $host)
""")

c.macroApplication match {
case TypeApply(_, List(tpe)) if tpe.symbol.companion != NoSymbol =>
val maybeExpr = findHintsInTree(tpe)
.flatMap(getDiscoverableFromHints(tpe))
.flatMap(getHostFromDiscoverable)
.map(hostToConsulDiscoverableExpr(tpe))
.map(hostToConsulDiscoverableExpr(tpe).tupled)

maybeExpr.fold(c.abort(c.enclosingPosition, _), identity)
case TypeApply(_, List(tpe)) if tpe.symbol.companion == NoSymbol =>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package com.dwolla.consul.smithy4s

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

import scala.reflect.macros.blackbox

Expand Down Expand Up @@ -36,7 +39,7 @@ object DiscoveryMacros {
.get(Discoverable.tagInstance)
.toRight(s"could not find Discoverable hint for ${cleanService.symbol.fullName}")
.flatMap {
case Discoverable(ServiceName(serviceName)) =>
case Discoverable(smithy.ServiceName(serviceName)) =>
Host.fromString(serviceName)
.leftMap(_.message)
}
Expand All @@ -45,6 +48,29 @@ object DiscoveryMacros {
}
.fold(c.abort(c.enclosingPosition, _), identity)
}

@nowarn212("msg=local val liftableServiceName in method makeServiceName is never used")
def makeServiceName[Alg[_[_, _, _, _, _]]](c: blackbox.Context)
(service: c.Expr[smithy4s.Service[Alg]]): c.Expr[ServiceName] = {
import c.universe.{Try => _, _}

implicit val liftableServiceName: Liftable[ServiceName] = Liftable { serviceName: ServiceName =>
q"""_root_.com.dwolla.consul.ServiceName(${serviceName.value})"""
}

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}")
.map {
case Discoverable(serviceName) => ServiceName(serviceName.value)
}
.map { serviceName =>
c.Expr[ServiceName](q"$serviceName")
}
.fold(c.abort(c.enclosingPosition, _), identity)
}
}

object UriAuthorityFromService {
Expand All @@ -61,3 +87,8 @@ object UriFromService {
def apply[Alg[_[_, _, _, _, _]]](service: Service[Alg]): Uri =
macro DiscoveryMacros.makeUri[Alg]
}

object ServiceNameFromService {
def apply[Alg[_[_, _, _, _, _]]](service: Service[Alg]): ServiceName =
macro DiscoveryMacros.makeServiceName[Alg]
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
package com.dwolla.consul.smithy4s

import cats.syntax.all._
import com.dwolla.consul.ServiceName
import org.http4s.Uri
import org.http4s.Uri.Host
import org.http4s.syntax.all._

import scala.annotation.implicitNotFound

@implicitNotFound("Instances are only available for Smithy4s Services annotated with @discoverable")
trait ConsulDiscoverable[Alg[_[_]]] {
def host: Host
sealed trait ConsulDiscoverable[Alg[_[_]]] {
def host: Uri.Host
def uriAuthority: Uri.Authority
def uri: Uri
def serviceName: ServiceName = ServiceName(host.value) // default implementation for MiMa compatibility, but it's never used in practice
}

object ConsulDiscoverable extends ConsulDiscoverablePlatform {
def apply[Alg[_[_]] : ConsulDiscoverable]: ConsulDiscoverable[Alg] = implicitly

def make[Alg[_[_]]](name: ServiceName, dest: Uri.Host): ConsulDiscoverable[Alg] = new ConsulDiscoverable[Alg] {
override def host: Uri.Host = dest
override def uriAuthority: Uri.Authority = Uri.Authority(host = host)
override def uri: Uri = Uri(scheme = scheme"consul".some, authority = uriAuthority.some)
override def serviceName: ServiceName = name
}
}