Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added forbidden_prior_day_booking_field_value, invalid_prior_notice_duration_min and forbidden_prior_notice_start_day notices #1860

Merged
merged 4 commits into from
Oct 2, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import org.mobilitydata.gtfsvalidator.annotation.GtfsValidationNotice;
import org.mobilitydata.gtfsvalidator.annotation.GtfsValidationNotice.FileRefs;
import org.mobilitydata.gtfsvalidator.annotation.GtfsValidator;
Expand All @@ -17,97 +18,127 @@ public class BookingRulesEntityValidator extends SingleEntityValidator<GtfsBooki

@Override
public void validate(GtfsBookingRules entity, NoticeContainer noticeContainer) {
validateForbiddenRealTimeFields(entity, noticeContainer);
validateSameDayFields(entity, noticeContainer);
validatePriorNotice(entity, noticeContainer);
validateBookingType(
entity,
GtfsBookingType.REALTIME,
BookingRulesEntityValidator::findForbiddenRealTimeFields,
ForbiddenRealTimeBookingFieldValueNotice::new,
noticeContainer);
validateBookingType(
entity,
GtfsBookingType.SAMEDAY,
BookingRulesEntityValidator::findForbiddenSameDayFields,
ForbiddenSameDayBookingFieldValueNotice::new,
noticeContainer);
validateBookingType(
entity,
GtfsBookingType.PRIORDAY,
BookingRulesEntityValidator::findForbiddenPriorDayFields,
ForbiddenPriorDayBookingFieldValueNotice::new,
noticeContainer);
validatePriorNoticeDurationMin(entity, noticeContainer);
validatePriorNoticeStartDay(entity, noticeContainer);
validatePriorNoticeDayRange(entity, noticeContainer);
}

private void validatePriorNotice(GtfsBookingRules entity, NoticeContainer noticeContainer) {
if (entity.hasPriorNoticeLastDay()
&& entity.hasPriorNoticeStartDay()
&& entity.priorNoticeLastDay() > entity.priorNoticeStartDay()) {
noticeContainer.addValidationNotice(new PriorNoticeLastDayAfterStartDayNotice(entity));
private static void validatePriorNoticeDurationMin(
GtfsBookingRules entity, NoticeContainer noticeContainer) {
if (entity.hasPriorNoticeDurationMin() && entity.hasPriorNoticeDurationMax()) {
if (entity.priorNoticeDurationMax() < entity.priorNoticeDurationMin()) {
noticeContainer.addValidationNotice(
new InvalidPriorNoticeDurationMinNotice(
entity, entity.priorNoticeDurationMin(), entity.priorNoticeDurationMax()));
}
}
}

private static void validateForbiddenRealTimeFields(
private static void validatePriorNoticeStartDay(
GtfsBookingRules entity, NoticeContainer noticeContainer) {
// Only validate entities with REALTIME booking type
if (entity.bookingType() != GtfsBookingType.REALTIME) {
return;
}

// Retrieve the list of forbidden fields
List<String> forbiddenFields = findForbiddenRealTimeFields(entity);

// If there are any forbidden fields, add a validation notice
if (!forbiddenFields.isEmpty()) {
if (entity.hasPriorNoticeDurationMax() && entity.hasPriorNoticeStartDay()) {
noticeContainer.addValidationNotice(
new ForbiddenRealTimeBookingFieldValueNotice(entity, forbiddenFields));
new ForbiddenPriorNoticeStartDayNotice(
entity, entity.priorNoticeStartDay(), entity.priorNoticeDurationMax()));
}
}

private static void validateSameDayFields(
GtfsBookingRules entity, NoticeContainer noticeContainer) {
// Only validate entities with SAME_DAY booking type
if (entity.bookingType() != GtfsBookingType.SAMEDAY) {
private static void validateBookingType(
GtfsBookingRules entity,
GtfsBookingType bookingType,
Function<GtfsBookingRules, List<String>> forbiddenFieldsFinder,
ValidationNoticeConstructor validationNoticeConstructor,
NoticeContainer noticeContainer) {

if (entity.bookingType() != bookingType) {
return;
}

// Retrieve the list of forbidden fields
List<String> forbiddenFields = findForbiddenSameDayFields(entity);
List<String> forbiddenFields = forbiddenFieldsFinder.apply(entity);

// If there are any forbidden fields, add a validation notice
if (!forbiddenFields.isEmpty()) {
noticeContainer.addValidationNotice(
new ForbiddenSameDayBookingFieldValueNotice(entity, forbiddenFields));
validationNoticeConstructor.create(entity, forbiddenFields));
}
}

private static List<String> findForbiddenSameDayFields(GtfsBookingRules bookingRule) {
List<String> fields = new ArrayList<>();
return findForbiddenFields(
bookingRule.hasPriorNoticeLastDay(),
GtfsBookingRules.PRIOR_NOTICE_LAST_DAY_FIELD_NAME,
bookingRule.hasPriorNoticeLastTime(),
GtfsBookingRules.PRIOR_NOTICE_LAST_TIME_FIELD_NAME,
bookingRule.hasPriorNoticeServiceId(),
GtfsBookingRules.PRIOR_NOTICE_SERVICE_ID_FIELD_NAME);
}

// Check each forbidden field and add its name to the list if it's present
if (bookingRule.hasPriorNoticeLastDay()) {
fields.add(GtfsBookingRules.PRIOR_NOTICE_LAST_DAY_FIELD_NAME);
}
if (bookingRule.hasPriorNoticeLastTime()) {
fields.add(GtfsBookingRules.PRIOR_NOTICE_LAST_TIME_FIELD_NAME);
}
if (bookingRule.hasPriorNoticeServiceId()) {
fields.add(GtfsBookingRules.PRIOR_NOTICE_SERVICE_ID_FIELD_NAME);
}
return fields;
private static List<String> findForbiddenPriorDayFields(GtfsBookingRules bookingRule) {
return findForbiddenFields(
bookingRule.hasPriorNoticeDurationMin(),
GtfsBookingRules.PRIOR_NOTICE_DURATION_MIN_FIELD_NAME,
bookingRule.hasPriorNoticeDurationMax(),
GtfsBookingRules.PRIOR_NOTICE_DURATION_MAX_FIELD_NAME);
}

/** Finds forbidden fields that should not be present for real-time booking rules. */
public static List<String> findForbiddenRealTimeFields(GtfsBookingRules bookingRule) {
List<String> fields = new ArrayList<>();
private static List<String> findForbiddenRealTimeFields(GtfsBookingRules bookingRule) {
return findForbiddenFields(
bookingRule.hasPriorNoticeDurationMin(),
GtfsBookingRules.PRIOR_NOTICE_DURATION_MIN_FIELD_NAME,
bookingRule.hasPriorNoticeDurationMax(),
GtfsBookingRules.PRIOR_NOTICE_DURATION_MAX_FIELD_NAME,
bookingRule.hasPriorNoticeLastDay(),
GtfsBookingRules.PRIOR_NOTICE_LAST_DAY_FIELD_NAME,
bookingRule.hasPriorNoticeLastTime(),
GtfsBookingRules.PRIOR_NOTICE_LAST_TIME_FIELD_NAME,
bookingRule.hasPriorNoticeStartDay(),
GtfsBookingRules.PRIOR_NOTICE_START_DAY_FIELD_NAME,
bookingRule.hasPriorNoticeStartTime(),
GtfsBookingRules.PRIOR_NOTICE_START_TIME_FIELD_NAME,
bookingRule.hasPriorNoticeServiceId(),
GtfsBookingRules.PRIOR_NOTICE_SERVICE_ID_FIELD_NAME);
}

// Check each forbidden field and add its name to the list if it's present
if (bookingRule.hasPriorNoticeDurationMin()) {
fields.add(GtfsBookingRules.PRIOR_NOTICE_DURATION_MIN_FIELD_NAME);
}
if (bookingRule.hasPriorNoticeDurationMax()) {
fields.add(GtfsBookingRules.PRIOR_NOTICE_DURATION_MAX_FIELD_NAME);
}
if (bookingRule.hasPriorNoticeLastDay()) {
fields.add(GtfsBookingRules.PRIOR_NOTICE_LAST_DAY_FIELD_NAME);
}
if (bookingRule.hasPriorNoticeLastTime()) {
fields.add(GtfsBookingRules.PRIOR_NOTICE_LAST_TIME_FIELD_NAME);
}
if (bookingRule.hasPriorNoticeStartDay()) {
fields.add(GtfsBookingRules.PRIOR_NOTICE_START_DAY_FIELD_NAME);
}
if (bookingRule.hasPriorNoticeStartTime()) {
fields.add(GtfsBookingRules.PRIOR_NOTICE_START_TIME_FIELD_NAME);
private static List<String> findForbiddenFields(Object... conditionsAndFields) {
List<String> fields = new ArrayList<>();
for (int i = 0; i < conditionsAndFields.length; i += 2) {
if ((Boolean) conditionsAndFields[i]) {
fields.add((String) conditionsAndFields[i + 1]);
}
}
if (bookingRule.hasPriorNoticeServiceId()) {
fields.add(GtfsBookingRules.PRIOR_NOTICE_SERVICE_ID_FIELD_NAME);
return fields;
}

private static void validatePriorNoticeDayRange(
GtfsBookingRules entity, NoticeContainer noticeContainer) {
if (entity.hasPriorNoticeLastDay()
&& entity.hasPriorNoticeStartDay()
&& entity.priorNoticeLastDay() > entity.priorNoticeStartDay()) {
noticeContainer.addValidationNotice(new PriorNoticeLastDayAfterStartDayNotice(entity));
}
}

return fields;
// Abstract Notice Creation using Functional Interface
@FunctionalInterface
private interface ValidationNoticeConstructor {
ValidationNotice create(GtfsBookingRules bookingRule, List<String> forbiddenFields);
}

/** A forbidden field value is present for a real-time booking rule in `booking_rules.txt`. */
Expand Down Expand Up @@ -154,6 +185,85 @@ static class ForbiddenSameDayBookingFieldValueNotice extends ValidationNotice {
}
}

/** A forbidden field value is present for a prior-day booking rule in `booking_rules.txt` */
@GtfsValidationNotice(
severity = SeverityLevel.ERROR,
files = @FileRefs(GtfsBookingRulesSchema.class))
static class ForbiddenPriorDayBookingFieldValueNotice extends ValidationNotice {
/** The row number of the faulty record. */
private final int csvRowNumber;

/** The `booking_rules.booking_rule_id` of the faulty record. */
private final String bookingRuleId;

/** The names of the forbidden fields comma-separated. */
private final String fieldNames;

ForbiddenPriorDayBookingFieldValueNotice(
GtfsBookingRules bookingRule, List<String> forbiddenFields) {
this.csvRowNumber = bookingRule.csvRowNumber();
this.bookingRuleId = bookingRule.bookingRuleId();
this.fieldNames = String.join(", ", forbiddenFields);
}
}

/**
* An invalid `prior_notice_duration_min` value is present in a booking rule.
*
* <p>The `prior_notice_duration_max` field value needs to be greater or equal to the
* `prior_notice_duration_min` field value.
*/
@GtfsValidationNotice(
severity = SeverityLevel.ERROR,
files = @FileRefs(GtfsBookingRulesSchema.class))
static class InvalidPriorNoticeDurationMinNotice extends ValidationNotice {
/** The row number of the faulty record. */
private final int csvRowNumber;

/** The `booking_rules.booking_rule_id` of the faulty record. */
private final String bookingRuleId;

/** The value of the `prior_notice_duration_min` field. */
private final int priorNoticeDurationMin;

/** The value of the `prior_notice_duration_max` field. */
private final int priorNoticeDurationMax;

InvalidPriorNoticeDurationMinNotice(
GtfsBookingRules bookingRule, int priorNoticeDurationMin, int priorNoticeDurationMax) {
this.csvRowNumber = bookingRule.csvRowNumber();
this.bookingRuleId = bookingRule.bookingRuleId();
this.priorNoticeDurationMin = priorNoticeDurationMin;
this.priorNoticeDurationMax = priorNoticeDurationMax;
}
}

/** `prior_notice_start_day` value is forbidden when `prior_notice_duration_max` is set. */
@GtfsValidationNotice(
severity = SeverityLevel.ERROR,
files = @FileRefs(GtfsBookingRulesSchema.class))
static class ForbiddenPriorNoticeStartDayNotice extends ValidationNotice {
/** The row number of the faulty record. */
private final int csvRowNumber;

/** The `booking_rules.booking_rule_id` of the faulty record. */
private final String bookingRuleId;

/** The value of the `prior_notice_duration_min` field. */
private final int priorNoticeStartDay;

/** The value of the `prior_notice_duration_max` field. */
private final int priorNoticeDurationMax;

ForbiddenPriorNoticeStartDayNotice(
GtfsBookingRules bookingRule, int priorNoticeStartDay, int priorNoticeDurationMax) {
this.csvRowNumber = bookingRule.csvRowNumber();
this.bookingRuleId = bookingRule.bookingRuleId();
this.priorNoticeStartDay = priorNoticeStartDay;
this.priorNoticeDurationMax = priorNoticeDurationMax;
}
}

/**
* Prior notice last day should not be greater than the prior notice start day in
* booking_rules.txt.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public void sameDayBookingWithForbiddenFieldsShouldGenerateNotice() {
.setBookingRuleId("rule-5")
.setBookingType(GtfsBookingType.SAMEDAY)
.setPriorNoticeLastDay(2) // Forbidden field
.setPriorNoticeStartTime(GtfsTime.fromSecondsSinceMidnight(5000)) // Forbidden field
.setPriorNoticeStartTime(GtfsTime.fromSecondsSinceMidnight(5000))
.build();

assertThat(generateNotices(bookingRule))
Expand All @@ -120,6 +120,59 @@ public void sameDayBookingWithoutForbiddenFieldsShouldNotGenerateNotice() {
assertThat(generateNotices(bookingRule)).isEmpty();
}

@Test
public void priorDayBookingWithForbiddenFieldsShouldGenerateNotice() {
GtfsBookingRules bookingRule =
new GtfsBookingRules.Builder()
.setCsvRowNumber(1)
.setBookingRuleId("rule-7")
.setBookingType(GtfsBookingType.PRIORDAY)
.setPriorNoticeDurationMin(30) // Forbidden field
.setPriorNoticeDurationMax(60) // Forbidden field
.build();

assertThat(generateNotices(bookingRule))
.containsExactly(
new BookingRulesEntityValidator.ForbiddenPriorDayBookingFieldValueNotice(
bookingRule,
List.of(
GtfsBookingRules.PRIOR_NOTICE_DURATION_MIN_FIELD_NAME,
GtfsBookingRules.PRIOR_NOTICE_DURATION_MAX_FIELD_NAME)));
}

@Test
public void invalidPriorNoticeDurationMinShouldGenerateNotice() {
GtfsBookingRules bookingRule =
new GtfsBookingRules.Builder()
.setCsvRowNumber(1)
.setBookingRuleId("rule-8")
.setBookingType(GtfsBookingType.SAMEDAY)
.setPriorNoticeDurationMin(60) // Invalid: greater than max
.setPriorNoticeDurationMax(30)
.build();

assertThat(generateNotices(bookingRule))
.containsExactly(
new BookingRulesEntityValidator.InvalidPriorNoticeDurationMinNotice(
bookingRule, 60, 30));
}

@Test
public void forbiddenPriorNoticeStartDayShouldGenerateNotice() {
GtfsBookingRules bookingRule =
new GtfsBookingRules.Builder()
.setCsvRowNumber(1)
.setBookingRuleId("rule-9")
.setBookingType(GtfsBookingType.SAMEDAY)
.setPriorNoticeDurationMax(30) // Duration max is set
.setPriorNoticeStartDay(5) // Forbidden when duration max is set
.build();

assertThat(generateNotices(bookingRule))
.containsExactly(
new BookingRulesEntityValidator.ForbiddenPriorNoticeStartDayNotice(bookingRule, 5, 30));
}

@Test
public void priorNoticeLastDayAfterStartDayShouldGenerateNotice() {
GtfsBookingRules bookingRule =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,8 +202,10 @@ public void testNoticeClassFieldNames() {
"locationId",
"bookingRuleId",
"fieldNames",
"priorNoticeLastDay",
"priorNoticeStartDay");
"priorNoticeDurationMin",
"priorNoticeDurationMax",
"priorNoticeStartDay",
"priorNoticeLastDay");
}

private static List<String> discoverValidationNoticeFieldNames() {
Expand Down
Loading