Skip to content

Commit 105c8dc

Browse files
authored
fix: add support for java.sql.Timestamp GraphQLScalar coercing (#283)
* fix: add support for java.sql.Timestamp GraphQLScalar coercing * fix: polish tests * fix: refactor date time converter helpers * fix: add zoned date time converter * fix: correct testTimestampParseLiteralStringValueLocalDate test
1 parent 65307a9 commit 105c8dc

File tree

2 files changed

+314
-27
lines changed

2 files changed

+314
-27
lines changed

graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/JavaScalars.java

+127-27
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.math.BigDecimal;
2121
import java.math.BigInteger;
2222
import java.nio.charset.StandardCharsets;
23+
import java.sql.Timestamp;
2324
import java.text.DateFormat;
2425
import java.text.ParseException;
2526
import java.text.SimpleDateFormat;
@@ -39,8 +40,11 @@
3940
import java.util.LinkedHashMap;
4041
import java.util.List;
4142
import java.util.Map;
43+
import java.util.Objects;
4244
import java.util.TimeZone;
4345
import java.util.UUID;
46+
import java.util.concurrent.CopyOnWriteArrayList;
47+
import java.util.function.Function;
4448
import java.util.stream.Collectors;
4549

4650
import org.slf4j.Logger;
@@ -60,6 +64,7 @@
6064
import graphql.language.Value;
6165
import graphql.language.VariableReference;
6266
import graphql.schema.Coercing;
67+
import graphql.schema.CoercingParseLiteralException;
6368
import graphql.schema.CoercingParseValueException;
6469
import graphql.schema.CoercingSerializeException;
6570
import graphql.schema.GraphQLScalarType;
@@ -577,45 +582,57 @@ private java.sql.Date parseStringToDate(String input) {
577582
}
578583
}
579584

580-
public static class GraphQLSqlTimestampCoercing implements Coercing<Object, Object> {
585+
public static class GraphQLSqlTimestampCoercing implements Coercing<Timestamp, Object> {
581586

582-
@Override
583-
public Object serialize(Object input) {
584-
if (input instanceof String) {
585-
return parseStringToTimestamp((String) input);
586-
} else if (input instanceof Date) {
587-
return new java.sql.Timestamp(((Date) input).getTime());
588-
} else if (input instanceof Long) {
589-
return new java.sql.Timestamp(((Long) input).longValue());
590-
} else if (input instanceof Integer) {
591-
return new java.sql.Timestamp(((Integer) input).longValue());
587+
private Timestamp doConvert(Object input) {
588+
if (input instanceof Long) {
589+
return new Timestamp(Long.class.cast(input));
590+
} else if (input instanceof String) {
591+
Instant instant = DateTimeHelper.parseDate(String.class.cast(input));
592+
593+
return Timestamp.from(instant);
594+
} else if (input instanceof Timestamp) {
595+
return Timestamp.class.cast(input);
592596
}
597+
593598
return null;
594599
}
595-
600+
596601
@Override
597-
public Object parseValue(Object input) {
598-
return serialize(input);
602+
public Object serialize(Object input) {
603+
Timestamp result = doConvert(input);
604+
605+
if (result == null) {
606+
throw new CoercingSerializeException("Invalid value '" + input + "' for Timestamp");
607+
}
608+
609+
return DateTimeFormatter.ISO_INSTANT.format(result.toInstant());
599610
}
600611

601612
@Override
602-
public Object parseLiteral(Object input) {
603-
if (input instanceof StringValue) {
604-
return parseStringToTimestamp(((StringValue) input).getValue());
605-
} else if (input instanceof IntValue) {
606-
BigInteger value = ((IntValue) input).getValue();
607-
return new java.sql.Date(value.longValue());
613+
public Timestamp parseValue(Object input) {
614+
Timestamp result = doConvert(input);
615+
616+
if (result == null) {
617+
throw new CoercingParseValueException("Invalid value '" + input + "' for Timestamp");
608618
}
609-
return null;
619+
return result;
610620
}
611621

612-
private java.sql.Timestamp parseStringToTimestamp(String input) {
613-
try {
614-
return new java.sql.Timestamp(DateFormat.getInstance().parse(input).getTime());
615-
} catch (ParseException e) {
616-
log.warn("Failed to parse Timestamp from input: " + input, e);
617-
return null;
622+
@Override
623+
public Timestamp parseLiteral(Object input) {
624+
Object value = null;
625+
626+
if (IntValue.class.isInstance(input)) {
627+
value = IntValue.class.cast(input).getValue().longValue();
628+
}
629+
else if (StringValue.class.isInstance(input)) {
630+
value = StringValue.class.cast(input).getValue();
631+
} else {
632+
throw new CoercingParseLiteralException("Invalid value '" + input + "' for Timestamp");
618633
}
634+
635+
return doConvert(value);
619636
}
620637
}
621638

@@ -724,5 +741,88 @@ public Object parseLiteral(Object value, Map<String, Object> variables) {
724741
}
725742

726743
}
744+
745+
public final static class DateTimeHelper {
746+
747+
static final List<Function<String, Instant>> CONVERTERS = new CopyOnWriteArrayList<>();
748+
749+
static {
750+
CONVERTERS.add(new InstantConverter());
751+
CONVERTERS.add(new OffsetDateTimeConverter());
752+
CONVERTERS.add(new ZonedDateTimeConverter());
753+
CONVERTERS.add(new LocalDateTimeConverter());
754+
CONVERTERS.add(new LocalDateConverter());
755+
}
756+
757+
public static Instant parseDate(String date) {
758+
Objects.requireNonNull(date, "date");
759+
760+
for (Function<String, Instant> converter : CONVERTERS) {
761+
try {
762+
return converter.apply(date);
763+
} catch (java.time.format.DateTimeParseException ignored) {
764+
}
765+
}
766+
767+
return null;
768+
}
769+
770+
static class InstantConverter implements Function<String, Instant> {
771+
772+
@Override
773+
public Instant apply(String date) {
774+
return Instant.parse(date);
775+
}
776+
}
777+
778+
static class OffsetDateTimeConverter implements Function<String, Instant> {
779+
780+
static final DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone(ZoneOffset.UTC);
781+
782+
@Override
783+
public Instant apply(String date) {
784+
return OffsetDateTime.parse(date, FORMATTER)
785+
.toInstant();
786+
}
787+
}
788+
789+
static class ZonedDateTimeConverter implements Function<String, Instant> {
790+
791+
static final DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_ZONED_DATE_TIME.withZone(ZoneOffset.UTC);
727792

793+
@Override
794+
public Instant apply(String date) {
795+
return ZonedDateTime.parse(date, FORMATTER)
796+
.toInstant();
797+
}
798+
}
799+
800+
801+
static class LocalDateTimeConverter implements Function<String, Instant> {
802+
803+
static final DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME.withZone(ZoneOffset.UTC);
804+
805+
@Override
806+
public Instant apply(String date) {
807+
return LocalDateTime.parse(date, FORMATTER)
808+
.toInstant(ZoneOffset.UTC);
809+
}
810+
}
811+
812+
static class LocalDateConverter implements Function<String, Instant> {
813+
814+
static final DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE.withZone(ZoneOffset.UTC);
815+
816+
@Override
817+
public Instant apply(String date) {
818+
LocalDate localDate = LocalDate.parse(date, FORMATTER);
819+
820+
return localDate.atStartOfDay()
821+
.toInstant(ZoneOffset.UTC);
822+
}
823+
}
824+
}
825+
826+
827+
728828
}

0 commit comments

Comments
 (0)