diff --git a/README.md b/README.md
index 082ff1b..16d6060 100644
--- a/README.md
+++ b/README.md
@@ -541,3 +541,26 @@ For example, imagine a `phoneNumber` scalar like this :
.build()
```
+
+## HexColorCode Scalar
+
+
+Scalar Definition |
+Description |
+
+
+
+scalar HexColorCode
+ @specifiedBy(url:
+ "https://tools.ietf.org/html/bcp47"
+ )
+ |
+The HexColorCode scalar handles hex color code. You can handle it via java.awt.Color (which was built into JDK) |
+
+
+It Support Following Formats:
+
+- #RRGGBB
+- #RGB
+- #RRGGBBAA
+- #RGBA
diff --git a/src/main/java/graphql/scalars/ExtendedScalars.java b/src/main/java/graphql/scalars/ExtendedScalars.java
index ce36232..0970206 100644
--- a/src/main/java/graphql/scalars/ExtendedScalars.java
+++ b/src/main/java/graphql/scalars/ExtendedScalars.java
@@ -2,6 +2,7 @@
import graphql.PublicApi;
import graphql.scalars.alias.AliasedScalar;
+import graphql.scalars.color.hex.HexColorCodeScalar;
import graphql.scalars.country.code.CountryCodeScalar;
import graphql.scalars.currency.CurrencyScalar;
import graphql.scalars.datetime.DateScalar;
@@ -150,6 +151,12 @@ public class ExtendedScalars {
*/
public static final GraphQLScalarType CountryCode = CountryCodeScalar.INSTANCE;
+ /**
+ * A field whose value is a hex color code
+ * See the Web colors for more details.
+ */
+ public static final GraphQLScalarType HexColorCode = HexColorCodeScalar.INSTANCE;
+
/**
* A UUID scalar that accepts a universally unique identifier and produces {@link
* java.util.UUID} objects at runtime.
diff --git a/src/main/java/graphql/scalars/color/hex/HexColorCodeScalar.java b/src/main/java/graphql/scalars/color/hex/HexColorCodeScalar.java
new file mode 100644
index 0000000..0cdc721
--- /dev/null
+++ b/src/main/java/graphql/scalars/color/hex/HexColorCodeScalar.java
@@ -0,0 +1,120 @@
+package graphql.scalars.color.hex;
+
+import graphql.language.StringValue;
+import graphql.language.Value;
+import graphql.schema.*;
+
+import java.awt.*;
+import java.util.function.Function;
+import java.util.regex.Pattern;
+
+import static graphql.scalars.util.Kit.typeName;
+/**
+ * Access this via {@link graphql.scalars.ExtendedScalars#HexColorCode}
+ * See the Web colors for more details.
+ * @implNote Supports the following formats: #RGB, #RGBA, #RRGGBB, #RRGGBBAA. Need to be prefixed with '#'
+ */
+public class HexColorCodeScalar {
+
+ public static final GraphQLScalarType INSTANCE;
+
+
+ static {
+ Coercing coercing = new Coercing() {
+
+ private final Pattern HEX_PATTERN = Pattern.compile("^(#([A-Fa-f0-9]{3,4}){1,2})$");
+
+ @Override
+ public String serialize(Object input) throws CoercingSerializeException {
+ Color color = parseColor(input, CoercingSerializeException::new);
+ boolean hasAlpha = color.getAlpha() != 255;
+ if (hasAlpha){
+ return String.format("#%02x%02x%02x%02x", color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
+ } else {
+ return String.format("#%02x%02x%02x", color.getRed(), color.getGreen(), color.getBlue());
+ }
+ }
+
+ @Override
+ public Color parseValue(Object input) throws CoercingParseValueException {
+ return parseColor(input, CoercingParseValueException::new);
+ }
+
+ @Override
+ public Color parseLiteral(Object input) throws CoercingParseLiteralException {
+ if (!(input instanceof StringValue)) {
+ throw new CoercingParseLiteralException("Expected type 'StringValue' but was '" + typeName(input) + "'.");
+ }
+ String stringValue = ((StringValue) input).getValue();
+ return parseColor(stringValue, CoercingParseLiteralException::new);
+ }
+
+ @Override
+ public Value> valueToLiteral(Object input) {
+ String s = serialize(input);
+ return StringValue.newStringValue(s).build();
+ }
+
+
+ private Color parseColor(Object input, Function exceptionMaker) {
+ final Color result;
+ if (input instanceof Color) {
+ result = (Color) input;
+ } else if (input instanceof String) {
+ try {
+ String hex = ((String) input);
+
+ //validation
+ //regex
+ if (!HEX_PATTERN.matcher(hex).matches()) {
+ throw new IllegalArgumentException("Invalid hex color code value : '" + input + "'.");
+ }
+
+ int i = Integer.decode(hex);
+ int inputLength = hex.length();
+
+ if (inputLength == 4) {
+ // #RGB
+ result = new Color(
+ (i >> 8 & 0xF) * 0x11,
+ (i >> 4 & 0xF) * 0x11,
+ (i & 0xF) * 0x11
+ );
+ } else if (inputLength == 5) {
+ // #RGBA
+ result = new Color(
+ (i >> 12 & 0xF) * 0x11,
+ (i >> 8 & 0xF) * 0x11,
+ (i >> 4 & 0xF) * 0x11,
+ (i & 0xF) * 0x11
+ );
+ } else if (inputLength == 7) {
+ // #RRGGBB
+ result = new Color(i);
+ } else {
+ // #RRGGBBAA
+ result = new Color(
+ (i >> 24 & 0xFF),
+ (i >> 16 & 0xFF),
+ (i >> 8 & 0xFF),
+ (i & 0xFF)
+ );
+ }
+ } catch (NullPointerException | IllegalArgumentException ex) {
+ throw exceptionMaker.apply("Invalid hex color code value : '" + input + "'. because of : '" + ex.getMessage() + "'");
+ }
+ } else {
+ throw exceptionMaker.apply("Expected a 'String' or 'Color' but was '" + typeName(input) + "'.");
+ }
+ return result;
+ }
+
+ };
+
+ INSTANCE = GraphQLScalarType.newScalar()
+ .name("HexColorCode")
+ .description("A field whose value is a hexadecimal color code: https://en.wikipedia.org/wiki/Web_colors.")
+ .coercing(coercing).build();
+ }
+
+}
diff --git a/src/test/groovy/graphql/scalars/color/hex/HexColorCodeScalarTest.groovy b/src/test/groovy/graphql/scalars/color/hex/HexColorCodeScalarTest.groovy
new file mode 100644
index 0000000..97c6e18
--- /dev/null
+++ b/src/test/groovy/graphql/scalars/color/hex/HexColorCodeScalarTest.groovy
@@ -0,0 +1,92 @@
+package graphql.scalars.color.hex
+
+import graphql.language.StringValue
+import graphql.scalars.ExtendedScalars
+import graphql.schema.CoercingParseValueException
+import spock.lang.Specification
+import spock.lang.Unroll
+
+import static graphql.scalars.util.TestKit.mkColor
+
+class HexColorCodeScalarTest extends Specification {
+
+ def coercing = ExtendedScalars.HexColorCode.getCoercing()
+
+ @Unroll
+ def "invoke parseValue for hexCode"() {
+ when:
+ def result = coercing.parseValue(input)
+ then:
+ result.equals(expectedValue)
+ where:
+ input | expectedValue
+ "#ff0000" | mkColor(0xff, 0, 0)
+ "#123" | mkColor(0x11, 0x22, 0x33)
+ "#11223344" | mkColor(0x11, 0x22, 0x33, 0x44)
+ "#1234" | mkColor(0x11, 0x22, 0x33, 0x44)
+ }
+
+ @Unroll
+ def "invoke parseLiteral for hexCode"() {
+ when:
+ def result = coercing.parseLiteral(input)
+ then:
+ result == expectedValue
+ where:
+ input | expectedValue
+ new StringValue("#ff0000") | mkColor(0xff, 0, 0)
+ new StringValue("#123") | mkColor(0x11, 0x22, 0x33)
+ new StringValue("#11223344") | mkColor(0x11, 0x22, 0x33, 0x44)
+ new StringValue("#1234") | mkColor(0x11, 0x22, 0x33, 0x44)
+ }
+
+ @Unroll
+ def "invoke serialize with hexCode"() {
+ when:
+ def result = coercing.serialize(input)
+ then:
+ result == expectedValue
+ where:
+ input | expectedValue
+ "#ff0000" | "#ff0000"
+ "#123" | "#112233"
+ "#11223344" | "#11223344"
+ "#1234" | "#11223344"
+ mkColor(0x21, 0x23, 0x33) | "#212333"
+ mkColor(0x21, 0x23, 0x33, 0x44) | "#21233344"
+ }
+
+ @Unroll
+ def "invoke valueToLiteral with hexCode"() {
+ when:
+ def result = coercing.valueToLiteral(input)
+ then:
+ result.isEqualTo(expectedValue)
+ where:
+ input | expectedValue
+ "#ff0000" | new StringValue("#ff0000")
+ "#123" | new StringValue("#112233")
+ "#11223344" | new StringValue("#11223344")
+ "#1234" | new StringValue("#11223344")
+ mkColor(0x21, 0x23, 0x33) | new StringValue("#212333")
+ mkColor(0x21, 0x23, 0x33, 0x44) | new StringValue("#21233344")
+ }
+
+ @Unroll
+ def "parseValue throws exception for invalid input #value"() {
+ when:
+ def result = coercing.parseValue(input)
+ then:
+ thrown(CoercingParseValueException)
+ where:
+ input | _
+ "ff000" | _
+ "" | _
+ "not a hex code" | _
+ "42.3" | _
+ new Double(42.3) | _
+ new Float(42.3) | _
+ new Object() | _
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/groovy/graphql/scalars/util/TestKit.groovy b/src/test/groovy/graphql/scalars/util/TestKit.groovy
index 2a96dee..e5acaac 100644
--- a/src/test/groovy/graphql/scalars/util/TestKit.groovy
+++ b/src/test/groovy/graphql/scalars/util/TestKit.groovy
@@ -5,6 +5,7 @@ import graphql.language.IntValue
import graphql.language.StringValue
import graphql.scalars.country.code.CountryCode
+import java.awt.Color
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
@@ -97,4 +98,11 @@ class TestKit {
static CountryCode mkCountryCode(String countryCode) {
return CountryCode.valueOf(countryCode)
}
-}
+
+ static Color mkColor(int r, int g, int b, int a) {
+ return new Color(r, g, b, a)
+ }
+ static Color mkColor(int r, int g, int b) {
+ return new Color(r, g, b)
+ }
+}
\ No newline at end of file