Skip to content

Commit 2eef778

Browse files
authored
Extend validation event ids to make them unique (#1527)
* Extend validation event ids to make them unique * address comments, mostly to simplify some of the eventIds * optimize assembleFullEventId method * minor edits for style * unroll eventId varargs in AbstractValidator for performance * fix eventIds for Waiter violations * remove unused method overloads
1 parent 32b111f commit 2eef778

File tree

33 files changed

+581
-194
lines changed

33 files changed

+581
-194
lines changed

smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/HttpChecksumTraitValidator.java

+11-7
Original file line numberDiff line numberDiff line change
@@ -185,11 +185,13 @@ private List<ValidationEvent> validateHeaderConflicts(OperationShape operation,
185185
.map(HttpPrefixHeadersTrait::getValue)
186186
.ifPresent(headerPrefix -> {
187187
if (HttpChecksumTrait.CHECKSUM_PREFIX.startsWith(headerPrefix)) {
188+
String memberName = member.getId().getName();
189+
String prefixString = headerPrefix.toLowerCase(Locale.US);
188190
events.add(danger(operation, format("The `httpPrefixHeaders` binding of `%s` uses"
189-
+ " the prefix `%s` that conflicts with the prefix `%s` used by the"
190-
+ " `httpChecksum` trait.",
191-
member.getId().getName(), headerPrefix.toLowerCase(Locale.US),
192-
HttpChecksumTrait.CHECKSUM_PREFIX)));
191+
+ " the prefix `%s` that conflicts with the prefix `%s` used by the"
192+
+ " `httpChecksum` trait.",
193+
memberName, prefixString, HttpChecksumTrait.CHECKSUM_PREFIX),
194+
"HttpPrefixHeaders", memberName, prefixString));
193195
}
194196
});
195197

@@ -200,10 +202,12 @@ private List<ValidationEvent> validateHeaderConflicts(OperationShape operation,
200202
.map(HttpHeaderTrait::getValue)
201203
.ifPresent(headerName -> {
202204
if (headerName.startsWith(HttpChecksumTrait.CHECKSUM_PREFIX)) {
205+
String memberName = member.getId().getName();
206+
String headerString = headerName.toLowerCase(Locale.US);
203207
events.add(warning(operation, format("The `httpHeader` binding of `%s` on `%s`"
204-
+ " starts with the prefix `%s` used by the `httpChecksum` trait.",
205-
headerName.toLowerCase(Locale.US), member.getId().getName(),
206-
HttpChecksumTrait.CHECKSUM_PREFIX)));
208+
+ " starts with the prefix `%s` used by the `httpChecksum` trait.",
209+
headerString, memberName, HttpChecksumTrait.CHECKSUM_PREFIX),
210+
"HttpHeader", memberName, headerString));
207211
}
208212
});
209213
}
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
[SUPPRESSED] smithy.example#HeaderConflicts: This shape applies a trait that is unstable: aws.protocols#httpChecksum | UnstableTrait
22
[SUPPRESSED] smithy.example#HeadersConflicts: This shape applies a trait that is unstable: aws.protocols#httpChecksum | UnstableTrait
33
[SUPPRESSED] smithy.example#NoConflicts: This shape applies a trait that is unstable: aws.protocols#httpChecksum | UnstableTrait
4-
[WARNING] smithy.example#HeaderConflicts: The `httpHeader` binding of `x-amz-checksum-crc32` on `HeaderConflictError` starts with the prefix `x-amz-checksum-` used by the `httpChecksum` trait. | HttpChecksumTrait
5-
[WARNING] smithy.example#HeaderConflicts: The `httpHeader` binding of `x-amz-checksum-crc32` on `HeaderConflictsInput` starts with the prefix `x-amz-checksum-` used by the `httpChecksum` trait. | HttpChecksumTrait
6-
[WARNING] smithy.example#HeaderConflicts: The `httpHeader` binding of `x-amz-checksum-crc32` on `HeaderConflictsOutput` starts with the prefix `x-amz-checksum-` used by the `httpChecksum` trait. | HttpChecksumTrait
7-
[DANGER] smithy.example#HeadersConflicts: The `httpPrefixHeaders` binding of `HeadersConflictError` uses the prefix `x-amz-checksum-` that conflicts with the prefix `x-amz-checksum-` used by the `httpChecksum` trait. | HttpChecksumTrait
8-
[DANGER] smithy.example#HeadersConflicts: The `httpPrefixHeaders` binding of `HeadersConflictsInput` uses the prefix `x-amz-checksum-` that conflicts with the prefix `x-amz-checksum-` used by the `httpChecksum` trait. | HttpChecksumTrait
9-
[DANGER] smithy.example#HeadersConflicts: The `httpPrefixHeaders` binding of `HeadersConflictsOutput` uses the prefix `x-amz-checksum-` that conflicts with the prefix `x-amz-checksum-` used by the `httpChecksum` trait. | HttpChecksumTrait
4+
[WARNING] smithy.example#HeaderConflicts: The `httpHeader` binding of `x-amz-checksum-crc32` on `HeaderConflictError` starts with the prefix `x-amz-checksum-` used by the `httpChecksum` trait. | HttpChecksumTrait.HttpHeader.HeaderConflictError.x-amz-checksum-crc32
5+
[WARNING] smithy.example#HeaderConflicts: The `httpHeader` binding of `x-amz-checksum-crc32` on `HeaderConflictsInput` starts with the prefix `x-amz-checksum-` used by the `httpChecksum` trait. | HttpChecksumTrait.HttpHeader.HeaderConflictsInput.x-amz-checksum-crc32
6+
[WARNING] smithy.example#HeaderConflicts: The `httpHeader` binding of `x-amz-checksum-crc32` on `HeaderConflictsOutput` starts with the prefix `x-amz-checksum-` used by the `httpChecksum` trait. | HttpChecksumTrait.HttpHeader.HeaderConflictsOutput.x-amz-checksum-crc32
7+
[DANGER] smithy.example#HeadersConflicts: The `httpPrefixHeaders` binding of `HeadersConflictError` uses the prefix `x-amz-checksum-` that conflicts with the prefix `x-amz-checksum-` used by the `httpChecksum` trait. | HttpChecksumTrait.HttpPrefixHeaders.HeadersConflictError.x-amz-checksum-
8+
[DANGER] smithy.example#HeadersConflicts: The `httpPrefixHeaders` binding of `HeadersConflictsInput` uses the prefix `x-amz-checksum-` that conflicts with the prefix `x-amz-checksum-` used by the `httpChecksum` trait. | HttpChecksumTrait.HttpPrefixHeaders.HeadersConflictsInput.x-amz-checksum-
9+
[DANGER] smithy.example#HeadersConflicts: The `httpPrefixHeaders` binding of `HeadersConflictsOutput` uses the prefix `x-amz-checksum-` that conflicts with the prefix `x-amz-checksum-` used by the `httpChecksum` trait. | HttpChecksumTrait.HttpPrefixHeaders.HeadersConflictsOutput.x-amz-checksum-

smithy-linters/src/main/java/software/amazon/smithy/linters/InputOutputStructureReuseValidator.java

+7-2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ public Provider() {
3939
}
4040
}
4141

42+
private static final String INPUT = "Input";
43+
private static final String OUTPUT = "Output";
44+
4245
@Override
4346
public List<ValidationEvent> validate(Model model) {
4447
List<ValidationEvent> events = new ArrayList<>();
@@ -62,13 +65,15 @@ private void validateInputOutputSet(
6265
"This structure is the input of `%s`, but it is not marked with the "
6366
+ "@input trait. The @input trait gives operations more flexibility to "
6467
+ "evolve their top-level input members in ways that would otherwise "
65-
+ "be backward incompatible.", operation.getId())));
68+
+ "be backward incompatible.", operation.getId()),
69+
INPUT, operation.getId().getName()));
6670
}
6771

6872
if (!output.hasTrait(OutputTrait.class)) {
6973
events.add(warning(output, String.format(
7074
"This structure is the output of `%s`, but it is not marked with "
71-
+ "the @output trait.", operation.getId())));
75+
+ "the @output trait.", operation.getId()),
76+
OUTPUT, operation.getId().getName()));
7277
}
7378
}
7479
}

smithy-linters/src/main/java/software/amazon/smithy/linters/NoninclusiveTermsValidator.java

+56-49
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.Collections;
2121
import java.util.HashMap;
2222
import java.util.List;
23+
import java.util.Locale;
2324
import java.util.Map;
2425
import java.util.stream.Collectors;
2526
import software.amazon.smithy.model.Model;
@@ -58,6 +59,10 @@ public Provider() {
5859
}
5960
}
6061

62+
private static final String TRAIT = "Trait";
63+
private static final String SHAPE = "Shape";
64+
private static final String NAMESPACE = "Namespace";
65+
6166
/**
6267
* NoninclusiveTermsValidator configuration.
6368
*/
@@ -121,7 +126,7 @@ public List<ValidationEvent> validate(Model model) {
121126
/**
122127
* Generates zero or more @see ValidationEvents and returns them in a collection.
123128
*
124-
* @param occurrence text occurrence found in the body of the model
129+
* @param instance text occurrence found in the body of the model
125130
*/
126131
private Collection<ValidationEvent> getValidationEvents(TextInstance instance) {
127132
final Collection<ValidationEvent> events = new ArrayList<>();
@@ -130,70 +135,72 @@ private Collection<ValidationEvent> getValidationEvents(TextInstance instance) {
130135
final int startIndex = instance.getText().toLowerCase().indexOf(termLower);
131136
if (startIndex != -1) {
132137
final String matchedText = instance.getText().substring(startIndex, startIndex + termLower.length());
133-
switch (instance.getLocationType()) {
134-
case NAMESPACE:
135-
//Cannot use any warning() overloads because there is no shape associated with the event.
136-
events.add(ValidationEvent.builder()
137-
.sourceLocation(SourceLocation.none())
138-
.id(this.getClass().getSimpleName().replaceFirst("Validator$", ""))
139-
.severity(Severity.WARNING)
140-
.message(formatNonInclusiveTermsValidationMessage(termEntry, matchedText, instance))
141-
.build());
142-
break;
143-
case APPLIED_TRAIT:
144-
events.add(warning(instance.getShape(),
145-
instance.getTrait().getSourceLocation(),
146-
formatNonInclusiveTermsValidationMessage(termEntry, matchedText, instance)));
147-
break;
148-
case SHAPE:
149-
default:
150-
events.add(warning(instance.getShape(),
151-
instance.getShape().getSourceLocation(),
152-
formatNonInclusiveTermsValidationMessage(termEntry, matchedText, instance)));
153-
}
138+
events.add(constructValidationEvent(instance, termEntry.getValue(), matchedText));
154139
}
155140
}
156141
return events;
157142
}
158143

159-
private static String formatNonInclusiveTermsValidationMessage(
160-
Map.Entry<String, List<String>> termEntry,
161-
String matchedText,
162-
TextInstance instance
163-
) {
164-
final List<String> caseCorrectedEntryValue = termEntry.getValue().stream()
165-
.map(replacement -> Character.isUpperCase(matchedText.charAt(0))
166-
? StringUtils.capitalize(replacement)
167-
: StringUtils.uncapitalize(replacement))
168-
.collect(Collectors.toList());
169-
String replacementAddendum = !termEntry.getValue().isEmpty()
170-
? String.format(" Consider using one of the following terms instead: %s",
171-
ValidationUtils.tickedList(caseCorrectedEntryValue))
172-
: "";
144+
private ValidationEvent constructValidationEvent(TextInstance instance,
145+
List<String> replacements,
146+
String matchedText) {
147+
String replacementAddendum = getReplacementAddendum(matchedText, replacements);
173148
switch (instance.getLocationType()) {
174-
case SHAPE:
175-
return String.format("%s shape uses a non-inclusive term `%s`.%s",
176-
StringUtils.capitalize(instance.getShape().getType().toString()),
177-
matchedText, replacementAddendum);
178149
case NAMESPACE:
179-
return String.format("%s namespace uses a non-inclusive term `%s`.%s",
180-
instance.getText(), matchedText, replacementAddendum);
150+
//Cannot use any warning() overloads because there is no shape associated with the event.
151+
return ValidationEvent.builder()
152+
.severity(Severity.WARNING)
153+
.sourceLocation(SourceLocation.none())
154+
.id(getName() + "." + NAMESPACE + "." + instance.getText()
155+
+ "." + matchedText.toLowerCase(Locale.US))
156+
.message(String.format("%s namespace uses a non-inclusive term `%s`.%s",
157+
instance.getText(), matchedText, replacementAddendum))
158+
.build();
181159
case APPLIED_TRAIT:
160+
ValidationEvent validationEvent =
161+
warning(instance.getShape(), instance.getTrait().getSourceLocation(), "");
162+
String idiomaticTraitName = Trait.getIdiomaticTraitName(instance.getTrait());
182163
if (instance.getTraitPropertyPath().isEmpty()) {
183-
return String.format("'%s' trait has a value that contains a non-inclusive term `%s`.%s",
184-
Trait.getIdiomaticTraitName(instance.getTrait()), matchedText,
185-
replacementAddendum);
164+
return validationEvent.toBuilder()
165+
.message(String.format("'%s' trait has a value that contains a non-inclusive term `%s`.%s",
166+
idiomaticTraitName, matchedText, replacementAddendum))
167+
.id(getName() + "." + TRAIT + "."
168+
+ matchedText.toLowerCase(Locale.US) + "." + idiomaticTraitName)
169+
.build();
186170
} else {
187171
String valuePropertyPathFormatted = formatPropertyPath(instance.getTraitPropertyPath());
188-
return String.format("'%s' trait value at path {%s} contains a non-inclusive term `%s`.%s",
189-
Trait.getIdiomaticTraitName(instance.getTrait()), valuePropertyPathFormatted,
190-
matchedText, replacementAddendum);
172+
return validationEvent.toBuilder()
173+
.message(String.format(
174+
"'%s' trait value at path {%s} contains a non-inclusive term `%s`.%s",
175+
idiomaticTraitName, valuePropertyPathFormatted, matchedText, replacementAddendum))
176+
.id(getName() + "." + TRAIT + "." + matchedText.toLowerCase(Locale.US)
177+
+ "." + idiomaticTraitName + "." + valuePropertyPathFormatted)
178+
.build();
191179
}
180+
case SHAPE:
192181
default:
193-
throw new IllegalStateException();
182+
return warning(instance.getShape(),
183+
instance.getShape().getSourceLocation(),
184+
String.format("%s shape uses a non-inclusive term `%s`.%s",
185+
StringUtils.capitalize(instance.getShape().getType().toString()),
186+
matchedText, replacementAddendum),
187+
SHAPE, matchedText.toLowerCase(Locale.US));
194188
}
195189
}
196190

191+
private static String getReplacementAddendum(String matchedText, List<String> replacements) {
192+
List<String> caseCorrectedEntryValue = replacements.stream()
193+
.map(replacement -> Character.isUpperCase(matchedText.charAt(0))
194+
? StringUtils.capitalize(replacement)
195+
: StringUtils.uncapitalize(replacement))
196+
.collect(Collectors.toList());
197+
String replacementAddendum = !replacements.isEmpty()
198+
? String.format(" Consider using one of the following terms instead: %s",
199+
ValidationUtils.tickedList(caseCorrectedEntryValue))
200+
: "";
201+
return replacementAddendum;
202+
}
203+
197204
private static String formatPropertyPath(List<String> traitPropertyPath) {
198205
return String.join("/", traitPropertyPath);
199206
}
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
[WARNING] smithy.example#GetFooInput: This structure is the input of `smithy.example#GetFoo`, but it is not marked with the @input trait. The @input trait gives operations more flexibility to evolve their top-level input members in ways that would otherwise be backward incompatible. | InputOutputStructureReuse
2-
[WARNING] smithy.example#GetFooOutput: This structure is the output of `smithy.example#GetFoo`, but it is not marked with the @output trait. | InputOutputStructureReuse
1+
[WARNING] smithy.example#GetFooInput: This structure is the input of `smithy.example#GetFoo`, but it is not marked with the @input trait. The @input trait gives operations more flexibility to evolve their top-level input members in ways that would otherwise be backward incompatible. | InputOutputStructureReuse.Input.GetFoo
2+
[WARNING] smithy.example#GetFooOutput: This structure is the output of `smithy.example#GetFoo`, but it is not marked with the @output trait. | InputOutputStructureReuse.Output.GetFoo

0 commit comments

Comments
 (0)