From 9602a55618eb7c42283477b28cf719a0bfc1dda4 Mon Sep 17 00:00:00 2001 From: Andreas Berger Date: Thu, 6 Aug 2020 13:45:48 +0200 Subject: [PATCH] Add support for custom scalars (resolves #9) (#99) Co-authored-by: Michael Hunger --- pom.xml | 6 + readme.adoc | 3 +- .../kotlin/org/neo4j/graphql/NoOpCoercing.kt | 11 + .../kotlin/org/neo4j/graphql/SchemaBuilder.kt | 21 +- .../kotlin/org/neo4j/graphql/CypherTests.kt | 5 +- .../translator-tests-custom-scalars.adoc | 202 ++++++++++++++++++ 6 files changed, 244 insertions(+), 4 deletions(-) create mode 100644 src/main/kotlin/org/neo4j/graphql/NoOpCoercing.kt create mode 100644 src/test/resources/translator-tests-custom-scalars.adoc diff --git a/pom.xml b/pom.xml index c7667f3c..c4977e4f 100755 --- a/pom.xml +++ b/pom.xml @@ -118,6 +118,12 @@ neo4j-cypher-dsl 2020.0.1 + + ch.qos.logback + logback-classic + 1.2.3 + test + diff --git a/readme.adoc b/readme.adoc index 170d4a5f..274b92bf 100644 --- a/readme.adoc +++ b/readme.adoc @@ -83,6 +83,7 @@ You find more usage examples in the: * link:src/test/resources/translator-tests1.adoc[Translator 1 TCK] * link:src/test/resources/translator-tests2.adoc[Translator 2 TCK] * link:src/test/resources/translator-tests3.adoc[Translator 3 TCK] +* link:src/test/resources/translator-tests-custom-scalars.adoc[Translator custom scalars TCK] * link:src/test/resources/optimized-query-for-filter.adoc[Alternative Filter TCK] == Demo @@ -246,6 +247,7 @@ This example doesn't handle introspection queries, but the one in the test direc * date(time) * interfaces * complex filter parameters, with optional query optimization strategy +* scalars * spatial === Next @@ -254,7 +256,6 @@ This example doesn't handle introspection queries, but the one in the test direc * sorting (nested) * input types * unions -* scalars == Documentation diff --git a/src/main/kotlin/org/neo4j/graphql/NoOpCoercing.kt b/src/main/kotlin/org/neo4j/graphql/NoOpCoercing.kt new file mode 100644 index 00000000..742ce17f --- /dev/null +++ b/src/main/kotlin/org/neo4j/graphql/NoOpCoercing.kt @@ -0,0 +1,11 @@ +package org.neo4j.graphql + +import graphql.schema.Coercing + +object NoOpCoercing : Coercing { + override fun parseLiteral(input: Any?) = input + + override fun serialize(dataFetcherResult: Any?) = dataFetcherResult + + override fun parseValue(input: Any?) = input +} diff --git a/src/main/kotlin/org/neo4j/graphql/SchemaBuilder.kt b/src/main/kotlin/org/neo4j/graphql/SchemaBuilder.kt index 5db55533..73d6d54f 100644 --- a/src/main/kotlin/org/neo4j/graphql/SchemaBuilder.kt +++ b/src/main/kotlin/org/neo4j/graphql/SchemaBuilder.kt @@ -4,6 +4,7 @@ import graphql.Scalars import graphql.language.* import graphql.schema.* import graphql.schema.idl.RuntimeWiring +import graphql.schema.idl.ScalarInfo.STANDARD_SCALAR_DEFINITIONS import graphql.schema.idl.SchemaGenerator import graphql.schema.idl.SchemaParser import graphql.schema.idl.TypeDefinitionRegistry @@ -40,7 +41,23 @@ object SchemaBuilder { enhancedRegistry.add(ObjectTypeDefinition.newObjectTypeDefinition().name(QUERY).build()) } - val builder = RuntimeWiring.newRuntimeWiring().scalar(DynamicProperties.INSTANCE) + val builder = RuntimeWiring.newRuntimeWiring() + typeDefinitionRegistry.scalars() + .filterNot { entry -> STANDARD_SCALAR_DEFINITIONS.containsKey(entry.key) } + .forEach { (name, definition) -> + val scalar = when (name) { + "DynamicProperties" -> DynamicProperties.INSTANCE + else -> GraphQLScalarType.newScalar() + .name(name) + .description(definition.description?.getContent() ?: "Scalar $name") + .withDirectives(*definition.directives.filterIsInstance().toTypedArray()) + .definition(definition) + .coercing(NoOpCoercing) + .build() + } + builder.scalar(scalar) + } + enhancedRegistry .getTypes(InterfaceTypeDefinition::class.java) @@ -205,4 +222,4 @@ object SchemaBuilder { typeDefinitionRegistry.add(inputType) return inputName } -} \ No newline at end of file +} diff --git a/src/test/kotlin/org/neo4j/graphql/CypherTests.kt b/src/test/kotlin/org/neo4j/graphql/CypherTests.kt index e8b20e50..6b9bfabe 100644 --- a/src/test/kotlin/org/neo4j/graphql/CypherTests.kt +++ b/src/test/kotlin/org/neo4j/graphql/CypherTests.kt @@ -29,6 +29,9 @@ class CypherTests { @TestFactory fun `translator-tests3`() = CypherTestSuite("translator-tests3.adoc").run() + @TestFactory + fun `translator-tests-custom-scalars`() = CypherTestSuite("translator-tests-custom-scalars.adoc").run() + @TestFactory fun `optimized-query-for-filter`() = CypherTestSuite("optimized-query-for-filter.adoc").run() -} \ No newline at end of file +} diff --git a/src/test/resources/translator-tests-custom-scalars.adoc b/src/test/resources/translator-tests-custom-scalars.adoc new file mode 100644 index 00000000..1095ceed --- /dev/null +++ b/src/test/resources/translator-tests-custom-scalars.adoc @@ -0,0 +1,202 @@ +:toc: + += Translator Tests + +== Schema + +[source,graphql,schema=true] +---- +scalar Date +type Movie { + _id: ID! + title: String! + released: Date +} +---- + +== Tests + +=== Create + +.GraphQL-Query +[source,graphql] +---- +mutation { + createMovie(title:"Forrest Gump", released: 1994) { + title + released + } +} +---- + +.Cypher params +[source,json] +---- +{ + "createMovieTitle": "Forrest Gump", + "createMovieReleased": 1994 +} +---- + +.Cypher +[source,cypher] +---- +CREATE (createMovie:Movie { title: $createMovieTitle, released: $createMovieReleased }) +WITH createMovie +RETURN createMovie { .title, .released } AS createMovie +---- + +=== Update + +.GraphQL-Query +[source,graphql] +---- +mutation { + updateMovie(_id: 1, released: 1995) { + title + released + } +} +---- + +.Cypher params +[source,json] +---- +{ + "updateMovie_id": 1, + "updateMovieReleased": 1995 +} +---- + +.Cypher +[source,cypher] +---- +MATCH (updateMovie: Movie) +WHERE ID(updateMovie) = toInteger($updateMovie_id) +SET updateMovie = { released: $updateMovieReleased } +WITH updateMovie +RETURN updateMovie { .title, .released } AS updateMovie +---- + +=== Merge + +.GraphQL-Query +[source,graphql] +---- +mutation { + mergeMovie(_id: 1, released: 1995) { + title + released + } +} +---- + +.Cypher params +[source,json] +---- +{ + "mergeMovie_id": 1, + "mergeMovieReleased": 1995 +} +---- + +.Cypher +[source,cypher] +---- +MATCH (mergeMovie: Movie) +WHERE ID(mergeMovie) = toInteger($mergeMovie_id) +SET mergeMovie += { released: $mergeMovieReleased } +WITH mergeMovie +RETURN mergeMovie { .title, .released } AS mergeMovie +---- + +=== Merge null + +.GraphQL-Query +[source,graphql] +---- +mutation { + updateMovie(_id: 1, released: null) { + title + released + } +} +---- + +.Cypher params +[source,json] +---- +{ + "updateMovie_id": 1, + "updateMovieReleased": null +} +---- + +.Cypher +[source,cypher] +---- +MATCH (updateMovie: Movie) +WHERE ID(updateMovie) = toInteger($updateMovie_id) +SET updateMovie = { released: $updateMovieReleased } +WITH updateMovie +RETURN updateMovie { .title, .released } AS updateMovie +---- + +=== Find + +.GraphQL-Query +[source,graphql] +---- +{ + movie(released: 1994) { + title + released + } +} +---- + +.Cypher params +[source,json] +---- +{ + "movieReleased": 1994 +} +---- + +.Cypher +[source,cypher] +---- +MATCH (movie: Movie) +WHERE movie.released = $movieReleased +RETURN movie { .title, .released } AS movie +---- + +=== Filter + +.GraphQL-Query +[source,graphql] +---- +{ + movie(filter:{released_gte: 1994}) { + title + released + } +} +---- + +.Cypher params +[source,json] +---- +{ + "filterMovieReleased_GTE": 1994 +} +---- + +.Cypher +[source,cypher] +---- +MATCH (movie: Movie) +WHERE movie.released >= $filterMovieReleased_GTE +RETURN movie { .title, .released } AS movie +---- +