Skip to content

Commit

Permalink
Tweaked schema rendering and added round-trip tests for extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
milessabin committed Nov 3, 2023
1 parent e798d64 commit 6f6713c
Show file tree
Hide file tree
Showing 2 changed files with 187 additions and 32 deletions.
112 changes: 80 additions & 32 deletions modules/core/src/main/scala/schema.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1796,25 +1796,34 @@ object SchemaValidator {

object SchemaRenderer {
def renderSchema(schema: Schema): String = {
def mkRootDef(fieldName: String)(tpe: NamedType): String =
s"$fieldName: ${tpe.name}"

val fields =
mkRootDef("query")(schema.queryType) ::
List(
schema.mutationType.map(mkRootDef("mutation")),
schema.subscriptionType.map(mkRootDef("subscription"))
).flatten

val schemaDefn = {
val dirs0 = schema.schemaType.directives
if (fields.sizeCompare(1) == 0 && schema.queryType =:= schema.ref("Query") && dirs0.isEmpty) ""
val dirs0 = schema.baseSchemaType.directives
if (
schema.queryType.name == "Query" &&
schema.mutationType.map(_.name == "Mutation").getOrElse(true) &&
schema.subscriptionType.map(_.name == "Subscription").getOrElse(true) &&
dirs0.isEmpty
) ""
else {
val fields =
schema.baseSchemaType match {
case twf: TypeWithFields => twf.fields.map(renderField)
case _ => Nil
}

val dirs = renderDirectives(dirs0)
fields.mkString(s"schema$dirs {\n ", "\n ", "\n}\n")
}
}

val schemaExtnDefns =
if(schema.schemaExtensions.isEmpty) ""
else "\n"+schema.schemaExtensions.map(renderSchemaExtension).mkString("\n")

val typeExtnDefns =
if(schema.typeExtensions.isEmpty) ""
else "\n"+schema.typeExtensions.map(renderTypeExtension).mkString("\n")

val dirDefns = {
val nonBuiltInDefns =
schema.directives.filter {
Expand All @@ -1827,7 +1836,9 @@ object SchemaRenderer {
}

schemaDefn ++
schema.types.map(renderTypeDefn).mkString("\n") ++
schema.baseTypes.map(renderTypeDefn).mkString("\n") ++
schemaExtnDefns ++
typeExtnDefns ++
dirDefns
}

Expand Down Expand Up @@ -1855,41 +1866,78 @@ object SchemaRenderer {
s"$nme(${args.map(renderInputValue).mkString(", ")}): ${renderType(tpe)}$dirs"
}

def renderExtension(extension: TypeExtension): String = {
def renderSchemaExtension(extension: SchemaExtension): String = {
val SchemaExtension(ops0, dirs0) = extension
val dirs = renderDirectives(dirs0)
val ops =
if (ops0.isEmpty) ""
else
s"""| {
| ${ops0.map(renderField).mkString("\n ")}
|}""".stripMargin

s"extend schema$dirs$ops"
}

def renderTypeExtension(extension: TypeExtension): String = {
extension match {
case ScalarExtension(nme, dirs0) =>
val dirs = renderDirectives(dirs0)
s"extend scalar $nme$dirs"

case ObjectExtension(nme, fields, ifs0, dirs0) =>
case ObjectExtension(nme, fields0, ifs0, dirs0) =>
val ifs = if (ifs0.isEmpty) "" else " implements " + ifs0.map(_.name).mkString("&")
val dirs = renderDirectives(dirs0)
s"""|extend type $nme$ifs$dirs {
| ${fields.map(renderField).mkString("\n ")}
|}""".stripMargin
val fields =
if(fields0.isEmpty) ""
else
s"""| {
| ${fields0.map(renderField).mkString("\n ")}
|}""".stripMargin

case InterfaceExtension(nme, fields, ifs0, dirs0) =>
s"extend type $nme$ifs$dirs$fields"

case InterfaceExtension(nme, fields0, ifs0, dirs0) =>
val ifs = if (ifs0.isEmpty) "" else " implements " + ifs0.map(_.name).mkString("&")
val dirs = renderDirectives(dirs0)
s"""|extend interface $nme$ifs$dirs {
| ${fields.map(renderField).mkString("\n ")}
|}""".stripMargin
val fields =
if(fields0.isEmpty) ""
else
s"""| {
| ${fields0.map(renderField).mkString("\n ")}
|}""".stripMargin

case UnionExtension(nme, members, dirs0) =>
s"extend interface $nme$ifs$dirs$fields"

case UnionExtension(nme, members0, dirs0) =>
val dirs = renderDirectives(dirs0)
s"extend union $nme$dirs = ${members.map(_.name).mkString(" | ")}"
val members =
if(members0.isEmpty) ""
else s" = ${members0.map(_.name).mkString(" | ")}"

s"extend union $nme$dirs$members"

case EnumExtension(nme, enumValues, dirs0) =>
case EnumExtension(nme, values0, dirs0) =>
val dirs = renderDirectives(dirs0)
s"""|extend enum $nme$dirs {
| ${enumValues.map(renderEnumValueDefinition).mkString("\n ")}
|}""".stripMargin
val values =
if(values0.isEmpty) ""
else
s"""| {
| ${values0.map(renderEnumValueDefinition).mkString("\n ")}
|}""".stripMargin

case InputObjectExtension(nme, inputFields, dirs0) =>
s"extend enum $nme$dirs$values"

case InputObjectExtension(nme, fields0, dirs0) =>
val dirs = renderDirectives(dirs0)
s"""|extend input $nme$dirs {
| ${inputFields.map(renderInputValue).mkString("\n ")}
|}""".stripMargin
val fields =
if(fields0.isEmpty) ""
else
s"""| {
| ${fields0.map(renderInputValue).mkString("\n ")}
|}""".stripMargin

s"extend input $nme$dirs$fields"
}
}

Expand Down
107 changes: 107 additions & 0 deletions modules/core/src/test/scala/sdl/SDLSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -312,4 +312,111 @@ final class SDLSuite extends CatsEffectSuite {

assertEquals(ser, schema.success)
}

test("round-trip extensions") {
val schema =
"""|schema {
| query: MyQuery
|}
|type MyQuery {
| foo(s: Scalar, i: Input, e: Enum): Union
|}
|type Mutation {
| bar: Int
|}
|scalar Scalar
|interface Intrf {
| bar: String
|}
|type Obj implements Intrf {
| bar: String
|}
|union Union = Intrf | Obj
|enum Enum {
| A
| B
|}
|input Input {
| baz: Float
|}
|type Quux {
| quux: String
|}
|extend schema @Sch {
| mutation: Mutation
|}
|extend scalar Scalar @Sca
|extend interface Intrf @Intrf {
| baz: Boolean
|}
|extend type Obj @Obj {
| baz: Boolean
| quux: String
|}
|extend union Union @Uni = Quux
|extend enum Enum @Enu {
| C
|}
|extend input Input @Inp {
| foo: Int
|}
|directive @Sch on SCHEMA
|directive @Sca on SCALAR
|directive @Obj on OBJECT
|directive @Intrf on INTERFACE
|directive @Uni on UNION
|directive @Enu on ENUM
|directive @Inp on INPUT_OBJECT
|""".stripMargin

val res = SchemaParser.parseText(schema)
val ser = res.map(_.toString)

assertEquals(ser, schema.success)
}

test("round-trip extensions (no fields or members") {
val schema =
"""|schema {
| query: MyQuery
|}
|type MyQuery {
| foo(s: Scalar, i: Input, e: Enum): Union
|}
|scalar Scalar
|interface Intrf {
| bar: String
|}
|type Obj implements Intrf {
| bar: String
|}
|union Union = Intrf | Obj
|enum Enum {
| A
| B
|}
|input Input {
| baz: Float
|}
|extend schema @Sch
|extend scalar Scalar @Sca
|extend interface Intrf @Intrf
|extend type Obj @Obj
|extend union Union @Uni
|extend enum Enum @Enu
|extend input Input @Inp
|directive @Sch on SCHEMA
|directive @Sca on SCALAR
|directive @Obj on OBJECT
|directive @Intrf on INTERFACE
|directive @Uni on UNION
|directive @Enu on ENUM
|directive @Inp on INPUT_OBJECT
|""".stripMargin

val res = SchemaParser.parseText(schema)
val ser = res.map(_.toString)

assertEquals(ser, schema.success)
}
}

0 comments on commit 6f6713c

Please sign in to comment.