Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve unknown annotation trait errors #644

Merged
merged 1 commit into from
Dec 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -692,12 +692,20 @@ private Node coerceTraitValue(
ShapeId traitId, Node value, boolean isAnnotation, Function<ShapeId, ShapeType> typeProvider) {
if (isAnnotation && value.isNullNode()) {
ShapeType targetType = typeProvider.apply(traitId);
if (targetType != null) {
if (targetType == ShapeType.STRUCTURE || targetType == ShapeType.MAP) {
return new ObjectNode(Collections.emptyMap(), value.getSourceLocation());
} else if (targetType == ShapeType.LIST || targetType == ShapeType.SET) {
return new ArrayNode(Collections.emptyList(), value.getSourceLocation());
}
if (targetType == null || targetType == ShapeType.STRUCTURE || targetType == ShapeType.MAP) {
// The targetType == null condition helps mitigate a confusing
// failure mode where a trait isn't defined in the model, but a
// TraitService is found as a service provider for the trait.
// If the TraitService creates an annotation trait, then using null
// instead of object results in a failure about passing null for an
// annotation trait, and that's confusing because the actual error
// message should be about the missing trait definition. Because the
// vast majority of annotation traits are modeled as objects, this
// makes the assumption that the value is an object (which addresses
// the above failure case).
return new ObjectNode(Collections.emptyMap(), value.getSourceLocation());
} else if (targetType == ShapeType.LIST || targetType == ShapeType.SET) {
return new ArrayNode(Collections.emptyList(), value.getSourceLocation());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.model.traits.DocumentationTrait;
import software.amazon.smithy.model.traits.DynamicTrait;
import software.amazon.smithy.model.traits.StringTrait;
import software.amazon.smithy.model.traits.TraitFactory;
import software.amazon.smithy.model.validation.Severity;
import software.amazon.smithy.model.validation.ValidatedResult;
import software.amazon.smithy.model.validation.ValidatedResultException;
Expand Down Expand Up @@ -182,4 +184,43 @@ public void properlyLoadsOperationsWithUseStatements() {
assertThat(identifiers.get("s"), equalTo(ShapeId.from("smithy.api#String")));
assertThat(identifiers.get("x"), equalTo(ShapeId.from("smithy.example.other#X")));
}

@Test
public void addressesConfusingEdgeCaseForUnknownTraits() {
TraitFactory defaultFactory = TraitFactory.createServiceFactory();

// This trait factory ensures that the trait under test is coerced to
// an object instead of a null. The definition of @foo can't be found,
// but we know it's an annotation trait, so it's coerced to the most
// common annotation trait type (an object). This makes the error
// message for annotation traits that have no trait definition but do
// have TraitService SPI more intelligible.
TraitFactory factory = (id, target, value) -> {
if (id.equals(ShapeId.from("smithy.example#foo"))) {
assertThat("Did not coerce the unknown annotation trait to an object", value.isObjectNode(), is(true));
return Optional.of(new DynamicTrait(id, value));
} else {
return defaultFactory.createTrait(id, target, value);
}
};

List<ValidationEvent> events = Model.assembler()
.traitFactory(factory)
.addUnparsedModel("foo.smithy", "namespace smithy.example\n@foo\nstring MyString\n")
.assemble()
.getValidationEvents();

// Ensure that there is also an event that the @foo trait couldn't be found.
// This is a bit overkill, but ensures that it works end-to-end.
for (ValidationEvent event : events) {
if (event.getShapeId().filter(id -> id.equals(ShapeId.from("smithy.example#MyString"))).isPresent()) {
if (event.getMessage().contains("Unable to resolve trait")) {
// Just break out of the test early when it's found. We're done.
return;
}
}
}

Assertions.fail("Did not find expected unknown trait event: " + events);
}
}