software.amazon.smithy.linters.StandardOperationVerbValidator Maven / Gradle / Ivy
Show all versions of smithy-linters Show documentation
/*
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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 software.amazon.smithy.linters;
import static java.lang.String.format;
import static software.amazon.smithy.model.validation.ValidationUtils.splitCamelCaseWord;
import static software.amazon.smithy.model.validation.ValidationUtils.tickedList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.SourceException;
import software.amazon.smithy.model.node.NodeMapper;
import software.amazon.smithy.model.shapes.OperationShape;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.validation.AbstractValidator;
import software.amazon.smithy.model.validation.ValidationEvent;
import software.amazon.smithy.model.validation.ValidatorService;
import software.amazon.smithy.utils.OptionalUtils;
/**
* Validates that operation shape names start with standard verbs.
*
*
Looks at each operation shape name and determines if the first
* word in the shape name is one of the pre-defined allowed verbs.
*
*
If the verb is a key in the map of verbs that have recommended
* alternatives, then the event that is emitted contains the list of
* alternate verbs to use.
*/
public final class StandardOperationVerbValidator extends AbstractValidator {
/**
* StandardOperationVerb configuration settings.
*/
public static final class Config {
private List verbs = Collections.emptyList();
private List prefixes = Collections.emptyList();
private Map> suggestAlternatives = Collections.emptyMap();
public List getVerbs() {
return verbs;
}
/**
* Sets the list of standard operation verbs.
*
* @param verbs Verbs to allow.
*/
public void setVerbs(List verbs) {
this.verbs = verbs;
}
public List getPrefixes() {
return prefixes;
}
/**
* Sets the list of acceptable verb prefixed (e.g., Batch).
*
* @param prefixes The list of verb prefixes.
*/
public void setPrefixes(List prefixes) {
this.prefixes = prefixes;
}
public Map> getSuggestAlternatives() {
return suggestAlternatives;
}
/**
* Sets a map of invalid verbs to suggested alternatives.
*
* @param suggestAlternatives Alternative verb suggestions.
*/
public void setSuggestAlternatives(Map> suggestAlternatives) {
this.suggestAlternatives = suggestAlternatives;
}
}
public static final class Provider extends ValidatorService.Provider {
public Provider() {
super(StandardOperationVerbValidator.class, node -> {
Config config = new NodeMapper().deserialize(node, Config.class);
if (config.getVerbs().isEmpty() && config.getSuggestAlternatives().isEmpty()) {
throw new SourceException("Either verbs or suggestAlternatives must be set when configuring "
+ "StandardOperationVerb", node);
}
return new StandardOperationVerbValidator(config);
});
}
}
private final Config config;
private StandardOperationVerbValidator(Config config) {
this.config = config;
}
@Override
public List validate(Model model) {
return model.shapes(OperationShape.class)
.flatMap(shape -> OptionalUtils.stream(validateShape(shape, config)))
.collect(Collectors.toList());
}
private Optional validateShape(OperationShape operation, Config config) {
List words = splitCamelCaseWord(operation.getId().getName());
String name;
String foundPrefix = null;
// Detect and remove any single prefix if needed.
if (!config.getPrefixes().contains(words.get(0))) {
name = words.get(0);
} else if (words.size() == 1) {
return Optional.of(danger(operation, format(
"Operation name consists of only a verb prefix: %s", operation.getId().getName())));
} else {
foundPrefix = words.get(0);
name = words.get(1);
}
if (config.getSuggestAlternatives().containsKey(name)) {
return Optional.of(danger(operation, format(
"%s Consider using one of the following verbs instead: %s",
createMessagePrefix(operation, name, foundPrefix),
tickedList(config.getSuggestAlternatives().get(name)))));
} else if (!config.getVerbs().contains(name)) {
return Optional.of(danger(operation, format(
"%s Expected one of the following verbs: %s",
createMessagePrefix(operation, name, foundPrefix),
tickedList(config.getVerbs()))));
} else {
return Optional.empty();
}
}
private static String createMessagePrefix(Shape shape, String name, String prefix) {
StringBuilder builder = new StringBuilder();
builder.append(format("Operation shape `%s` uses a non-standard verb, `%s`", shape.getId().getName(), name));
if (prefix != null) {
builder.append(format(", with a detected prefix of `%s`", prefix));
}
return builder.append(".").toString();
}
}