Skip to content

Commit

Permalink
add support for federation 2.9 and 2.10
Browse files Browse the repository at this point in the history
  • Loading branch information
paulpdaniels committed Oct 21, 2024
1 parent ad30eda commit 5fca38c
Show file tree
Hide file tree
Showing 6 changed files with 233 additions and 17 deletions.
22 changes: 13 additions & 9 deletions federation/src/main/scala/caliban/federation/package.scala
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
package caliban

import caliban.federation.v2x.{
FederationDirectivesV2_10,
FederationDirectivesV2_3,
FederationDirectivesV2_5,
FederationDirectivesV2_6,
FederationDirectivesV2_8,
FederationDirectivesV2_9,
FederationV2,
Versions
}

package object federation {

lazy val v1 = new FederationV1 with FederationDirectives
lazy val v2_0 = new FederationV2(List(Versions.v2_0))
lazy val v2_1 = new FederationV2(List(Versions.v2_1))
lazy val v2_3 = new FederationV2(List(Versions.v2_3)) with FederationDirectivesV2_3
lazy val v2_4 = new FederationV2(List(Versions.v2_4)) with FederationDirectivesV2_3
lazy val v2_5 = new FederationV2(List(Versions.v2_5)) with FederationDirectivesV2_5
lazy val v2_6 = new FederationV2(List(Versions.v2_6)) with FederationDirectivesV2_6
lazy val v2_7 = new FederationV2(List(Versions.v2_7)) with FederationDirectivesV2_6
lazy val v2_8 = new FederationV2(List(Versions.v2_8)) with FederationDirectivesV2_8
lazy val v1 = new FederationV1 with FederationDirectives
lazy val v2_0 = new FederationV2(List(Versions.v2_0))
lazy val v2_1 = new FederationV2(List(Versions.v2_1))
lazy val v2_3 = new FederationV2(List(Versions.v2_3)) with FederationDirectivesV2_3
lazy val v2_4 = new FederationV2(List(Versions.v2_4)) with FederationDirectivesV2_3
lazy val v2_5 = new FederationV2(List(Versions.v2_5)) with FederationDirectivesV2_5
lazy val v2_6 = new FederationV2(List(Versions.v2_6)) with FederationDirectivesV2_6
lazy val v2_7 = new FederationV2(List(Versions.v2_7)) with FederationDirectivesV2_6
lazy val v2_8 = new FederationV2(List(Versions.v2_8)) with FederationDirectivesV2_8
lazy val v2_9 = new FederationV2(List(Versions.v2_9)) with FederationDirectivesV2_9
lazy val v2_10 = new FederationV2(List(Versions.v2_10, FederationV2.connect)) with FederationDirectivesV2_10

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package caliban.federation.v2x

import caliban.{ InputValue, Value }
import caliban.parsing.adt.Directive
import caliban.schema.Annotations.GQLDirective

trait FederationDirectivesV2_10 extends FederationDirectivesV2_9 {

case class JSONSelection(select: String)
case class HTTPHeaderMapping(
name: String,
from: Option[String],
value: Option[String]
)

sealed trait Method {
def url: String
def body: Option[JSONSelection] = None
}

object Method {
case class GET(url: String) extends Method
case class DELETE(url: String) extends Method
case class POST(url: String, override val body: Option[JSONSelection]) extends Method
case class PUT(url: String, override val body: Option[JSONSelection]) extends Method
case class PATCH(url: String, override val body: Option[JSONSelection]) extends Method
}

case class ConnectHTTP(
method: Method,
headers: List[HTTPHeaderMapping] = Nil
)

def Connect(
http: ConnectHTTP,
selection: JSONSelection,
source: Option[String],
entity: Option[Boolean]
): Directive = {
val connectBuilder = Map.newBuilder[String, InputValue]
val httpBuilder = Map.newBuilder[String, InputValue]

http.method match {
case Method.GET(url) => "GET" -> Value.StringValue(url)
case Method.DELETE(url) => "DELETE" -> Value.StringValue(url)
case Method.POST(url, _) => "POST" -> Value.StringValue(url)
case Method.PUT(url, _) => "PUT" -> Value.StringValue(url)
case Method.PATCH(url, _) => "PATCH" -> Value.StringValue(url)
}
http.method.body.foreach(body => httpBuilder += "body" -> Value.StringValue(body.select))
if (http.headers.nonEmpty)
httpBuilder += "headers" -> InputValue.ListValue(
http.headers.map(h =>
InputValue.ObjectValue(
Map(
"name" -> Value.StringValue(h.name),
"from" -> h.from.fold[InputValue](InputValue.ObjectValue(Map()))(from =>
InputValue.ObjectValue(Map("from" -> Value.StringValue(from)))
),
"value" -> h.value.fold[InputValue](InputValue.ObjectValue(Map()))(value =>
InputValue.ObjectValue(Map("value" -> Value.StringValue(value)))
)
)
)
)
)

connectBuilder += "http" -> InputValue.ObjectValue(httpBuilder.result())
connectBuilder += "selection" -> Value.StringValue(selection.select)
source.foreach(s => connectBuilder += "source" -> Value.StringValue(s))
entity.foreach(e => connectBuilder += "entity" -> Value.BooleanValue(e))

Directive("source", httpBuilder.result())
}

case class GQLConnect(
http: ConnectHTTP,
selection: JSONSelection,
source: Option[String] = None,
entity: Option[Boolean] = None
) extends GQLDirective(Connect(http, selection, source, entity))

def Source(
name: String,
baseURL: String,
headers: List[HTTPHeaderMapping] = Nil
): Directive = {
val sourceBuilder = Map.newBuilder[String, InputValue]
sourceBuilder += "name" -> Value.StringValue(name)
sourceBuilder += "baseURL" -> Value.StringValue(baseURL)
if (headers.nonEmpty)
sourceBuilder += "headers" -> InputValue.ListValue(
headers.map(h =>
InputValue.ObjectValue(
Map(
"name" -> Value.StringValue(h.name),
"from" -> h.from.fold[InputValue](InputValue.ObjectValue(Map()))(from =>
InputValue.ObjectValue(Map("from" -> Value.StringValue(from)))
),
"value" -> h.value.fold[InputValue](InputValue.ObjectValue(Map()))(value =>
InputValue.ObjectValue(Map("value" -> Value.StringValue(value)))
)
)
)
)
)

Directive("source", sourceBuilder.result())
}

case class GQLSource(
name: String,
baseURL: String,
headers: List[HTTPHeaderMapping] = Nil
) extends GQLDirective(Source(name, baseURL, headers))

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package caliban.federation.v2x

import caliban.InputValue
import caliban.InputValue.ListValue
import caliban.Value.{ BooleanValue, IntValue, StringValue }
import caliban.parsing.adt.Directive
import caliban.schema.Annotations.GQLDirective

trait FederationDirectivesV2_9 extends FederationDirectivesV2_8 {

def Cost(weight: Int) = Directive("cost", Map("weight" -> IntValue(weight)))

case class GQLCost(weight: Int) extends GQLDirective(Cost(weight))

def ListSize(
assumedSize: Option[Int] = None,
slicingArguments: Option[List[String]] = None,
sizedFields: Option[List[String]] = None,
requireOneSlicingArgument: Boolean = true
) = {
val builder = Map.newBuilder[String, InputValue]

assumedSize.foreach(size => builder += "assumedSize" -> IntValue(size))
slicingArguments.foreach { args =>
builder += "slicingArguments" -> ListValue(args.map(StringValue.apply))
}
sizedFields.foreach { fields =>
builder += "sizedFields" -> ListValue(fields.map(StringValue.apply))
}
builder += "requireOneSlicingArgument" -> BooleanValue(requireOneSlicingArgument)

Directive(
"listSize",
builder.result()
)
}

case class GQLListSize(
assumedSize: Option[Int] = None,
slicingArguments: Option[List[String]] = None,
sizedFields: Option[List[String]] = None,
requireOneSlicingArgument: Boolean = true
) extends GQLDirective(
ListSize(
assumedSize,
slicingArguments,
sizedFields,
requireOneSlicingArgument
)
)

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class FederationV2(extensions: List[Extension])
object FederationV2 {

val federationV2Url = "https://specs.apollo.dev/federation"
val connectUrl = "https://specs.apollo.dev/connect"

def DefaultDirectives: List[Import] = List(
Import("@key"),
Expand Down Expand Up @@ -62,4 +63,19 @@ object FederationV2 {
`import` = v2_7.`import` :+ Import("@context") :+ Import("@fromContext")
)

private[v2x] val v2_9 = Link(
url = s"$federationV2Url/v2.9",
`import` = v2_8.`import` :+ Import("@cost") :+ Import("@listSize")
)

private[v2x] val v2_10 = Link(
url = s"$federationV2Url/v2.10",
`import` = v2_9.`import`
)

val connect: Link = Link(
url = s"$connectUrl/v0.1",
`import` = List(Import("@connect"), Import("@source"))
)

}
18 changes: 10 additions & 8 deletions federation/src/main/scala/caliban/federation/v2x/Versions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ package caliban.federation.v2x

object Versions {

val v2_0 = FederationV2.v2_0
val v2_1 = FederationV2.v2_1
val v2_3 = FederationV2.v2_3
val v2_4 = FederationV2.v2_4
val v2_5 = FederationV2.v2_5
val v2_6 = FederationV2.v2_6
val v2_7 = FederationV2.v2_7
val v2_8 = FederationV2.v2_8
val v2_0 = FederationV2.v2_0
val v2_1 = FederationV2.v2_1
val v2_3 = FederationV2.v2_3
val v2_4 = FederationV2.v2_4
val v2_5 = FederationV2.v2_5
val v2_6 = FederationV2.v2_6
val v2_7 = FederationV2.v2_7
val v2_8 = FederationV2.v2_8
val v2_9 = FederationV2.v2_9
val v2_10 = FederationV2.v2_10

}
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,31 @@ object FederationV2Spec extends ZIOSpecDefault {
.contains(StringValue("_FieldSet"))
)
)
},
test("connect spec includes the correct directives") {
import caliban.federation.v2_10._
makeSchemaDirectives(federated(_)).map { schemaDirectives =>
val linkDirectives = schemaDirectives.filter(_.name == "link")
val connectDirective = linkDirectives
.find(_.arguments.get("url").exists {
case StringValue(value) => value.startsWith("https://specs.apollo.dev/connect")
case _ => false
})

assertTrue(
connectDirective.get == Directive(
"link",
Map(
"url" -> StringValue("https://specs.apollo.dev/connect/v0.1"),
"import" -> ListValue(
StringValue("@connect") ::
StringValue("@source") :: Nil
)
)
)
)
}

}
)

Expand Down

0 comments on commit 5fca38c

Please sign in to comment.