Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/plan-connection' into dev-2.x
Browse files Browse the repository at this point in the history
  • Loading branch information
vesameskanen committed May 8, 2024
2 parents 22218c2 + 22a9763 commit e3dbb5e
Show file tree
Hide file tree
Showing 51 changed files with 7,353 additions and 643 deletions.
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -852,12 +852,12 @@
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java</artifactId>
<version>21.5</version>
<version>22.0</version>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-extended-scalars</artifactId>
<version>21.0</version>
<version>22.0</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters;
import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters;
import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters;
import graphql.execution.instrumentation.parameters.InstrumentationFieldParameters;
import graphql.execution.instrumentation.parameters.InstrumentationValidationParameters;
import graphql.language.Document;
import graphql.schema.GraphQLTypeUtil;
Expand All @@ -22,7 +21,6 @@
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Timer;
import java.util.List;
import java.util.concurrent.CompletableFuture;

/**
* Using this instrumentation we can precisely measure how queries and data fetchers are executed
Expand Down Expand Up @@ -107,21 +105,13 @@ public ExecutionStrategyInstrumentationContext beginExecutionStrategy(
) {
return new ExecutionStrategyInstrumentationContext() {
@Override
public void onDispatched(CompletableFuture<ExecutionResult> result) {}
public void onDispatched() {}

@Override
public void onCompleted(ExecutionResult result, Throwable t) {}
};
}

@Override
public InstrumentationContext<ExecutionResult> beginField(
InstrumentationFieldParameters parameters,
InstrumentationState state
) {
return noOp();
}

@Override
public InstrumentationContext<Object> beginFieldFetch(
InstrumentationFieldFetchParameters parameters,
Expand Down
286 changes: 275 additions & 11 deletions src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import com.bedatadriven.jackson.datatype.jts.JtsModule;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import graphql.language.FloatValue;
import graphql.language.IntValue;
import graphql.language.StringValue;
import graphql.relay.Relay;
import graphql.schema.Coercing;
Expand All @@ -14,18 +16,20 @@
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Optional;
import javax.annotation.Nonnull;
import org.locationtech.jts.geom.Geometry;
import org.opentripplanner.framework.geometry.GeometryUtils;
import org.opentripplanner.framework.graphql.scalar.DurationScalarFactory;
import org.opentripplanner.framework.model.Cost;
import org.opentripplanner.framework.model.Grams;
import org.opentripplanner.framework.time.OffsetDateTimeParser;

public class GraphQLScalars {

private static final ObjectMapper geoJsonMapper = new ObjectMapper()
private static final ObjectMapper GEOJSON_MAPPER = new ObjectMapper()
.registerModule(new JtsModule(GeometryUtils.getGeometryFactory()));
public static GraphQLScalarType DURATION_SCALAR = DurationScalarFactory.createDurationScalar();
public static final GraphQLScalarType DURATION_SCALAR = DurationScalarFactory.createDurationScalar();

public static final GraphQLScalarType POLYLINE_SCALAR = GraphQLScalarType
.newScalar()
Expand Down Expand Up @@ -112,6 +116,127 @@ public OffsetDateTime parseLiteral(Object input) throws CoercingParseLiteralExce
)
.build();

public static final GraphQLScalarType COORDINATE_VALUE_SCALAR = GraphQLScalarType
.newScalar()
.name("CoordinateValue")
.coercing(
new Coercing<Double, Double>() {
private static final String VALIDATION_ERROR_MESSAGE = "Not a valid WGS84 coordinate value";

@Override
public Double serialize(@Nonnull Object dataFetcherResult)
throws CoercingSerializeException {
if (dataFetcherResult instanceof Double doubleValue) {
return doubleValue;
} else if (dataFetcherResult instanceof Float floatValue) {
return floatValue.doubleValue();
} else {
throw new CoercingSerializeException(
"Cannot serialize object of class %s as a coordinate number".formatted(
dataFetcherResult.getClass().getSimpleName()
)
);
}
}

@Override
public Double parseValue(Object input) throws CoercingParseValueException {
if (input instanceof Double doubleValue) {
return validateCoordinate(doubleValue)
.orElseThrow(() -> new CoercingParseValueException(VALIDATION_ERROR_MESSAGE));
}
if (input instanceof Integer intValue) {
return validateCoordinate(intValue)
.orElseThrow(() -> new CoercingParseValueException(VALIDATION_ERROR_MESSAGE));
}
throw new CoercingParseValueException(
"Expected a number, got %s %s".formatted(input.getClass().getSimpleName(), input)
);
}

@Override
public Double parseLiteral(Object input) throws CoercingParseLiteralException {
if (input instanceof FloatValue coordinate) {
return validateCoordinate(coordinate.getValue().doubleValue())
.orElseThrow(() -> new CoercingParseLiteralException(VALIDATION_ERROR_MESSAGE));
}
if (input instanceof IntValue coordinate) {
return validateCoordinate(coordinate.getValue().doubleValue())
.orElseThrow(() -> new CoercingParseLiteralException(VALIDATION_ERROR_MESSAGE));
}
throw new CoercingParseLiteralException(
"Expected a number, got: " + input.getClass().getSimpleName()
);
}

private static Optional<Double> validateCoordinate(double coordinate) {
if (coordinate >= -180.001 && coordinate <= 180.001) {
return Optional.of(coordinate);
}
return Optional.empty();
}
}
)
.build();

public static final GraphQLScalarType COST_SCALAR = GraphQLScalarType
.newScalar()
.name("Cost")
.coercing(
new Coercing<Cost, Integer>() {
private static final int MAX_COST = 1000000;
private static final String VALIDATION_ERROR_MESSAGE =
"Cost cannot be negative or greater than %d".formatted(MAX_COST);

@Override
public Integer serialize(@Nonnull Object dataFetcherResult)
throws CoercingSerializeException {
if (dataFetcherResult instanceof Integer intValue) {
return intValue;
} else if (dataFetcherResult instanceof Cost costValue) {
return costValue.toSeconds();
} else {
throw new CoercingSerializeException(
"Cannot serialize object of class %s as a cost".formatted(
dataFetcherResult.getClass().getSimpleName()
)
);
}
}

@Override
public Cost parseValue(Object input) throws CoercingParseValueException {
if (input instanceof Integer intValue) {
return validateCost(intValue)
.orElseThrow(() -> new CoercingParseValueException(VALIDATION_ERROR_MESSAGE));
}
throw new CoercingParseValueException(
"Expected an integer, got %s %s".formatted(input.getClass().getSimpleName(), input)
);
}

@Override
public Cost parseLiteral(Object input) throws CoercingParseLiteralException {
if (input instanceof IntValue intValue) {
var value = intValue.getValue().intValue();
return validateCost(value)
.orElseThrow(() -> new CoercingParseLiteralException(VALIDATION_ERROR_MESSAGE));
}
throw new CoercingParseLiteralException(
"Expected an integer, got: " + input.getClass().getSimpleName()
);
}

private static Optional<Cost> validateCost(int cost) {
if (cost >= 0 && cost <= MAX_COST) {
return Optional.of(Cost.costOfSeconds(cost));
}
return Optional.empty();
}
}
)
.build();

public static final GraphQLScalarType GEOJSON_SCALAR = GraphQLScalarType
.newScalar()
.name("GeoJson")
Expand All @@ -122,7 +247,7 @@ public OffsetDateTime parseLiteral(Object input) throws CoercingParseLiteralExce
public JsonNode serialize(Object dataFetcherResult) throws CoercingSerializeException {
if (dataFetcherResult instanceof Geometry) {
var geom = (Geometry) dataFetcherResult;
return geoJsonMapper.valueToTree(geom);
return GEOJSON_MAPPER.valueToTree(geom);
}
return null;
}
Expand Down Expand Up @@ -196,20 +321,159 @@ public Double serialize(Object dataFetcherResult) throws CoercingSerializeExcept

@Override
public Grams parseValue(Object input) throws CoercingParseValueException {
if (input instanceof Double) {
var grams = (Double) input;
return new Grams(grams);
if (input instanceof Double doubleValue) {
return new Grams(doubleValue);
}
return null;
if (input instanceof Integer intValue) {
return new Grams(intValue);
}
throw new CoercingParseValueException(
"Expected a number, got %s %s".formatted(input.getClass().getSimpleName(), input)
);
}

@Override
public Grams parseLiteral(Object input) throws CoercingParseLiteralException {
if (input instanceof Double) {
var grams = (Double) input;
return new Grams(grams);
if (input instanceof FloatValue coordinate) {
return new Grams(coordinate.getValue().doubleValue());
}
return null;
if (input instanceof IntValue coordinate) {
return new Grams(coordinate.getValue().doubleValue());
}
throw new CoercingParseLiteralException(
"Expected a number, got: " + input.getClass().getSimpleName()
);
}
}
)
.build();

public static final GraphQLScalarType RATIO_SCALAR = GraphQLScalarType
.newScalar()
.name("Ratio")
.coercing(
new Coercing<Double, Double>() {
private static final String VALIDATION_ERROR_MESSAGE =
"Value is under 0 or greater than 1.";

@Override
public Double serialize(@Nonnull Object dataFetcherResult)
throws CoercingSerializeException {
var validationException = new CoercingSerializeException(VALIDATION_ERROR_MESSAGE);
if (dataFetcherResult instanceof Double doubleValue) {
return validateRatio(doubleValue).orElseThrow(() -> validationException);
} else if (dataFetcherResult instanceof Float floatValue) {
return validateRatio(floatValue.doubleValue()).orElseThrow(() -> validationException);
} else {
throw new CoercingSerializeException(
"Cannot serialize object of class %s as a ratio".formatted(
dataFetcherResult.getClass().getSimpleName()
)
);
}
}

@Override
public Double parseValue(Object input) throws CoercingParseValueException {
if (input instanceof Double doubleValue) {
return validateRatio(doubleValue)
.orElseThrow(() -> new CoercingParseValueException(VALIDATION_ERROR_MESSAGE));
}
if (input instanceof Integer intValue) {
return validateRatio(intValue)
.orElseThrow(() -> new CoercingParseValueException(VALIDATION_ERROR_MESSAGE));
}
throw new CoercingParseValueException(
"Expected a number, got %s %s".formatted(input.getClass().getSimpleName(), input)
);
}

@Override
public Double parseLiteral(Object input) throws CoercingParseLiteralException {
if (input instanceof FloatValue ratio) {
return validateRatio(ratio.getValue().doubleValue())
.orElseThrow(() -> new CoercingParseLiteralException(VALIDATION_ERROR_MESSAGE));
}
if (input instanceof IntValue ratio) {
return validateRatio(ratio.getValue().doubleValue())
.orElseThrow(() -> new CoercingParseLiteralException(VALIDATION_ERROR_MESSAGE));
}
throw new CoercingParseLiteralException(
"Expected a number, got: " + input.getClass().getSimpleName()
);
}

private static Optional<Double> validateRatio(double ratio) {
if (ratio >= -0.001 && ratio <= 1.001) {
return Optional.of(ratio);
}
return Optional.empty();
}
}
)
.build();

public static final GraphQLScalarType RELUCTANCE_SCALAR = GraphQLScalarType
.newScalar()
.name("Reluctance")
.coercing(
new Coercing<Double, Double>() {
private static final double MIN_Reluctance = 0.1;
private static final double MAX_Reluctance = 100000;
private static final String VALIDATION_ERROR_MESSAGE =
"Reluctance needs to be between %s and %s".formatted(MIN_Reluctance, MAX_Reluctance);

@Override
public Double serialize(@Nonnull Object dataFetcherResult)
throws CoercingSerializeException {
if (dataFetcherResult instanceof Double doubleValue) {
return doubleValue;
} else if (dataFetcherResult instanceof Float floatValue) {
return floatValue.doubleValue();
} else {
throw new CoercingSerializeException(
"Cannot serialize object of class %s as a reluctance".formatted(
dataFetcherResult.getClass().getSimpleName()
)
);
}
}

@Override
public Double parseValue(Object input) throws CoercingParseValueException {
if (input instanceof Double doubleValue) {
return validateReluctance(doubleValue)
.orElseThrow(() -> new CoercingParseValueException(VALIDATION_ERROR_MESSAGE));
}
if (input instanceof Integer intValue) {
return validateReluctance(intValue)
.orElseThrow(() -> new CoercingParseValueException(VALIDATION_ERROR_MESSAGE));
}
throw new CoercingParseValueException(
"Expected a number, got %s %s".formatted(input.getClass().getSimpleName(), input)
);
}

@Override
public Double parseLiteral(Object input) throws CoercingParseLiteralException {
if (input instanceof FloatValue reluctance) {
return validateReluctance(reluctance.getValue().doubleValue())
.orElseThrow(() -> new CoercingParseLiteralException(VALIDATION_ERROR_MESSAGE));
}
if (input instanceof IntValue reluctance) {
return validateReluctance(reluctance.getValue().doubleValue())
.orElseThrow(() -> new CoercingParseLiteralException(VALIDATION_ERROR_MESSAGE));
}
throw new CoercingParseLiteralException(
"Expected a number, got: " + input.getClass().getSimpleName()
);
}

private static Optional<Double> validateReluctance(double reluctance) {
if (reluctance >= MIN_Reluctance - 0.001 && reluctance <= MAX_Reluctance + 0.001) {
return Optional.of(reluctance);
}
return Optional.empty();
}
}
)
Expand Down
Loading

0 comments on commit e3dbb5e

Please sign in to comment.