Skip to content

Commit

Permalink
Added nodes field to NodeDefinition. Closes #18
Browse files Browse the repository at this point in the history
  • Loading branch information
OlegIlyenko committed Jan 16, 2017
1 parent cb608d0 commit a5ec9dc
Show file tree
Hide file tree
Showing 10 changed files with 254 additions and 49 deletions.
12 changes: 3 additions & 9 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,21 +1,15 @@
language: scala
scala:
- 2.12.0
- 2.12.1
- 2.11.8
jdk:
- oraclejdk8

script: |
if [[ "$TRAVIS_SCALA_VERSION" == 2.11.* ]]; then
sbt ++$TRAVIS_SCALA_VERSION clean coverage test
else
sbt ++$TRAVIS_SCALA_VERSION clean test
fi
sbt ++$TRAVIS_SCALA_VERSION clean coverage test
after_success: |
if [[ "$TRAVIS_SCALA_VERSION" == 2.11.* ]]; then
sbt ++$TRAVIS_SCALA_VERSION coverageReport coveralls
fi
sbt ++$TRAVIS_SCALA_VERSION coverageReport coveralls
cache:
directories:
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## Upcoming

* Updated sangria to v1.0.0
* Added `nodes` field to `NodeDefinition` (#18). It's a minor breaking change. Please note that because of this change the `resolve` function of `Node.definition` now should return `LeafAction` instead of `Action`. This slightly limits it's capabilities, but it was a necessary change to implement `nodes` field.

## v1.0.0-RC5 (2016-11-28)

* Updated sangria to v1.0.0-RC5
Expand Down
6 changes: 3 additions & 3 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
name := "sangria-relay"
organization := "org.sangria-graphql"
version := "1.0.0-RC6-SNAPSHOT"
version := "1.0.0-SNAPSHOT"

description := "Sangria Relay Support"
homepage := Some(url("http://sangria-graphql.org"))
licenses := Seq("Apache License, ASL Version 2.0" url("http://www.apache.org/licenses/LICENSE-2.0"))

scalaVersion := "2.12.0"
scalaVersion := "2.12.1"
crossScalaVersions := Seq("2.11.8", "2.12.0")

scalacOptions ++= Seq("-deprecation", "-feature")
Expand All @@ -19,7 +19,7 @@ scalacOptions ++= {
}

libraryDependencies ++= Seq(
"org.sangria-graphql" %% "sangria" % "1.0.0-RC5",
"org.sangria-graphql" %% "sangria" % "1.0.0-SNAPSHOT",
"org.scalatest" %% "scalatest" % "3.0.0" % "test")

// Publishing
Expand Down
2 changes: 1 addition & 1 deletion project/coverage.sbt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.4.0")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.5.0")
addSbtPlugin("org.scoverage" % "sbt-coveralls" % "1.1.0")
24 changes: 15 additions & 9 deletions src/main/scala/sangria/relay/Node.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,12 @@ object Node {
val GlobalIdFieldDescription = "The ID of an object"

object Args {
val ID = Argument("id", IDType, description = "The ID of an object")

val All = ID :: Nil
val Id = Argument("id", IDType, description = "The ID of an object")
val Ids = Argument("ids", ListInputType(IDType), description = "The IDs of objects")
}

def definitionById[Ctx, Val, Res](
resolve: (String, Context[Ctx, Val]) Action[Ctx, Option[Res]],
resolve: (String, Context[Ctx, Val]) LeafAction[Ctx, Option[Res]],
possibleTypes: List[PossibleNodeObject[Ctx, Node]] = Nil,
tags: List[FieldTag] = Nil,
complexity: Option[(Ctx, Args, Double) Double] = None) = {
Expand All @@ -39,15 +38,22 @@ object Node {
val nodeField = Field("node", OptionType(interfaceType), Some("Fetches an object given its ID"),
tags = tags,
complexity = complexity,
arguments = Args.All,
arguments = Args.Id :: Nil,
possibleTypes = possibleTypes map (pt PossibleObject(pt.objectType)),
resolve = (ctx: Context[Ctx, Val]) resolve(ctx.arg(Args.Id), ctx))

val nodesField = Field("nodes", ListType(OptionType(interfaceType)), Some("Fetches objects given their IDs"),
tags = tags,
complexity = complexity,
arguments = Args.Ids :: Nil,
possibleTypes = possibleTypes map (pt PossibleObject(pt.objectType)),
resolve = (ctx: Context[Ctx, Val]) resolve(ctx.arg(Args.ID), ctx))
resolve = (ctx: Context[Ctx, Val]) Action.sequence(ctx.arg(Args.Ids).map(resolve(_, ctx))))

NodeDefinition(interfaceType, nodeField)
NodeDefinition(interfaceType, nodeField, nodesField)
}

def definition[Ctx, Val, Res](
resolve: (GlobalId, Context[Ctx, Val]) Action[Ctx, Option[Res]],
resolve: (GlobalId, Context[Ctx, Val]) LeafAction[Ctx, Option[Res]],
possibleTypes: List[PossibleNodeObject[Ctx, Node]] = Nil,
tags: List[FieldTag] = Nil,
complexity: Option[(Ctx, Args, Double) Double] = None) =
Expand Down Expand Up @@ -110,7 +116,7 @@ case class UnknownPossibleType(value: Any) extends IllegalArgumentException(s"Un

case class WrongGlobalId(id: String) extends Exception(s"Invalid Global ID: $id") with UserFacingError

case class NodeDefinition[Ctx, Val, Res](interface: InterfaceType[Ctx, Res], field: Field[Ctx, Val])
case class NodeDefinition[Ctx, Val, Res](interface: InterfaceType[Ctx, Res], nodeField: Field[Ctx, Val], nodeFields: Field[Ctx, Val])

@implicitNotFound("Type ${T} is not identifiable. Please consider defining implicit instance of sangria.relay.Identifiable or sangria.relay.IdentifiableNode for type ${T} or extending sangria.relay.Node trait.")
trait Identifiable[T] {
Expand Down
3 changes: 2 additions & 1 deletion src/test/scala/sangria/relay/GlobalIdSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class GlobalIdSpec extends WordSpec with Matchers with AwaitSupport {
)
}

val NodeDefinition(nodeInterface, nodeField) = Node.definition((id: GlobalId, ctx: Context[Repo, Unit]) {
val NodeDefinition(nodeInterface, nodeField, nodesField) = Node.definition((id: GlobalId, ctx: Context[Repo, Unit]) {
if (id.typeName == "User") ctx.ctx.Users.find(_.id == id.id)
else if (id.typeName == "CustomPhoto") ctx.ctx.Photos.find(_.photoId == id.id)
else ctx.ctx.Posts.find(_.postId == id.id.toInt)
Expand All @@ -76,6 +76,7 @@ class GlobalIdSpec extends WordSpec with Matchers with AwaitSupport {
val QueryType: ObjectType[Repo, Unit] = ObjectType("Query",
fields[Repo, Unit](
nodeField,
nodesField,
Field("allObjects", OptionType(ListType(nodeInterface)),
resolve = ctx ctx.ctx.Users ++ ctx.ctx.Photos ++ ctx.ctx.Posts)))

Expand Down
132 changes: 110 additions & 22 deletions src/test/scala/sangria/relay/NodeSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package sangria.relay
import org.scalatest.{Matchers, WordSpec}
import sangria.execution.Executor
import sangria.parser.QueryParser
import sangria.relay.util.{ResultHelper, AwaitSupport}
import sangria.relay.util.{AwaitSupport, DebugUtil, ResultHelper}
import sangria.schema._

import scala.concurrent.ExecutionContext.Implicits.global
Expand Down Expand Up @@ -31,7 +31,7 @@ class NodeSpec extends WordSpec with Matchers with AwaitSupport with ResultHelpe
)
}

val NodeDefinition(nodeInterface, nodeField) = Node.definitionById((id: String, ctx: Context[Repo, Unit]) {
val NodeDefinition(nodeInterface, nodeField, nodesField) = Node.definitionById((id: String, ctx: Context[Repo, Unit]) {
if (ctx.ctx.Users exists (_.id == id)) ctx.ctx.Users.find(_.id == id)
else ctx.ctx.Photos.find(_.photoId == id)
}, Node.possibleNodeTypes[Repo, Node](UserType, PhotoType))
Expand All @@ -46,7 +46,7 @@ class NodeSpec extends WordSpec with Matchers with AwaitSupport with ResultHelpe
Field("id", IDType, resolve = _.value.photoId),
Field("width", OptionType(IntType), resolve = _.value.width)))

val QueryType: ObjectType[Repo, Unit] = ObjectType("Query", fields[Repo, Unit](nodeField))
val QueryType: ObjectType[Repo, Unit] = ObjectType("Query", fields[Repo, Unit](nodeField, nodesField))

val schema = Schema(QueryType)

Expand All @@ -63,12 +63,28 @@ class NodeSpec extends WordSpec with Matchers with AwaitSupport with ResultHelpe
""")


Executor.execute(schema, doc, userContext = new Repo).await should be (
Executor.execute(schema, doc, userContext = new Repo).await should be (
Map(
"data" Map(
"node" Map("id" "1"))))
}

"gets the correct IDs for users" in {
val Success(doc) = QueryParser.parse(
"""
{
nodes(ids: ["1", "2"]) {
id
}
}
""")

Executor.execute(schema, doc, userContext = new Repo).await should be (
Map(
"data" Map(
"nodes" List(Map("id" "1"), Map("id" "2")))))
}

"Gets the correct ID for photos" in {
val Success(doc) = QueryParser.parse(
"""
Expand All @@ -80,7 +96,7 @@ class NodeSpec extends WordSpec with Matchers with AwaitSupport with ResultHelpe
""")


Executor.execute(schema, doc, userContext = new Repo).await should be (
Executor.execute(schema, doc, userContext = new Repo).await should be (
Map(
"data" Map(
"node" Map("id" "4"))))
Expand All @@ -100,14 +116,63 @@ class NodeSpec extends WordSpec with Matchers with AwaitSupport with ResultHelpe
""")


Executor.execute(schema, doc, userContext = new Repo).await should be (
Executor.execute(schema, doc, userContext = new Repo).await should be (
Map(
"data" Map(
"node" Map(
"id" "4",
"width" 400))))
}

"gets the correct IDs for photos" in {
val Success(doc) = QueryParser.parse(
"""
{
nodes(ids: ["3", "4"]) {
id
... on Photo {
width
}
}
}
""")


Executor.execute(schema, doc, userContext = new Repo).await should be (
Map(
"data" Map(
"nodes" List(
Map("id" "3", "width" 300),
Map("id" "4", "width" 400)))))
}

"gets the correct IDs for multiple types" in {
val Success(doc) = QueryParser.parse(
"""
{
nodes(ids: ["1", "3"]) {
id
... on Photo {
width
}
... on User {
name
}
}
}
""")


Executor.execute(schema, doc, userContext = new Repo).await should be (
Map(
"data" Map(
"nodes" List(
Map("id" "1", "name" "John Doe"),
Map("id" "3", "width" 300)))))
}

"Gets the correct type name for users" in {
val Success(doc) = QueryParser.parse(
"""
Expand All @@ -120,7 +185,7 @@ class NodeSpec extends WordSpec with Matchers with AwaitSupport with ResultHelpe
""")


Executor.execute(schema, doc, userContext = new Repo).await should be (
Executor.execute(schema, doc, userContext = new Repo).await should be (
Map(
"data" Map(
"node" Map(
Expand All @@ -140,7 +205,7 @@ class NodeSpec extends WordSpec with Matchers with AwaitSupport with ResultHelpe
""")


Executor.execute(schema, doc, userContext = new Repo).await should be (
Executor.execute(schema, doc, userContext = new Repo).await should be (
Map(
"data" Map(
"node" Map(
Expand All @@ -162,7 +227,7 @@ class NodeSpec extends WordSpec with Matchers with AwaitSupport with ResultHelpe
""")


Executor.execute(schema, doc, userContext = new Repo).await should be (
Executor.execute(schema, doc, userContext = new Repo).await should be (
Map(
"data" Map(
"node" Map(
Expand All @@ -180,11 +245,28 @@ class NodeSpec extends WordSpec with Matchers with AwaitSupport with ResultHelpe
""")


Executor.execute(schema, doc, userContext = new Repo).await should be (
Executor.execute(schema, doc, userContext = new Repo).await should be (
Map(
"data" Map(
"node" null)))
}

"Returns nulls for bad IDs" in {
val Success(doc) = QueryParser.parse(
"""
{
nodes(ids: ["3", "5"]) {
id
}
}
""")


Executor.execute(schema, doc, userContext = new Repo).await should be (
Map(
"data" Map(
"nodes" List(Map("id" "3"), null))))
}
}

"Correctly introspects" should {
Expand Down Expand Up @@ -227,7 +309,7 @@ class NodeSpec extends WordSpec with Matchers with AwaitSupport with ResultHelpe
""")


Executor.execute(schema, doc, userContext = new Repo).await should be (
Executor.execute(schema, doc, userContext = new Repo).await should be (
Map(
"data" Map(
"__type" Map(
Expand Down Expand Up @@ -276,8 +358,7 @@ class NodeSpec extends WordSpec with Matchers with AwaitSupport with ResultHelpe
}
""")


Executor.execute(schema, doc, userContext = new Repo).await should be (
Executor.execute(schema, doc, userContext = new Repo).await should be (
Map(
"data" Map(
"__schema" Map(
Expand All @@ -287,21 +368,28 @@ class NodeSpec extends WordSpec with Matchers with AwaitSupport with ResultHelpe
"name" "node",
"type" Map(
"name" "Node",
"kind" "INTERFACE"
),
"args" List(
"kind" "INTERFACE"),
"args" Vector(
Map(
"name" "id",
"type" Map(
"kind" "NON_NULL",
"ofType" Map(
"name" "ID",
"kind" "SCALAR"
)
)
)
)
)
"kind" "SCALAR"))))),
Map(
"name" "nodes",
"type" Map(
"name" null,
"kind" "NON_NULL"),
"args" Vector(
Map(
"name" "ids",
"type" Map(
"kind" "NON_NULL",
"ofType" Map(
"name" null,
"kind" "LIST")))))
)
)
))))
Expand Down
Loading

0 comments on commit a5ec9dc

Please sign in to comment.