Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add HexColorCode Scalar #122

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -541,3 +541,26 @@ For example, imagine a `phoneNumber` scalar like this :
.build()

```

## HexColorCode Scalar
<table>
<tr>
<td>Scalar Definition</td>
<td>Description</td>
</tr>
<tr>
<td><pre lang="graphql">
scalar HexColorCode
@specifiedBy(url:
"https://tools.ietf.org/html/bcp47"
)
</pre></td>
<td>The HexColorCode scalar handles <a href="https://en.wikipedia.org/wiki/Web_colors">hex color code</a>. You can handle it via <a href="https://docs.oracle.com/javase/8/docs/api/java/awt/Color.html">java.awt.Color </a>(which was built into JDK)</td>
</tr>
</table>
It Support Following Formats:

- #RRGGBB
- #RGB
- #RRGGBBAA
- #RGBA
7 changes: 7 additions & 0 deletions src/main/java/graphql/scalars/ExtendedScalars.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 <a href="https://en.wikipedia.org/wiki/Web_colors">Web colors</a> 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.
Expand Down
120 changes: 120 additions & 0 deletions src/main/java/graphql/scalars/color/hex/HexColorCodeScalar.java
Original file line number Diff line number Diff line change
@@ -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 <a href="https://en.wikipedia.org/wiki/Web_colors">Web colors</a> 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<Color, String> coercing = new Coercing<Color, String>() {

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<String, RuntimeException> 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();
}

}
Original file line number Diff line number Diff line change
@@ -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() | _
}

}
10 changes: 9 additions & 1 deletion src/test/groovy/graphql/scalars/util/TestKit.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
}