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

Reduce false positives of ShouldHaveUsedTimestampValidator #2480

Merged
merged 2 commits into from
Dec 10, 2024
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 @@ -24,6 +24,7 @@
import java.util.stream.Stream;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.node.NodeMapper;
import software.amazon.smithy.model.shapes.EnumShape;
import software.amazon.smithy.model.shapes.MemberShape;
import software.amazon.smithy.model.shapes.NumberShape;
import software.amazon.smithy.model.shapes.Shape;
Expand Down Expand Up @@ -112,20 +113,25 @@ public List<ValidationEvent> validate(Model model) {
@Override
protected List<ValidationEvent> getDefault(Shape shape) {
if (shape.isStringShape() || shape instanceof NumberShape) {
return validateSimpleShape(shape, patterns);
return validateSimpleShape(shape);
} else {
return Collections.emptyList();
}
}

@Override
public List<ValidationEvent> structureShape(StructureShape shape) {
return validateStructure(shape, model, patterns);
return validateStructure(shape, model);
}

@Override
public List<ValidationEvent> unionShape(UnionShape shape) {
return validateUnion(shape, model, patterns);
return validateUnion(shape, model);
}

@Override
public List<ValidationEvent> enumShape(EnumShape shape) {
return Collections.emptyList();
}
};

Expand All @@ -134,57 +140,63 @@ public List<ValidationEvent> unionShape(UnionShape shape) {

private List<ValidationEvent> validateStructure(
StructureShape structure,
Model model,
List<Pattern> patterns
Model model
) {
return structure
.getAllMembers()
.entrySet()
.stream()
.flatMap(entry -> validateTargetShape(entry.getKey(), entry.getValue(), model, patterns))
.flatMap(entry -> validateTargetShape(entry.getKey(), entry.getValue(), model))
.collect(Collectors.toList());
}

private List<ValidationEvent> validateUnion(
UnionShape union,
Model model,
List<Pattern> patterns
Model model
) {
return union
.getAllMembers()
.entrySet()
.stream()
.flatMap(entry -> validateTargetShape(entry.getKey(), entry.getValue(), model, patterns))
.flatMap(entry -> validateTargetShape(entry.getKey(), entry.getValue(), model))
.collect(Collectors.toList());
}

private Stream<ValidationEvent> validateTargetShape(
String name,
MemberShape target,
Model model,
List<Pattern> patterns
MemberShape memberShape,
Model model
) {
return OptionalUtils.stream(model.getShape(target.getTarget())
.flatMap(shape -> validateName(name, shape.getType(), target, patterns)));
return OptionalUtils.stream(model.getShape(memberShape.getTarget())
.flatMap(targetShape -> validateName(name, targetShape, memberShape, patterns, model)));
rchache marked this conversation as resolved.
Show resolved Hide resolved
}

private List<ValidationEvent> validateSimpleShape(
Shape shape,
List<Pattern> patterns
Shape shape
) {
return validateName(shape.getId().getName(), shape.getType(), shape, patterns)
.map(ListUtils::of)
.orElse(ListUtils.of());
String name = shape.getId().getName();

return patterns
.stream()
.filter(pattern -> pattern.matcher(name).matches())
.map(matcher -> buildEvent(shape, name, shape.getType()))
.collect(Collectors.toList());
}

private Optional<ValidationEvent> validateName(
String name,
ShapeType type,
Shape targetShape,
Shape context,
List<Pattern> patterns
List<Pattern> patterns,
Model model
) {
if (type == ShapeType.TIMESTAMP) {
ShapeType type = targetShape.getType();
if (type == ShapeType.TIMESTAMP || type == ShapeType.ENUM) {
return Optional.empty();
} else if (type == ShapeType.STRUCTURE || type == ShapeType.LIST) {
hpmellema marked this conversation as resolved.
Show resolved Hide resolved
if (this.onlyContainsTimestamps(targetShape, model)) {
return Optional.empty();
}
}
return patterns
.stream()
Expand All @@ -193,6 +205,16 @@ private Optional<ValidationEvent> validateName(
.findAny();
}

private boolean onlyContainsTimestamps(Shape shape, Model model) {
return shape.getAllMembers()
.values()
.stream()
.map(memberShape -> model.getShape(memberShape.getTarget()))
.filter(Optional::isPresent)
.map(Optional::get)
.allMatch(Shape::isTimestampShape);
}

private ValidationEvent buildEvent(Shape context, String name, ShapeType type) {
return danger(context, context.isMemberShape()
? String.format("Member `%s` is named like a timestamp but references a `%s` shape", name, type)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,48 @@
},
"example.namespace#CreatedOn": {
"type": "timestamp"
},
"example.namespace#DateEnum": {
"type": "enum",
"members": {
"ANOMALOUS": {
"target": "smithy.api#Unit",
"traits": {
"smithy.api#enumValue": "anomalous"
}
},
"NORMAL": {
"target": "smithy.api#Unit",
"traits": {
"smithy.api#enumValue": "normal"
}
}
}
},
"example.namespace#DateStructureOuter": {
"type": "structure",
"members": {
"TimestampStructure": {
"target": "example.namespace#DateStructure"
},
"ListOfTimestamp": {
"target": "example.namespace#DateList"
}
}
},
"example.namespace#DateStructure": {
"type": "structure",
"members": {
"TimeOn": {
"target": "example.namespace#CreatedOn"
}
}
},
"example.namespace#DateList": {
"type": "list",
"member": {
"target": "example.namespace#CreatedOn"
}
}
},
"metadata": {
Expand Down
Loading