diff --git a/core/src/main/java/org/mobilitydata/gtfsvalidator/notice/UnusedStationNotice.java b/core/src/main/java/org/mobilitydata/gtfsvalidator/notice/UnusedStationNotice.java new file mode 100644 index 0000000000..1afb03be6d --- /dev/null +++ b/core/src/main/java/org/mobilitydata/gtfsvalidator/notice/UnusedStationNotice.java @@ -0,0 +1,28 @@ +package org.mobilitydata.gtfsvalidator.notice; + +import static org.mobilitydata.gtfsvalidator.notice.SeverityLevel.INFO; + +import org.mobilitydata.gtfsvalidator.annotation.GtfsValidationNotice; + +/** + * Unused station. + * + *

A stop has `location_type` STATION (1) but does not appear in any stop's `parent_station`. + */ +@GtfsValidationNotice(severity = INFO) +public class UnusedStationNotice extends ValidationNotice { + /** The row number of the faulty record. */ + private final int csvRowNumber; + + /** The id of the faulty stop. */ + private final String stopId; + + /** The name of the faulty stop. */ + private final String stopName; + + public UnusedStationNotice(int csvRowNumber, String stopId, String stopName) { + this.csvRowNumber = csvRowNumber; + this.stopId = stopId; + this.stopName = stopName; + } +} diff --git a/core/src/main/java/org/mobilitydata/gtfsvalidator/notice/deprecated/MissingRecommendedColumnNotice.java b/core/src/main/java/org/mobilitydata/gtfsvalidator/notice/deprecated/MissingRecommendedColumnNotice.java new file mode 100644 index 0000000000..e515a7805e --- /dev/null +++ b/core/src/main/java/org/mobilitydata/gtfsvalidator/notice/deprecated/MissingRecommendedColumnNotice.java @@ -0,0 +1,43 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mobilitydata.gtfsvalidator.notice.deprecated; + +import static org.mobilitydata.gtfsvalidator.annotation.GtfsValidationNotice.SectionRef.TERM_DEFINITIONS; +import static org.mobilitydata.gtfsvalidator.notice.SeverityLevel.WARNING; + +import org.mobilitydata.gtfsvalidator.annotation.GtfsValidationNotice; +import org.mobilitydata.gtfsvalidator.annotation.GtfsValidationNotice.SectionRefs; +import org.mobilitydata.gtfsvalidator.notice.ValidationNotice; + +/** A recommended column is missing in the input file. */ +@GtfsValidationNotice( + severity = WARNING, + sections = @SectionRefs(TERM_DEFINITIONS), + deprecated = true, + deprecationVersion = "7.0.0", + deprecationReason = "Unused validation notice") +public class MissingRecommendedColumnNotice extends ValidationNotice { + /** The name of the faulty file. */ + private final String filename; + + /** The name of the missing column. */ + private final String fieldName; + + public MissingRecommendedColumnNotice(String filename, String fieldName) { + this.filename = filename; + this.fieldName = fieldName; + } +} diff --git a/core/src/main/java/org/mobilitydata/gtfsvalidator/notice/deprecated/UnusedParentStationNotice.java b/core/src/main/java/org/mobilitydata/gtfsvalidator/notice/deprecated/UnusedParentStationNotice.java new file mode 100644 index 0000000000..b4ede1fe8e --- /dev/null +++ b/core/src/main/java/org/mobilitydata/gtfsvalidator/notice/deprecated/UnusedParentStationNotice.java @@ -0,0 +1,35 @@ +package org.mobilitydata.gtfsvalidator.notice.deprecated; + +import static org.mobilitydata.gtfsvalidator.notice.SeverityLevel.INFO; + +import org.mobilitydata.gtfsvalidator.annotation.GtfsValidationNotice; +import org.mobilitydata.gtfsvalidator.notice.UnusedStationNotice; +import org.mobilitydata.gtfsvalidator.notice.ValidationNotice; + +/** + * Unused parent station. + * + *

A stop has `location_type` STATION (1) but does not appear in any stop's `parent_station`. + */ +@GtfsValidationNotice( + severity = INFO, + deprecated = true, + deprecationVersion = "7.0.0", + deprecationReason = "Renamed to `unused_station`", + replacementNotice = UnusedStationNotice.class) +class UnusedParentStationNotice extends ValidationNotice { + /** The row number of the faulty record. */ + private final int csvRowNumber; + + /** The id of the faulty stop. */ + private final String stopId; + + /** The name of the faulty stop. */ + private final String stopName; + + UnusedParentStationNotice(int csvRowNumber, String stopId, String stopName) { + this.csvRowNumber = csvRowNumber; + this.stopId = stopId; + this.stopName = stopName; + } +} diff --git a/core/src/main/java/org/mobilitydata/gtfsvalidator/notice/schema/NoticeSchema.java b/core/src/main/java/org/mobilitydata/gtfsvalidator/notice/schema/NoticeSchema.java index fba9e292b5..305fb21998 100644 --- a/core/src/main/java/org/mobilitydata/gtfsvalidator/notice/schema/NoticeSchema.java +++ b/core/src/main/java/org/mobilitydata/gtfsvalidator/notice/schema/NoticeSchema.java @@ -38,6 +38,30 @@ public class NoticeSchema { */ private Map properties = new TreeMap<>(); + /** + * Whether the notice is deprecated. Deprecated notices are not used in the validator, but are + * still supported in the documentation. + */ + private boolean deprecated = false; + + /** + * Reason for the deprecation of the notice. This field is only used if {@link #deprecated} is + * true. + */ + @Nullable private String deprecationReason; + + /** + * Version on which the notice was deprecated. This field is only used if {@link #deprecated} is + * true. + */ + @Nullable private String deprecationVersion; + + /** + * Replacement notice code for the deprecated notice. This field is only used if {@link + * #deprecated} is true and the notice has a replacement. + */ + @Nullable private String replacementNoticeCode; + public NoticeSchema(String code, SeverityLevel severityLevel) { this.code = code; this.severityLevel = severityLevel; @@ -83,4 +107,39 @@ public void addField(FieldSchema field) { public Map getFields() { return properties; } + + public boolean isDeprecated() { + return deprecated; + } + + public void setDeprecated(boolean deprecated) { + this.deprecated = deprecated; + } + + @Nullable + public String getDeprecationReason() { + return deprecationReason; + } + + public void setDeprecationReason(@Nullable String deprecationReason) { + this.deprecationReason = deprecationReason; + } + + @Nullable + public String getDeprecationVersion() { + return deprecationVersion; + } + + public void setDeprecationVersion(@Nullable String deprecationVersion) { + this.deprecationVersion = deprecationVersion; + } + + @Nullable + public String getReplacementNoticeCode() { + return replacementNoticeCode; + } + + public void setReplacementNoticeCode(@Nullable String replacementNoticeCode) { + this.replacementNoticeCode = replacementNoticeCode; + } } diff --git a/core/src/main/java/org/mobilitydata/gtfsvalidator/notice/schema/NoticeSchemaGenerator.java b/core/src/main/java/org/mobilitydata/gtfsvalidator/notice/schema/NoticeSchemaGenerator.java index 621fccaa58..a31e8cd4a5 100644 --- a/core/src/main/java/org/mobilitydata/gtfsvalidator/notice/schema/NoticeSchemaGenerator.java +++ b/core/src/main/java/org/mobilitydata/gtfsvalidator/notice/schema/NoticeSchemaGenerator.java @@ -40,6 +40,7 @@ import org.mobilitydata.gtfsvalidator.notice.Notice; import org.mobilitydata.gtfsvalidator.notice.NoticeDocComments; import org.mobilitydata.gtfsvalidator.notice.SeverityLevel; +import org.mobilitydata.gtfsvalidator.notice.ValidationNotice; import org.mobilitydata.gtfsvalidator.notice.schema.ReferencesSchema.UrlReference; import org.mobilitydata.gtfsvalidator.table.GtfsEntity; import org.mobilitydata.gtfsvalidator.table.GtfsEnum; @@ -82,6 +83,21 @@ static NoticeSchema generateSchemaForNotice(Class noticeClass) NoticeSchema schema = new NoticeSchema(Notice.getCode(noticeClass), severity); + if (noticeAnnotation != null) { + if (noticeAnnotation.deprecated()) { + schema.setDeprecated(true); + schema.setDeprecationReason(noticeAnnotation.deprecationReason()); + schema.setDeprecationVersion(noticeAnnotation.deprecationVersion()); + // Validate that replacement notice is not Void.class and that it extends ValidationNotice + if (noticeAnnotation.replacementNotice() != Void.class + && ValidationNotice.class.isAssignableFrom(noticeAnnotation.replacementNotice())) { + String replacementNoticeCode = + Notice.getCode(noticeAnnotation.replacementNotice().asSubclass(Notice.class)); + schema.setReplacementNoticeCode(replacementNoticeCode); + } + } + } + NoticeDocComments comments = loadComments(noticeClass); if (comments.getShortSummary() != null) { schema.setShortSummary(comments.getShortSummary()); diff --git a/core/src/test/resources/org/mobilitydata/gtfsvalidator/notice/schema/NoticeSchemaGeneratorTest-generateJsonSchemaForNotice_duplicateKeyNotice.json b/core/src/test/resources/org/mobilitydata/gtfsvalidator/notice/schema/NoticeSchemaGeneratorTest-generateJsonSchemaForNotice_duplicateKeyNotice.json index 420545c594..2237db3962 100644 --- a/core/src/test/resources/org/mobilitydata/gtfsvalidator/notice/schema/NoticeSchemaGeneratorTest-generateJsonSchemaForNotice_duplicateKeyNotice.json +++ b/core/src/test/resources/org/mobilitydata/gtfsvalidator/notice/schema/NoticeSchemaGeneratorTest-generateJsonSchemaForNotice_duplicateKeyNotice.json @@ -53,5 +53,6 @@ "description": "The row of the first occurrence.", "type": "integer" } - } + }, + "deprecated": false } \ No newline at end of file diff --git a/core/src/test/resources/org/mobilitydata/gtfsvalidator/notice/schema/NoticeSchemaGeneratorTest-generateJsonSchemaForNotice_s2LatLngNotice.json b/core/src/test/resources/org/mobilitydata/gtfsvalidator/notice/schema/NoticeSchemaGeneratorTest-generateJsonSchemaForNotice_s2LatLngNotice.json index 18b7a11f31..d4e24a33fa 100644 --- a/core/src/test/resources/org/mobilitydata/gtfsvalidator/notice/schema/NoticeSchemaGeneratorTest-generateJsonSchemaForNotice_s2LatLngNotice.json +++ b/core/src/test/resources/org/mobilitydata/gtfsvalidator/notice/schema/NoticeSchemaGeneratorTest-generateJsonSchemaForNotice_s2LatLngNotice.json @@ -12,5 +12,6 @@ "minItems": 2, "maxItems": 2 } - } + }, + "deprecated": false } \ No newline at end of file diff --git a/main/src/main/java/org/mobilitydata/gtfsvalidator/validator/ParentStationValidator.java b/main/src/main/java/org/mobilitydata/gtfsvalidator/validator/ParentStationValidator.java index 30e864c95b..804d16f547 100644 --- a/main/src/main/java/org/mobilitydata/gtfsvalidator/validator/ParentStationValidator.java +++ b/main/src/main/java/org/mobilitydata/gtfsvalidator/validator/ParentStationValidator.java @@ -16,7 +16,6 @@ package org.mobilitydata.gtfsvalidator.validator; import static org.mobilitydata.gtfsvalidator.notice.SeverityLevel.ERROR; -import static org.mobilitydata.gtfsvalidator.notice.SeverityLevel.INFO; import java.util.HashSet; import java.util.Optional; @@ -26,6 +25,7 @@ import org.mobilitydata.gtfsvalidator.annotation.GtfsValidationNotice.FileRefs; import org.mobilitydata.gtfsvalidator.annotation.GtfsValidator; import org.mobilitydata.gtfsvalidator.notice.NoticeContainer; +import org.mobilitydata.gtfsvalidator.notice.UnusedStationNotice; import org.mobilitydata.gtfsvalidator.notice.ValidationNotice; import org.mobilitydata.gtfsvalidator.table.GtfsLocationType; import org.mobilitydata.gtfsvalidator.table.GtfsStop; @@ -188,27 +188,4 @@ static class WrongParentLocationTypeNotice extends ValidationNotice { this.expectedLocationType = expectedLocationType; } } - - /** - * Unused station. - * - *

A stop has `location_type` STATION (1) but does not appear in any stop's `parent_station`. - */ - @GtfsValidationNotice(severity = INFO, files = @FileRefs({GtfsStopSchema.class})) - static class UnusedStationNotice extends ValidationNotice { - /** The row number of the faulty record. */ - private final int csvRowNumber; - - /** The id of the faulty stop. */ - private final String stopId; - - /** The name of the faulty stop. */ - private final String stopName; - - UnusedStationNotice(int csvRowNumber, String stopId, String stopName) { - this.csvRowNumber = csvRowNumber; - this.stopId = stopId; - this.stopName = stopName; - } - } } diff --git a/main/src/test/java/org/mobilitydata/gtfsvalidator/validator/ParentStationValidatorTest.java b/main/src/test/java/org/mobilitydata/gtfsvalidator/validator/ParentStationValidatorTest.java index f2144f1639..2468b798bf 100644 --- a/main/src/test/java/org/mobilitydata/gtfsvalidator/validator/ParentStationValidatorTest.java +++ b/main/src/test/java/org/mobilitydata/gtfsvalidator/validator/ParentStationValidatorTest.java @@ -24,11 +24,11 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mobilitydata.gtfsvalidator.notice.NoticeContainer; +import org.mobilitydata.gtfsvalidator.notice.UnusedStationNotice; import org.mobilitydata.gtfsvalidator.notice.ValidationNotice; import org.mobilitydata.gtfsvalidator.table.GtfsLocationType; import org.mobilitydata.gtfsvalidator.table.GtfsStop; import org.mobilitydata.gtfsvalidator.table.GtfsStopTableContainer; -import org.mobilitydata.gtfsvalidator.validator.ParentStationValidator.UnusedStationNotice; import org.mobilitydata.gtfsvalidator.validator.ParentStationValidator.WrongParentLocationTypeNotice; @RunWith(JUnit4.class) diff --git a/model/src/main/java/org/mobilitydata/gtfsvalidator/annotation/GtfsValidationNotice.java b/model/src/main/java/org/mobilitydata/gtfsvalidator/annotation/GtfsValidationNotice.java index 2cb2c92014..d100735548 100644 --- a/model/src/main/java/org/mobilitydata/gtfsvalidator/annotation/GtfsValidationNotice.java +++ b/model/src/main/java/org/mobilitydata/gtfsvalidator/annotation/GtfsValidationNotice.java @@ -21,6 +21,30 @@ */ SeverityLevel severity(); + /** + * Whether the notice is deprecated. Deprecated notices are still supported in the documentation + * but are not actively used in validation. + */ + boolean deprecated() default false; + + /** + * Reason for the deprecation of the notice. This field is only used if {@link #deprecated()} is + * true. + */ + String deprecationReason() default ""; + + /** + * Version on which the notice was deprecated. This field is only used if {@link #deprecated()} is + * true. + */ + String deprecationVersion() default ""; + + /** + * Replacement notice class for the deprecated notice. This field is only used if {@link + * #deprecated()} is true and the notice has a replacement. + */ + Class replacementNotice() default Void.class; + /** * GTFS specification section references. For specific file references, use {@link #files()} * instead. diff --git a/scripts/notice-migration-generator/notice_migration_generator.py b/scripts/notice-migration-generator/notice_migration_generator.py index b79483df6a..a8248fb3cb 100644 --- a/scripts/notice-migration-generator/notice_migration_generator.py +++ b/scripts/notice-migration-generator/notice_migration_generator.py @@ -14,7 +14,10 @@ def read_rule_file(filepath): """ with open(filepath + "/rules.json", 'r') as f: rules = json.load(f) - return {key: rules[key]["severityLevel"] for key in rules} + return { + key: rules[key]["severityLevel"] for key in rules + if not 'deprecated' in rules[key] or not rules[key]['deprecated'] + } def get_severity_symbol(severity): diff --git a/web/client/src/css/components.css b/web/client/src/css/components.css index 0a4f48bd47..2bc7faa7ad 100644 --- a/web/client/src/css/components.css +++ b/web/client/src/css/components.css @@ -19,3 +19,14 @@ active:ring-4 ring-mobi-purple/50; } + +.deprecated-tag { + font-weight: bold; + @apply + bg-mobi-purple/10 + border border-mobi-dark-blue/75 + text-mobi-dark-blue/80 + rounded + px-2 + py-1; +} diff --git a/web/client/src/routes/rules.html/+page.svelte b/web/client/src/routes/rules.html/+page.svelte index 831bbff019..28495c9c4b 100644 --- a/web/client/src/routes/rules.html/+page.svelte +++ b/web/client/src/routes/rules.html/+page.svelte @@ -5,8 +5,8 @@ import SectionRefLink from './SectionRefLink.svelte'; - const rules = $page.data.rules; - const summaryMetadata = $page.data.summaryMetadata; + const rules = $page.data.rules || []; + const summaryMetadata = $page.data.summaryMetadata || []; // Group rules by severity level $: categories = _.chain(rules) @@ -15,6 +15,12 @@ .sortBy(([severityLevel]) => ['ERROR', 'WARNING', 'INFO'].indexOf(severityLevel)) .fromPairs() .value(); + $: deprecatedRules = _.chain(rules) + .filter(rule => rule.deprecated) + .groupBy('severityLevel') + .sortBy(([severityLevel]) => ['ERROR', 'WARNING', 'INFO'].indexOf(severityLevel)) + .flatMap() + .value(); /** @param {string} filename */ function getSpecRef(filename) { @@ -125,6 +131,7 @@ {#each rules as rule} + {#if !rule.deprecated} @@ -136,6 +143,7 @@ {@html marked.parse(rule.description ?? '')} + {/if} {/each} @@ -143,6 +151,59 @@ {/each} + +

+

+ Top + +
+ Table of deprecated notices + + + +
+

+ +
+ + + + + + + + + + + {#each deprecatedRules as rule} + + + + + + + {/each} + +
Notice codeSeverityDeprecated sinceDeprecation reason
+ + {rule.code} + + {rule.severityLevel} + + {rule.deprecationVersion} + + + {@html marked.parse(rule?.deprecationReason ?? '')} +
+
+
+ + +

Notice details

{#each Object.entries(rules) as [code, rule]} @@ -154,6 +215,8 @@
{code} + Deprecated
+
+ This rule is deprecated from the validator since version + + {rule.deprecationVersion} + . + {#if (rule.replacementNoticeCode)} + It has been replaced by + {rule.replacementNoticeCode}. + {/if} +
+ Deprecation reason: + {@html marked.parse(rule?.deprecationReason ?? '')} +
+
{@html marked.parse(rule.shortSummary ?? '')}