Skip to content

Commit

Permalink
Add support for interfaces implementing interfaces
Browse files Browse the repository at this point in the history
This is per the RFC 373 found here: graphql/graphql-spec#373
  • Loading branch information
Tapped committed Mar 9, 2021
1 parent 4619516 commit 7b563f2
Show file tree
Hide file tree
Showing 16 changed files with 119 additions and 35 deletions.
8 changes: 7 additions & 1 deletion modules/core/src/main/scala/sangria/ast/QueryAst.scala
Expand Up @@ -451,6 +451,7 @@ case class ObjectTypeDefinition(

case class InterfaceTypeDefinition(
name: String,
interfaces: Vector[NamedType],
fields: Vector[FieldDefinition],
directives: Vector[Directive] = Vector.empty,
description: Option[StringValue] = None,
Expand Down Expand Up @@ -528,6 +529,7 @@ case class ObjectTypeExtensionDefinition(

case class InterfaceTypeExtensionDefinition(
name: String,
interfaces: Vector[NamedType],
fields: Vector[FieldDefinition],
directives: Vector[Directive] = Vector.empty,
comments: Vector[Comment] = Vector.empty,
Expand Down Expand Up @@ -670,6 +672,7 @@ sealed trait TypeExtensionDefinition extends TypeSystemExtensionDefinition with

sealed trait ObjectLikeTypeExtensionDefinition extends TypeExtensionDefinition {
def fields: Vector[FieldDefinition]
def interfaces: Vector[NamedType]
}

object AstNode {
Expand Down Expand Up @@ -993,13 +996,15 @@ object AstVisitor {
}
case n @ InterfaceTypeDefinition(
_,
interfaces,
fields,
dirs,
description,
comment,
trailingComments,
_) =>
if (breakOrSkip(onEnter(n))) {
interfaces.foreach(d => loop(d))
fields.foreach(d => loop(d))
dirs.foreach(d => loop(d))
description.foreach(s => loop(s))
Expand Down Expand Up @@ -1055,8 +1060,9 @@ object AstVisitor {
tc.foreach(s => loop(s))
breakOrSkip(onLeave(n))
}
case n @ InterfaceTypeExtensionDefinition(_, fields, dirs, comment, tc, _) =>
case n @ InterfaceTypeExtensionDefinition(_, interfaces, fields, dirs, comment, tc, _) =>
if (breakOrSkip(onEnter(n))) {
interfaces.foreach(d => loop(d))
fields.foreach(d => loop(d))
dirs.foreach(d => loop(d))
comment.foreach(s => loop(s))
Expand Down
Expand Up @@ -48,10 +48,7 @@ object IntrospectionParser {
.map(um.getListValue)
.getOrElse(Vector.empty)
.map(field => parseField(field, path :+ "fields")),
interfaces = mapFieldOpt(tpe, "interfaces")
.map(um.getListValue)
.getOrElse(Vector.empty)
.map(i => parseNamedTypeRef(i, path :+ "interfaces"))
interfaces = parseNamedInterfacesTypeRef(tpe, path),
)

private def parseInterface[In: InputUnmarshaller](tpe: In, path: Vector[String]) =
Expand All @@ -62,6 +59,7 @@ object IntrospectionParser {
.map(um.getListValue)
.getOrElse(Vector.empty)
.map(field => parseField(field, path :+ "fields")),
interfaces = parseNamedInterfacesTypeRef(tpe, path),
possibleTypes = mapFieldOpt(tpe, "possibleTypes")
.map(um.getListValue)
.getOrElse(Vector.empty)
Expand Down Expand Up @@ -166,6 +164,12 @@ object IntrospectionParser {
case _ => parseNamedTypeRef(in, path)
}

private def parseNamedInterfacesTypeRef[In: InputUnmarshaller](in: In, path: Vector[String]): Seq[IntrospectionNamedTypeRef] =
mapFieldOpt(in, "interfaces")
.map(um.getListValue)
.getOrElse(Vector.empty)
.map(i => parseNamedTypeRef(i, path :+ "interfaces"))

private def required[T](obj: Option[T], path: Vector[String]) = obj match {
case Some(o) => o
case None => error(s"Required property is missing at path: ${path.mkString(".")}")
Expand Down
Expand Up @@ -59,6 +59,7 @@ case class IntrospectionInterfaceType(
name: String,
description: Option[String],
fields: Seq[IntrospectionField],
interfaces: Seq[IntrospectionNamedTypeRef],
possibleTypes: Seq[IntrospectionNamedTypeRef])
extends IntrospectionType {
val kind = TypeKind.Interface
Expand Down
Expand Up @@ -267,7 +267,7 @@ package object introspection {
"interfaces",
OptionType(ListType(__Type)),
resolve = _.value._2 match {
case t: ObjectType[_, _] =>
case t: ObjectLikeType[_, _] =>
Some(t.allInterfaces.asInstanceOf[Vector[Type]].map(true -> _))
case _ => None
}
Expand Down
8 changes: 4 additions & 4 deletions modules/core/src/main/scala/sangria/macros/AstLiftable.scala
Expand Up @@ -81,8 +81,8 @@ trait AstLiftable {

case ObjectTypeExtensionDefinition(n, i, f, d, c, tc, p) =>
q"_root_.sangria.ast.ObjectTypeExtensionDefinition($n, $i, $f, $d, $c, $tc, $p)"
case InterfaceTypeExtensionDefinition(n, f, d, c, tc, p) =>
q"_root_.sangria.ast.InterfaceTypeExtensionDefinition($n, $f, $d, $c, $tc, $p)"
case InterfaceTypeExtensionDefinition(n, i, f, d, c, tc, p) =>
q"_root_.sangria.ast.InterfaceTypeExtensionDefinition($n, $i, $f, $d, $c, $tc, $p)"
case InputObjectTypeExtensionDefinition(n, f, d, c, tc, p) =>
q"_root_.sangria.ast.InputObjectTypeExtensionDefinition($n, $f, $d, $c, $tc, $p)"
case UnionTypeExtensionDefinition(n, t, d, c, p) =>
Expand All @@ -98,8 +98,8 @@ trait AstLiftable {
q"_root_.sangria.ast.EnumTypeDefinition($n, $v, $d, $desc, $c, $tc, $p)"
case InputObjectTypeDefinition(n, f, d, desc, c, tc, p) =>
q"_root_.sangria.ast.InputObjectTypeDefinition($n, $f, $d, $desc, $c, $tc, $p)"
case InterfaceTypeDefinition(n, f, d, desc, c, tc, p) =>
q"_root_.sangria.ast.InterfaceTypeDefinition($n, $f, $d, $desc, $c, $tc, $p)"
case InterfaceTypeDefinition(n, i, f, d, desc, c, tc, p) =>
q"_root_.sangria.ast.InterfaceTypeDefinition($n, $i, $f, $d, $desc, $c, $tc, $p)"
case ObjectTypeDefinition(n, i, f, d, desc, c, tc, p) =>
q"_root_.sangria.ast.ObjectTypeDefinition($n, $i, $f, $d, $desc, $c, $tc, $p)"
case ScalarTypeDefinition(n, d, desc, c, p) =>
Expand Down
42 changes: 30 additions & 12 deletions modules/core/src/main/scala/sangria/parser/QueryParser.scala
Expand Up @@ -233,8 +233,8 @@ trait TypeSystemDefinitions {
}

def ObjectTypeDefinition = rule {
Description ~ Comments ~ trackPos ~ `type` ~ Name ~ (ImplementsInterfaces.? ~> (_.getOrElse(
Vector.empty))) ~ (DirectivesConst.? ~> (_.getOrElse(Vector.empty))) ~ FieldsDefinition.? ~> (
Description ~ Comments ~ trackPos ~ `type` ~ Name ~ OptImplementsInterfaces ~
(DirectivesConst.? ~> (_.getOrElse(Vector.empty))) ~ FieldsDefinition.? ~> (
(descr, comment, location, name, interfaces, dirs, fields) =>
ast.ObjectTypeDefinition(
name,
Expand Down Expand Up @@ -272,8 +272,8 @@ trait TypeSystemDefinitions {
}

def ObjectTypeExtensionDefinition = rule {
(Comments ~ trackPos ~ extend ~ `type` ~ Name ~ (ImplementsInterfaces.? ~> (_.getOrElse(
Vector.empty))) ~ (DirectivesConst.? ~> (_.getOrElse(Vector.empty))) ~ FieldsDefinition ~> (
(Comments ~ trackPos ~ extend ~ `type` ~ Name ~ OptImplementsInterfaces ~
(DirectivesConst.? ~> (_.getOrElse(Vector.empty))) ~ FieldsDefinition ~> (
(comment, location, name, interfaces, dirs, fields) =>
ast.ObjectTypeExtensionDefinition(
name,
Expand All @@ -283,8 +283,8 @@ trait TypeSystemDefinitions {
comment,
fields._2,
location))) |
(Comments ~ trackPos ~ extend ~ `type` ~ Name ~ (ImplementsInterfaces.? ~> (_.getOrElse(
Vector.empty))) ~ DirectivesConst ~> ((comment, location, name, interfaces, dirs) =>
(Comments ~ trackPos ~ extend ~ `type` ~ Name ~ OptImplementsInterfaces ~
DirectivesConst ~> ((comment, location, name, interfaces, dirs) =>
ast.ObjectTypeExtensionDefinition(
name,
interfaces,
Expand All @@ -306,23 +306,35 @@ trait TypeSystemDefinitions {
}

def InterfaceTypeExtensionDefinition = rule {
(Comments ~ trackPos ~ extend ~ interface ~ Name ~ (DirectivesConst.? ~> (_.getOrElse(
Vector.empty))) ~ FieldsDefinition ~> ((comment, location, name, dirs, fields) =>
(Comments ~ trackPos ~ extend ~ interface ~ Name ~ OptImplementsInterfaces ~ (DirectivesConst.? ~> (_.getOrElse(
Vector.empty))) ~ FieldsDefinition ~> ((comment, location, name, interfaces, dirs, fields) =>
ast.InterfaceTypeExtensionDefinition(
name,
interfaces,
fields._1.toVector,
dirs,
comment,
fields._2,
location))) |
(Comments ~ trackPos ~ extend ~ interface ~ Name ~ DirectivesConst ~> (
(comment, location, name, dirs) =>
(Comments ~ trackPos ~ extend ~ interface ~ Name ~ OptImplementsInterfaces ~ DirectivesConst ~> (
(comment, location, name, interfaces, dirs) =>
ast.InterfaceTypeExtensionDefinition(
name,
interfaces,
Vector.empty,
dirs,
comment,
Vector.empty,
location))) |
(Comments ~ trackPos ~ extend ~ interface ~ Name ~ ImplementsInterfaces ~> (
(comment, location, name, interfaces) =>
ast.InterfaceTypeExtensionDefinition(
name,
interfaces,
Vector.empty,
Vector.empty,
comment,
Vector.empty,
location)))
}

Expand Down Expand Up @@ -388,6 +400,11 @@ trait TypeSystemDefinitions {
implements ~ ws('&').? ~ NamedType.+(ws('&')) ~> (_.toVector)
}

def OptImplementsInterfaces = rule {
ImplementsInterfaces.? ~> (_.getOrElse(
Vector.empty))
}

def FieldsDefinition = rule {
wsNoComment('{') ~ (test(
legacyEmptyFields) ~ FieldDefinition.* | FieldDefinition.+) ~ Comments ~ wsNoComment(
Expand All @@ -413,10 +430,11 @@ trait TypeSystemDefinitions {
}

def InterfaceTypeDefinition = rule {
Description ~ Comments ~ trackPos ~ interface ~ Name ~ (DirectivesConst.? ~> (_.getOrElse(
Vector.empty))) ~ FieldsDefinition.? ~> ((descr, comment, location, name, dirs, fields) =>
Description ~ Comments ~ trackPos ~ interface ~ Name ~ OptImplementsInterfaces ~ (DirectivesConst.? ~> (_.getOrElse(
Vector.empty))) ~ FieldsDefinition.? ~> ((descr, comment, location, name, interfaces, dirs, fields) =>
ast.InterfaceTypeDefinition(
name,
interfaces,
fields.fold(Vector.empty[ast.FieldDefinition])(_._1.toVector),
dirs,
descr,
Expand Down
16 changes: 10 additions & 6 deletions modules/core/src/main/scala/sangria/renderer/QueryRenderer.scala
Expand Up @@ -659,12 +659,14 @@ object QueryRenderer {
renderDirs(dirs, config, indent, frontSep = true) +
renderInputFieldDefinitions(fields, itd, indent, config, frontSep = true)

case itd @ InterfaceTypeDefinition(name, fields, dirs, description, _, _, _) =>
case itd @ InterfaceTypeDefinition(name, interfaces, fields, dirs, description, _, _, _) =>
renderDescription(itd, prev, indent, config) +
renderComment(itd, description.orElse(prev), indent, config) +
indent.str + "interface" + config.mandatorySeparator + name +
renderDirs(dirs, config, indent, frontSep = true) +
renderFieldDefinitions(fields, itd, indent, config, frontSep = true)
config.mandatorySeparator +
renderInterfaces(interfaces, config, indent) +
renderDirs(dirs, config, indent, withSep = fields.nonEmpty) +
renderFieldDefinitions(fields, itd, indent, config)

case utd @ UnionTypeDefinition(name, types, dirs, description, _, _) =>
val typesString =
Expand Down Expand Up @@ -719,11 +721,13 @@ object QueryRenderer {
renderDirs(dirs, config, indent, withSep = fields.nonEmpty) +
renderFieldDefinitions(fields, ted, indent, config)

case ext @ InterfaceTypeExtensionDefinition(name, fields, dirs, _, _, _) =>
case ext @ InterfaceTypeExtensionDefinition(name, interfaces, fields, dirs, _, _, _) =>
renderComment(ext, prev, indent, config) +
indent.str + "extend" + config.mandatorySeparator + "interface" + config.mandatorySeparator + name +
renderDirs(dirs, config, indent, frontSep = true) +
renderFieldDefinitions(fields, ext, indent, config, frontSep = true)
config.mandatorySeparator +
renderInterfaces(interfaces, config, indent) +
renderDirs(dirs, config, indent, withSep = fields.nonEmpty) +
renderFieldDefinitions(fields, ext, indent, config)

case ext @ UnionTypeExtensionDefinition(name, types, dirs, _, _) =>
val typesString =
Expand Down
Expand Up @@ -48,6 +48,9 @@ object SchemaRenderer {
def renderImplementedInterfaces(tpe: IntrospectionObjectType) =
tpe.interfaces.map(t => ast.NamedType(t.name)).toVector

def renderImplementedInterfaces(tpe: IntrospectionInterfaceType) =
tpe.interfaces.map(t => ast.NamedType(t.name)).toVector

def renderImplementedInterfaces(tpe: ObjectLikeType[_, _]) =
tpe.allInterfaces.map(t => ast.NamedType(t.name))

Expand Down Expand Up @@ -219,12 +222,14 @@ object SchemaRenderer {
def renderInterface(tpe: IntrospectionInterfaceType) =
ast.InterfaceTypeDefinition(
tpe.name,
renderImplementedInterfaces(tpe),
renderFieldsI(tpe.fields),
description = renderDescription(tpe.description))

def renderInterface(tpe: InterfaceType[_, _]) =
ast.InterfaceTypeDefinition(
tpe.name,
renderImplementedInterfaces(tpe),
renderFields(tpe.uniqueFields),
tpe.astDirectives,
renderDescription(tpe.description))
Expand Down
Expand Up @@ -69,6 +69,7 @@ trait AstSchemaBuilder[Ctx] {
definition: ast.InterfaceTypeDefinition,
extensions: List[ast.InterfaceTypeExtensionDefinition],
fields: () => List[Field[Ctx, Any]],
interfaces: List[InterfaceType[Ctx, Any]],
mat: AstSchemaMaterializer[Ctx]): Option[InterfaceType[Ctx, Any]]

def extendInterfaceType(
Expand Down Expand Up @@ -443,6 +444,7 @@ class DefaultAstSchemaBuilder[Ctx] extends AstSchemaBuilder[Ctx] {
definition: ast.InterfaceTypeDefinition,
extensions: List[ast.InterfaceTypeExtensionDefinition],
fields: () => List[Field[Ctx, Any]],
interfaces: List[InterfaceType[Ctx, Any]],
mat: AstSchemaMaterializer[Ctx]) = {
val directives = definition.directives ++ extensions.flatMap(_.directives)

Expand Down
Expand Up @@ -642,7 +642,7 @@ class AstSchemaMaterializer[Ctx] private (
tpe,
extensions.toList,
() => buildFields(origin, tpe, tpe.fields, extensions).toList,
buildInterfaces(origin, tpe, tpe.interfaces, extensions).toList,
buildInterfaces(origin, tpe.interfaces, extensions).toList,
this
)
}
Expand All @@ -667,6 +667,7 @@ class AstSchemaMaterializer[Ctx] private (
tpe,
extensions.toList,
() => buildFields(origin, tpe, tpe.fields, extensions).toList,
buildInterfaces(origin, tpe.interfaces, extensions).toList,
this)
}

Expand Down Expand Up @@ -712,9 +713,8 @@ class AstSchemaMaterializer[Ctx] private (

def buildInterfaces(
origin: MatOrigin,
tpe: ast.ObjectTypeDefinition,
interfaces: Vector[ast.NamedType],
extensions: Vector[ast.ObjectTypeExtensionDefinition]) = {
extensions: Vector[ast.ObjectLikeTypeExtensionDefinition]) = {
val extraInts = extensions.flatMap(_.interfaces)
val allInts = interfaces ++ extraInts

Expand Down
Expand Up @@ -36,6 +36,7 @@ trait IntrospectionSchemaBuilder[Ctx] {
def buildInterfaceType(
definition: IntrospectionInterfaceType,
fields: () => List[Field[Ctx, Any]],
interfaces: List[InterfaceType[Ctx, Any]],
mat: IntrospectionSchemaMaterializer[Ctx, _]): Option[InterfaceType[Ctx, Any]]

def buildUnionType(
Expand Down Expand Up @@ -157,13 +158,14 @@ class DefaultIntrospectionSchemaBuilder[Ctx] extends IntrospectionSchemaBuilder[
def buildInterfaceType(
definition: IntrospectionInterfaceType,
fields: () => List[Field[Ctx, Any]],
interfaces: List[InterfaceType[Ctx, Any]],
mat: IntrospectionSchemaMaterializer[Ctx, _]) =
Some(
InterfaceType[Ctx, Any](
name = typeName(definition),
description = typeDescription(definition),
fieldsFn = fields,
interfaces = Nil,
interfaces = interfaces,
manualPossibleTypes = () => Nil,
astDirectives = Vector.empty,
astNodes = Vector.empty
Expand Down
Expand Up @@ -140,7 +140,7 @@ class IntrospectionSchemaMaterializer[Ctx, T: InputUnmarshaller](
this)

def buildInterfaceDef(tpe: IntrospectionInterfaceType) =
builder.buildInterfaceType(tpe, () => tpe.fields.toList.flatMap(buildField(tpe, _)), this)
builder.buildInterfaceType(tpe, () => tpe.fields.toList.flatMap(buildField(tpe, _)), tpe.interfaces.toList.map(getInterfaceType), this)

def buildUnionDef(tpe: IntrospectionUnionType) =
builder.buildUnionType(tpe, tpe.possibleTypes.toList.map(getObjectType), this)
Expand Down
Expand Up @@ -38,7 +38,9 @@ interface Bar {
four(argument: String = "string"): String
}
interface AnnotatedInterface @onInterface {
interface AnnotatedInterface implements Bar @onInterface {
one: Type
four(argument: String = "string"): String
annotatedField(arg: Type @onArg): Type @onField
}
Expand Down
Expand Up @@ -977,6 +977,7 @@ class LiteralMacroSpec extends AnyWordSpec with Matchers {
),
InterfaceTypeDefinition(
"Bar",
Vector.empty,
Vector(
FieldDefinition(
"one",
Expand Down Expand Up @@ -1012,6 +1013,7 @@ class LiteralMacroSpec extends AnyWordSpec with Matchers {
),
InterfaceTypeDefinition(
"AnnotatedInterface",
Vector.empty,
Vector(FieldDefinition(
"annotatedField",
NamedType("Type", None),
Expand Down

0 comments on commit 7b563f2

Please sign in to comment.