diff --git a/gbfs-validator-java-loader/src/main/java/org/entur/gbfs/validator/loader/Loader.java b/gbfs-validator-java-loader/src/main/java/org/entur/gbfs/validator/loader/Loader.java index 64e7b30f..1a6133b8 100644 --- a/gbfs-validator-java-loader/src/main/java/org/entur/gbfs/validator/loader/Loader.java +++ b/gbfs-validator-java-loader/src/main/java/org/entur/gbfs/validator/loader/Loader.java @@ -167,12 +167,17 @@ public List load(String discoveryURIString, Authentication auth) JSONObject discoveryFileJson = new JSONObject( new JSONTokener(new ByteArrayInputStream(discoveryFileBytes)) ); - String version = discoveryFileJson.getString("version"); + // Default to version 1.0 if no version field is present (as in GBFS v1.0) + String version = discoveryFileJson.optString("version", "1.0"); List loadedFiles = new ArrayList<>(); + // Normalize discovery file name to "gbfs" (without extension) for validator compatibility + String discoveryFileName = discoveryLoadedFile + .fileName() + .replaceFirst("\\.json$", ""); loadedFiles.add( new LoadedFile( - discoveryLoadedFile.fileName(), + discoveryFileName, discoveryLoadedFile.url(), new ByteArrayInputStream(discoveryFileBytes), discoveryLoadedFile.language(), @@ -200,35 +205,40 @@ private List getV3Files( ) { List loadedFeedFiles = new ArrayList<>(); - List> futures = discoveryFileJson - .getJSONObject("data") - .getJSONArray("feeds") - .toList() - .stream() - .map(feed -> { - @SuppressWarnings("unchecked") - Map feedMap = (Map) feed; - String url = (String) feedMap.get("url"); - String name = (String) feedMap.get("name"); - - return CompletableFuture.supplyAsync( - () -> { - LoadedFile loadedFile = loadFile(URI.create(url), auth); - return new LoadedFile( - name, - url, - loadedFile.fileContents(), - loadedFile.language(), - loadedFile.loaderErrors() - ); - }, - executorService - ); - }) - .toList(); - loadedFeedFiles.addAll( - futures.stream().map(CompletableFuture::join).toList() - ); + try { + List> futures = discoveryFileJson + .getJSONObject("data") + .getJSONArray("feeds") + .toList() + .stream() + .map(feed -> { + @SuppressWarnings("unchecked") + Map feedMap = (Map) feed; + String url = (String) feedMap.get("url"); + String name = (String) feedMap.get("name"); + + return CompletableFuture.supplyAsync( + () -> { + LoadedFile loadedFile = loadFile(URI.create(url), auth); + return new LoadedFile( + name, + url, + loadedFile.fileContents(), + loadedFile.language(), + loadedFile.loaderErrors() + ); + }, + executorService + ); + }) + .toList(); + loadedFeedFiles.addAll( + futures.stream().map(CompletableFuture::join).toList() + ); + } catch (Exception e) { + // If we can't parse the discovery file structure, return empty list + // so the discovery file itself can be validated and report proper errors + } return loadedFeedFiles; } @@ -241,42 +251,47 @@ private List getPreV3Files( List loadedFeedFiles = new ArrayList<>(); List> futures = new ArrayList<>(); - discoveryFileJson - .getJSONObject("data") - .keys() - .forEachRemaining(languageKey -> { - discoveryFileJson - .getJSONObject("data") - .getJSONObject(languageKey) - .getJSONArray("feeds") - .toList() - .forEach(feed -> { - @SuppressWarnings("unchecked") - Map feedMap = (Map) feed; - String url = (String) feedMap.get("url"); - String name = (String) feedMap.get("name"); - - futures.add( - CompletableFuture.supplyAsync( - () -> { - LoadedFile loadedFile = loadFile(URI.create(url), auth); - return new LoadedFile( - name, - url, - loadedFile.fileContents(), - languageKey, - loadedFile.loaderErrors() - ); - }, - executorService - ) - ); - }); - }); - - loadedFeedFiles.addAll( - futures.stream().map(CompletableFuture::join).toList() - ); + try { + discoveryFileJson + .getJSONObject("data") + .keys() + .forEachRemaining(languageKey -> { + discoveryFileJson + .getJSONObject("data") + .getJSONObject(languageKey) + .getJSONArray("feeds") + .toList() + .forEach(feed -> { + @SuppressWarnings("unchecked") + Map feedMap = (Map) feed; + String url = (String) feedMap.get("url"); + String name = (String) feedMap.get("name"); + + futures.add( + CompletableFuture.supplyAsync( + () -> { + LoadedFile loadedFile = loadFile(URI.create(url), auth); + return new LoadedFile( + name, + url, + loadedFile.fileContents(), + languageKey, + loadedFile.loaderErrors() + ); + }, + executorService + ) + ); + }); + }); + + loadedFeedFiles.addAll( + futures.stream().map(CompletableFuture::join).toList() + ); + } catch (Exception e) { + // If we can't parse the discovery file structure, return empty list + // so the discovery file itself can be validated and report proper errors + } return loadedFeedFiles; } diff --git a/gbfs-validator-java-loader/src/test/java/org/entur/gbfs/validator/loader/LoaderTest.java b/gbfs-validator-java-loader/src/test/java/org/entur/gbfs/validator/loader/LoaderTest.java index 95a3b046..91bbd0a7 100644 --- a/gbfs-validator-java-loader/src/test/java/org/entur/gbfs/validator/loader/LoaderTest.java +++ b/gbfs-validator-java-loader/src/test/java/org/entur/gbfs/validator/loader/LoaderTest.java @@ -349,7 +349,7 @@ void testLoad_WithDiscoveryFileAndFeed_V3_WithAuth() throws IOException { LoadedFile discoveryFile = files .stream() - .filter(f -> f.fileName().equals("gbfs-v3.json")) + .filter(f -> f.fileName().equals("gbfs-v3")) .findFirst() .orElse(null); LoadedFile systemInfoFile = files diff --git a/gbfs-validator-java/src/main/java/org/entur/gbfs/validation/validator/GbfsJsonValidator.java b/gbfs-validator-java/src/main/java/org/entur/gbfs/validation/validator/GbfsJsonValidator.java index 3683564f..7637ae65 100644 --- a/gbfs-validator-java/src/main/java/org/entur/gbfs/validation/validator/GbfsJsonValidator.java +++ b/gbfs-validator-java/src/main/java/org/entur/gbfs/validation/validator/GbfsJsonValidator.java @@ -158,13 +158,10 @@ private Version detectVersionFromParsedFeeds( ) { ParsedFeedContainer gbfsContainer = parsedFeeds.get("gbfs"); if (gbfsContainer != null && gbfsContainer.jsonObject() != null) { - try { - String versionStr = gbfsContainer.jsonObject().getString("version"); - if (versionStr != null) { - return VersionFactory.createVersion(versionStr); - } - } catch (JSONException e) { - LOG.warn("Could not extract version from gbfs.json, using default.", e); + // Use optString to handle v1.0 feeds that don't have a version field + String versionStr = gbfsContainer.jsonObject().optString("version", null); + if (versionStr != null && !versionStr.isEmpty()) { + return VersionFactory.createVersion(versionStr); } } return VersionFactory.createVersion(GbfsJsonValidator.DEFAULT_VERSION);