From d7768dcde76ff223403b0fcd4dfa1ff0cc5a43b9 Mon Sep 17 00:00:00 2001 From: Ravi Kancherla Date: Wed, 6 Nov 2019 00:55:06 -0600 Subject: [PATCH 1/3] Scalar type for UUID --- readme.md | 5 ++ .../java/graphql/scalars/ExtendedScalars.java | 9 ++ .../java/graphql/scalars/id/UUIDScalar.java | 87 ++++++++++++++++++ .../graphql/scalars/id/UUIDScalarTest.groovy | 90 +++++++++++++++++++ .../graphql/scalars/util/TestKit.groovy | 4 + 5 files changed, 195 insertions(+) create mode 100644 src/main/java/graphql/scalars/id/UUIDScalar.java create mode 100644 src/test/groovy/graphql/scalars/id/UUIDScalarTest.groovy diff --git a/readme.md b/readme.md index 75436e8..835e546 100644 --- a/readme.md +++ b/readme.md @@ -73,6 +73,11 @@ And example query might look like: } ``` +## ID Scalars + +* `UUID` + * A universally unique identifier scalar that accepts uuid values like `2423f0a0-3b81-4115-a189-18df8b35e8fc` and produces + `java.util.UUID` type at runtime ## Object / JSON Scalars diff --git a/src/main/java/graphql/scalars/ExtendedScalars.java b/src/main/java/graphql/scalars/ExtendedScalars.java index 2d15434..5ca2aac 100644 --- a/src/main/java/graphql/scalars/ExtendedScalars.java +++ b/src/main/java/graphql/scalars/ExtendedScalars.java @@ -5,6 +5,7 @@ import graphql.scalars.datetime.DateScalar; import graphql.scalars.datetime.DateTimeScalar; import graphql.scalars.datetime.TimeScalar; +import graphql.scalars.id.UUIDScalar; import graphql.scalars.numeric.NegativeFloatScalar; import graphql.scalars.numeric.NegativeIntScalar; import graphql.scalars.numeric.NonNegativeFloatScalar; @@ -20,6 +21,8 @@ import graphql.scalars.locale.LocaleScalar; import graphql.schema.GraphQLScalarType; +import java.util.UUID; + /** * This is the API entry point for all the extended scalars */ @@ -118,6 +121,12 @@ public class ExtendedScalars { */ public static GraphQLScalarType Locale = new LocaleScalar(); + /** + * A UUID scalar that accepts a universally unique identifier and produces {@link + * java.util.UUID} objects at runtime. + */ + public static GraphQLScalarType UUID = new UUIDScalar(); + /** * An `Int` scalar that MUST be greater than zero * diff --git a/src/main/java/graphql/scalars/id/UUIDScalar.java b/src/main/java/graphql/scalars/id/UUIDScalar.java new file mode 100644 index 0000000..29cde68 --- /dev/null +++ b/src/main/java/graphql/scalars/id/UUIDScalar.java @@ -0,0 +1,87 @@ +package graphql.scalars.id; + +import graphql.Internal; +import graphql.language.StringValue; +import graphql.schema.Coercing; +import graphql.schema.CoercingParseLiteralException; +import graphql.schema.CoercingParseValueException; +import graphql.schema.CoercingSerializeException; +import graphql.schema.GraphQLScalarType; + +import java.time.DateTimeException; +import java.time.format.DateTimeFormatter; +import java.util.UUID; + +import static graphql.scalars.util.Kit.typeName; + +/** + * Access this via {@link graphql.scalars.ExtendedScalars#UUID} + */ +@Internal +public class UUIDScalar extends GraphQLScalarType { + + public UUIDScalar() { + super("UUID", "A universally unique identifier compliant UUID Scalar", new Coercing() { + @Override + public String serialize(Object input) throws CoercingSerializeException { + if (input instanceof String) { + try { + return (UUID.fromString((String)input)).toString(); + } catch ( IllegalArgumentException ex) { + throw new CoercingSerializeException( + "Expected a UUID value that can be converted : '" + ex.getMessage() + "'." + ); + } + } + else if(input instanceof UUID) { + return input.toString(); + } + else { + throw new CoercingSerializeException( + "Expected something we can convert to 'java.util.UUID' but was '" + typeName(input) + "'." + ); + } + } + + @Override + public UUID parseValue(Object input) throws CoercingParseValueException { + if(input instanceof String) { + try { + return UUID.fromString((String) input); + } catch (IllegalArgumentException ex) { + throw new CoercingParseValueException( + "Expected a 'String' of UUID type but was '" + typeName(input) + "'." + ); + } + } + else if(input instanceof UUID) { + return (UUID) input; + } + else { + throw new CoercingParseValueException( + "Expected a 'String' or 'UUID' type but was '" + typeName(input) + "'." + ); + } + } + + @Override + public UUID parseLiteral(Object input) throws CoercingParseLiteralException { + if (!(input instanceof StringValue)) { + throw new CoercingParseLiteralException( + "Expected a 'java.util.UUID' AST type object but was '" + typeName(input) + "'." + ); + } + try { + return UUID.fromString(((StringValue) input).getValue()); + } catch (IllegalArgumentException ex) { + throw new CoercingParseLiteralException( + "Expected something that we can convert to a UUID but was invalid" + ); + } + + } + + }); + } + +} diff --git a/src/test/groovy/graphql/scalars/id/UUIDScalarTest.groovy b/src/test/groovy/graphql/scalars/id/UUIDScalarTest.groovy new file mode 100644 index 0000000..043a74d --- /dev/null +++ b/src/test/groovy/graphql/scalars/id/UUIDScalarTest.groovy @@ -0,0 +1,90 @@ +package graphql.scalars.id + +import graphql.language.StringValue +import graphql.schema.CoercingParseLiteralException +import graphql.schema.CoercingParseValueException +import graphql.schema.CoercingSerializeException +import spock.lang.Specification +import spock.lang.Unroll + +import static graphql.scalars.util.TestKit.* + +class UUIDScalarTest extends Specification { + + def coercing = new UUIDScalar().getCoercing() + + @Unroll + def "UUID parseValue"() { + + when: + def result = coercing.parseValue(input) + then: + result == expectedValue + where: + input | expectedValue + "43f20307-603c-4ad1-83c6-6010d224fabf" | mkUUIDValue("43f20307-603c-4ad1-83c6-6010d224fabf") + "787dbc2b-3ddb-4098-ad1d-63d026bac111" | mkUUIDValue("787dbc2b-3ddb-4098-ad1d-63d026bac111") + } + + @Unroll + def "UUID parseValue bad inputs"() { + + when: + coercing.parseValue(input) + then: + thrown(expectedValue) + where: + input | expectedValue + "a-string-that-is-not-uuid" | CoercingParseValueException + 100 | CoercingParseValueException + "1985-04-12" | CoercingParseValueException + } + + def "UUID AST literal"() { + + when: + def result = coercing.parseLiteral(input) + then: + result == expectedValue + where: + input | expectedValue + new StringValue("6972117d-3963-4214-ab2c-fa973d7e996b") | mkUUIDValue("6972117d-3963-4214-ab2c-fa973d7e996b") + } + + def "UUID AST literal bad inputs"() { + + when: + coercing.parseLiteral(input) + then: + thrown(expectedValue) + where: + input | expectedValue + new StringValue("a-string-that-us-not-uuid") | CoercingParseLiteralException + } + + def "UUID serialization"() { + + when: + def result = coercing.serialize(input) + then: + result == expectedValue + where: + input | expectedValue + "42287d47-c5bd-45e4-b470-53e426d3d503" | "42287d47-c5bd-45e4-b470-53e426d3d503" + "423df0f3-cf05-4eb5-b708-ae2f4b4a052d" | "423df0f3-cf05-4eb5-b708-ae2f4b4a052d" + mkUUIDValue("6a90b1e6-20f3-43e5-a7ba-34db8010c071") | "6a90b1e6-20f3-43e5-a7ba-34db8010c071" + } + + def "UUID serialization bad inputs"() { + + when: + coercing.serialize(input) + then: + thrown(expectedValue) + where: + input | expectedValue + "1985-04-12" | CoercingSerializeException + 100 | CoercingSerializeException + } + +} diff --git a/src/test/groovy/graphql/scalars/util/TestKit.groovy b/src/test/groovy/graphql/scalars/util/TestKit.groovy index 35ca49e..142902d 100644 --- a/src/test/groovy/graphql/scalars/util/TestKit.groovy +++ b/src/test/groovy/graphql/scalars/util/TestKit.groovy @@ -75,4 +75,8 @@ class TestKit { return new FloatValue(new BigDecimal(d)) } + static UUID mkUUIDValue(String s) { + return UUID.fromString(s) + } + } From 612c02968a5c273fe03f9d8eafee4fd47481ff41 Mon Sep 17 00:00:00 2001 From: Brad Baker Date: Fri, 25 Feb 2022 08:37:15 +1100 Subject: [PATCH 2/3] Now with toLiteral and tests --- .../java/graphql/scalars/id/UUIDScalar.java | 45 ++++++++-------- .../graphql/scalars/id/UUIDScalarTest.groovy | 51 ++++++++++++------- 2 files changed, 55 insertions(+), 41 deletions(-) diff --git a/src/main/java/graphql/scalars/id/UUIDScalar.java b/src/main/java/graphql/scalars/id/UUIDScalar.java index eb5dbf0..79868d9 100644 --- a/src/main/java/graphql/scalars/id/UUIDScalar.java +++ b/src/main/java/graphql/scalars/id/UUIDScalar.java @@ -3,6 +3,7 @@ import graphql.Internal; import graphql.language.StringValue; import graphql.language.Value; +import graphql.scalars.util.Kit; import graphql.schema.Coercing; import graphql.schema.CoercingParseLiteralException; import graphql.schema.CoercingParseValueException; @@ -23,42 +24,39 @@ public class UUIDScalar { static { Coercing coercing = new Coercing() { - @Override - public String serialize(Object input) throws CoercingSerializeException { + private UUID convertImpl(Object input) { if (input instanceof String) { try { - return (UUID.fromString((String) input)).toString(); + return (UUID.fromString((String) input)); } catch (IllegalArgumentException ex) { - throw new CoercingSerializeException( - "Expected a UUID value that can be converted : '" + ex.getMessage() + "'." - ); + return null; } } else if (input instanceof UUID) { - return input.toString(); - } else { + return (UUID) input; + } + return null; + } + + @Override + public String serialize(Object input) throws CoercingSerializeException { + UUID result = convertImpl(input); + if (result == null) { throw new CoercingSerializeException( - "Expected something we can convert to 'java.util.UUID' but was '" + typeName(input) + "'." + "Expected type 'UUID' but was '" + Kit.typeName(input) + "'." ); } + return result.toString(); } @Override public UUID parseValue(Object input) throws CoercingParseValueException { - if (input instanceof String) { - try { - return UUID.fromString((String) input); - } catch (IllegalArgumentException ex) { - throw new CoercingParseValueException( - "Expected a 'String' of UUID type but was '" + typeName(input) + "'." - ); - } - } else if (input instanceof UUID) { - return (UUID) input; - } else { + UUID result = convertImpl(input); + if (result == null) { throw new CoercingParseValueException( - "Expected a 'String' or 'UUID' type but was '" + typeName(input) + "'." + "Expected type 'UUID' but was '" + Kit.typeName(input) + "'." ); } + return result; } @Override @@ -78,8 +76,9 @@ public UUID parseLiteral(Object input) throws CoercingParseLiteralException { } @Override - public Value valueToLiteral(Object input) { - return Coercing.super.valueToLiteral(input); + public Value valueToLiteral(Object input) { + String s = serialize(input); + return StringValue.newStringValue(s).build(); } }; diff --git a/src/test/groovy/graphql/scalars/id/UUIDScalarTest.groovy b/src/test/groovy/graphql/scalars/id/UUIDScalarTest.groovy index 043a74d..f9c1e91 100644 --- a/src/test/groovy/graphql/scalars/id/UUIDScalarTest.groovy +++ b/src/test/groovy/graphql/scalars/id/UUIDScalarTest.groovy @@ -1,17 +1,19 @@ package graphql.scalars.id import graphql.language.StringValue +import graphql.scalars.ExtendedScalars import graphql.schema.CoercingParseLiteralException import graphql.schema.CoercingParseValueException import graphql.schema.CoercingSerializeException import spock.lang.Specification import spock.lang.Unroll -import static graphql.scalars.util.TestKit.* +import static graphql.scalars.util.TestKit.mkStringValue +import static graphql.scalars.util.TestKit.mkUUIDValue class UUIDScalarTest extends Specification { - def coercing = new UUIDScalar().getCoercing() + def coercing = ExtendedScalars.UUID.getCoercing() @Unroll def "UUID parseValue"() { @@ -21,9 +23,9 @@ class UUIDScalarTest extends Specification { then: result == expectedValue where: - input | expectedValue - "43f20307-603c-4ad1-83c6-6010d224fabf" | mkUUIDValue("43f20307-603c-4ad1-83c6-6010d224fabf") - "787dbc2b-3ddb-4098-ad1d-63d026bac111" | mkUUIDValue("787dbc2b-3ddb-4098-ad1d-63d026bac111") + input | expectedValue + "43f20307-603c-4ad1-83c6-6010d224fabf" | mkUUIDValue("43f20307-603c-4ad1-83c6-6010d224fabf") + "787dbc2b-3ddb-4098-ad1d-63d026bac111" | mkUUIDValue("787dbc2b-3ddb-4098-ad1d-63d026bac111") } @Unroll @@ -34,10 +36,10 @@ class UUIDScalarTest extends Specification { then: thrown(expectedValue) where: - input | expectedValue - "a-string-that-is-not-uuid" | CoercingParseValueException - 100 | CoercingParseValueException - "1985-04-12" | CoercingParseValueException + input | expectedValue + "a-string-that-is-not-uuid" | CoercingParseValueException + 100 | CoercingParseValueException + "1985-04-12" | CoercingParseValueException } def "UUID AST literal"() { @@ -58,8 +60,8 @@ class UUIDScalarTest extends Specification { then: thrown(expectedValue) where: - input | expectedValue - new StringValue("a-string-that-us-not-uuid") | CoercingParseLiteralException + input | expectedValue + new StringValue("a-string-that-us-not-uuid") | CoercingParseLiteralException } def "UUID serialization"() { @@ -69,10 +71,10 @@ class UUIDScalarTest extends Specification { then: result == expectedValue where: - input | expectedValue - "42287d47-c5bd-45e4-b470-53e426d3d503" | "42287d47-c5bd-45e4-b470-53e426d3d503" - "423df0f3-cf05-4eb5-b708-ae2f4b4a052d" | "423df0f3-cf05-4eb5-b708-ae2f4b4a052d" - mkUUIDValue("6a90b1e6-20f3-43e5-a7ba-34db8010c071") | "6a90b1e6-20f3-43e5-a7ba-34db8010c071" + input | expectedValue + "42287d47-c5bd-45e4-b470-53e426d3d503" | "42287d47-c5bd-45e4-b470-53e426d3d503" + "423df0f3-cf05-4eb5-b708-ae2f4b4a052d" | "423df0f3-cf05-4eb5-b708-ae2f4b4a052d" + mkUUIDValue("6a90b1e6-20f3-43e5-a7ba-34db8010c071") | "6a90b1e6-20f3-43e5-a7ba-34db8010c071" } def "UUID serialization bad inputs"() { @@ -82,9 +84,22 @@ class UUIDScalarTest extends Specification { then: thrown(expectedValue) where: - input | expectedValue - "1985-04-12" | CoercingSerializeException - 100 | CoercingSerializeException + input | expectedValue + "1985-04-12" | CoercingSerializeException + 100 | CoercingSerializeException } + @Unroll + def "UUID valueToLiteral"() { + + when: + def result = coercing.valueToLiteral(input) + then: + result.isEqualTo(expectedValue) + where: + input | expectedValue + "42287d47-c5bd-45e4-b470-53e426d3d503" | mkStringValue("42287d47-c5bd-45e4-b470-53e426d3d503") + "423df0f3-cf05-4eb5-b708-ae2f4b4a052d" | mkStringValue("423df0f3-cf05-4eb5-b708-ae2f4b4a052d") + mkUUIDValue("6a90b1e6-20f3-43e5-a7ba-34db8010c071") | mkStringValue("6a90b1e6-20f3-43e5-a7ba-34db8010c071") + } } From 3080b81ff636c2a857169907d98bda82d49e0886 Mon Sep 17 00:00:00 2001 From: Brad Baker Date: Fri, 25 Feb 2022 08:39:37 +1100 Subject: [PATCH 3/3] better read me --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index cfbfc2a..def76bb 100644 --- a/readme.md +++ b/readme.md @@ -97,7 +97,7 @@ And example query might look like: * `UUID` * A universally unique identifier scalar that accepts uuid values like `2423f0a0-3b81-4115-a189-18df8b35e8fc` and produces - `java.util.UUID` type at runtime + `java.util.UUID` instances at runtime. ## Object / JSON Scalars