diff --git a/docs/content/Reference/Type System/scalars.md b/docs/content/Reference/Type System/scalars.md index f7d3865a..624341d5 100644 --- a/docs/content/Reference/Type System/scalars.md +++ b/docs/content/Reference/Type System/scalars.md @@ -31,3 +31,6 @@ stringScalar { } } ``` + +In addition to the built-in types, KGraphQL provides support for `Long` and `Short` which can be added to a schema +using `extendedScalars()`. diff --git a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/SchemaPrinter.kt b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/SchemaPrinter.kt index 44f2ce51..826a1766 100644 --- a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/SchemaPrinter.kt +++ b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/SchemaPrinter.kt @@ -1,7 +1,6 @@ package com.apurebase.kgraphql.schema import com.apurebase.kgraphql.request.isIntrospectionType -import com.apurebase.kgraphql.schema.builtin.BuiltInScalars import com.apurebase.kgraphql.schema.directive.Directive import com.apurebase.kgraphql.schema.introspection.TypeKind import com.apurebase.kgraphql.schema.introspection.__Described @@ -30,6 +29,8 @@ data class SchemaPrinterConfig( ) class SchemaPrinter(private val config: SchemaPrinterConfig = SchemaPrinterConfig()) { + private val builtInScalarNames = setOf("Int", "Float", "String", "Boolean", "ID") + /** * Returns the given [schema] in schema definition language (SDL). Types and fields are sorted * ascending by their name and appear in order of their corresponding spec section, i.e. @@ -229,7 +230,7 @@ class SchemaPrinter(private val config: SchemaPrinterConfig = SchemaPrinterConfi private fun __Directive.isBuiltIn(): Boolean = name in setOf(Directive.DEPRECATED.name, Directive.INCLUDE.name, Directive.SKIP.name) - private fun __Type.isBuiltInScalar(): Boolean = name in BuiltInScalars.entries.map { it.typeDef.name } + private fun __Type.isBuiltInScalar(): Boolean = name in builtInScalarNames private fun __Type.implements(): String = interfaces diff --git a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/builtin/BuiltInScalars.kt b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/builtin/BuiltInScalars.kt index 8a030459..0b32d9e8 100644 --- a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/builtin/BuiltInScalars.kt +++ b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/builtin/BuiltInScalars.kt @@ -31,19 +31,20 @@ private const val BOOLEAN_DESCRIPTION = "The Boolean scalar type represents true or false" /** - * These scalars are created only for sake of documentation in introspection, not during execution - * * https://spec.graphql.org/October2021/#sec-Scalars.Built-in-Scalars */ enum class BuiltInScalars(val typeDef: TypeDef.Scalar<*>) { STRING(TypeDef.Scalar(String::class.defaultKQLTypeName(), String::class, STRING_COERCION, STRING_DESCRIPTION)), - SHORT(TypeDef.Scalar(Short::class.defaultKQLTypeName(), Short::class, SHORT_COERCION, SHORT_DESCRIPTION)), INT(TypeDef.Scalar(Int::class.defaultKQLTypeName(), Int::class, INT_COERCION, INT_DESCRIPTION)), // GraphQL does not differentiate between float and double, treat double like float DOUBLE(TypeDef.Scalar(Float::class.defaultKQLTypeName(), Double::class, DOUBLE_COERCION, FLOAT_DESCRIPTION)), FLOAT(TypeDef.Scalar(Float::class.defaultKQLTypeName(), Float::class, FLOAT_COERCION, FLOAT_DESCRIPTION)), BOOLEAN(TypeDef.Scalar(Boolean::class.defaultKQLTypeName(), Boolean::class, BOOLEAN_COERCION, BOOLEAN_DESCRIPTION)), +} + +enum class ExtendedBuiltInScalars(val typeDef: TypeDef.Scalar<*>) { + SHORT(TypeDef.Scalar(Short::class.defaultKQLTypeName(), Short::class, SHORT_COERCION, SHORT_DESCRIPTION)), LONG(TypeDef.Scalar(Long::class.defaultKQLTypeName(), Long::class, LONG_COERCION, LONG_DESCRIPTION)) } diff --git a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/dsl/SchemaBuilder.kt b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/dsl/SchemaBuilder.kt index f0c8f9b5..782c0ca6 100644 --- a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/dsl/SchemaBuilder.kt +++ b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/dsl/SchemaBuilder.kt @@ -3,6 +3,7 @@ package com.apurebase.kgraphql.schema.dsl import com.apurebase.kgraphql.schema.Publisher import com.apurebase.kgraphql.schema.Schema import com.apurebase.kgraphql.schema.SchemaException +import com.apurebase.kgraphql.schema.builtin.ExtendedBuiltInScalars import com.apurebase.kgraphql.schema.dsl.operations.MutationDSL import com.apurebase.kgraphql.schema.dsl.operations.QueryDSL import com.apurebase.kgraphql.schema.dsl.operations.SubscriptionDSL @@ -140,6 +141,12 @@ class SchemaBuilder internal constructor() { booleanScalar(T::class, block) } + fun extendedScalars() { + ExtendedBuiltInScalars.values().forEach { + model.addScalar(it.typeDef) + } + } + //================================================================================ // TYPE //================================================================================ diff --git a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/execution/DataLoaderPreparedRequestExecutor.kt b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/execution/DataLoaderPreparedRequestExecutor.kt index 3336a130..72b69ae8 100644 --- a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/execution/DataLoaderPreparedRequestExecutor.kt +++ b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/execution/DataLoaderPreparedRequestExecutor.kt @@ -144,6 +144,7 @@ class DataLoaderPreparedRequestExecutor(val schema: DefaultSchema) : RequestExec value is Double -> node.aliasOrKey toValue JsonPrimitive(value) value is Boolean -> node.aliasOrKey toValue JsonPrimitive(value) value is Long -> node.aliasOrKey toValue JsonPrimitive(value) + value is Short -> node.aliasOrKey toValue JsonPrimitive(value) value is Deferred<*> -> { deferredLaunch { applyKeyToElement(ctx, value.await(), node, returnType, parentCount) diff --git a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/execution/ParallelRequestExecutor.kt b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/execution/ParallelRequestExecutor.kt index 2dbe5cd4..592d6414 100644 --- a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/execution/ParallelRequestExecutor.kt +++ b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/execution/ParallelRequestExecutor.kt @@ -164,7 +164,7 @@ class ParallelRequestExecutor(val schema: DefaultSchema) : RequestExecutor { value is Double -> jsonNodeFactory.numberNode(value) value is Boolean -> jsonNodeFactory.booleanNode(value) value is Long -> jsonNodeFactory.numberNode(value) - //big decimal etc? + value is Short -> jsonNodeFactory.numberNode(value) node.children.isNotEmpty() -> { createObjectNode(ctx, value, node, returnType) diff --git a/kgraphql/src/test/kotlin/com/apurebase/kgraphql/integration/LongScalarTest.kt b/kgraphql/src/test/kotlin/com/apurebase/kgraphql/integration/LongScalarTest.kt index 95c53337..d16b7140 100644 --- a/kgraphql/src/test/kotlin/com/apurebase/kgraphql/integration/LongScalarTest.kt +++ b/kgraphql/src/test/kotlin/com/apurebase/kgraphql/integration/LongScalarTest.kt @@ -13,12 +13,13 @@ class LongScalarTest { @Test fun testLongField() { val schema = defaultSchema { + extendedScalars() query("long") { resolver { -> Long.MAX_VALUE } } } - val response = schema.executeBlocking("{long}") + val response = schema.executeBlocking("{ long }") val long = deserialize(response).extract("data/long") assertThat(long, equalTo(Long.MAX_VALUE)) } @@ -26,13 +27,21 @@ class LongScalarTest { @Test fun testLongArgument() { val schema = defaultSchema { + extendedScalars() query("isLong") { - resolver { long: Long -> if (long > Int.MAX_VALUE) "YES" else "NO" } + resolver { long: Long -> + if (long > Int.MAX_VALUE) { + "YES" + } else { + "NO" + } + } } } - val isLong = - deserialize(schema.executeBlocking("{isLong(long: ${Int.MAX_VALUE.toLong() + 1})}")).extract("data/isLong") + val isLong = deserialize( + schema.executeBlocking("{ isLong(long: ${Int.MAX_VALUE.toLong() + 1}) }") + ).extract("data/isLong") assertThat(isLong, equalTo("YES")) } @@ -52,7 +61,7 @@ class LongScalarTest { } val value = Int.MAX_VALUE.toLong() + 2 - val response = deserialize(schema.executeBlocking("{number(number: $value)}")) + val response = deserialize(schema.executeBlocking("{ number(number: $value) }")) assertThat(response.extract("data/number"), equalTo(value)) } diff --git a/kgraphql/src/test/kotlin/com/apurebase/kgraphql/integration/github/GitHubIssue75.kt b/kgraphql/src/test/kotlin/com/apurebase/kgraphql/integration/github/GitHubIssue75.kt index fc957770..56cd0abc 100644 --- a/kgraphql/src/test/kotlin/com/apurebase/kgraphql/integration/github/GitHubIssue75.kt +++ b/kgraphql/src/test/kotlin/com/apurebase/kgraphql/integration/github/GitHubIssue75.kt @@ -47,6 +47,8 @@ class GitHubIssue75 { useDefaultPrettyPrinter = true } + extendedScalars() + query("findTrace") { resolver { traceID: String -> Trace( @@ -132,6 +134,6 @@ class GitHubIssue75 { } } """, "{\"traceID\": \"646851f15cb2dad1\"}" - ).let(::println) + ) } } diff --git a/kgraphql/src/test/kotlin/com/apurebase/kgraphql/schema/SchemaBuilderTest.kt b/kgraphql/src/test/kotlin/com/apurebase/kgraphql/schema/SchemaBuilderTest.kt index d510ddb7..bb40d1a3 100644 --- a/kgraphql/src/test/kotlin/com/apurebase/kgraphql/schema/SchemaBuilderTest.kt +++ b/kgraphql/src/test/kotlin/com/apurebase/kgraphql/schema/SchemaBuilderTest.kt @@ -750,12 +750,12 @@ class SchemaBuilderTest { @Test fun `Short int types are mapped to Short Scalar`() { val schema = defaultSchema { + extendedScalars() query("shortQuery") { resolver { -> 1.toShort() } } } - val typesIntrospection = deserialize(schema.executeBlocking("{__schema{types{name}}}")) val types = typesIntrospection.extract>>("data/__schema/types") val names = types.map { it["name"] } diff --git a/kgraphql/src/test/kotlin/com/apurebase/kgraphql/schema/SchemaPrinterTest.kt b/kgraphql/src/test/kotlin/com/apurebase/kgraphql/schema/SchemaPrinterTest.kt index 4b20424b..ba138d90 100644 --- a/kgraphql/src/test/kotlin/com/apurebase/kgraphql/schema/SchemaPrinterTest.kt +++ b/kgraphql/src/test/kotlin/com/apurebase/kgraphql/schema/SchemaPrinterTest.kt @@ -537,6 +537,7 @@ class SchemaPrinterTest { @Test fun `schema with descriptions should be printed as expected if descriptions are included`() { val schema = KGraphQL.schema { + extendedScalars() type { property(TestObject::name) { description = "This is the name" @@ -582,6 +583,12 @@ class SchemaPrinterTest { subscription: Subscription } + "The Long scalar type represents a signed 64-bit numeric non-fractional value" + scalar Long + + "The Short scalar type represents a signed 16-bit numeric non-fractional value" + scalar Short + "Mutation object" type Mutation { "Add a test object" diff --git a/kgraphql/src/test/kotlin/com/apurebase/kgraphql/specification/typesystem/ScalarsSpecificationTest.kt b/kgraphql/src/test/kotlin/com/apurebase/kgraphql/specification/typesystem/ScalarsSpecificationTest.kt index f08f515f..7ee86ba0 100644 --- a/kgraphql/src/test/kotlin/com/apurebase/kgraphql/specification/typesystem/ScalarsSpecificationTest.kt +++ b/kgraphql/src/test/kotlin/com/apurebase/kgraphql/specification/typesystem/ScalarsSpecificationTest.kt @@ -5,6 +5,7 @@ import com.apurebase.kgraphql.KGraphQL import com.apurebase.kgraphql.Specification import com.apurebase.kgraphql.deserialize import com.apurebase.kgraphql.extract +import com.apurebase.kgraphql.schema.SchemaException import com.apurebase.kgraphql.schema.model.ast.ValueNode import com.apurebase.kgraphql.schema.scalar.StringScalarCoercion import org.amshove.kluent.invoking @@ -21,6 +22,71 @@ import java.util.UUID @Specification("3.1.1 Scalars") class ScalarsSpecificationTest { + @Test + fun `built-in scalars should be available by default`() { + val schema = KGraphQL.schema { + query("int") { + resolver { 1 } + } + query("float") { + resolver { 2.0f } + } + query("double") { + resolver { 3.0 } + } + query("string") { + resolver { "foo" } + } + query("boolean") { + resolver { true } + } + // TODO: ID, cf. https://github.com/stuebingerb/KGraphQL/issues/83 + } + + val response = deserialize(schema.executeBlocking("{ int float double string boolean }")) + assertThat(response.extract("data/int"), equalTo(1)) + assertThat(response.extract("data/float"), equalTo(2.0)) + assertThat(response.extract("data/double"), equalTo(3.0)) + assertThat(response.extract("data/string"), equalTo("foo")) + assertThat(response.extract("data/boolean"), equalTo(true)) + } + + @Test + fun `extended scalars should not be available by default`() { + invoking { + KGraphQL.schema { + query("long") { + resolver { 1L } + } + } + } shouldThrow SchemaException::class withMessage "An Object type must define one or more fields. Found none on type Long" + + invoking { + KGraphQL.schema { + query("short") { + resolver { 2.toShort() } + } + } + } shouldThrow SchemaException::class withMessage "An Object type must define one or more fields. Found none on type Short" + } + + @Test + fun `extended scalars should be available if included`() { + val schema = KGraphQL.schema { + extendedScalars() + query("long") { + resolver { Long.MAX_VALUE } + } + query("short") { + resolver { 2.toShort() } + } + } + + val response = deserialize(schema.executeBlocking("{ long short }")) + assertThat(response.extract("data/long"), equalTo(9223372036854775807L)) + assertThat(response.extract("data/short"), equalTo(2)) + } + data class Person(val uuid: UUID, val name: String) @Test @@ -44,12 +110,12 @@ class ScalarsSpecificationTest { } } - val queryResponse = deserialize(testedSchema.executeBlocking("{person{uuid}}")) + val queryResponse = deserialize(testedSchema.executeBlocking("{ person{ uuid } }")) assertThat(queryResponse.extract("data/person/uuid"), equalTo(uuid.toString())) val mutationResponse = deserialize( testedSchema.executeBlocking( - "mutation{createPerson(uuid: \"$uuid\", name: \"John\"){uuid name}}" + "mutation { createPerson(uuid: \"$uuid\", name: \"John\"){ uuid name } }" ) ) assertThat(mutationResponse.extract("data/createPerson/uuid"), equalTo(uuid.toString())) @@ -68,7 +134,7 @@ class ScalarsSpecificationTest { } invoking { - schema.executeBlocking("mutation{Int(int: ${Integer.MAX_VALUE.toLong() + 2L})}") + schema.executeBlocking("mutation { Int(int: ${Integer.MAX_VALUE.toLong() + 2L}) }") } shouldThrow GraphQLError::class with { message shouldBeEqualTo "Cannot coerce to type of Int as '${Integer.MAX_VALUE.toLong() + 2L}' is greater than (2^-31)-1" } @@ -84,7 +150,7 @@ class ScalarsSpecificationTest { resolver { float: Float -> float } } } - val map = deserialize(schema.executeBlocking("mutation{float(float: 1)}")) + val map = deserialize(schema.executeBlocking("mutation { float(float: 1) }")) assertThat(map.extract("data/float"), equalTo(1.0)) } @@ -104,7 +170,7 @@ class ScalarsSpecificationTest { val randomUUID = UUID.randomUUID() val map = - deserialize(testedSchema.executeBlocking("query(\$id: ID = \"$randomUUID\"){personById(id: \$id){uuid, name}}")) + deserialize(testedSchema.executeBlocking("query(\$id: ID = \"$randomUUID\"){ personById(id: \$id) { uuid, name } }")) assertThat(map.extract("data/personById/uuid"), equalTo(randomUUID.toString())) } @@ -121,7 +187,7 @@ class ScalarsSpecificationTest { } invoking { - schema.executeBlocking("mutation{Int(int: \"223\")}") + schema.executeBlocking("mutation { Int(int: \"223\") }") } shouldThrow GraphQLError::class withMessage "Cannot coerce \"223\" to numeric constant" } @@ -129,7 +195,6 @@ class ScalarsSpecificationTest { @Test fun `Schema may declare custom int scalar type`() { - val schema = KGraphQL.schema { intScalar { deserialize = ::Number @@ -142,7 +207,7 @@ class ScalarsSpecificationTest { } val value = 3434 - val response = deserialize(schema.executeBlocking("{number(number: $value)}")) + val response = deserialize(schema.executeBlocking("{ number(number: $value) }")) assertThat(response.extract("data/number"), equalTo(value)) } @@ -150,7 +215,6 @@ class ScalarsSpecificationTest { @Test fun `Schema may declare custom boolean scalar type`() { - val schema = KGraphQL.schema { booleanScalar { deserialize = ::Bool @@ -163,7 +227,7 @@ class ScalarsSpecificationTest { } val value = true - val response = deserialize(schema.executeBlocking("{boolean(boolean: $value)}")) + val response = deserialize(schema.executeBlocking("{ boolean(boolean: $value) }")) assertThat(response.extract("data/boolean"), equalTo(value)) } @@ -177,7 +241,6 @@ class ScalarsSpecificationTest { @Test fun `Schema may declare custom double scalar type`() { - val schema = KGraphQL.schema { floatScalar { deserialize = ::Dob @@ -190,7 +253,7 @@ class ScalarsSpecificationTest { } val value = 232.33 - val response = deserialize(schema.executeBlocking("{double(double: $value)}")) + val response = deserialize(schema.executeBlocking("{ double(double: $value) }")) assertThat(response.extract("data/double"), equalTo(value)) } @@ -240,7 +303,7 @@ class ScalarsSpecificationTest { val d = '$' val req = """ - query Query(${d}boo: Boo!, ${d}sho: Sho!, ${d}lon: Lon!, ${d}dob: Dob!, ${d}num: Num!, ${d}str: Str!){ + query Query(${d}boo: Boo!, ${d}sho: Sho!, ${d}lon: Lon!, ${d}dob: Dob!, ${d}num: Num!, ${d}str: Str!) { boo(boo: ${d}boo) sho(sho: ${d}sho) lon(lon: ${d}lon)