Skip to content

Commit e91dff6

Browse files
Add offsetTime Scalar to Typefunction (#84)
Add offsetTime Scalar to Typefunction
1 parent e7d83ae commit e91dff6

File tree

5 files changed

+158
-12
lines changed

5 files changed

+158
-12
lines changed

hypertrace-core-graphql-common-schema/src/main/java/org/hypertrace/core/graphql/common/schema/CommonSchemaFragment.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.hypertrace.core.graphql.common.schema.typefunctions.AttributeScopeDynamicEnum;
88
import org.hypertrace.core.graphql.common.schema.typefunctions.DateTimeScalar;
99
import org.hypertrace.core.graphql.common.schema.typefunctions.DurationScalar;
10+
import org.hypertrace.core.graphql.common.schema.typefunctions.OffsetTimeScalar;
1011
import org.hypertrace.core.graphql.common.schema.typefunctions.UnknownScalar;
1112
import org.hypertrace.core.graphql.spi.schema.GraphQlSchemaFragment;
1213

@@ -16,17 +17,20 @@ class CommonSchemaFragment implements GraphQlSchemaFragment {
1617
private final UnknownScalar unknownScalar;
1718
private final AttributeScopeDynamicEnum attributeScopeDynamicEnum;
1819
private final DurationScalar durationScalar;
20+
private final OffsetTimeScalar offsetTimeScalar;
1921

2022
@Inject
2123
CommonSchemaFragment(
2224
DateTimeScalar dateTimeScalar,
2325
UnknownScalar unknownScalar,
2426
AttributeScopeDynamicEnum attributeScopeDynamicEnum,
25-
DurationScalar durationScalar) {
27+
DurationScalar durationScalar,
28+
OffsetTimeScalar offsetTimeScalar) {
2629
this.dateTimeScalar = dateTimeScalar;
2730
this.unknownScalar = unknownScalar;
2831
this.attributeScopeDynamicEnum = attributeScopeDynamicEnum;
2932
this.durationScalar = durationScalar;
33+
this.offsetTimeScalar = offsetTimeScalar;
3034
}
3135

3236
@Override
@@ -41,6 +45,7 @@ public List<TypeFunction> typeFunctions() {
4145
this.unknownScalar,
4246
this.dateTimeScalar,
4347
this.attributeScopeDynamicEnum,
44-
this.durationScalar);
48+
this.durationScalar,
49+
this.offsetTimeScalar);
4550
}
4651
}

hypertrace-core-graphql-common-schema/src/main/java/org/hypertrace/core/graphql/common/schema/typefunctions/DateTimeScalar.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ private <E extends GraphqlErrorException> Instant toInstant(
6464

6565
@Override
6666
public boolean canBuildType(Class<?> aClass, AnnotatedType annotatedType) {
67-
return TemporalAccessor.class.isAssignableFrom(aClass);
67+
return Instant.class.isAssignableFrom(aClass);
6868
}
6969

7070
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package org.hypertrace.core.graphql.common.schema.typefunctions;
2+
3+
import graphql.GraphqlErrorException;
4+
import graphql.annotations.processor.ProcessingElementsContainer;
5+
import graphql.annotations.processor.typeFunctions.TypeFunction;
6+
import graphql.language.StringValue;
7+
import graphql.schema.Coercing;
8+
import graphql.schema.CoercingParseLiteralException;
9+
import graphql.schema.CoercingParseValueException;
10+
import graphql.schema.CoercingSerializeException;
11+
import graphql.schema.GraphQLScalarType;
12+
import java.lang.reflect.AnnotatedType;
13+
import java.time.DateTimeException;
14+
import java.time.OffsetTime;
15+
import java.time.temporal.TemporalAccessor;
16+
import java.util.function.Function;
17+
18+
public class OffsetTimeScalar implements TypeFunction {
19+
20+
private static final GraphQLScalarType OFFSET_TIME_SCALAR =
21+
GraphQLScalarType.newScalar()
22+
.name("OffsetTime")
23+
.description("An ISO-8601 formatted OffsetTime Scalar")
24+
.coercing(
25+
new Coercing<OffsetTime, String>() {
26+
@Override
27+
public String serialize(Object fetcherResult) throws CoercingSerializeException {
28+
return toOffsetTime(fetcherResult, CoercingSerializeException::new).toString();
29+
}
30+
31+
@Override
32+
public OffsetTime parseValue(Object input) throws CoercingParseValueException {
33+
return toOffsetTime(input, CoercingParseValueException::new);
34+
}
35+
36+
@Override
37+
public OffsetTime parseLiteral(Object input) throws CoercingParseLiteralException {
38+
return toOffsetTime(input, CoercingParseLiteralException::new);
39+
}
40+
41+
private <E extends GraphqlErrorException> OffsetTime toOffsetTime(
42+
Object offsetInput, Function<Exception, E> errorWrapper) throws E {
43+
try {
44+
if (offsetInput instanceof TemporalAccessor) {
45+
return OffsetTime.from((TemporalAccessor) offsetInput);
46+
}
47+
if (offsetInput instanceof CharSequence) {
48+
return OffsetTime.parse((CharSequence) offsetInput);
49+
}
50+
if (offsetInput instanceof StringValue) {
51+
return OffsetTime.parse(((StringValue) offsetInput).getValue());
52+
}
53+
} catch (DateTimeException exception) {
54+
throw errorWrapper.apply(exception);
55+
}
56+
throw errorWrapper.apply(
57+
new DateTimeException(
58+
String.format(
59+
"Cannot convert provided format '%s' to OffsetTime",
60+
offsetInput.getClass().getCanonicalName())));
61+
}
62+
})
63+
.build();
64+
65+
@Override
66+
public boolean canBuildType(Class<?> aClass, AnnotatedType annotatedType) {
67+
return OffsetTime.class.isAssignableFrom(aClass);
68+
}
69+
70+
@Override
71+
public GraphQLScalarType buildType(
72+
boolean input,
73+
Class<?> aClass,
74+
AnnotatedType annotatedType,
75+
ProcessingElementsContainer container) {
76+
return OFFSET_TIME_SCALAR;
77+
}
78+
}

hypertrace-core-graphql-common-schema/src/test/java/org/hypertrace/core/graphql/common/schema/scalars/DateTimeScalarTest.java

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,7 @@
88
import graphql.schema.GraphQLScalarType;
99
import java.lang.reflect.AnnotatedType;
1010
import java.time.Instant;
11-
import java.time.LocalDateTime;
12-
import java.time.OffsetDateTime;
1311
import java.time.ZoneOffset;
14-
import java.time.ZonedDateTime;
15-
import java.time.chrono.ChronoLocalDateTime;
1612
import org.hypertrace.core.graphql.common.schema.typefunctions.DateTimeScalar;
1713
import org.junit.jupiter.api.BeforeEach;
1814
import org.junit.jupiter.api.Test;
@@ -44,11 +40,6 @@ void beforeEach() {
4440
@Test
4541
void canDetermineIfConvertible() {
4642
assertTrue(this.dateTimeFunction.canBuildType(Instant.class, this.mockAnnotatedType));
47-
assertTrue(this.dateTimeFunction.canBuildType(OffsetDateTime.class, this.mockAnnotatedType));
48-
assertTrue(this.dateTimeFunction.canBuildType(LocalDateTime.class, this.mockAnnotatedType));
49-
assertTrue(
50-
this.dateTimeFunction.canBuildType(ChronoLocalDateTime.class, this.mockAnnotatedType));
51-
assertTrue(this.dateTimeFunction.canBuildType(ZonedDateTime.class, this.mockAnnotatedType));
5243
}
5344

5445
@Test
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package org.hypertrace.core.graphql.common.schema.scalars;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertTrue;
5+
6+
import graphql.annotations.processor.ProcessingElementsContainer;
7+
import graphql.language.StringValue;
8+
import graphql.schema.GraphQLScalarType;
9+
import java.lang.reflect.AnnotatedType;
10+
import java.time.OffsetTime;
11+
import java.time.ZoneOffset;
12+
import org.hypertrace.core.graphql.common.schema.typefunctions.OffsetTimeScalar;
13+
import org.junit.jupiter.api.BeforeEach;
14+
import org.junit.jupiter.api.Test;
15+
import org.junit.jupiter.api.extension.ExtendWith;
16+
import org.mockito.Mock;
17+
import org.mockito.junit.jupiter.MockitoExtension;
18+
19+
@ExtendWith(MockitoExtension.class)
20+
class OffsetTimeScalarTest {
21+
22+
private static final String TEST_SCALAR_TIME_STRING = "21:30:12.748+12:30";
23+
private static final OffsetTime TEST_OFFSET_TIME = OffsetTime.parse(TEST_SCALAR_TIME_STRING);
24+
private OffsetTimeScalar offsetTimeFunction;
25+
private GraphQLScalarType offsetTimeType;
26+
@Mock AnnotatedType mockAnnotatedType;
27+
// Can't actually mock class, but it's not used so to convey intent using the Mock class.
28+
private final Class<?> mockAnnotatedClass = Mock.class;
29+
@Mock ProcessingElementsContainer mockProcessingElementsContainer;
30+
31+
@BeforeEach
32+
void beforeEach() {
33+
this.offsetTimeFunction = new OffsetTimeScalar();
34+
// Can't actually mock class, but it's not used so to convey intent using
35+
this.offsetTimeType =
36+
this.offsetTimeFunction.buildType(
37+
false, mockAnnotatedClass, mockAnnotatedType, mockProcessingElementsContainer);
38+
}
39+
40+
@Test
41+
void canDetermineIfConvertible() {
42+
assertTrue(this.offsetTimeFunction.canBuildType(OffsetTime.class, this.mockAnnotatedType));
43+
}
44+
45+
@Test
46+
void canConvertFromLiteral() {
47+
assertEquals(
48+
TEST_OFFSET_TIME, offsetTimeType.getCoercing().parseLiteral(TEST_SCALAR_TIME_STRING));
49+
}
50+
51+
@Test
52+
void canSerialize() {
53+
assertEquals(TEST_SCALAR_TIME_STRING, offsetTimeType.getCoercing().serialize(TEST_OFFSET_TIME));
54+
assertEquals(
55+
TEST_SCALAR_TIME_STRING, offsetTimeType.getCoercing().serialize(TEST_SCALAR_TIME_STRING));
56+
57+
assertEquals(
58+
TEST_SCALAR_TIME_STRING,
59+
offsetTimeType
60+
.getCoercing()
61+
.serialize(TEST_OFFSET_TIME.withOffsetSameLocal(ZoneOffset.ofHoursMinutes(12, 30))));
62+
}
63+
64+
@Test
65+
void canConvertFromValue() {
66+
assertEquals(
67+
TEST_OFFSET_TIME,
68+
offsetTimeType
69+
.getCoercing()
70+
.parseValue(StringValue.newStringValue().value(TEST_SCALAR_TIME_STRING).build()));
71+
}
72+
}

0 commit comments

Comments
 (0)