From 4afe9b551915c21abe3454a98a85a6b3196a724d Mon Sep 17 00:00:00 2001 From: jcpitre Date: Wed, 22 Jul 2026 11:22:37 -0400 Subject: [PATCH] Revert commits from PR 198 (station status or vehicle status should be required) --- example/pom.xml | 8 +- .../example/GbfsValidatorExample.java | 103 ++++++------ .../cli/formatter/ConsoleReportFormatter.java | 2 - .../model/FileValidationResult.java | 6 - .../validation/validator/FileValidator.java | 4 +- .../validator/GbfsJsonValidator.java | 56 ------- .../model/ValidationResultTest.java | 1 - .../validator/GbfsJsonValidatorTest.java | 153 +----------------- .../fixtures/v1.0/free_bike_status.json | 1 - .../fixtures/v1.1/free_bike_status.json | 1 - .../fixtures/v2.0/free_bike_status.json | 1 - 11 files changed, 56 insertions(+), 280 deletions(-) delete mode 100644 gbfs-validator-java/src/test/resources/fixtures/v1.0/free_bike_status.json delete mode 100644 gbfs-validator-java/src/test/resources/fixtures/v1.1/free_bike_status.json delete mode 100644 gbfs-validator-java/src/test/resources/fixtures/v2.0/free_bike_status.json diff --git a/example/pom.xml b/example/pom.xml index 1c0c1d0f..cd31436a 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -20,7 +20,7 @@ 17 UTF-8 - 3.1.0 + 3.0.3 @@ -44,12 +44,6 @@ gbfs-validator-java ${gbfs-validator.version} - - - org.mobilitydata - gbfs-validator-java-loader - ${gbfs-validator.version} - diff --git a/example/src/main/java/org/mobilitydata/gbfs/validator/example/GbfsValidatorExample.java b/example/src/main/java/org/mobilitydata/gbfs/validator/example/GbfsValidatorExample.java index 1366f18d..6d4e1fcc 100644 --- a/example/src/main/java/org/mobilitydata/gbfs/validator/example/GbfsValidatorExample.java +++ b/example/src/main/java/org/mobilitydata/gbfs/validator/example/GbfsValidatorExample.java @@ -9,25 +9,21 @@ import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; import java.util.HashMap; -import java.util.List; import java.util.Map; import org.mobilitydata.gbfs.validation.GbfsValidator; import org.mobilitydata.gbfs.validation.GbfsValidatorFactory; import org.mobilitydata.gbfs.validation.model.FileValidationError; import org.mobilitydata.gbfs.validation.model.FileValidationResult; import org.mobilitydata.gbfs.validation.model.ValidationResult; -import org.mobilitydata.gbfs.validator.loader.LoadedFile; -import org.mobilitydata.gbfs.validator.loader.Loader; /** * Example showing how to validate a GBFS feed using gbfs-validator-java. * - *

Two use cases are demonstrated: + *

This example: *

    - *
  1. Single-file validation via {@link GbfsValidator#validateFile}
  2. - *
  3. Full-feed validation via {@link Loader} + {@link GbfsValidator#validate}: - * the Loader fetches gbfs.json and automatically discovers and loads all - * linked feed files, so no manual URL construction is needed.
  4. + *
  5. Fetches gbfs.json from a public GBFS feed
  6. + *
  7. Validates the file using GbfsValidatorFactory
  8. + *
  9. Prints the validation results to stdout
  10. *
* *

Usage: @@ -46,54 +42,57 @@ public class GbfsValidatorExample { public static void main(String[] args) throws IOException, InterruptedException { System.out.println("=== GBFS Validator Java Example ===\n"); - GbfsValidator validator = GbfsValidatorFactory.getGbfsJsonValidator(); - // --- Example 1: Validate a single file --- - // Useful when you already have the file content and just want schema validation. System.out.println("Example 1: Validate a single file"); System.out.println("Fetching: " + GBFS_FEED_URL); - String gbfsContent = fetchUrl(GBFS_FEED_URL); - InputStream gbfsStream = new ByteArrayInputStream(gbfsContent.getBytes(StandardCharsets.UTF_8)); - FileValidationResult fileResult = validator.validateFile("gbfs", gbfsStream); + String fileContents = fetchUrl(GBFS_FEED_URL); + + GbfsValidator validator = GbfsValidatorFactory.getGbfsJsonValidator(); + InputStream fileStream = new ByteArrayInputStream( + fileContents.getBytes(StandardCharsets.UTF_8) + ); + + // The API expects file names WITHOUT the .json extension (e.g. "gbfs", not "gbfs.json") + FileValidationResult fileResult = validator.validateFile("gbfs", fileStream); printFileResult(fileResult); - // --- Example 2: Validate a full feed --- - // The Loader fetches gbfs.json, parses the feed URLs from its discovery data, - // and loads all linked files — handling language prefixes and auth automatically. + // --- Example 2: Validate a full feed (multiple files) --- System.out.println("\nExample 2: Validate a full feed"); - Loader loader = new Loader(); - try { - List loadedFiles = loader.load(GBFS_FEED_URL); - - Map fileMap = new HashMap<>(); - for (LoadedFile file : loadedFiles) { - // Keep the discovery file (no language) and only "en" language files. - // If a feed does not publish "en", swap "en" for the desired language code. - String lang = file.language(); - if (lang != null && !lang.equals("en")) { - continue; - } - if (file.fileContents() != null) { - System.out.println(" Loaded: " + file.fileName() + " (" + file.url() + ")"); - fileMap.put(file.fileName(), file.fileContents()); - } else { - file.loaderErrors().forEach(e -> - System.out.println(" Skipped: " + file.fileName() - + " (" + e.error() + ": " + e.message() + ")") - ); - } + Map feedFiles = new HashMap<>(); + + // Keys must be the GBFS file type name (no .json extension) + feedFiles.put("gbfs", new ByteArrayInputStream( + fileContents.getBytes(StandardCharsets.UTF_8) + )); + + // Fetch additional files — URL uses .json, but map key does not + String[] additionalFileNames = { + "system_information", + "station_information", + "station_status", + "free_bike_status", + }; + String baseUrl = GBFS_FEED_URL.substring(0, GBFS_FEED_URL.lastIndexOf('/') + 1); + for (String fileType : additionalFileNames) { + try { + String content = fetchUrl(baseUrl + fileType + ".json"); + feedFiles.put(fileType, new ByteArrayInputStream( + content.getBytes(StandardCharsets.UTF_8) + )); + System.out.println(" Loaded: " + fileType); + } catch (Exception e) { + System.out.println(" Skipped: " + fileType + " (" + e.getMessage() + ")"); } - - ValidationResult feedResult = validator.validate(fileMap); - printFeedResult(feedResult); - } finally { - loader.close(); } + + ValidationResult feedResult = validator.validate(feedFiles); + printFeedResult(feedResult); } private static void printFileResult(FileValidationResult result) { System.out.println(" File : " + result.file()); System.out.println(" Version : " + result.version()); + System.out.println(" Schema : " + result.schema()); System.out.println(" Exists : " + result.exists()); System.out.println(" Required: " + result.required()); System.out.println(" Errors : " + result.errorsCount()); @@ -116,17 +115,14 @@ private static void printFileResult(FileValidationResult result) { private static void printFeedResult(ValidationResult result) { System.out.println(" Summary : " + result.summary()); - var presentFiles = result.files().entrySet().stream() - .filter(e -> e.getValue().exists()) - .toList(); - System.out.println(" Files validated: " + presentFiles.size()); - presentFiles.forEach(e -> { + System.out.println(" Files validated: " + result.files().size()); + result.files().forEach((name, fileResult) -> { System.out.printf(" %-35s errors=%d version=%s%n", - e.getKey(), e.getValue().errorsCount(), e.getValue().version()); + name, fileResult.errorsCount(), fileResult.version()); }); - long totalErrors = presentFiles.stream() - .mapToLong(e -> e.getValue().errorsCount()) + long totalErrors = result.files().values().stream() + .mapToLong(FileValidationResult::errorsCount) .sum(); System.out.println("\n Total errors across all files: " + totalErrors); @@ -143,11 +139,12 @@ private static String fetchUrl(String url) throws IOException, InterruptedExcept .uri(URI.create(url)) .GET() .build(); - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + HttpResponse response = client.send( + request, HttpResponse.BodyHandlers.ofString() + ); if (response.statusCode() != 200) { throw new IOException("HTTP " + response.statusCode() + " for " + url); } return response.body(); } } - diff --git a/gbfs-validator-java-cli/src/main/java/org/mobilitydata/gbfs/validator/cli/formatter/ConsoleReportFormatter.java b/gbfs-validator-java-cli/src/main/java/org/mobilitydata/gbfs/validator/cli/formatter/ConsoleReportFormatter.java index 814266ae..d5549c22 100644 --- a/gbfs-validator-java-cli/src/main/java/org/mobilitydata/gbfs/validator/cli/formatter/ConsoleReportFormatter.java +++ b/gbfs-validator-java-cli/src/main/java/org/mobilitydata/gbfs/validator/cli/formatter/ConsoleReportFormatter.java @@ -82,8 +82,6 @@ public String format( if (fileResult.required()) { sb.append(" [REQUIRED]"); - } else if (fileResult.conditionallyRequired()) { - sb.append(" [CONDITIONALLY REQUIRED]"); } if (!fileResult.exists()) { diff --git a/gbfs-validator-java/src/main/java/org/mobilitydata/gbfs/validation/model/FileValidationResult.java b/gbfs-validator-java/src/main/java/org/mobilitydata/gbfs/validation/model/FileValidationResult.java index 42f751ca..4273ecf6 100644 --- a/gbfs-validator-java/src/main/java/org/mobilitydata/gbfs/validation/model/FileValidationResult.java +++ b/gbfs-validator-java/src/main/java/org/mobilitydata/gbfs/validation/model/FileValidationResult.java @@ -27,8 +27,6 @@ * The result of validating a single GBFS file * @param file The name of the file that was validated * @param required Whether the file is required in the given version of GBFS - * @param conditionallyRequired Whether the file is conditionally required (e.g. station_status or - * vehicle_status must be present — neither is individually required, but at least one must exist) * @param exists Whether the file existed in the validation input * @param errorsCount The number of errors found while validating the file * @param schema The schema used to validate the file @@ -40,7 +38,6 @@ public record FileValidationResult( String file, boolean required, - boolean conditionallyRequired, boolean exists, int errorsCount, String schema, @@ -64,8 +61,6 @@ public String toString() { '\'' + ", required=" + required + - ", conditionallyRequired=" + - conditionallyRequired + ", exists=" + exists + ", errorsCount=" + @@ -91,7 +86,6 @@ public String toString() { public boolean sameAs(FileValidationResult other) { if (other == null) return false; if (required != other.required) return false; - if (conditionallyRequired != other.conditionallyRequired) return false; if (exists != other.exists) return false; if (errorsCount != other.errorsCount) return false; // This should ideally reflect both validation and system errors count if (!Objects.equals(file, other.file)) return false; diff --git a/gbfs-validator-java/src/main/java/org/mobilitydata/gbfs/validation/validator/FileValidator.java b/gbfs-validator-java/src/main/java/org/mobilitydata/gbfs/validation/validator/FileValidator.java index bb88a172..96098728 100644 --- a/gbfs-validator-java/src/main/java/org/mobilitydata/gbfs/validation/validator/FileValidator.java +++ b/gbfs-validator-java/src/main/java/org/mobilitydata/gbfs/validation/validator/FileValidator.java @@ -80,14 +80,13 @@ public FileValidationResult validate( return new FileValidationResult( feedName, isRequired(feedName), - false, feed != null, errorsCount, schema.toString(), Optional.ofNullable(feed).map(JSONObject::toString).orElse(null), version.getVersionString(), validationErrors, - java.util.Collections.emptyList() + java.util.Collections.emptyList() // Added for systemErrors ); } @@ -131,7 +130,6 @@ public FileValidationResult validateMissingFile(String file) { file, isRequired, false, - false, isRequired ? 1 : 0, version.getSchema(file).toString(), null, diff --git a/gbfs-validator-java/src/main/java/org/mobilitydata/gbfs/validation/validator/GbfsJsonValidator.java b/gbfs-validator-java/src/main/java/org/mobilitydata/gbfs/validation/validator/GbfsJsonValidator.java index 33c220c8..db813273 100644 --- a/gbfs-validator-java/src/main/java/org/mobilitydata/gbfs/validation/validator/GbfsJsonValidator.java +++ b/gbfs-validator-java/src/main/java/org/mobilitydata/gbfs/validation/validator/GbfsJsonValidator.java @@ -131,7 +131,6 @@ public ValidationResult validate(Map rawFeeds) { List missingFiles = findMissingFiles(version, fileValidations); handleMissingFiles(fileValidations, missingFiles, version); // This creates FVRs for missing files - checkStatusFilePresence(fileValidations, version); ValidationSummary summary = new ValidationSummary( version.getVersionString(), @@ -148,60 +147,6 @@ public ValidationResult validate(Map rawFeeds) { return new ValidationResult(summary, fileValidations); } - /** - * Per GBFS spec, a feed must be dock-based, free-floating, or hybrid, so at least one of - * station_status / vehicle_status (v3+) or station_status / free_bike_status (pre-v3) must - * be present. If neither is present, both are flagged as required with an error. - */ - private void checkStatusFilePresence( - Map fileValidations, - Version version - ) { - String freeFloatingFile = version.getFileNames().contains("vehicle_status") - ? "vehicle_status" - : "free_bike_status"; - - boolean stationStatusAbsent = !isPresent(fileValidations, "station_status"); - boolean freeFloatingAbsent = !isPresent(fileValidations, freeFloatingFile); - - if (stationStatusAbsent && freeFloatingAbsent) { - markAsConditionallyRequired(fileValidations, "station_status"); - markAsConditionallyRequired(fileValidations, freeFloatingFile); - } - } - - private boolean isPresent( - Map fileValidations, - String file - ) { - FileValidationResult result = fileValidations.get(file); - return result != null && result.exists(); - } - - private void markAsConditionallyRequired( - Map fileValidations, - String file - ) { - FileValidationResult existing = fileValidations.get(file); - if (existing != null) { - fileValidations.put( - file, - new FileValidationResult( - existing.file(), - false, - true, - false, - 1, - existing.schema(), - null, - existing.version(), - Collections.emptyList(), - Collections.emptyList() - ) - ); - } - } - private Version detectVersionFromParsedFeeds( Map parsedFeeds ) { @@ -412,7 +357,6 @@ private FileValidationResult createParsingErrorResult( return new FileValidationResult( feedName, supportedFeed && schemaVersion.isFileRequired(feedName), - false, true, 0, supportedFeed ? schemaVersion.getSchema(feedName).toString() : null, diff --git a/gbfs-validator-java/src/test/java/org/mobilitydata/gbfs/validation/model/ValidationResultTest.java b/gbfs-validator-java/src/test/java/org/mobilitydata/gbfs/validation/model/ValidationResultTest.java index 6829a928..a3ebcb99 100644 --- a/gbfs-validator-java/src/test/java/org/mobilitydata/gbfs/validation/model/ValidationResultTest.java +++ b/gbfs-validator-java/src/test/java/org/mobilitydata/gbfs/validation/model/ValidationResultTest.java @@ -64,7 +64,6 @@ private FileValidationResult generateFileValidationResult( return new FileValidationResult( "gbfs", true, - false, true, 2, null, diff --git a/gbfs-validator-java/src/test/java/org/mobilitydata/gbfs/validation/validator/GbfsJsonValidatorTest.java b/gbfs-validator-java/src/test/java/org/mobilitydata/gbfs/validation/validator/GbfsJsonValidatorTest.java index 5bc8d8d7..b9e601cf 100644 --- a/gbfs-validator-java/src/test/java/org/mobilitydata/gbfs/validation/validator/GbfsJsonValidatorTest.java +++ b/gbfs-validator-java/src/test/java/org/mobilitydata/gbfs/validation/validator/GbfsJsonValidatorTest.java @@ -40,9 +40,9 @@ void testEmptyDeliveryMapValidation() { Map deliveryMap = new HashMap<>(); ValidationResult result = validator.validate(deliveryMap); - // The expected error count is 4: gbfs.json, system_information.json, station_status.json, - // and free_bike_status.json are all required (directly or conditionally) but missing. - Assertions.assertEquals(4, result.summary().errorsCount()); + // The expected error count is 2, because there are two required files + // missing in an empty delivery, gbfs.json, and system_information.json + Assertions.assertEquals(2, result.summary().errorsCount()); } @Test @@ -59,10 +59,6 @@ void testSuccessfulV1_0Validation() { "system_hours", getFixture("fixtures/v1.0/system_hours.json") ); - deliveryMap.put( - "free_bike_status", - getFixture("fixtures/v1.0/free_bike_status.json") - ); ValidationResult result = validator.validate(deliveryMap); @@ -90,10 +86,6 @@ void testSuccessfulV1_1Validation() { "system_hours", getFixture("fixtures/v1.1/system_hours.json") ); - deliveryMap.put( - "free_bike_status", - getFixture("fixtures/v1.1/free_bike_status.json") - ); ValidationResult result = validator.validate(deliveryMap); @@ -121,10 +113,6 @@ void testSuccessfulV2_0Validation() { "system_hours", getFixture("fixtures/v2.0/system_hours.json") ); - deliveryMap.put( - "free_bike_status", - getFixture("fixtures/v2.0/free_bike_status.json") - ); ValidationResult result = validator.validate(deliveryMap); @@ -487,8 +475,7 @@ void testMissingRequiredFile() { Assertions.assertTrue(result.files().get("system_information").required()); Assertions.assertFalse(result.files().get("system_information").exists()); - // 3 errors: system_information, station_status, and free_bike_status are all missing - Assertions.assertEquals(3, result.summary().errorsCount()); + Assertions.assertEquals(1, result.summary().errorsCount()); } @Test @@ -517,16 +504,6 @@ void testMissingNotRequiredFile() { "system_information", getFixture("fixtures/v2.2/system_information.json") ); - // Minimal station_status (empty stations) — satisfies the conditional requirement - // without triggering custom-rule violations that depend on other files. - String minimalStationStatus = - "{\"last_updated\":1609866235,\"ttl\":0,\"version\":\"2.2\",\"data\":{\"stations\":[]}}"; - deliveryMap.put( - "station_status", - new ByteArrayInputStream( - minimalStationStatus.getBytes(java.nio.charset.StandardCharsets.UTF_8) - ) - ); ValidationResult result = validator.validate(deliveryMap); @@ -652,128 +629,6 @@ void testReadError() throws IOException { ); } - @Test - void testMissingBothStatusFilesV3IsInvalid() { - GbfsJsonValidator validator = new GbfsJsonValidator(); - - // v3.0 feed with neither station_status nor vehicle_status - Map deliveryMap = new HashMap<>(); - deliveryMap.put("gbfs", getFixture("fixtures/v3.0/gbfs.json")); - deliveryMap.put( - "system_information", - getFixture("fixtures/v3.0/system_information.json") - ); - - ValidationResult result = validator.validate(deliveryMap); - - assertTrue( - result.summary().errorsCount() > 0, - "Feed missing both station_status and vehicle_status should be invalid" - ); - - FileValidationResult stationStatus = result.files().get("station_status"); - FileValidationResult vehicleStatus = result.files().get("vehicle_status"); - - assertNotNull(stationStatus); - assertNotNull(vehicleStatus); - assertTrue( - stationStatus.conditionallyRequired(), - "station_status should be marked conditionallyRequired when vehicle_status is also absent" - ); - assertTrue( - vehicleStatus.conditionallyRequired(), - "vehicle_status should be marked conditionallyRequired when station_status is also absent" - ); - assertEquals(1, stationStatus.errorsCount()); - assertEquals(1, vehicleStatus.errorsCount()); - } - - @Test - void testMissingBothStatusFilesPreV3IsInvalid() { - GbfsJsonValidator validator = new GbfsJsonValidator(); - - // v2.3 feed with neither station_status nor free_bike_status - Map deliveryMap = new HashMap<>(); - deliveryMap.put("gbfs", getFixture("fixtures/v2.3/gbfs.json")); - deliveryMap.put( - "system_information", - getFixture("fixtures/v2.3/system_information.json") - ); - - ValidationResult result = validator.validate(deliveryMap); - - assertTrue( - result.summary().errorsCount() > 0, - "Feed missing both station_status and free_bike_status should be invalid" - ); - - FileValidationResult stationStatus = result.files().get("station_status"); - FileValidationResult freeBikeStatus = result - .files() - .get("free_bike_status"); - - assertNotNull(stationStatus); - assertNotNull(freeBikeStatus); - assertTrue(stationStatus.conditionallyRequired()); - assertTrue(freeBikeStatus.conditionallyRequired()); - assertEquals(1, stationStatus.errorsCount()); - assertEquals(1, freeBikeStatus.errorsCount()); - } - - @Test - void testOnlyStationStatusPresentV3IsValid() { - GbfsJsonValidator validator = new GbfsJsonValidator(); - - // v3.0 dock-based feed — vehicle_status absent, but station_status present - Map deliveryMap = new HashMap<>(); - deliveryMap.put("gbfs", getFixture("fixtures/v3.0/gbfs.json")); - deliveryMap.put( - "system_information", - getFixture("fixtures/v3.0/system_information.json") - ); - deliveryMap.put( - "station_status", - getFixture("fixtures/v3.0/station_status.json") - ); - - ValidationResult result = validator.validate(deliveryMap); - - FileValidationResult vehicleStatus = result.files().get("vehicle_status"); - assertNotNull(vehicleStatus); - assertFalse( - vehicleStatus.required(), - "vehicle_status should not be required when station_status is present" - ); - assertEquals(0, vehicleStatus.errorsCount()); - } - - @Test - void testOnlyVehicleStatusPresentV3IsValid() { - GbfsJsonValidator validator = new GbfsJsonValidator(); - - // v3.0 free-floating feed — station_status absent, but vehicle_status present - Map deliveryMap = new HashMap<>(); - deliveryMap.put("gbfs", getFixture("fixtures/v3.0/gbfs.json")); - deliveryMap.put( - "system_information", - getFixture("fixtures/v3.0/system_information.json") - ); - deliveryMap.put( - "vehicle_status", - getFixture("fixtures/v3.0/vehicle_status.json") - ); - - ValidationResult result = validator.validate(deliveryMap); - - FileValidationResult stationStatus = result.files().get("station_status"); - assertNotNull(stationStatus); - assertFalse( - stationStatus.required(), - "station_status should not be required when vehicle_status is present" - ); - assertEquals(0, stationStatus.errorsCount()); - } - // Helper class for testing IOException during read private static class ThrowingInputStream extends InputStream { diff --git a/gbfs-validator-java/src/test/resources/fixtures/v1.0/free_bike_status.json b/gbfs-validator-java/src/test/resources/fixtures/v1.0/free_bike_status.json deleted file mode 100644 index 1a45cc4e..00000000 --- a/gbfs-validator-java/src/test/resources/fixtures/v1.0/free_bike_status.json +++ /dev/null @@ -1 +0,0 @@ -{"last_updated":1609866235,"ttl":0,"data":{"bikes":[]}} diff --git a/gbfs-validator-java/src/test/resources/fixtures/v1.1/free_bike_status.json b/gbfs-validator-java/src/test/resources/fixtures/v1.1/free_bike_status.json deleted file mode 100644 index e8c3612e..00000000 --- a/gbfs-validator-java/src/test/resources/fixtures/v1.1/free_bike_status.json +++ /dev/null @@ -1 +0,0 @@ -{"last_updated":1609866235,"ttl":0,"version":"1.1","data":{"bikes":[]}} diff --git a/gbfs-validator-java/src/test/resources/fixtures/v2.0/free_bike_status.json b/gbfs-validator-java/src/test/resources/fixtures/v2.0/free_bike_status.json deleted file mode 100644 index 4c59714f..00000000 --- a/gbfs-validator-java/src/test/resources/fixtures/v2.0/free_bike_status.json +++ /dev/null @@ -1 +0,0 @@ -{"last_updated":1609866235,"ttl":0,"version":"2.0","data":{"bikes":[]}}