diff --git a/main/src/main/java/org/mobilitydata/gtfsvalidator/table/GtfsRouteNetworkSchema.java b/main/src/main/java/org/mobilitydata/gtfsvalidator/table/GtfsRouteNetworkSchema.java new file mode 100644 index 0000000000..fb14d1257f --- /dev/null +++ b/main/src/main/java/org/mobilitydata/gtfsvalidator/table/GtfsRouteNetworkSchema.java @@ -0,0 +1,17 @@ +package org.mobilitydata.gtfsvalidator.table; + +import org.mobilitydata.gtfsvalidator.annotation.*; + +@GtfsTable("route_networks.txt") +public interface GtfsRouteNetworkSchema extends GtfsEntity { + @FieldType(FieldTypeEnum.ID) + @ForeignKey(table = "routes.txt", field = "route_id") + @Required + @PrimaryKey + String routeId(); + + @FieldType(FieldTypeEnum.ID) + @ForeignKey(table = "networks.txt", field = "network_id") + @Required + String networkId(); +} diff --git a/main/src/main/java/org/mobilitydata/gtfsvalidator/validator/NetworkIdConsistencyValidator.java b/main/src/main/java/org/mobilitydata/gtfsvalidator/validator/NetworkIdConsistencyValidator.java new file mode 100644 index 0000000000..610ce4d4fb --- /dev/null +++ b/main/src/main/java/org/mobilitydata/gtfsvalidator/validator/NetworkIdConsistencyValidator.java @@ -0,0 +1,81 @@ +package org.mobilitydata.gtfsvalidator.validator; + +import static org.mobilitydata.gtfsvalidator.notice.SeverityLevel.ERROR; + +import javax.inject.Inject; +import org.mobilitydata.gtfsvalidator.annotation.GtfsValidationNotice; +import org.mobilitydata.gtfsvalidator.annotation.GtfsValidator; +import org.mobilitydata.gtfsvalidator.notice.NoticeContainer; +import org.mobilitydata.gtfsvalidator.notice.ValidationNotice; +import org.mobilitydata.gtfsvalidator.table.*; + +@GtfsValidator +public class NetworkIdConsistencyValidator extends FileValidator { + private final GtfsRouteTableContainer routeTableContainer; + private final GtfsRouteNetworkTableContainer routeNetworkTableContainer; + + private final GtfsNetworkTableContainer networkTableContainer; + + @Inject + NetworkIdConsistencyValidator( + GtfsRouteTableContainer routes, + GtfsRouteNetworkTableContainer routeNetworks, + GtfsNetworkTableContainer networks) { + this.routeTableContainer = routes; + this.routeNetworkTableContainer = routeNetworks; + this.networkTableContainer = networks; + } + + @Override + public void validate(NoticeContainer noticeContainer) { + // Validate the presence of network_id in routes and its specification in either route_network + // or network files + boolean hasNetworkIdField = this.routeTableContainer.hasColumn(GtfsRoute.NETWORK_ID_FIELD_NAME); + if (hasNetworkIdField) { + if (!this.routeNetworkTableContainer.isMissingFile()) { + noticeContainer.addValidationNotice( + new RouteNetworksSpecifiedInMoreThanOneFileNotice( + GtfsRoute.FILENAME, GtfsRouteNetwork.FILENAME, GtfsRoute.NETWORK_ID_FIELD_NAME)); + } + if (!this.networkTableContainer.isMissingFile()) { + noticeContainer.addValidationNotice( + new RouteNetworksSpecifiedInMoreThanOneFileNotice( + GtfsRoute.FILENAME, GtfsNetwork.FILENAME, GtfsRoute.NETWORK_ID_FIELD_NAME)); + } + } + } + + /** + * Indicates that route network identifiers are specified across multiple files. + * + *
This notice highlights a data integrity issue where route network specifications are
+ * redundantly defined in more than one file. According to specifications, a route network
+ * identifier should be uniquely defined in a single file. Any additional definitions of route
+ * network specifications in other files are considered conditionally forbidden.
+ */
+ @GtfsValidationNotice(
+ severity = ERROR,
+ files =
+ @GtfsValidationNotice.FileRefs({
+ GtfsRouteSchema.class,
+ GtfsRouteNetworkSchema.class,
+ GtfsNetworkSchema.class
+ }))
+ static class RouteNetworksSpecifiedInMoreThanOneFileNotice extends ValidationNotice {
+ /** Name of the field in fileNameA */
+ private final String fieldName;
+
+ /** The name of the first file. */
+ private final String fileNameA;
+
+ /** The name of the second file which presence duplicates route networks specification. */
+ private final String fileNameB;
+
+ RouteNetworksSpecifiedInMoreThanOneFileNotice(
+ String filename1, String filename2, String fieldName) {
+ this.fileNameA = filename1;
+ this.fileNameB = filename2;
+ this.fieldName = fieldName;
+ }
+ }
+}
diff --git a/main/src/test/java/org/mobilitydata/gtfsvalidator/validator/NetworkIdConsistencyValidatorTest.java b/main/src/test/java/org/mobilitydata/gtfsvalidator/validator/NetworkIdConsistencyValidatorTest.java
new file mode 100644
index 0000000000..0dfe65a6d2
--- /dev/null
+++ b/main/src/test/java/org/mobilitydata/gtfsvalidator/validator/NetworkIdConsistencyValidatorTest.java
@@ -0,0 +1,86 @@
+package org.mobilitydata.gtfsvalidator.validator;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import org.junit.Before;
+import org.junit.Test;
+import org.mobilitydata.gtfsvalidator.notice.NoticeContainer;
+import org.mobilitydata.gtfsvalidator.parsing.CsvHeader;
+import org.mobilitydata.gtfsvalidator.table.*;
+
+public class NetworkIdConsistencyValidatorTest {
+
+ private NoticeContainer noticeContainer;
+ private GtfsRouteTableContainer routeTableContainer;
+ private GtfsRouteNetworkTableContainer routeNetworkTableContainer;
+ private GtfsNetworkTableContainer networkTableContainer;
+
+ @Before
+ public void setup() {
+ noticeContainer = new NoticeContainer();
+ routeTableContainer =
+ GtfsRouteTableContainer.forHeaderAndEntities(
+ new GtfsRouteTableDescriptor(),
+ new CsvHeader(ImmutableList.of("route_id", "network_id").toArray(new String[0])),
+ ImmutableList.of(
+ new GtfsRoute.Builder().setRouteId("123").setNetworkId("network1").build()),
+ noticeContainer);
+ routeNetworkTableContainer =
+ new GtfsRouteNetworkTableContainer(
+ new GtfsRouteNetworkTableDescriptor(), GtfsTableContainer.TableStatus.MISSING_FILE);
+ networkTableContainer =
+ new GtfsNetworkTableContainer(
+ new GtfsNetworkTableDescriptor(), GtfsTableContainer.TableStatus.MISSING_FILE);
+ }
+
+ @Test
+ public void validatesConditionalForbiddenFilePresenceNoNotice() {
+ NetworkIdConsistencyValidator validator =
+ new NetworkIdConsistencyValidator(
+ routeTableContainer, routeNetworkTableContainer, networkTableContainer);
+ validator.validate(noticeContainer);
+ assertThat(noticeContainer.getValidationNotices().isEmpty());
+ }
+
+ @Test
+ public void validatesConditionalForbiddenFilePresence1() {
+ routeNetworkTableContainer =
+ GtfsRouteNetworkTableContainer.forEntities(
+ ImmutableList.of(
+ new GtfsRouteNetwork.Builder().setRouteId("123").setNetworkId("network1").build()),
+ noticeContainer);
+ NetworkIdConsistencyValidator validator =
+ new NetworkIdConsistencyValidator(
+ routeTableContainer, routeNetworkTableContainer, networkTableContainer);
+ validator.validate(noticeContainer);
+ assertThat(noticeContainer.getValidationNotices().size() == 1);
+ NetworkIdConsistencyValidator.RouteNetworksSpecifiedInMoreThanOneFileNotice notice =
+ (NetworkIdConsistencyValidator.RouteNetworksSpecifiedInMoreThanOneFileNotice)
+ noticeContainer.getValidationNotices().get(0);
+ NetworkIdConsistencyValidator.RouteNetworksSpecifiedInMoreThanOneFileNotice expectedNotice =
+ new NetworkIdConsistencyValidator.RouteNetworksSpecifiedInMoreThanOneFileNotice(
+ "routes.txt", "route_networks.txt", "network_id");
+ assertThat(notice.toString().equals(expectedNotice.toString()));
+ }
+
+ @Test
+ public void validatesConditionalForbiddenFilePresence2() {
+ networkTableContainer =
+ GtfsNetworkTableContainer.forEntities(
+ ImmutableList.of(new GtfsNetwork.Builder().setNetworkId("network1").build()),
+ noticeContainer);
+ NetworkIdConsistencyValidator validator =
+ new NetworkIdConsistencyValidator(
+ routeTableContainer, routeNetworkTableContainer, networkTableContainer);
+ validator.validate(noticeContainer);
+ assertThat(noticeContainer.getValidationNotices().size() == 1);
+ NetworkIdConsistencyValidator.RouteNetworksSpecifiedInMoreThanOneFileNotice notice =
+ (NetworkIdConsistencyValidator.RouteNetworksSpecifiedInMoreThanOneFileNotice)
+ noticeContainer.getValidationNotices().get(0);
+ NetworkIdConsistencyValidator.RouteNetworksSpecifiedInMoreThanOneFileNotice expectedNotice =
+ new NetworkIdConsistencyValidator.RouteNetworksSpecifiedInMoreThanOneFileNotice(
+ "routes.txt", "networks.txt", "network_id");
+ assertThat(notice.toString().equals(expectedNotice.toString()));
+ }
+}
diff --git a/main/src/test/java/org/mobilitydata/gtfsvalidator/validator/NoticeFieldsTest.java b/main/src/test/java/org/mobilitydata/gtfsvalidator/validator/NoticeFieldsTest.java
index 7877aa1d0f..6ed212c10e 100644
--- a/main/src/test/java/org/mobilitydata/gtfsvalidator/validator/NoticeFieldsTest.java
+++ b/main/src/test/java/org/mobilitydata/gtfsvalidator/validator/NoticeFieldsTest.java
@@ -193,7 +193,9 @@ public void testNoticeClassFieldNames() {
"validator",
"value",
"maxShapeDistanceTraveled",
- "maxTripDistanceTraveled");
+ "maxTripDistanceTraveled",
+ "fileNameA",
+ "fileNameB");
}
private static List