Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .github/workflows/CI.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,21 @@ jobs:
- name: Build and test
run: ./gradlew build

- name: Extract snapshot version
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
id: snapshot
run: |
VERSION=$(grep '^VERSION_NAME=' gradle.properties | cut -d'=' -f2)
echo "VERSION=${VERSION}-SNAPSHOT" >> $GITHUB_OUTPUT

- name: Publish SNAPSHOT (main only)
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
env:
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
run: ./gradlew publishAllPublicationsToCentralPortalSnapshots -x test
run: ./gradlew publishAllPublicationsToCentralPortalSnapshots -Pversion=${{ steps.snapshot.outputs.VERSION }} -x test

- name: Update dependency graph
uses: gradle/actions/dependency-submission@v5
Expand Down
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,7 @@ test-detail:
.PHONY: clean
clean:
./gradlew clean --no-configuration-cache

.PHONY: lint
lint:
./gradlew check --rerun-tasks --no-configuration-cache
22 changes: 20 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ plugins {
`java-library`
`maven-publish`
signing
id("com.gradleup.nmcp") version "1.2.1"
id("com.gradleup.nmcp") version "1.4.3"
id("com.diffplug.spotless") version "6.25.0"
}

// Load .env file if it exists
Expand Down Expand Up @@ -68,13 +69,26 @@ val javaLauncherForTargetVersion = javaToolchainService.launcherFor {
}

dependencies {
implementation(platform("tools.jackson:jackson-bom:3.0.3"))
implementation(platform("tools.jackson:jackson-bom:3.0.4"))
implementation("tools.jackson.core:jackson-databind")
implementation("com.fasterxml.jackson.core:jackson-annotations")
testImplementation("org.junit.jupiter:junit-jupiter:6.0.2")
testRuntimeOnly("org.junit.platform:junit-platform-launcher:6.0.2")
}

spotless {
java {
target("src/**/*.java")
trimTrailingWhitespace()
endWithNewline()
}
format("misc") {
target("*.gradle.kts", "*.md", ".gitignore", "Makefile")
trimTrailingWhitespace()
endWithNewline()
}
}

tasks.withType<JavaCompile>().configureEach {
options.release.set(javaTargetVersion)
options.compilerArgs.add("-Xlint:unchecked")
Expand Down Expand Up @@ -136,6 +150,10 @@ tasks.withType<JavaExec>().configureEach {
javaLauncher.set(javaLauncherForTargetVersion)
}

tasks.named("check") {
dependsOn("spotlessCheck")
}

tasks.register<JavaExec>("cli") {
description = "Runs the Apple Maps CLI against the live API."
group = "application"
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/williamcallahan/applemaps/AppleMaps.java
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ public List<SearchResponse> resolveCompletionUrls(List<AutocompleteResult> resul
if (results.isEmpty()) {
return List.of();
}

List<CompletableFuture<SearchResponse>> futures = results.stream()
.map(result -> CompletableFuture.supplyAsync(() -> gateway.resolveCompletionUrl(result.completionUrl())))
.toList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.time.Instant;
import java.util.Base64;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;

Expand All @@ -29,7 +30,7 @@ public final class AppleMapsAuthorizationService {
private final URI tokenUri;
private final Duration timeout;
private final String authToken;
private final String origin;
private final Optional<String> origin;
private final Clock clock;
private final ReentrantLock refreshLock = new ReentrantLock();
private final AtomicReference<AccessToken> accessToken = new AtomicReference<>();
Expand All @@ -39,6 +40,7 @@ public final class AppleMapsAuthorizationService {
*
* @param authToken the Apple Maps Server API authorization token
* @param timeout request timeout for token exchange
* @param origin optional Origin header value for token requests
*/
public AppleMapsAuthorizationService(String authToken, Duration timeout, String origin) {
this(new Dependencies(authToken, timeout, origin));
Expand All @@ -50,11 +52,17 @@ public AppleMapsAuthorizationService(String authToken, Duration timeout, String
this.tokenUri = dependencies.tokenUri();
this.timeout = dependencies.timeout();
this.authToken = dependencies.authToken();
this.origin = dependencies.origin();
this.origin = Optional.ofNullable(dependencies.origin())
.filter(value -> !value.isBlank());
this.clock = dependencies.clock();
}

public String getOrigin() {

/**
* Returns the configured Origin header value, if any.
*
* @return the Origin header value, or empty when not set
*/
public Optional<String> getOrigin() {
return origin;
}

Expand Down Expand Up @@ -87,10 +95,8 @@ private AccessToken refreshAccessToken() {
.timeout(timeout)
.uri(tokenUri)
.setHeader("Authorization", "Bearer " + authToken);

if (origin != null) {
builder.setHeader("Origin", origin);
}

origin.ifPresent(value -> builder.setHeader("Origin", value));

HttpRequest httpRequest = builder.build();
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ public HttpAppleMapsGateway(String authToken, Duration timeout) {
this(new Dependencies(authToken, timeout, null));
}

/**
* Creates an HTTP gateway that calls the Apple Maps Server API with an optional Origin header.
*
* @param authToken the Apple Maps Server API authorization token
* @param timeout request timeout
* @param origin optional Origin header value to include in requests
*/
public HttpAppleMapsGateway(String authToken, Duration timeout, String origin) {
this(new Dependencies(authToken, timeout, origin));
}
Expand Down Expand Up @@ -170,17 +177,15 @@ private URI buildUri(String path, String queryString) {
}

private <T> T invokeApi(String operation, URI uri, Class<T> responseType) {

HttpRequest.Builder builder = HttpRequest.newBuilder()
.GET()
.uri(uri)
.timeout(timeout)
.setHeader("Authorization", "Bearer " + authorizationService.getAccessToken());

if (authorizationService.getOrigin() != null) {
builder.setHeader("Origin", authorizationService.getOrigin());
}

authorizationService.getOrigin()
.ifPresent(value -> builder.setHeader("Origin", value));

HttpRequest httpRequest = builder.build();
try {
HttpResponse<byte[]> response = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofByteArray());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ private static Optional<String> normalizeOptional(Optional<String> optionalInput
}

private static List<String> normalizeList(List<String> rawList) {
return List.copyOf(Objects.requireNonNullElse(rawList, List.of()));
if (rawList == null) {
return List.of();
}
return rawList.stream()
.filter(Objects::nonNull)
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ public record AlternateIdsResponse(List<AlternateIdsEntry> results, List<PlaceLo
}

private static <T> List<T> normalizeList(List<T> rawList) {
return List.copyOf(Objects.requireNonNullElse(rawList, List.of()));
if (rawList == null) {
return List.of();
}
return rawList.stream()
.filter(Objects::nonNull)
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ private static <T> Optional<T> normalizeOptional(Optional<T> optionalInput) {
}

private static List<String> normalizeList(List<String> rawList) {
return List.copyOf(Objects.requireNonNullElse(rawList, List.of()));
if (rawList == null) {
return List.of();
}
return rawList.stream()
.filter(Objects::nonNull)
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public static DirectionsEndpoint fromAddress(String address) {
* @return a directions endpoint
*/
public static DirectionsEndpoint fromLatitudeLongitude(double latitude, double longitude) {
Location.validateLatitudeLongitude(latitude, longitude);
return new DirectionsEndpoint(formatCoordinatePair(latitude, longitude));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,30 @@ private static <T> Optional<T> normalizeOptional(Optional<T> optionalInput) {
}

private static <T> List<T> normalizeList(List<T> rawList) {
return List.copyOf(Objects.requireNonNullElse(rawList, List.of()));
if (rawList == null) {
return List.of();
}
return rawList.stream()
.filter(Objects::nonNull)
.toList();
}

private static List<List<Location>> normalizeStepPaths(List<List<Location>> rawList) {
List<List<Location>> normalizedPaths = Objects.requireNonNullElse(rawList, List.<List<Location>>of()).stream()
.map(path -> List.copyOf(Objects.requireNonNullElse(path, List.<Location>of())))
if (rawList == null) {
return List.of();
}
return rawList.stream()
.map(DirectionsResponse::normalizeStepPath)
.toList();
return List.copyOf(normalizedPaths);
}

private static List<Location> normalizeStepPath(List<Location> rawPath) {
if (rawPath == null) {
return List.of();
}
return rawPath.stream()
.filter(Objects::nonNull)
.toList();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ private static <T> Optional<T> normalizeOptional(Optional<T> optionalInput) {
}

private static <T> List<T> normalizeList(List<T> rawList) {
return List.copyOf(Objects.requireNonNullElse(rawList, List.of()));
if (rawList == null) {
return List.of();
}
return rawList.stream()
.filter(Objects::nonNull)
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ public record ErrorResponse(String message, List<String> details) {
*/
public ErrorResponse {
message = Objects.requireNonNull(message, "message");
details = List.copyOf(Objects.requireNonNullElse(details, List.of()));
details = normalizeList(details);
}

private static List<String> normalizeList(List<String> rawList) {
if (rawList == null) {
return List.of();
}
return rawList.stream()
.filter(Objects::nonNull)
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ public record EtaResponse(List<EtaEstimate> etas) {
}

private static <T> List<T> normalizeList(List<T> rawList) {
return List.copyOf(Objects.requireNonNullElse(rawList, List.of()));
if (rawList == null) {
return List.of();
}
return rawList.stream()
.filter(Objects::nonNull)
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,44 @@
/**
* An object that describes a location in terms of its latitude and longitude.
*/
public record Location(double latitude, double longitude) {}
public record Location(double latitude, double longitude) {
private static final double MIN_LATITUDE = -90.0;
private static final double MAX_LATITUDE = 90.0;
private static final double MIN_LONGITUDE = -180.0;
private static final double MAX_LONGITUDE = 180.0;
private static final String LATITUDE_LABEL = "latitude";
private static final String LONGITUDE_LABEL = "longitude";
private static final String FINITE_MESSAGE_TEMPLATE = "%s must be a finite value.";
private static final String RANGE_MESSAGE_TEMPLATE = "%s must be between %s and %s.";

/**
* Canonical constructor that validates coordinate bounds and finiteness.
*
* @param latitude latitude in decimal degrees
* @param longitude longitude in decimal degrees
*/
public Location {
validateLatitudeLongitude(latitude, longitude);
}

static void validateLatitudeLongitude(double latitude, double longitude) {
validateCoordinate(latitude, LATITUDE_LABEL, MIN_LATITUDE, MAX_LATITUDE);
validateCoordinate(longitude, LONGITUDE_LABEL, MIN_LONGITUDE, MAX_LONGITUDE);
}

private static void validateCoordinate(
double coordinate,
String coordinateLabel,
double minimum,
double maximum
) {
if (!Double.isFinite(coordinate)) {
throw new IllegalArgumentException(FINITE_MESSAGE_TEMPLATE.formatted(coordinateLabel));
}
if (coordinate < minimum || coordinate > maximum) {
throw new IllegalArgumentException(
RANGE_MESSAGE_TEMPLATE.formatted(coordinateLabel, minimum, maximum)
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ private static <T> Optional<T> normalizeOptional(Optional<T> optionalInput) {
}

private static <T> List<T> normalizeList(List<T> rawList) {
return List.copyOf(Objects.requireNonNullElse(rawList, List.of()));
if (rawList == null) {
return List.of();
}
return rawList.stream()
.filter(Objects::nonNull)
.toList();
}
}
Loading
Loading