Skip to content

Commit a378fdb

Browse files
committed
Allow lowering input validation severity on examples
1 parent 14d4d68 commit a378fdb

File tree

14 files changed

+394
-48
lines changed

14 files changed

+394
-48
lines changed

docs/source-2.0/spec/documentation-traits.rst

+18-6
Original file line numberDiff line numberDiff line change
@@ -139,11 +139,22 @@ Each ``example`` trait value is a structure with the following members:
139139
- :ref:`examples-ErrorExample-structure`
140140
- Provides an error shape ID and example error parameters for the
141141
operation.
142-
143-
The values provided for the ``input`` and ``output`` members MUST be
144-
compatible with the shapes and constraints of the corresponding structure.
145-
These values use the same semantics and format as
146-
:ref:`custom trait values <trait-node-values>`.
142+
* - lowerInputValidationSeverity
143+
- ``[string]``
144+
- Lowers input validation events from ERROR to WARNING for specific
145+
validation types. List can include: ``BLOB_LENGTH_WARNING``,
146+
``MAP_LENGTH_WARNING``, ``PATTERN_TRAIT_WARNING``,
147+
``RANGE_TRAIT_WARNING``, ``REQUIRED_TRAIT_WARNING``,
148+
``STRING_LENGTH_WARNING``.
149+
150+
151+
When ``input`` and ``output`` members are present, both MUST be compatible
152+
with the shapes and constraints of the corresponding structure. When ``input``
153+
and ``error`` members are present, input validation events will be emitted as
154+
an ``ERROR`` by default. Specific validation events for the ``input`` can be
155+
lowered to a ``WARNING`` by setting the appropriate
156+
``lowerInputValidationSeverity`` value. ``input`` and ``output`` members use
157+
the same semantics and format as :ref:`custom trait values <trait-node-values>`.
147158

148159
A value for ``output`` or ``error`` SHOULD be provided. However, both
149160
MUST NOT be defined for the same example.
@@ -186,7 +197,8 @@ MUST NOT be defined for the same example.
186197
content: {
187198
message: "Invalid 'foo'. Special character not allowed."
188199
}
189-
}
200+
},
201+
lowerInputValidationSeverity: ["PATTERN_TRAIT_WARNING"]
190202
}
191203
])
192204

smithy-model/src/main/java/software/amazon/smithy/model/traits/ExamplesTrait.java

+51-3
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,15 @@
1919
import java.util.List;
2020
import java.util.Objects;
2121
import java.util.Optional;
22+
import java.util.stream.Collectors;
2223
import software.amazon.smithy.model.node.ArrayNode;
2324
import software.amazon.smithy.model.node.Node;
2425
import software.amazon.smithy.model.node.ObjectNode;
2526
import software.amazon.smithy.model.node.ToNode;
2627
import software.amazon.smithy.model.shapes.ShapeId;
28+
import software.amazon.smithy.model.validation.NodeValidationVisitor;
2729
import software.amazon.smithy.model.validation.validators.ExamplesTraitValidator;
30+
import software.amazon.smithy.utils.BuilderRef;
2831
import software.amazon.smithy.utils.SmithyBuilder;
2932
import software.amazon.smithy.utils.ToSmithyBuilder;
3033

@@ -55,6 +58,24 @@ protected Node createNode() {
5558
return examples.stream().map(Example::toNode).collect(ArrayNode.collect(getSourceLocation()));
5659
}
5760

61+
@Override
62+
public boolean equals(Object other) {
63+
if (!(other instanceof ExamplesTrait)) {
64+
return false;
65+
} else if (other == this) {
66+
return true;
67+
} else {
68+
ExamplesTrait trait = (ExamplesTrait) other;
69+
return this.examples.size() == trait.examples.size() && this.examples.containsAll(trait.examples);
70+
}
71+
}
72+
73+
@Override
74+
public int hashCode() {
75+
return Objects.hash(toShapeId(), examples);
76+
}
77+
78+
5879
@Override
5980
public Builder toBuilder() {
6081
Builder builder = new Builder().sourceLocation(getSourceLocation());
@@ -70,7 +91,7 @@ public static Builder builder() {
7091
}
7192

7293
/**
73-
* Builds and examples trait.
94+
* Builds an examples trait.
7495
*/
7596
public static final class Builder extends AbstractTraitBuilder<ExamplesTrait, Builder> {
7697
private final List<Example> examples = new ArrayList<>();
@@ -100,13 +121,15 @@ public static final class Example implements ToNode, ToSmithyBuilder<Example> {
100121
private final ObjectNode input;
101122
private final ObjectNode output;
102123
private final ErrorExample error;
124+
private final List<NodeValidationVisitor.Feature> lowerInputValidationSeverity;
103125

104126
private Example(Builder builder) {
105127
this.title = Objects.requireNonNull(builder.title, "Example title must not be null");
106128
this.documentation = builder.documentation;
107129
this.input = builder.input;
108130
this.output = builder.output;
109131
this.error = builder.error;
132+
this.lowerInputValidationSeverity = builder.lowerInputValidationSeverity.get();
110133
}
111134

112135
/**
@@ -144,6 +167,13 @@ public Optional<ErrorExample> getError() {
144167
return Optional.ofNullable(error);
145168
}
146169

170+
/**
171+
* @return Gets the list of lowered input validation severities.
172+
*/
173+
public Optional<List<NodeValidationVisitor.Feature>> getLowerInputValidationSeverity() {
174+
return Optional.ofNullable(lowerInputValidationSeverity);
175+
}
176+
147177
@Override
148178
public Node toNode() {
149179
ObjectNode.Builder builder = Node.objectNodeBuilder()
@@ -157,13 +187,20 @@ public Node toNode() {
157187
if (this.getOutput().isPresent()) {
158188
builder.withMember("output", output);
159189
}
190+
if (this.getLowerInputValidationSeverity().isPresent()) {
191+
builder.withMember("lowerInputValidationSeverity", ArrayNode.fromNodes(lowerInputValidationSeverity
192+
.stream()
193+
.map(NodeValidationVisitor.Feature::toNode)
194+
.collect(Collectors.toList())));
195+
}
160196

161197
return builder.build();
162198
}
163199

164200
@Override
165201
public Builder toBuilder() {
166-
return new Builder().documentation(documentation).title(title).input(input).output(output).error(error);
202+
return new Builder().documentation(documentation).title(title).input(input).output(output).error(error)
203+
.lowerInputValidationSeverity(lowerInputValidationSeverity);
167204
}
168205

169206
public static Builder builder() {
@@ -179,6 +216,7 @@ public static final class Builder implements SmithyBuilder<Example> {
179216
private ObjectNode input = Node.objectNode();
180217
private ObjectNode output;
181218
private ErrorExample error;
219+
private BuilderRef<List<NodeValidationVisitor.Feature>> lowerInputValidationSeverity = BuilderRef.forList();
182220

183221
@Override
184222
public Example build() {
@@ -209,6 +247,14 @@ public Builder error(ErrorExample error) {
209247
this.error = error;
210248
return this;
211249
}
250+
251+
public Builder lowerInputValidationSeverity(
252+
List<NodeValidationVisitor.Feature> lowerInputValidationSeverity
253+
) {
254+
this.lowerInputValidationSeverity.clear();
255+
this.lowerInputValidationSeverity.get().addAll(lowerInputValidationSeverity);
256+
return this;
257+
}
212258
}
213259
}
214260

@@ -302,7 +348,9 @@ private static Example exampleFromNode(ObjectNode node) {
302348
.getStringMember("documentation", builder::documentation)
303349
.getObjectMember("input", builder::input)
304350
.getObjectMember("output", builder::output)
305-
.getMember("error", ErrorExample::fromNode, builder::error);
351+
.getMember("error", ErrorExample::fromNode, builder::error)
352+
.getArrayMember("lowerInputValidationSeverity", NodeValidationVisitor.Feature::fromNode,
353+
builder::lowerInputValidationSeverity);
306354
return builder.build();
307355
}
308356
}

smithy-model/src/main/java/software/amazon/smithy/model/validation/NodeValidationVisitor.java

+32-4
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,36 @@ public enum Feature {
104104
* Emit a warning when a range trait is incompatible with a default value of 0.
105105
*
106106
* <p>This was a common pattern in Smithy 1.0 and earlier. It implies that the value is effectively
107-
* required. However, chaning the type of the value by un-boxing it or adjusting the range trait would
108-
* be a lossy tranformation when migrating a model from 1.0 to 2.0.
107+
* required. However, changing the type of the value by un-boxing it or adjusting the range trait would
108+
* be a lossy transformation when migrating a model from 1.0 to 2.0.
109109
*/
110-
RANGE_TRAIT_ZERO_VALUE_WARNING
110+
RANGE_TRAIT_ZERO_VALUE_WARNING,
111+
112+
// Lowers severity of Length Trait validations on blobs to a WARNING.
113+
BLOB_LENGTH_WARNING,
114+
115+
// Lowers severity of Length Trait validations on maps to a WARNING.
116+
MAP_LENGTH_WARNING,
117+
118+
// Lowers severity of Pattern Trait validations to a WARNING.
119+
PATTERN_TRAIT_WARNING,
120+
121+
// Lowers severity of Range Trait validations to a WARNING.
122+
RANGE_TRAIT_WARNING,
123+
124+
// Lowers severity of Required Trait validations to a WARNING.
125+
REQUIRED_TRAIT_WARNING,
126+
127+
// Lowers severity of Length Trait validations on strings to a WARNING.
128+
STRING_LENGTH_WARNING,;
129+
130+
public static Feature fromNode(Node node) {
131+
return Feature.valueOf(node.expectStringNode().getValue());
132+
}
133+
134+
public static Node toNode(Feature feature) {
135+
return StringNode.from(feature.toString());
136+
}
111137
}
112138

113139
public static Builder builder() {
@@ -317,9 +343,11 @@ public List<ValidationEvent> structureShape(StructureShape shape) {
317343

318344
for (MemberShape member : members.values()) {
319345
if (member.isRequired() && !object.getMember(member.getMemberName()).isPresent()) {
346+
Severity severity = this.validationContext.hasFeature(Feature.REQUIRED_TRAIT_WARNING)
347+
? Severity.WARNING : Severity.ERROR;
320348
events.add(event(String.format(
321349
"Missing required structure member `%s` for `%s`",
322-
member.getMemberName(), shape.getId())));
350+
member.getMemberName(), shape.getId()), severity));
323351
}
324352
}
325353
return events;

smithy-model/src/main/java/software/amazon/smithy/model/validation/node/BlobLengthPlugin.java

+13-4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import software.amazon.smithy.model.shapes.BlobShape;
2121
import software.amazon.smithy.model.shapes.Shape;
2222
import software.amazon.smithy.model.traits.LengthTrait;
23+
import software.amazon.smithy.model.validation.NodeValidationVisitor;
24+
import software.amazon.smithy.model.validation.Severity;
2325
import software.amazon.smithy.utils.SmithyInternalApi;
2426

2527
/**
@@ -39,16 +41,23 @@ protected void check(Shape shape, LengthTrait trait, StringNode node, Context co
3941

4042
trait.getMin().ifPresent(min -> {
4143
if (size < min) {
42-
emitter.accept(node, "Value provided for `" + shape.getId() + "` must have at least "
43-
+ min + " bytes, but the provided value only has " + size + " bytes");
44+
emitter.accept(node, getSeverity(context), "Value provided for `" + shape.getId()
45+
+ "` must have at least " + min + " bytes, but the provided value only has " + size
46+
+ " bytes");
4447
}
4548
});
4649

4750
trait.getMax().ifPresent(max -> {
4851
if (value.getBytes(StandardCharsets.UTF_8).length > max) {
49-
emitter.accept(node, "Value provided for `" + shape.getId() + "` must have no more than "
50-
+ max + " bytes, but the provided value has " + size + " bytes");
52+
emitter.accept(node, getSeverity(context), "Value provided for `" + shape.getId()
53+
+ "` must have no more than " + max + " bytes, but the provided value has " + size
54+
+ " bytes");
5155
}
5256
});
5357
}
58+
59+
private Severity getSeverity(Context context) {
60+
return context.hasFeature(NodeValidationVisitor.Feature.BLOB_LENGTH_WARNING)
61+
? Severity.WARNING : Severity.ERROR;
62+
}
5463
}

smithy-model/src/main/java/software/amazon/smithy/model/validation/node/MapLengthPlugin.java

+9-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import software.amazon.smithy.model.shapes.MapShape;
2020
import software.amazon.smithy.model.shapes.Shape;
2121
import software.amazon.smithy.model.traits.LengthTrait;
22+
import software.amazon.smithy.model.validation.NodeValidationVisitor;
23+
import software.amazon.smithy.model.validation.Severity;
2224
import software.amazon.smithy.utils.SmithyInternalApi;
2325

2426
/**
@@ -35,18 +37,23 @@ final class MapLengthPlugin extends MemberAndShapeTraitPlugin<MapShape, ObjectNo
3537
protected void check(Shape shape, LengthTrait trait, ObjectNode node, Context context, Emitter emitter) {
3638
trait.getMin().ifPresent(min -> {
3739
if (node.size() < min) {
38-
emitter.accept(node, String.format(
40+
emitter.accept(node, getSeverity(context), String.format(
3941
"Value provided for `%s` must have at least %d entries, but the provided value only "
4042
+ "has %d entries", shape.getId(), min, node.size()));
4143
}
4244
});
4345

4446
trait.getMax().ifPresent(max -> {
4547
if (node.size() > max) {
46-
emitter.accept(node, String.format(
48+
emitter.accept(node, getSeverity(context), String.format(
4749
"Value provided for `%s` must have no more than %d entries, but the provided value "
4850
+ "has %d entries", shape.getId(), max, node.size()));
4951
}
5052
});
5153
}
54+
55+
private Severity getSeverity(Context context) {
56+
return context.hasFeature(NodeValidationVisitor.Feature.MAP_LENGTH_WARNING)
57+
? Severity.WARNING : Severity.ERROR;
58+
}
5259
}

smithy-model/src/main/java/software/amazon/smithy/model/validation/node/PatternTraitPlugin.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import software.amazon.smithy.model.shapes.Shape;
2020
import software.amazon.smithy.model.shapes.StringShape;
2121
import software.amazon.smithy.model.traits.PatternTrait;
22+
import software.amazon.smithy.model.validation.NodeValidationVisitor;
23+
import software.amazon.smithy.model.validation.Severity;
2224

2325
/**
2426
* Validates the pattern trait on string shapes or members that target them.
@@ -32,9 +34,15 @@ final class PatternTraitPlugin extends MemberAndShapeTraitPlugin<StringShape, St
3234
@Override
3335
protected void check(Shape shape, PatternTrait trait, StringNode node, Context context, Emitter emitter) {
3436
if (!trait.getPattern().matcher(node.getValue()).find()) {
35-
emitter.accept(node, String.format(
37+
emitter.accept(node, getSeverity(context), String.format(
3638
"String value provided for `%s` must match regular expression: %s",
3739
shape.getId(), trait.getPattern().pattern()));
3840
}
3941
}
42+
43+
44+
private Severity getSeverity(Context context) {
45+
return context.hasFeature(NodeValidationVisitor.Feature.PATTERN_TRAIT_WARNING)
46+
? Severity.WARNING : Severity.ERROR;
47+
}
4048
}

0 commit comments

Comments
 (0)