Skip to content

Commit

Permalink
added new scalar Calendar (and GregorianCalendar) as a new DateTime s…
Browse files Browse the repository at this point in the history
…calar
  • Loading branch information
mskacelik authored and jmartisk committed Oct 12, 2023
1 parent 1b9605a commit 3b1c333
Show file tree
Hide file tree
Showing 16 changed files with 409 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.List;
Expand Down Expand Up @@ -336,6 +337,9 @@ private JsonValue scalarValue(Object value) {
if (value instanceof Date) {
return Json.createValue(((Date) value).toInstant().toString());
}
if (value instanceof Calendar) {
return Json.createValue(((Calendar) value).toInstant().toString());
}
if (value instanceof Enum) {
return Json.createValue(((Enum<?>) value).name());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;

Expand Down Expand Up @@ -41,6 +42,9 @@ Object read() {
}
if (java.util.UUID.class.equals(this.type.getRawType()))
return java.util.UUID.fromString(value.getString());
if (java.util.Calendar.class.isAssignableFrom(this.type.getRawType())) {
return formattedCalendar(value.getString());
}

if (Number.class.isAssignableFrom(this.type.getRawType())
&& field != null &&
Expand Down Expand Up @@ -98,6 +102,52 @@ private Date formattedDate(String value) {
}
}

private Calendar formattedCalendar(String value) {
if (field != null) {
String format = null;
String locale = null;

JsonbDateFormat jsonbDateFormat = field.getAnnotation(JsonbDateFormat.class);
if (jsonbDateFormat != null) {
format = jsonbDateFormat.value();
locale = jsonbDateFormat.locale();
}

DateFormat dateFormat = field.getAnnotation(DateFormat.class);
if (dateFormat != null) {
format = dateFormat.value();
locale = dateFormat.locale();
}

SimpleDateFormat sdf;
if (format != null) {
if (locale == null || locale.isEmpty()) {
sdf = new SimpleDateFormat(format);
} else {
sdf = new SimpleDateFormat(format, Locale.forLanguageTag(locale));
}
} else {
// Use the default format
sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
}
try {
Date date = sdf.parse(value);

// Create a Calendar instance and set its time
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);

return calendar;
} catch (ParseException e) {
throw new RuntimeException("Cannot parse date", e);
}
} else {
Calendar calendar = Calendar.getInstance();
calendar.setTime(Date.from(Instant.parse(value)));
return calendar;
}
}

private Number formattedNumber(String input) {
String locale = null;
String format = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ private String graphQlInputTypeName(TypeInfo type) {
case "OffsetDateTime":
case "ZonedDateTime":
case "Instant":
case "Calendar":
case "GregorianCalendar":
return "DateTime";
default:
return type.getSimpleName() + (type.isScalar() || type.isEnum() ? "" : "Input");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ public boolean isScalar() {
|| CharSequence.class.isAssignableFrom(getRawType())
|| Character.class.equals(getRawType()) // has a valueOf(char), not valueOf(String)
|| java.util.Date.class.equals(getRawType())
|| java.util.Calendar.class.isAssignableFrom(getRawType())
|| java.util.UUID.class.equals(getRawType())
|| scalarConstructor().isPresent()
|| java.util.OptionalInt.class.equals(getRawType())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
import java.time.Period;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.Deque;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
Expand Down Expand Up @@ -125,7 +127,7 @@ public static boolean isNumberLikeTypeOrContainedIn(Type type) {
*/
public static boolean isDateLikeTypeOrContainedIn(Type type) {
return isTypeOrContainedIn(type, LOCALDATE, LOCALTIME, LOCALDATETIME, ZONEDDATETIME, OFFSETDATETIME, OFFSETTIME,
UTIL_DATE, SQL_DATE, SQL_TIMESTAMP, SQL_TIME, INSTANT);
UTIL_DATE, SQL_DATE, SQL_TIMESTAMP, SQL_TIME, INSTANT, CALENDAR, GREGORIAN_CALENDAR);
}

private static boolean isTypeOrContainedIn(Type type, DotName... valid) {
Expand Down Expand Up @@ -306,6 +308,9 @@ public static boolean isUnwrappedType(Type type) {
public static final DotName OFFSETTIME = DotName.createSimple(OffsetTime.class.getName());
public static final DotName INSTANT = DotName.createSimple(Instant.class.getName());

public static final DotName CALENDAR = DotName.createSimple(Calendar.class.getName());
public static final DotName GREGORIAN_CALENDAR = DotName.createSimple(GregorianCalendar.class.getName());

public static final DotName PERIOD = DotName.createSimple(Period.class.getName());
public static final DotName DURATION = DotName.createSimple(Duration.class.getName());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import java.time.OffsetTime;
import java.time.Period;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Map;
import java.util.OptionalDouble;
Expand Down Expand Up @@ -156,6 +158,8 @@ public static Reference getIDScalar(String className) {
populateScalar(ZonedDateTime.class.getName(), DATETIME, String.class.getName());
populateScalar(OffsetDateTime.class.getName(), DATETIME, String.class.getName());
populateScalar(Instant.class.getName(), DATETIME, String.class.getName());
populateScalar(Calendar.class.getName(), DATETIME, String.class.getName());
populateScalar(GregorianCalendar.class.getName(), DATETIME, String.class.getName());

// Duration
populateScalar(Duration.class.getName(), DURATION, String.class.getName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ public static boolean isDateLikeType(String className) {
public static final String SQL_TIMESTAMP = java.sql.Timestamp.class.getName();
public static final String SQL_TIME = java.sql.Time.class.getName();

public static final String CALENDAR = java.util.Calendar.class.getName();
public static final String GREGORIAN_CALENDAR = java.util.GregorianCalendar.class.getName();

public static final String DURATION = Duration.class.getName();
public static final String PERIOD = Period.class.getName();

Expand Down Expand Up @@ -182,6 +185,8 @@ public static boolean isDateLikeType(String className) {
DATES.add(SQL_DATE);
DATES.add(SQL_TIMESTAMP);
DATES.add(SQL_TIME);
DATES.add(CALENDAR);
DATES.add(GREGORIAN_CALENDAR);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,11 @@ protected Object afterRecursiveTransform(Object fieldValue, Field field, DataFet
} else if (Classes.isPrimitiveOf(expectedType, receivedType)) {
//expected is a primitive, we got the wrapper
return fieldValue;
} else if (expectedType.equals("java.util.Calendar") &&
receivedType.equals("java.util.GregorianCalendar")) {
// special case since in the CalendarTransformer#in it creates 'java.util.GregorianCalendar' object
// if the argument is type of 'java.util.Calendar'
return fieldValue;
} else if (field.getReference().getType().equals(ReferenceType.ENUM)) {
Class<?> enumClass = classloadingService.loadClass(field.getReference().getClassName());
return Enum.valueOf((Class<Enum>) enumClass, fieldValue.toString());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

/**
* Scalar for DateTime.
Expand All @@ -16,7 +18,7 @@ public class DateTimeScalar extends AbstractDateScalar {

public DateTimeScalar() {
super("DateTime", LocalDateTime.class, Date.class, Timestamp.class, ZonedDateTime.class,
OffsetDateTime.class, Instant.class);
OffsetDateTime.class, Instant.class, Calendar.class, GregorianCalendar.class);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package io.smallrye.graphql.transformation;

import static io.smallrye.graphql.SmallRyeGraphQLServerMessages.msg;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;

import io.smallrye.graphql.schema.model.Field;
import io.smallrye.graphql.schema.model.Transformation;

public class CalendarTransformer implements Transformer<Calendar, String> {

private final String targetClassName;
private final DateFormat dateFormat;

public CalendarTransformer(final Field field, final String targetClassName) {
this.dateFormat = getDateFormat(field.getTransformation());
this.targetClassName = targetClassName;
}

public CalendarTransformer(final Field field) {
this(field, field.getReference().getClassName());
}

@Override
public Calendar in(String o) throws Exception {
if (dateFormat == null) {
throw msg.notValidDateOrTimeType(targetClassName);
}
return new Calendar.Builder().setInstant(dateFormat.parse(o)).build();
}

@Override
public String out(Calendar o) {
return dateFormat.format(o.getTime());
}

private static DateFormat getDateFormat(Transformation formatter) {
if (formatter == null) {
// Default format if no formatter is provided
return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
}
String format = formatter.getFormat();
if (format == null)
return null;
String localeTag = formatter.getLocale();

// Create SimpleDateFormat with the specified format and locale
Locale locale = (localeTag != null) ? Locale.forLanguageTag(localeTag) : Locale.getDefault();
return new SimpleDateFormat(format, locale);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ static Transformer dateTransformer(Field field) {
if (LegacyDateTransformer.SUPPORTED_TYPES.contains(field.getReference().getClassName())) {
return new LegacyDateTransformer(field);
}
if (java.util.Calendar.class.getName().equals(field.getReference().getClassName()) ||
java.util.GregorianCalendar.class.getName().equals(field.getReference().getClassName())) {
return new CalendarTransformer(field);
}
return new DateTransformer(field);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package io.smallrye.graphql.tests.calendar;

import static org.assertj.core.api.Assertions.assertThat;

import java.net.URL;

import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.container.test.api.RunAsClient;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Test;
import org.junit.runner.RunWith;

import io.smallrye.graphql.tests.GraphQLAssured;

@RunWith(Arquillian.class)
@RunAsClient
public class CalendarTest {

@Deployment
public static WebArchive deployment() {
return ShrinkWrap.create(WebArchive.class, "calendar-test.war")
.addClasses(SomeApi.class);
}

@ArquillianResource
URL testingURL;

@Test
public void queryWithCalendarReturnTypeAndArgumentTest() {
GraphQLAssured graphQLAssured = new GraphQLAssured(testingURL);

String response = graphQLAssured
.post("{ someCalendar(calendar: \"2018-05-05T11:50:45.314Z\") }");
assertThat(response).contains("{\"data\":{\"someCalendar\":\"2018-05-05T11:50:45.314Z\"}}")
.doesNotContain("error");
}

@Test
public void queryWithGregorianCalendarReturnTypeAndArgumentTest() {
GraphQLAssured graphQLAssured = new GraphQLAssured(testingURL);

String response = graphQLAssured
.post("{ someGregorianCalendar(calendar: \"2011-05-05T11:50:45.112Z\") }");
assertThat(response).contains("{\"data\":{\"someGregorianCalendar\":\"2011-05-05T11:50:45.112Z\"}}")
.doesNotContain("error");
}

@Test
public void queryWithFormattedCalendarReturnTypeAndArgumentTest() {
GraphQLAssured graphQLAssured = new GraphQLAssured(testingURL);
String response = graphQLAssured
.post("{ someFormattedCalendar(calendar: \"2023 04 at 13 hours\") }");
assertThat(response).contains("{\"data\":{\"someFormattedCalendar\":\"01. April 2023 at 01:00 PM\"}}")
.doesNotContain("error");
}

@Test
public void queryWithFormattedGregorianCalendarReturnTypeAndArgumentTest() {
GraphQLAssured graphQLAssured = new GraphQLAssured(testingURL);
String response = graphQLAssured
.post("{ someFormattedGregorianCalendar(calendar: \"2023 04 at 13 hours\") }");
assertThat(response).contains("{\"data\":{\"someFormattedGregorianCalendar\":\"01. April 2023 at 01:00 PM\"}}")
.doesNotContain("error");
}

@Test
public void queryWithWrongCalendarFormatTest() {
GraphQLAssured graphQLAssured = new GraphQLAssured(testingURL);

String response = graphQLAssured
.post("{ someCalendar(calendar: \"30th of August 2000\") }");
assertThat(response).containsIgnoringWhitespaces("{\n" +
" \"errors\": [\n" +
" {\n" +
" \"message\": \"argument 'calendar' with value 'StringValue{value='30th of August 2000'}' is not a valid 'DateTime'\",\n"
+
" \"locations\": [\n" +
" {\n" +
" \"line\": 1,\n" +
" \"column\": 16\n" +
" }\n" +
" ],\n" +
" \"extensions\": {\n" +
" \"classification\": \"ValidationError\"\n" +
" }\n" +
" }\n" +
" ],\n" +
" \"data\": {\n" +
" \"someCalendar\": null\n" +
" }\n" +
"}");
}

@Test
public void queryWithWrongGregorianCalendarFormatTest() {
GraphQLAssured graphQLAssured = new GraphQLAssured(testingURL);

String response = graphQLAssured
.post("{ someGregorianCalendar(calendar: \"30th of August 2000\") }");
assertThat(response).containsIgnoringWhitespaces("{\n" +
" \"errors\": [\n" +
" {\n" +
" \"message\": \"argument 'calendar' with value 'StringValue{value='30th of August 2000'}' is not a valid 'DateTime'\",\n"
+
" \"locations\": [\n" +
" {\n" +
" \"line\": 1,\n" +
" \"column\": 25\n" +
" }\n" +
" ],\n" +
" \"extensions\": {\n" +
" \"classification\": \"ValidationError\"\n" +
" }\n" +
" }\n" +
" ],\n" +
" \"data\": {\n" +
" \"someGregorianCalendar\": null\n" +
" }\n" +
"}");
}
}
Loading

0 comments on commit 3b1c333

Please sign in to comment.