Skip to content

Commit

Permalink
Add supoort for mutation
Browse files Browse the repository at this point in the history
  • Loading branch information
VerachadW committed Sep 27, 2016
1 parent eb5f999 commit b68b3dc
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 37 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
# KraphQL [![Build Status](https://travis-ci.org/taskworld/kraph.svg?branch=master)](https://travis-ci.org/taskworld/kraph) [ ![Download](https://api.bintray.com/packages/tw/maven/kraph/images/download.svg) ](https://bintray.com/tw/maven/kraph/_latestVersion)
GraphQL query builder written in Kotlin

Note that this library is still in alpha stage. The syntax usage may subject to change. Please see the example from unit tests file for now.
26 changes: 20 additions & 6 deletions src/main/kotlin/com/taskworld/kraph/KraphQL.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,27 @@ class KraphQL(f: KraphQL.() -> Unit) {
f.invoke(this)
}

fun query(name: String = "", builder: FieldBuilder.() -> Unit) {
fun query(name: String? = null , builder: FieldBuilder.() -> Unit) {
document = DocumentNode(OperationNode(OperationType.QUERY, fields = FieldBuilder().apply(builder).fields, name = name))
}

fun mutation(name: String? = null, builder: MutationBuilder.() -> Unit) {
document = DocumentNode(OperationNode(OperationType.MUTATION, fields = MutationBuilder().apply(builder).mutations, name = name))
}

override fun toString(): String {
return document.print()
}

inner class MutationBuilder() {
internal val mutations = arrayListOf<MutationNode>()

fun func(name: String, args: Map<String, Any>, builder: FieldBuilder.() -> Unit) {
mutations += MutationNode(name, MutationArgumentNode(args), selectionSet(builder))
}

}

inner class FieldBuilder() {
internal val fields = arrayListOf<FieldNode>()
internal var selectionSet: SelectionSetNode? = null
Expand All @@ -32,17 +45,18 @@ class KraphQL(f: KraphQL.() -> Unit) {
}

private fun addField(name: String, params: Map<String, Any>? = null, builder: (FieldBuilder.() -> Unit)? = null) {
val args = params?.let { ArgumentsNode(it) }
val args = params?.let(::ArgumentNode)
selectionSet = builder?.let {
selectionSet(builder)
}
fields += FieldNode(name, arguments = args, selectionSet = selectionSet)
}

private fun selectionSet(f: FieldBuilder.() -> Unit): SelectionSetNode {
val builder = FieldBuilder().apply(f)
return SelectionSetNode(builder.fields)
}
}

private fun selectionSet(f: FieldBuilder.() -> Unit): SelectionSetNode {
val builder = FieldBuilder().apply(f)
return SelectionSetNode(builder.fields)
}

}
Expand Down
53 changes: 35 additions & 18 deletions src/main/kotlin/com/taskworld/kraph/LangNodes.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.taskworld.kraph

import com.sun.javadoc.FieldDoc

/**
* Created by VerachadW on 9/19/2016 AD.
*/
Expand All @@ -25,7 +27,7 @@ internal class SelectionSetNode(internal val fields: List<FieldNode>) : Node {
}

internal class OperationNode(internal val type: OperationType, internal val fields: List<FieldNode>,
internal val name: String? = null, internal val arguments: ArgumentsNode? = null): Node {
internal val name: String? = null, internal val arguments: ArgumentNode? = null): Node {

override fun print(): String {
val namePart = name?.let { " " + it } ?: ""
Expand All @@ -34,38 +36,53 @@ internal class OperationNode(internal val type: OperationType, internal val fiel
}
}

internal class ArgumentsNode(internal val args: Map<String, Any> = mapOf()) : Node {
internal open class ArgumentNode(internal val args: Map<String, Any> = mapOf()) : Node {

operator fun get(key: String): Any? = args[key]
fun size(): Int = args.size

override fun print(): String {
return "(${args.entries.foldIndexed(""){ index, acc, entry ->
var string = acc + "${entry.key}: ${
if(entry.value is String) {
"\"" + entry.value +"\""
} else {
entry.value
}
}"
if (index != args.size - 1) {
string += ", "
}
string
}})"
return "(${args.print()})"
}

}

internal class FieldNode(internal val name: String, internal val arguments: ArgumentsNode? = null, internal val selectionSet: SelectionSetNode? = null): Node {
internal open class FieldNode(internal val name: String, internal val arguments: ArgumentNode? = null, internal val selectionSet: SelectionSetNode? = null): Node {
override fun print(): String {
val selectionSetPart = selectionSet?.let { " " + it.print() } ?: ""
return "$name${ arguments?.print() ?: "" }$selectionSetPart"
}
}

fun <T: Node> List<T>.print() =
internal class MutationNode(name: String, arguments: MutationArgumentNode, selectionSet: SelectionSetNode) : FieldNode(name, arguments, selectionSet)

internal class MutationArgumentNode(args: Map<String, Any>) : ArgumentNode(args) {
override fun print(): String {
return "(input: { ${args.print()} })"
}
}

internal fun <T: Node> List<T>.print() =
this.fold(""){ acc, node ->
acc + node.print() + "\r\n"
}

internal fun Map<String, Any>.print() =
this.entries.foldIndexed(""){ index, acc, entry ->
var string = acc + "${entry.key}: ${
when(entry.value) {
is String -> {
"\"" + entry.value + "\""
}
else ->{
entry.value
}
}}"
if (index != this.size - 1) {
string += ", "
}
string
}

internal enum class OperationType {
QUERY,
MUTATION
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import org.junit.runner.RunWith
* Created by VerachadW on 9/20/2016 AD.
*/
@RunWith(JUnitPlatform::class)
class LangSpek : Spek({
class BuilderSpek : Spek({
describe("KraphQL Query DSL Builder") {
given("smaple query") {
val query = KraphQL {
Expand Down Expand Up @@ -59,7 +59,6 @@ class LangSpek : Spek({
assertThat(query.document.operation.fields[0].selectionSet!!.fields[3].name, equalTo("avatarUrl"))
}
it("should have size argument with value 100 for avatarUrl field") {
assertThat(query.document.operation.fields[0].selectionSet!!.fields[3].arguments!!.args.keys, hasElement("size"))
assertThat(query.document.operation.fields[0].selectionSet!!.fields[3].arguments!!.args["size"] as Int, equalTo(100))
}
it("should have two fields inside author object") {
Expand All @@ -72,5 +71,45 @@ class LangSpek : Spek({
assertThat(query.document.operation.fields[0].selectionSet!!.fields[2].selectionSet!!.fields[1].name, equalTo("email"))
}
}
given("sample mutation") {
val query = KraphQL {
mutation {
func("registerUser", mapOf("email" to "[email protected]", "password" to "abcd1234", "age" to 30)) {
field("id")
field("token")
}
}
}
it("should have mutation operation type") {
assertThat(query.document.operation.type, isA(equalTo(OperationType.MUTATION)))
}
it("should have only 1 mutation") {
assertThat(query.document.operation.fields, hasSize(equalTo(1)))
}
it("should have mutation named registerUser") {
assertThat(query.document.operation.fields[0].name, equalTo("registerUser"))
}
it("should have 3 arguments in registerUser mutation") {
assertThat(query.document.operation.fields[0].arguments!!.args.entries, hasSize(equalTo(3)))
}
it("should have argument in registerUser mutation with named email and value as [email protected]") {
assertThat(query.document.operation.fields[0].arguments!!.args["email"] as String, equalTo("[email protected]"))
}
it("should have argument in registerUser mutation with named password and value as abcd1234") {
assertThat(query.document.operation.fields[0].arguments!!.args["password"] as String, equalTo("abcd1234"))
}
it("should have argument in registerUser mutation with named age and value as 30") {
assertThat(query.document.operation.fields[0].arguments!!.args["age"] as Int, equalTo(30))
}
it("should contains 2 field in registerUser payload") {
assertThat(query.document.operation.fields[0].selectionSet!!.fields , hasSize(equalTo(2)))
}
it("should have id field in registerUser payload") {
assertThat(query.document.operation.fields[0].selectionSet!!.fields[0].name , equalTo("id"))
}
it("should have token field in mutation payload") {
assertThat(query.document.operation.fields[0].selectionSet!!.fields[1].name , equalTo("token"))
}
}
}
})
48 changes: 37 additions & 11 deletions src/test/kotlin/com/taskworld/kraph/test/NodePrintSpek.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,29 @@ import org.junit.runner.RunWith

@RunWith(JUnitPlatform::class)
class NodePrintSpek : Spek({
describe("ArgumentNode print()") {
given("\"id\" as argument and value as 1") {
val node = ArgumentsNode(mapOf("id" to 1))
describe("ArgumentNode print function") {
given("id as argument and value as 1") {
val node = ArgumentNode(mapOf("id" to 1))
it("should print (id: 1)") {
assertThat(node.print(), equalTo("(id: 1)"))
}
}
given("\"id\" and \"title\" as arguments and value as 1 and \"KraphQL\"") {
val node = ArgumentsNode(mapOf("id" to 1, "title" to "KraphQL"))
given("id and title as arguments and value as 1 and \"KraphQL\"") {
val node = ArgumentNode(mapOf("id" to 1, "title" to "KraphQL"))
it("should print (id: 1, title: \"KraphQL\")") {
assertThat(node.print(), equalTo("(id: 1, title: \"KraphQL\")"))
}
}
}
describe("SelectionSetNode print()") {
describe("MutationArgumentNode print function") {
given("id as argument and value as 1") {
val node = MutationArgumentNode(mapOf("id" to 1))
it("should print (input: { id: 1 })") {
assertThat(node.print(), equalTo("(input: { id: 1 })"))
}
}
}
describe("SelectionSetNode print function") {
given("two fields; id and title") {
val fields = listOf(FieldNode("id"), FieldNode("title"))
val node = SelectionSetNode(fields)
Expand All @@ -44,15 +52,25 @@ class NodePrintSpek : Spek({
}
}
}
describe("FieldNode print()") {
describe("MutationNode print function") {
given("name registerUser with email and password as argument and payload contains id and token") {
val argNode = MutationArgumentNode(mapOf("email" to "[email protected]", "password" to "abcd1234"))
val setNode = SelectionSetNode(listOf(FieldNode("id"), FieldNode("token")))
val node = MutationNode("registerUser", argNode, setNode)
it("should print registerUser(input: {email: \"[email protected]\", password: \"abcd1234\"}){ id token }") {
assertThat(node.print(), equalTo("registerUser(input: { email: \"[email protected]\", password: \"abcd1234\" }) {\r\nid\r\ntoken\r\n}"))
}
}
}
describe("FieldNode print function") {
given("name id") {
val node = FieldNode("id")
it("should print id") {
assertThat(node.print(), equalTo("id"))
}
}
given("name avatarSize and size argument with value as 20") {
val argNode = ArgumentsNode(mapOf("size" to 20))
val argNode = ArgumentNode(mapOf("size" to 20))
val node = FieldNode("avatarSize", arguments = argNode)
it("should print avatarSize(size: 20)") {
assertThat(node.print(), equalTo("avatarSize(size: 20)"))
Expand All @@ -65,8 +83,16 @@ class NodePrintSpek : Spek({
assertThat(node.print(), equalTo("assignee {\r\nname\r\nemail\r\n}"))
}
}
given("name user and id argument with value as 10 and contains name and email") {
val argNode = ArgumentNode(mapOf("id" to 10))
val setNode = SelectionSetNode(listOf(FieldNode("name"), FieldNode("email")))
val node = FieldNode("user", argNode, setNode)
it("should print user(id: 10){ name email }") {
assertThat(node.print(), equalTo("user(id: 10) {\r\nname\r\nemail\r\n}"))
}
}
}
describe("OperationNode print()") {
describe("OperationNode print function") {
given("query type and field named id") {
val node = OperationNode(OperationType.QUERY, listOf(FieldNode("id")))
it("should print query { id }") {
Expand All @@ -80,14 +106,14 @@ class NodePrintSpek : Spek({
}
}
given("query type with name \"getTask\" and id(1234) as argument and field title") {
val argNode = ArgumentsNode(mapOf("id" to 1234))
val argNode = ArgumentNode(mapOf("id" to 1234))
val node = OperationNode(OperationType.QUERY, name = "getTask", arguments = argNode, fields = listOf(FieldNode("title")))
it("should print query getTask(id: 1234) { title }") {
assertThat(node.print(), equalTo("query getTask(id: 1234) {\r\ntitle\r\n}"))
}
}
}
describe("DocumentNode print()") {
describe("DocumentNode print function") {
given("document with simple query") {
val queryNode = OperationNode(OperationType.QUERY, fields = listOf(FieldNode("id")))
val node = DocumentNode(queryNode)
Expand Down

0 comments on commit b68b3dc

Please sign in to comment.