com.xlrit.gears.server.graphql.OffsetDateTimeScalar Maven / Gradle / Ivy
package com.xlrit.gears.server.graphql;
import java.time.DateTimeException;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoField;
import java.util.function.Function;
import graphql.language.StringValue;
import graphql.language.Value;
import graphql.schema.*;
import static graphql.scalar.CoercingUtil.typeName;
public final class OffsetDateTimeScalar {
public static final GraphQLScalarType INSTANCE;
private OffsetDateTimeScalar() {}
private static final DateTimeFormatter customOutputFormatter = getCustomDateTimeFormatter();
static {
Coercing coercing = new Coercing<>() {
@Override
public String serialize(Object input) throws CoercingSerializeException {
if (!(input instanceof OffsetDateTime offsetDateTime)) {
throw new CoercingSerializeException(
"Expected something we can convert to 'java.time.OffsetDateTime' but was '" + typeName(input) + "'."
);
}
try {
return customOutputFormatter.format(offsetDateTime);
}
catch (DateTimeException e) {
throw new CoercingSerializeException(
"Unable to turn TemporalAccessor into OffsetDateTime because of : '" + e.getMessage() + "'."
);
}
}
@Override
public OffsetDateTime parseValue(Object input) throws CoercingParseValueException {
if (input instanceof OffsetDateTime offsetDateTime) {
return offsetDateTime;
}
else if (input instanceof ZonedDateTime zonedDateTime) {
return zonedDateTime.toOffsetDateTime();
}
else if (input instanceof String s) {
return parseOffsetDateTime(s, CoercingParseValueException::new);
}
else {
throw new CoercingParseValueException(
"Expected a 'String' but was '" + typeName(input) + "'."
);
}
}
@Override
public OffsetDateTime parseLiteral(Object input) throws CoercingParseLiteralException {
if (!(input instanceof StringValue)) {
throw new CoercingParseLiteralException(
"Expected AST type 'StringValue' but was '" + typeName(input) + "'."
);
}
return parseOffsetDateTime(((StringValue) input).getValue(), CoercingParseLiteralException::new);
}
@Override
public Value> valueToLiteral(Object input) {
String s = serialize(input);
return StringValue.newStringValue(s).build();
}
private OffsetDateTime parseOffsetDateTime(String s, Function exceptionMaker) {
try {
OffsetDateTime parse = OffsetDateTime.parse(s, DateTimeFormatter.ISO_OFFSET_DATE_TIME);
if (parse.get(ChronoField.OFFSET_SECONDS) == 0 && s.endsWith("-00:00")) {
throw exceptionMaker.apply("Invalid value : '" + s + "'. Negative zero offset is not allowed");
}
return parse;
} catch (DateTimeParseException e) {
throw exceptionMaker.apply("Invalid RFC3339 value : '" + s + "'. because of : '" + e.getMessage() + "'");
}
}
};
INSTANCE = GraphQLScalarType.newScalar()
.name("DateTime")
.description("A slightly refined version of RFC-3339 compliant DateTime Scalar")
.specifiedByUrl("https://scalars.graphql.org/andimarek/date-time")
.coercing(coercing)
.build();
}
private static DateTimeFormatter getCustomDateTimeFormatter() {
return new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.append(DateTimeFormatter.ISO_LOCAL_DATE)
.appendLiteral('T')
.appendValue(ChronoField.HOUR_OF_DAY, 2)
.appendLiteral(':')
.appendValue(ChronoField.MINUTE_OF_HOUR, 2)
.appendLiteral(':')
.appendValue(ChronoField.SECOND_OF_MINUTE, 2)
.appendFraction(ChronoField.NANO_OF_SECOND, 3, 3, true)
.appendOffset("+HH:MM", "Z")
.toFormatter();
}
}