diff --git a/docs/content/Reference/Type System/overview.md b/docs/content/Reference/Type System/overview.md index ba6ae321..755134b9 100644 --- a/docs/content/Reference/Type System/overview.md +++ b/docs/content/Reference/Type System/overview.md @@ -19,6 +19,7 @@ By default, every schema has following built in types: ### Scalars * **String** - represents textual data, represented as UTF‐8 character sequences +* **Short** - represents a signed 16‐bit numeric non‐fractional value * **Int** - represents a signed 32‐bit numeric non‐fractional value * **Long** - represents a signed 64‐bit numeric non‐fractional value. Long type is not part of GraphQL specification, but it is built in primitive type in Kotlin language. * **Float** - represents signed double‐precision fractional values as specified by IEEE 754. KGraphQL represents Kotlin primitive Double and Float values as GraphQL Float. diff --git a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/builtin/BUILT_IN_TYPE.kt b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/builtin/BUILT_IN_TYPE.kt index e1434691..8b030e43 100644 --- a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/builtin/BUILT_IN_TYPE.kt +++ b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/builtin/BUILT_IN_TYPE.kt @@ -15,6 +15,9 @@ import com.apurebase.kgraphql.schema.scalar.StringScalarCoercion private const val STRING_DESCRIPTION = "The String scalar type represents textual data, represented as UTF-8 character sequences" +private const val SHORT_DESCRIPTION = + "The Short scalar type represents a signed 16-bit numeric non-fractional value" + private const val INT_DESCRIPTION = "The Int scalar type represents a signed 32-bit numeric non-fractional value" @@ -34,6 +37,8 @@ object BUILT_IN_TYPE { val STRING = TypeDef.Scalar(String::class.defaultKQLTypeName(), String::class, STRING_COERCION, STRING_DESCRIPTION) + val SHORT = TypeDef.Scalar(Short::class.defaultKQLTypeName(), Short::class, SHORT_COERCION, SHORT_DESCRIPTION) + val INT = TypeDef.Scalar(Int::class.defaultKQLTypeName(), Int::class, INT_COERCION, INT_DESCRIPTION) //GraphQL does not differ float and double, treat double like float @@ -116,6 +121,32 @@ object INT_COERCION : StringScalarCoercion{ } } +object SHORT_COERCION : StringScalarCoercion{ + override fun serialize(instance: Short): String = instance.toString() + + override fun deserialize(raw: String, valueNode: ValueNode?) = when (valueNode) { + null -> { + if(!raw.isLiteral()) raw.toShort() + else throw GraphQLError("Cannot coerce string literal, expected numeric string constant") + } + is NumberValueNode -> when { + valueNode.value > Short.MAX_VALUE -> throw GraphQLError( + "Cannot coerce to type of Int as '${valueNode.value}' is greater than (2^-15)-1", + valueNode + ) + valueNode.value < Short.MIN_VALUE -> throw GraphQLError( + "Cannot coerce to type of Int as '${valueNode.value}' is less than -(2^-15)", + valueNode + ) + else -> valueNode.value.toShort() + } + else -> throw GraphQLError( + "Cannot coerce ${valueNode.valueNodeName} to numeric constant", + valueNode + ) + } +} + object LONG_COERCION : StringScalarCoercion { override fun serialize(instance: Long): String = instance.toString() 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 d6a6ed1e..26441a79 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 @@ -80,6 +80,16 @@ class SchemaBuilder internal constructor() { stringScalar(T::class, block) } + fun shortScalar(kClass: KClass, block: ScalarDSL.() -> Unit) { + val scalar = ShortScalarDSL(kClass).apply(block) + configuration.appendMapper(scalar, kClass) + model.addScalar(TypeDef.Scalar(scalar.name, kClass, scalar.createCoercion(), scalar.description)) + } + + inline fun shortScalar(noinline block: ScalarDSL.() -> Unit) { + shortScalar(T::class, block) + } + fun intScalar(kClass: KClass, block: ScalarDSL.() -> Unit) { val scalar = IntScalarDSL(kClass).apply(block) configuration.appendMapper(scalar, kClass) diff --git a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/dsl/types/ShortScalarDSL.kt b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/dsl/types/ShortScalarDSL.kt new file mode 100644 index 00000000..29a34146 --- /dev/null +++ b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/dsl/types/ShortScalarDSL.kt @@ -0,0 +1,26 @@ +package com.apurebase.kgraphql.schema.dsl.types + + +import com.apurebase.kgraphql.schema.SchemaException +import com.apurebase.kgraphql.schema.model.ast.ValueNode +import com.apurebase.kgraphql.schema.scalar.ShortScalarCoercion +import com.apurebase.kgraphql.schema.scalar.ScalarCoercion +import kotlin.reflect.KClass + + +class ShortScalarDSL(kClass: KClass) : ScalarDSL(kClass) { + + override fun createCoercionFromFunctions(): ScalarCoercion { + return object : ShortScalarCoercion { + + val serializeImpl = serialize ?: throw SchemaException(PLEASE_SPECIFY_COERCION) + + val deserializeImpl = deserialize ?: throw SchemaException(PLEASE_SPECIFY_COERCION) + + override fun serialize(instance: T): Short = serializeImpl(instance) + + override fun deserialize(raw: Short, valueNode: ValueNode?): T = deserializeImpl(raw) + } + } + +} diff --git a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/model/MutableSchemaDefinition.kt b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/model/MutableSchemaDefinition.kt index f2eb8ac6..9c8ac86f 100644 --- a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/model/MutableSchemaDefinition.kt +++ b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/model/MutableSchemaDefinition.kt @@ -26,8 +26,10 @@ data class MutableSchemaDefinition ( BUILT_IN_TYPE.BOOLEAN, BUILT_IN_TYPE.DOUBLE, BUILT_IN_TYPE.FLOAT, + BUILT_IN_TYPE.SHORT, BUILT_IN_TYPE.INT, - BUILT_IN_TYPE.LONG + BUILT_IN_TYPE.LONG, + ), private val mutations: ArrayList> = arrayListOf(), private val subscriptions: ArrayList> = arrayListOf(), diff --git a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/scalar/Coercion.kt b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/scalar/Coercion.kt index a24df8b5..7efa9786 100644 --- a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/scalar/Coercion.kt +++ b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/scalar/Coercion.kt @@ -3,16 +3,11 @@ package com.apurebase.kgraphql.schema.scalar import com.apurebase.kgraphql.ExecutionException import com.apurebase.kgraphql.dropQuotes import com.fasterxml.jackson.databind.node.JsonNodeFactory -import com.apurebase.kgraphql.schema.builtin.BOOLEAN_COERCION -import com.apurebase.kgraphql.schema.builtin.DOUBLE_COERCION -import com.apurebase.kgraphql.schema.builtin.FLOAT_COERCION -import com.apurebase.kgraphql.schema.builtin.INT_COERCION -import com.apurebase.kgraphql.schema.builtin.LONG_COERCION -import com.apurebase.kgraphql.schema.builtin.STRING_COERCION import com.apurebase.kgraphql.schema.execution.Execution import com.apurebase.kgraphql.schema.model.ast.ValueNode import com.apurebase.kgraphql.schema.model.ast.ValueNode.* import com.apurebase.kgraphql.GraphQLError +import com.apurebase.kgraphql.schema.builtin.* import com.apurebase.kgraphql.schema.structure.Type import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonPrimitive @@ -28,11 +23,13 @@ fun deserializeScalar(scalar: Type.Scalar, value : ValueNode): T { STRING_COERCION -> STRING_COERCION.deserialize(value.valueNodeName, value as StringValueNode) as T FLOAT_COERCION -> FLOAT_COERCION.deserialize(value.valueNodeName, value) as T DOUBLE_COERCION -> DOUBLE_COERCION.deserialize(value.valueNodeName, value) as T + SHORT_COERCION -> SHORT_COERCION.deserialize(value.valueNodeName, value) as T INT_COERCION -> INT_COERCION.deserialize(value.valueNodeName, value) as T BOOLEAN_COERCION -> BOOLEAN_COERCION.deserialize(value.valueNodeName, value) as T LONG_COERCION -> LONG_COERCION.deserialize(value.valueNodeName, value) as T is StringScalarCoercion -> scalar.coercion.deserialize(value.valueNodeName.dropQuotes(), value) + is ShortScalarCoercion -> scalar.coercion.deserialize(value.valueNodeName.toShort(), value) is IntScalarCoercion -> scalar.coercion.deserialize(value.valueNodeName.toInt(), value) is DoubleScalarCoercion -> scalar.coercion.deserialize(value.valueNodeName.toDouble(), value) is BooleanScalarCoercion -> scalar.coercion.deserialize(value.valueNodeName.toBoolean(), value) @@ -57,6 +54,9 @@ fun serializeScalar(jsonNodeFactory: JsonNodeFactory, scalar: Type.Scalar<*> is StringScalarCoercion<*> -> { jsonNodeFactory.textNode((scalar.coercion as StringScalarCoercion).serialize(value)) } + is ShortScalarCoercion<*> -> { + jsonNodeFactory.numberNode((scalar.coercion as ShortScalarCoercion).serialize(value)) + } is IntScalarCoercion<*> -> { jsonNodeFactory.numberNode((scalar.coercion as IntScalarCoercion).serialize(value)) } @@ -77,6 +77,9 @@ fun serializeScalar(scalar: Type.Scalar<*>, value: T, executionNode: Executi is StringScalarCoercion<*> -> { JsonPrimitive((scalar.coercion as StringScalarCoercion).serialize(value)) } + is ShortScalarCoercion<*> -> { + JsonPrimitive((scalar.coercion as ShortScalarCoercion).serialize(value)) + } is IntScalarCoercion<*> -> { JsonPrimitive((scalar.coercion as IntScalarCoercion).serialize(value)) } diff --git a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/scalar/ShortScalarCoercion.kt b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/scalar/ShortScalarCoercion.kt new file mode 100644 index 00000000..1fb791b3 --- /dev/null +++ b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/scalar/ShortScalarCoercion.kt @@ -0,0 +1,3 @@ +package com.apurebase.kgraphql.schema.scalar + +interface ShortScalarCoercion : ScalarCoercion \ No newline at end of file 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 23b8f089..66d04ccb 100644 --- a/kgraphql/src/test/kotlin/com/apurebase/kgraphql/schema/SchemaBuilderTest.kt +++ b/kgraphql/src/test/kotlin/com/apurebase/kgraphql/schema/SchemaBuilderTest.kt @@ -592,6 +592,25 @@ class SchemaBuilderTest { assertThat(names, hasItem("TypeAsObject")) } + @Test + fun `Short int types are mapped to Short Scalar`(){ + val schema = defaultSchema { + query("shortQuery") { + resolver { -> 1 as Short } + } + } + + + val typesIntrospection = deserialize(schema.executeBlocking("{__schema{types{name}}}")) + val types = typesIntrospection.extract>>("data/__schema/types") + val names = types.map {it["name"]} + assertThat(names, hasItem("Short")) + + val response = deserialize(schema.executeBlocking("{__schema{queryType{fields{ type { ofType { kind name }}}}}}")) + assertThat(response.extract("data/__schema/queryType/fields[0]/type/ofType/kind"), equalTo("SCALAR")) + assertThat(response.extract("data/__schema/queryType/fields[0]/type/ofType/name"), equalTo("Short")) + } + @Test fun `Resolver cannot return an Unit value`(){ invoking { 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 29f0da72..62a1e71b 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 @@ -152,6 +152,7 @@ class ScalarsSpecificationTest { } data class Boo(val boolean: Boolean) + data class Sho(val short: Short) data class Lon(val long: Long) data class Dob(val double: Double) data class Num(val int: Int) @@ -192,6 +193,10 @@ class ScalarsSpecificationTest { deserialize = ::Dob serialize = { (double) -> double } } + shortScalar { + deserialize = ::Sho + serialize = { (short) -> short } + } intScalar { deserialize = ::Num serialize = { (num) -> num } @@ -203,6 +208,7 @@ class ScalarsSpecificationTest { query("boo") { resolver { boo: Boo -> boo } } query("lon") { resolver { lon: Lon -> lon } } + query("sho") { resolver { sho: Sho -> sho } } query("dob") { resolver { dob: Dob -> dob } } query("num") { resolver { num: Num -> num } } query("str") { resolver { str: Str -> str } } @@ -211,14 +217,16 @@ class ScalarsSpecificationTest { val booValue = true val lonValue = 124L + val shoValue: Short = 1 val dobValue = 2.5 val numValue = 155 val strValue = "Test" val d = '$' val req = """ - query Query(${d}boo: Boo!, ${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) dob(dob: ${d}dob) num(num: ${d}num) @@ -230,6 +238,7 @@ class ScalarsSpecificationTest { val values = """ { "boo": $booValue, + "sho": $shoValue, "lon": $lonValue, "dob": $dobValue, "num": $numValue, @@ -240,6 +249,7 @@ class ScalarsSpecificationTest { try { val response = deserialize(schema.executeBlocking(req, values)) assertThat(response.extract("data/boo"), equalTo(booValue)) + assertThat(response.extract("data/sho"), equalTo(shoValue.toInt())) assertThat(response.extract("data/lon"), equalTo(lonValue.toInt())) assertThat(response.extract("data/dob"), equalTo(dobValue)) assertThat(response.extract("data/num"), equalTo(numValue))