Skip to content

Commit 089ad3e

Browse files
authored
Merge 4f1776c into 2ff1980
2 parents 2ff1980 + 4f1776c commit 089ad3e

File tree

8 files changed

+327
-1
lines changed

8 files changed

+327
-1
lines changed

RULES.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ Each Notice is associated with a severity: `INFO`, `WARNING`, `ERROR`.
9898
| Notice code | Description |
9999
|-----------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|
100100
| [`attribution_without_role`](#attribution_without_role) | Attribution with no role. |
101+
| [`duplicate_fare_media`](#duplicate_fare_medium) | Two distinct fare media have the same fare medium name and type. |
101102
| [`duplicate_route_name`](#duplicate_route_name) | Two distinct routes have either the same `route_short_name`, the same `route_long_name`, or the same combination of `route_short_name` and `route_long_name`. |
102103
| [`empty_row`](#empty_row) | A row in the input file has only spaces. |
103104
| [`equal_shape_distance_same_coordinates`](#equal_shape_distance_same_coordinates) | Two consecutive points have equal `shape_dist_traveled` and the same lat/lon coordinates in `shapes.txt`. |
@@ -1715,6 +1716,30 @@ At least one of the fields `is_producer`, `is_operator`, or `is_authority` shoul
17151716

17161717
</details>
17171718

1719+
<a name="DuplicateFareMediaNotice"/>
1720+
1721+
### duplicate_fare_media
1722+
1723+
Two distinct fare media have the same fare medium name and type.
1724+
1725+
Fare media should have a unique combination of fare medium name and type.
1726+
1727+
#### References
1728+
* [fare_media.txt specification](http://gtfs.org/reference/static/#faremediatxt)
1729+
1730+
<details>
1731+
1732+
#### Notice fields description
1733+
| Field name | Description | Type |
1734+
|--------------|--------------------------------------------|-------- |
1735+
| `fareMedia1` | Reference to the first fare medium. | Long |
1736+
| `fareMedia2` | Reference to the second fare medium. | String |
1737+
1738+
#### Affected files
1739+
* [fare_media.txt](http://gtfs.org/reference/static/#faremediatxt)
1740+
1741+
</details>
1742+
17181743
<a name="DuplicateRouteNameNotice"/>
17191744

17201745
### duplicate_route_name
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package org.mobilitydata.gtfsvalidator.table;
2+
3+
import org.mobilitydata.gtfsvalidator.annotation.FieldType;
4+
import org.mobilitydata.gtfsvalidator.annotation.FieldTypeEnum;
5+
import org.mobilitydata.gtfsvalidator.annotation.GtfsTable;
6+
import org.mobilitydata.gtfsvalidator.annotation.PrimaryKey;
7+
import org.mobilitydata.gtfsvalidator.annotation.Required;
8+
9+
@GtfsTable("fare_media.txt")
10+
public interface GtfsFareMediaSchema extends GtfsEntity {
11+
@FieldType(FieldTypeEnum.ID)
12+
@PrimaryKey
13+
@Required
14+
String fareMediaId();
15+
16+
String fareMediaName();
17+
18+
@Required
19+
GtfsFareMediaType fareMediaType();
20+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.mobilitydata.gtfsvalidator.table;
2+
3+
import org.mobilitydata.gtfsvalidator.annotation.GtfsEnumValue;
4+
5+
/** */
6+
@GtfsEnumValue(name = "NONE", value = 0)
7+
@GtfsEnumValue(name = "PAPER_TICKET", value = 1)
8+
@GtfsEnumValue(name = "TRANSIT_CARD", value = 2)
9+
@GtfsEnumValue(name = "CONTACTLESS_EMV", value = 3)
10+
@GtfsEnumValue(name = "MOBILE_APP", value = 4)
11+
public interface GtfsFareMediaTypeEnum {}

main/src/main/java/org/mobilitydata/gtfsvalidator/table/GtfsFareProductSchema.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,16 @@
1616

1717
package org.mobilitydata.gtfsvalidator.table;
1818

19+
import static org.mobilitydata.gtfsvalidator.annotation.TranslationRecordIdType.UNSUPPORTED;
20+
1921
import java.math.BigDecimal;
2022
import java.util.Currency;
2123
import org.mobilitydata.gtfsvalidator.annotation.CurrencyAmount;
2224
import org.mobilitydata.gtfsvalidator.annotation.FieldType;
2325
import org.mobilitydata.gtfsvalidator.annotation.FieldTypeEnum;
26+
import org.mobilitydata.gtfsvalidator.annotation.ForeignKey;
2427
import org.mobilitydata.gtfsvalidator.annotation.GtfsTable;
28+
import org.mobilitydata.gtfsvalidator.annotation.Index;
2529
import org.mobilitydata.gtfsvalidator.annotation.NonNegative;
2630
import org.mobilitydata.gtfsvalidator.annotation.PrimaryKey;
2731
import org.mobilitydata.gtfsvalidator.annotation.Required;
@@ -30,7 +34,8 @@
3034
public interface GtfsFareProductSchema extends GtfsEntity {
3135
@FieldType(FieldTypeEnum.ID)
3236
@Required
33-
@PrimaryKey
37+
@PrimaryKey(translationRecordIdType = UNSUPPORTED)
38+
@Index
3439
String fareProductId();
3540

3641
String fareProductName();
@@ -42,4 +47,9 @@ public interface GtfsFareProductSchema extends GtfsEntity {
4247

4348
@Required
4449
Currency currency();
50+
51+
@FieldType(FieldTypeEnum.ID)
52+
@ForeignKey(table = "fare_media.txt", field = "fare_media_id")
53+
@PrimaryKey(translationRecordIdType = UNSUPPORTED)
54+
String fareMediaId();
4555
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package org.mobilitydata.gtfsvalidator.validator;
2+
3+
import com.google.auto.value.AutoValue;
4+
import java.util.HashMap;
5+
import java.util.Map;
6+
import javax.inject.Inject;
7+
import org.mobilitydata.gtfsvalidator.annotation.GtfsValidator;
8+
import org.mobilitydata.gtfsvalidator.notice.NoticeContainer;
9+
import org.mobilitydata.gtfsvalidator.notice.SeverityLevel;
10+
import org.mobilitydata.gtfsvalidator.notice.ValidationNotice;
11+
import org.mobilitydata.gtfsvalidator.table.GtfsFareMedia;
12+
import org.mobilitydata.gtfsvalidator.table.GtfsFareMediaTableContainer;
13+
import org.mobilitydata.gtfsvalidator.table.GtfsFareMediaType;
14+
15+
@GtfsValidator
16+
public class DuplicateFareMediaValidator extends FileValidator {
17+
18+
private final GtfsFareMediaTableContainer fareMediaTable;
19+
20+
@Inject
21+
DuplicateFareMediaValidator(GtfsFareMediaTableContainer fareMediaTable) {
22+
this.fareMediaTable = fareMediaTable;
23+
}
24+
25+
@Override
26+
public void validate(NoticeContainer noticeContainer) {
27+
Map<Key, GtfsFareMedia> mediaByKey = new HashMap<>();
28+
for (GtfsFareMedia medium : fareMediaTable.getEntities()) {
29+
GtfsFareMedia existing = mediaByKey.putIfAbsent(Key.create(medium), medium);
30+
if (existing != null) {
31+
noticeContainer.addValidationNotice(new DuplicateFareMediaNotice(existing, medium));
32+
}
33+
}
34+
}
35+
36+
/**
37+
* Describes two fare medias that have the same name and type.
38+
*
39+
* <p>Severity: {@code SeverityLevel.WARNING}
40+
*/
41+
static class DuplicateFareMediaNotice extends ValidationNotice {
42+
43+
private final FareMediaReference fareMedia1;
44+
private final FareMediaReference fareMedia2;
45+
46+
DuplicateFareMediaNotice(GtfsFareMedia lhs, GtfsFareMedia rhs) {
47+
super(SeverityLevel.WARNING);
48+
this.fareMedia1 = FareMediaReference.create(lhs);
49+
this.fareMedia2 = FareMediaReference.create(rhs);
50+
}
51+
}
52+
53+
@AutoValue
54+
abstract static class FareMediaReference {
55+
abstract int csvRowNumber();
56+
57+
abstract String fareMediaId();
58+
59+
static FareMediaReference create(GtfsFareMedia media) {
60+
return new AutoValue_DuplicateFareMediaValidator_FareMediaReference(
61+
media.csvRowNumber(), media.fareMediaId());
62+
}
63+
}
64+
65+
@AutoValue
66+
abstract static class Key {
67+
public abstract String name();
68+
69+
public abstract GtfsFareMediaType type();
70+
71+
static Key create(GtfsFareMedia media) {
72+
return new AutoValue_DuplicateFareMediaValidator_Key(
73+
media.fareMediaName(), media.fareMediaType());
74+
}
75+
}
76+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package org.mobilitydata.gtfsvalidator.validator;
2+
3+
import org.mobilitydata.gtfsvalidator.annotation.GtfsValidator;
4+
import org.mobilitydata.gtfsvalidator.notice.MissingRecommendedFieldNotice;
5+
import org.mobilitydata.gtfsvalidator.notice.NoticeContainer;
6+
import org.mobilitydata.gtfsvalidator.table.GtfsFareMedia;
7+
import org.mobilitydata.gtfsvalidator.table.GtfsFareMediaType;
8+
9+
@GtfsValidator
10+
public class FareMediaNameValidator extends SingleEntityValidator<GtfsFareMedia> {
11+
12+
@Override
13+
public void validate(GtfsFareMedia entity, NoticeContainer noticeContainer) {
14+
if (shouldHaveName(entity.fareMediaType()) && !entity.hasFareMediaName()) {
15+
noticeContainer.addValidationNotice(
16+
new MissingRecommendedFieldNotice(
17+
GtfsFareMedia.FILENAME,
18+
entity.csvRowNumber(),
19+
GtfsFareMedia.FARE_MEDIA_NAME_FIELD_NAME));
20+
}
21+
}
22+
23+
private static boolean shouldHaveName(GtfsFareMediaType type) {
24+
switch (type) {
25+
case TRANSIT_CARD:
26+
case MOBILE_APP:
27+
return true;
28+
case NONE:
29+
case PAPER_TICKET:
30+
case CONTACTLESS_EMV:
31+
default:
32+
return false;
33+
}
34+
}
35+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package org.mobilitydata.gtfsvalidator.validator;
2+
3+
import static com.google.common.truth.Truth.assertThat;
4+
5+
import com.google.common.collect.ImmutableList;
6+
import java.util.List;
7+
import org.junit.Test;
8+
import org.junit.runner.RunWith;
9+
import org.junit.runners.JUnit4;
10+
import org.mobilitydata.gtfsvalidator.notice.NoticeContainer;
11+
import org.mobilitydata.gtfsvalidator.notice.ValidationNotice;
12+
import org.mobilitydata.gtfsvalidator.table.GtfsFareMedia;
13+
import org.mobilitydata.gtfsvalidator.table.GtfsFareMediaTableContainer;
14+
import org.mobilitydata.gtfsvalidator.table.GtfsFareMediaType;
15+
import org.mobilitydata.gtfsvalidator.validator.DuplicateFareMediaValidator.DuplicateFareMediaNotice;
16+
17+
@RunWith(JUnit4.class)
18+
public class DuplicateFareMediaValidatorTest {
19+
20+
private static List<ValidationNotice> generateNotices(List<GtfsFareMedia> media) {
21+
NoticeContainer noticeContainer = new NoticeContainer();
22+
new DuplicateFareMediaValidator(GtfsFareMediaTableContainer.forEntities(media, noticeContainer))
23+
.validate(noticeContainer);
24+
return noticeContainer.getValidationNotices();
25+
}
26+
27+
@Test
28+
public void testUniqueEntries() {
29+
ImmutableList<GtfsFareMedia> media =
30+
ImmutableList.of(
31+
new GtfsFareMedia.Builder()
32+
.setCsvRowNumber(1)
33+
.setFareMediaId("a")
34+
.setFareMediaName("Transit Card")
35+
.setFareMediaType(GtfsFareMediaType.TRANSIT_CARD)
36+
.build(),
37+
new GtfsFareMedia.Builder()
38+
.setCsvRowNumber(2)
39+
.setFareMediaId("b")
40+
.setFareMediaName("Transit App")
41+
.setFareMediaType(GtfsFareMediaType.MOBILE_APP)
42+
.build());
43+
44+
assertThat(generateNotices(media)).isEmpty();
45+
}
46+
47+
@Test
48+
public void testDuplicateEntriesTransitCard() {
49+
ImmutableList<GtfsFareMedia> media =
50+
ImmutableList.of(
51+
new GtfsFareMedia.Builder()
52+
.setCsvRowNumber(1)
53+
.setFareMediaId("a")
54+
.setFareMediaName("Transit Card")
55+
.setFareMediaType(GtfsFareMediaType.TRANSIT_CARD)
56+
.build(),
57+
new GtfsFareMedia.Builder()
58+
.setCsvRowNumber(2)
59+
.setFareMediaId("b")
60+
.setFareMediaName("Transit Card")
61+
.setFareMediaType(GtfsFareMediaType.TRANSIT_CARD)
62+
.build());
63+
64+
assertThat(generateNotices(media))
65+
.containsExactly(new DuplicateFareMediaNotice(media.get(0), media.get(1)));
66+
}
67+
68+
@Test
69+
public void testTransitCardsWithDifferentNames() {
70+
ImmutableList<GtfsFareMedia> media =
71+
ImmutableList.of(
72+
new GtfsFareMedia.Builder()
73+
.setCsvRowNumber(1)
74+
.setFareMediaId("a")
75+
.setFareMediaName("Transit Card A")
76+
.setFareMediaType(GtfsFareMediaType.TRANSIT_CARD)
77+
.build(),
78+
new GtfsFareMedia.Builder()
79+
.setCsvRowNumber(2)
80+
.setFareMediaId("b")
81+
.setFareMediaName("Transit Card B")
82+
.setFareMediaType(GtfsFareMediaType.TRANSIT_CARD)
83+
.build());
84+
85+
assertThat(generateNotices(media)).isEmpty();
86+
}
87+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package org.mobilitydata.gtfsvalidator.validator;
2+
3+
import static com.google.common.truth.Truth.assertThat;
4+
5+
import java.util.List;
6+
import org.junit.Test;
7+
import org.junit.runner.RunWith;
8+
import org.junit.runners.JUnit4;
9+
import org.mobilitydata.gtfsvalidator.notice.MissingRecommendedFieldNotice;
10+
import org.mobilitydata.gtfsvalidator.notice.NoticeContainer;
11+
import org.mobilitydata.gtfsvalidator.notice.ValidationNotice;
12+
import org.mobilitydata.gtfsvalidator.table.GtfsFareMedia;
13+
import org.mobilitydata.gtfsvalidator.table.GtfsFareMediaType;
14+
15+
@RunWith(JUnit4.class)
16+
public class FareMediaNameValidatorTest {
17+
18+
@Test
19+
public void testTransitCard() {
20+
assertThat(
21+
validationNoticesFor(
22+
new GtfsFareMedia.Builder()
23+
.setCsvRowNumber(2)
24+
.setFareMediaName("Go! Pass")
25+
.setFareMediaType(GtfsFareMediaType.TRANSIT_CARD)
26+
.build()))
27+
.isEmpty();
28+
assertThat(
29+
validationNoticesFor(
30+
new GtfsFareMedia.Builder()
31+
.setCsvRowNumber(2)
32+
.setFareMediaType(GtfsFareMediaType.TRANSIT_CARD)
33+
.build()))
34+
.containsExactly(new MissingRecommendedFieldNotice("fare_media.txt", 2, "fare_media_name"));
35+
}
36+
37+
@Test
38+
public void testPaperTicket() {
39+
assertThat(
40+
validationNoticesFor(
41+
new GtfsFareMedia.Builder()
42+
.setCsvRowNumber(2)
43+
.setFareMediaName("Some Ticket")
44+
.setFareMediaType(GtfsFareMediaType.PAPER_TICKET)
45+
.build()))
46+
.isEmpty();
47+
assertThat(
48+
validationNoticesFor(
49+
new GtfsFareMedia.Builder()
50+
.setCsvRowNumber(2)
51+
.setFareMediaType(GtfsFareMediaType.PAPER_TICKET)
52+
.build()))
53+
.isEmpty();
54+
}
55+
56+
private List<ValidationNotice> validationNoticesFor(GtfsFareMedia entity) {
57+
FareMediaNameValidator validator = new FareMediaNameValidator();
58+
NoticeContainer noticeContainer = new NoticeContainer();
59+
validator.validate(entity, noticeContainer);
60+
return noticeContainer.getValidationNotices();
61+
}
62+
}

0 commit comments

Comments
 (0)