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

add support for federation 2.9 and 2.10 #2446

Merged
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
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](Value.NullValue)(from =>
InputValue.ObjectValue(Map("from" -> Value.StringValue(from)))
),
"value" -> h.value.fold[InputValue](Value.NullValue)(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
Loading