Skip to content

Commit 566fa52

Browse files
authored
Merge 57bf65f into 63dab81
2 parents 63dab81 + 57bf65f commit 566fa52

File tree

3 files changed

+232
-67
lines changed

3 files changed

+232
-67
lines changed

main/src/main/java/org/mobilitydata/gtfsvalidator/validator/BookingRulesEntityValidator.java

+174-64
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.util.ArrayList;
44
import java.util.List;
5+
import java.util.function.Function;
56
import org.mobilitydata.gtfsvalidator.annotation.GtfsValidationNotice;
67
import org.mobilitydata.gtfsvalidator.annotation.GtfsValidationNotice.FileRefs;
78
import org.mobilitydata.gtfsvalidator.annotation.GtfsValidator;
@@ -17,97 +18,127 @@ public class BookingRulesEntityValidator extends SingleEntityValidator<GtfsBooki
1718

1819
@Override
1920
public void validate(GtfsBookingRules entity, NoticeContainer noticeContainer) {
20-
validateForbiddenRealTimeFields(entity, noticeContainer);
21-
validateSameDayFields(entity, noticeContainer);
22-
validatePriorNotice(entity, noticeContainer);
21+
validateBookingType(
22+
entity,
23+
GtfsBookingType.REALTIME,
24+
BookingRulesEntityValidator::findForbiddenRealTimeFields,
25+
ForbiddenRealTimeBookingFieldValueNotice::new,
26+
noticeContainer);
27+
validateBookingType(
28+
entity,
29+
GtfsBookingType.SAMEDAY,
30+
BookingRulesEntityValidator::findForbiddenSameDayFields,
31+
ForbiddenSameDayBookingFieldValueNotice::new,
32+
noticeContainer);
33+
validateBookingType(
34+
entity,
35+
GtfsBookingType.PRIORDAY,
36+
BookingRulesEntityValidator::findForbiddenPriorDayFields,
37+
ForbiddenPriorDayBookingFieldValueNotice::new,
38+
noticeContainer);
39+
validatePriorNoticeDurationMin(entity, noticeContainer);
40+
validatePriorNoticeStartDay(entity, noticeContainer);
41+
validatePriorNoticeDayRange(entity, noticeContainer);
2342
}
2443

25-
private void validatePriorNotice(GtfsBookingRules entity, NoticeContainer noticeContainer) {
26-
if (entity.hasPriorNoticeLastDay()
27-
&& entity.hasPriorNoticeStartDay()
28-
&& entity.priorNoticeLastDay() > entity.priorNoticeStartDay()) {
29-
noticeContainer.addValidationNotice(new PriorNoticeLastDayAfterStartDayNotice(entity));
44+
private static void validatePriorNoticeDurationMin(
45+
GtfsBookingRules entity, NoticeContainer noticeContainer) {
46+
if (entity.hasPriorNoticeDurationMin() && entity.hasPriorNoticeDurationMax()) {
47+
if (entity.priorNoticeDurationMax() < entity.priorNoticeDurationMin()) {
48+
noticeContainer.addValidationNotice(
49+
new InvalidPriorNoticeDurationMinNotice(
50+
entity, entity.priorNoticeDurationMin(), entity.priorNoticeDurationMax()));
51+
}
3052
}
3153
}
3254

33-
private static void validateForbiddenRealTimeFields(
55+
private static void validatePriorNoticeStartDay(
3456
GtfsBookingRules entity, NoticeContainer noticeContainer) {
35-
// Only validate entities with REALTIME booking type
36-
if (entity.bookingType() != GtfsBookingType.REALTIME) {
37-
return;
38-
}
39-
40-
// Retrieve the list of forbidden fields
41-
List<String> forbiddenFields = findForbiddenRealTimeFields(entity);
42-
43-
// If there are any forbidden fields, add a validation notice
44-
if (!forbiddenFields.isEmpty()) {
57+
if (entity.hasPriorNoticeDurationMax() && entity.hasPriorNoticeStartDay()) {
4558
noticeContainer.addValidationNotice(
46-
new ForbiddenRealTimeBookingFieldValueNotice(entity, forbiddenFields));
59+
new ForbiddenPriorNoticeStartDayNotice(
60+
entity, entity.priorNoticeStartDay(), entity.priorNoticeDurationMax()));
4761
}
4862
}
4963

50-
private static void validateSameDayFields(
51-
GtfsBookingRules entity, NoticeContainer noticeContainer) {
52-
// Only validate entities with SAME_DAY booking type
53-
if (entity.bookingType() != GtfsBookingType.SAMEDAY) {
64+
private static void validateBookingType(
65+
GtfsBookingRules entity,
66+
GtfsBookingType bookingType,
67+
Function<GtfsBookingRules, List<String>> forbiddenFieldsFinder,
68+
ValidationNoticeConstructor validationNoticeConstructor,
69+
NoticeContainer noticeContainer) {
70+
71+
if (entity.bookingType() != bookingType) {
5472
return;
5573
}
5674

57-
// Retrieve the list of forbidden fields
58-
List<String> forbiddenFields = findForbiddenSameDayFields(entity);
75+
List<String> forbiddenFields = forbiddenFieldsFinder.apply(entity);
5976

60-
// If there are any forbidden fields, add a validation notice
6177
if (!forbiddenFields.isEmpty()) {
6278
noticeContainer.addValidationNotice(
63-
new ForbiddenSameDayBookingFieldValueNotice(entity, forbiddenFields));
79+
validationNoticeConstructor.create(entity, forbiddenFields));
6480
}
6581
}
6682

6783
private static List<String> findForbiddenSameDayFields(GtfsBookingRules bookingRule) {
68-
List<String> fields = new ArrayList<>();
84+
return findForbiddenFields(
85+
bookingRule.hasPriorNoticeLastDay(),
86+
GtfsBookingRules.PRIOR_NOTICE_LAST_DAY_FIELD_NAME,
87+
bookingRule.hasPriorNoticeLastTime(),
88+
GtfsBookingRules.PRIOR_NOTICE_LAST_TIME_FIELD_NAME,
89+
bookingRule.hasPriorNoticeServiceId(),
90+
GtfsBookingRules.PRIOR_NOTICE_SERVICE_ID_FIELD_NAME);
91+
}
6992

70-
// Check each forbidden field and add its name to the list if it's present
71-
if (bookingRule.hasPriorNoticeLastDay()) {
72-
fields.add(GtfsBookingRules.PRIOR_NOTICE_LAST_DAY_FIELD_NAME);
73-
}
74-
if (bookingRule.hasPriorNoticeLastTime()) {
75-
fields.add(GtfsBookingRules.PRIOR_NOTICE_LAST_TIME_FIELD_NAME);
76-
}
77-
if (bookingRule.hasPriorNoticeServiceId()) {
78-
fields.add(GtfsBookingRules.PRIOR_NOTICE_SERVICE_ID_FIELD_NAME);
79-
}
80-
return fields;
93+
private static List<String> findForbiddenPriorDayFields(GtfsBookingRules bookingRule) {
94+
return findForbiddenFields(
95+
bookingRule.hasPriorNoticeDurationMin(),
96+
GtfsBookingRules.PRIOR_NOTICE_DURATION_MIN_FIELD_NAME,
97+
bookingRule.hasPriorNoticeDurationMax(),
98+
GtfsBookingRules.PRIOR_NOTICE_DURATION_MAX_FIELD_NAME);
8199
}
82100

83-
/** Finds forbidden fields that should not be present for real-time booking rules. */
84-
public static List<String> findForbiddenRealTimeFields(GtfsBookingRules bookingRule) {
85-
List<String> fields = new ArrayList<>();
101+
private static List<String> findForbiddenRealTimeFields(GtfsBookingRules bookingRule) {
102+
return findForbiddenFields(
103+
bookingRule.hasPriorNoticeDurationMin(),
104+
GtfsBookingRules.PRIOR_NOTICE_DURATION_MIN_FIELD_NAME,
105+
bookingRule.hasPriorNoticeDurationMax(),
106+
GtfsBookingRules.PRIOR_NOTICE_DURATION_MAX_FIELD_NAME,
107+
bookingRule.hasPriorNoticeLastDay(),
108+
GtfsBookingRules.PRIOR_NOTICE_LAST_DAY_FIELD_NAME,
109+
bookingRule.hasPriorNoticeLastTime(),
110+
GtfsBookingRules.PRIOR_NOTICE_LAST_TIME_FIELD_NAME,
111+
bookingRule.hasPriorNoticeStartDay(),
112+
GtfsBookingRules.PRIOR_NOTICE_START_DAY_FIELD_NAME,
113+
bookingRule.hasPriorNoticeStartTime(),
114+
GtfsBookingRules.PRIOR_NOTICE_START_TIME_FIELD_NAME,
115+
bookingRule.hasPriorNoticeServiceId(),
116+
GtfsBookingRules.PRIOR_NOTICE_SERVICE_ID_FIELD_NAME);
117+
}
86118

87-
// Check each forbidden field and add its name to the list if it's present
88-
if (bookingRule.hasPriorNoticeDurationMin()) {
89-
fields.add(GtfsBookingRules.PRIOR_NOTICE_DURATION_MIN_FIELD_NAME);
90-
}
91-
if (bookingRule.hasPriorNoticeDurationMax()) {
92-
fields.add(GtfsBookingRules.PRIOR_NOTICE_DURATION_MAX_FIELD_NAME);
93-
}
94-
if (bookingRule.hasPriorNoticeLastDay()) {
95-
fields.add(GtfsBookingRules.PRIOR_NOTICE_LAST_DAY_FIELD_NAME);
96-
}
97-
if (bookingRule.hasPriorNoticeLastTime()) {
98-
fields.add(GtfsBookingRules.PRIOR_NOTICE_LAST_TIME_FIELD_NAME);
99-
}
100-
if (bookingRule.hasPriorNoticeStartDay()) {
101-
fields.add(GtfsBookingRules.PRIOR_NOTICE_START_DAY_FIELD_NAME);
102-
}
103-
if (bookingRule.hasPriorNoticeStartTime()) {
104-
fields.add(GtfsBookingRules.PRIOR_NOTICE_START_TIME_FIELD_NAME);
119+
private static List<String> findForbiddenFields(Object... conditionsAndFields) {
120+
List<String> fields = new ArrayList<>();
121+
for (int i = 0; i < conditionsAndFields.length; i += 2) {
122+
if ((Boolean) conditionsAndFields[i]) {
123+
fields.add((String) conditionsAndFields[i + 1]);
124+
}
105125
}
106-
if (bookingRule.hasPriorNoticeServiceId()) {
107-
fields.add(GtfsBookingRules.PRIOR_NOTICE_SERVICE_ID_FIELD_NAME);
126+
return fields;
127+
}
128+
129+
private static void validatePriorNoticeDayRange(
130+
GtfsBookingRules entity, NoticeContainer noticeContainer) {
131+
if (entity.hasPriorNoticeLastDay()
132+
&& entity.hasPriorNoticeStartDay()
133+
&& entity.priorNoticeLastDay() > entity.priorNoticeStartDay()) {
134+
noticeContainer.addValidationNotice(new PriorNoticeLastDayAfterStartDayNotice(entity));
108135
}
136+
}
109137

110-
return fields;
138+
// Abstract Notice Creation using Functional Interface
139+
@FunctionalInterface
140+
private interface ValidationNoticeConstructor {
141+
ValidationNotice create(GtfsBookingRules bookingRule, List<String> forbiddenFields);
111142
}
112143

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

188+
/** A forbidden field value is present for a prior-day booking rule in `booking_rules.txt` */
189+
@GtfsValidationNotice(
190+
severity = SeverityLevel.ERROR,
191+
files = @FileRefs(GtfsBookingRulesSchema.class))
192+
static class ForbiddenPriorDayBookingFieldValueNotice extends ValidationNotice {
193+
/** The row number of the faulty record. */
194+
private final int csvRowNumber;
195+
196+
/** The `booking_rules.booking_rule_id` of the faulty record. */
197+
private final String bookingRuleId;
198+
199+
/** The names of the forbidden fields comma-separated. */
200+
private final String fieldNames;
201+
202+
ForbiddenPriorDayBookingFieldValueNotice(
203+
GtfsBookingRules bookingRule, List<String> forbiddenFields) {
204+
this.csvRowNumber = bookingRule.csvRowNumber();
205+
this.bookingRuleId = bookingRule.bookingRuleId();
206+
this.fieldNames = String.join(", ", forbiddenFields);
207+
}
208+
}
209+
210+
/**
211+
* An invalid `prior_notice_duration_min` value is present in a booking rule.
212+
*
213+
* <p>The `prior_notice_duration_max` field value needs to be greater or equal to the
214+
* `prior_notice_duration_min` field value.
215+
*/
216+
@GtfsValidationNotice(
217+
severity = SeverityLevel.ERROR,
218+
files = @FileRefs(GtfsBookingRulesSchema.class))
219+
static class InvalidPriorNoticeDurationMinNotice extends ValidationNotice {
220+
/** The row number of the faulty record. */
221+
private final int csvRowNumber;
222+
223+
/** The `booking_rules.booking_rule_id` of the faulty record. */
224+
private final String bookingRuleId;
225+
226+
/** The value of the `prior_notice_duration_min` field. */
227+
private final int priorNoticeDurationMin;
228+
229+
/** The value of the `prior_notice_duration_max` field. */
230+
private final int priorNoticeDurationMax;
231+
232+
InvalidPriorNoticeDurationMinNotice(
233+
GtfsBookingRules bookingRule, int priorNoticeDurationMin, int priorNoticeDurationMax) {
234+
this.csvRowNumber = bookingRule.csvRowNumber();
235+
this.bookingRuleId = bookingRule.bookingRuleId();
236+
this.priorNoticeDurationMin = priorNoticeDurationMin;
237+
this.priorNoticeDurationMax = priorNoticeDurationMax;
238+
}
239+
}
240+
241+
/** `prior_notice_start_day` value is forbidden when `prior_notice_duration_max` is set. */
242+
@GtfsValidationNotice(
243+
severity = SeverityLevel.ERROR,
244+
files = @FileRefs(GtfsBookingRulesSchema.class))
245+
static class ForbiddenPriorNoticeStartDayNotice extends ValidationNotice {
246+
/** The row number of the faulty record. */
247+
private final int csvRowNumber;
248+
249+
/** The `booking_rules.booking_rule_id` of the faulty record. */
250+
private final String bookingRuleId;
251+
252+
/** The value of the `prior_notice_duration_min` field. */
253+
private final int priorNoticeStartDay;
254+
255+
/** The value of the `prior_notice_duration_max` field. */
256+
private final int priorNoticeDurationMax;
257+
258+
ForbiddenPriorNoticeStartDayNotice(
259+
GtfsBookingRules bookingRule, int priorNoticeStartDay, int priorNoticeDurationMax) {
260+
this.csvRowNumber = bookingRule.csvRowNumber();
261+
this.bookingRuleId = bookingRule.bookingRuleId();
262+
this.priorNoticeStartDay = priorNoticeStartDay;
263+
this.priorNoticeDurationMax = priorNoticeDurationMax;
264+
}
265+
}
266+
157267
/**
158268
* Prior notice last day should not be greater than the prior notice start day in
159269
* booking_rules.txt.

main/src/test/java/org/mobilitydata/gtfsvalidator/validator/BookingRulesEntityValidatorTest.java

+54-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ public void sameDayBookingWithForbiddenFieldsShouldGenerateNotice() {
9999
.setBookingRuleId("rule-5")
100100
.setBookingType(GtfsBookingType.SAMEDAY)
101101
.setPriorNoticeLastDay(2) // Forbidden field
102-
.setPriorNoticeStartTime(GtfsTime.fromSecondsSinceMidnight(5000)) // Forbidden field
102+
.setPriorNoticeStartTime(GtfsTime.fromSecondsSinceMidnight(5000))
103103
.build();
104104

105105
assertThat(generateNotices(bookingRule))
@@ -120,6 +120,59 @@ public void sameDayBookingWithoutForbiddenFieldsShouldNotGenerateNotice() {
120120
assertThat(generateNotices(bookingRule)).isEmpty();
121121
}
122122

123+
@Test
124+
public void priorDayBookingWithForbiddenFieldsShouldGenerateNotice() {
125+
GtfsBookingRules bookingRule =
126+
new GtfsBookingRules.Builder()
127+
.setCsvRowNumber(1)
128+
.setBookingRuleId("rule-7")
129+
.setBookingType(GtfsBookingType.PRIORDAY)
130+
.setPriorNoticeDurationMin(30) // Forbidden field
131+
.setPriorNoticeDurationMax(60) // Forbidden field
132+
.build();
133+
134+
assertThat(generateNotices(bookingRule))
135+
.containsExactly(
136+
new BookingRulesEntityValidator.ForbiddenPriorDayBookingFieldValueNotice(
137+
bookingRule,
138+
List.of(
139+
GtfsBookingRules.PRIOR_NOTICE_DURATION_MIN_FIELD_NAME,
140+
GtfsBookingRules.PRIOR_NOTICE_DURATION_MAX_FIELD_NAME)));
141+
}
142+
143+
@Test
144+
public void invalidPriorNoticeDurationMinShouldGenerateNotice() {
145+
GtfsBookingRules bookingRule =
146+
new GtfsBookingRules.Builder()
147+
.setCsvRowNumber(1)
148+
.setBookingRuleId("rule-8")
149+
.setBookingType(GtfsBookingType.SAMEDAY)
150+
.setPriorNoticeDurationMin(60) // Invalid: greater than max
151+
.setPriorNoticeDurationMax(30)
152+
.build();
153+
154+
assertThat(generateNotices(bookingRule))
155+
.containsExactly(
156+
new BookingRulesEntityValidator.InvalidPriorNoticeDurationMinNotice(
157+
bookingRule, 60, 30));
158+
}
159+
160+
@Test
161+
public void forbiddenPriorNoticeStartDayShouldGenerateNotice() {
162+
GtfsBookingRules bookingRule =
163+
new GtfsBookingRules.Builder()
164+
.setCsvRowNumber(1)
165+
.setBookingRuleId("rule-9")
166+
.setBookingType(GtfsBookingType.SAMEDAY)
167+
.setPriorNoticeDurationMax(30) // Duration max is set
168+
.setPriorNoticeStartDay(5) // Forbidden when duration max is set
169+
.build();
170+
171+
assertThat(generateNotices(bookingRule))
172+
.containsExactly(
173+
new BookingRulesEntityValidator.ForbiddenPriorNoticeStartDayNotice(bookingRule, 5, 30));
174+
}
175+
123176
@Test
124177
public void priorNoticeLastDayAfterStartDayShouldGenerateNotice() {
125178
GtfsBookingRules bookingRule =

main/src/test/java/org/mobilitydata/gtfsvalidator/validator/NoticeFieldsTest.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -202,8 +202,10 @@ public void testNoticeClassFieldNames() {
202202
"locationId",
203203
"bookingRuleId",
204204
"fieldNames",
205-
"priorNoticeLastDay",
206-
"priorNoticeStartDay");
205+
"priorNoticeDurationMin",
206+
"priorNoticeDurationMax",
207+
"priorNoticeStartDay",
208+
"priorNoticeLastDay");
207209
}
208210

209211
private static List<String> discoverValidationNoticeFieldNames() {

0 commit comments

Comments
 (0)