software.amazon.smithy.mqtt.traits.validators.MqttTopicLabelValidator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of smithy-mqtt-traits Show documentation
Show all versions of smithy-mqtt-traits Show documentation
This module provides the implementation of MQTT binding traits for Smithy.
/*
* 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.mqtt.traits.validators;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.knowledge.OperationIndex;
import software.amazon.smithy.model.shapes.MemberShape;
import software.amazon.smithy.model.shapes.OperationShape;
import software.amazon.smithy.model.shapes.StructureShape;
import software.amazon.smithy.model.traits.Trait;
import software.amazon.smithy.model.validation.AbstractValidator;
import software.amazon.smithy.model.validation.ValidationEvent;
import software.amazon.smithy.model.validation.ValidationUtils;
import software.amazon.smithy.mqtt.traits.PublishTrait;
import software.amazon.smithy.mqtt.traits.SubscribeTrait;
import software.amazon.smithy.mqtt.traits.Topic;
import software.amazon.smithy.mqtt.traits.TopicLabelTrait;
import software.amazon.smithy.utils.SmithyInternalApi;
/**
* Validates that labels are correct for MQTT topics on
* {@code subscribe} and {@code publish} operations.
*
*
* - Operation input is required when a topic has labels.
* - Topic labels must be found in the input.
* - The input must not contain extraneous labels.
*
*/
@SmithyInternalApi
public class MqttTopicLabelValidator extends AbstractValidator {
@Override
public List validate(Model model) {
OperationIndex index = OperationIndex.of(model);
List events = new ArrayList<>();
for (OperationShape operation : model.getOperationShapes()) {
TopicCollection topics = createTopics(operation);
if (topics != null) {
events.addAll(validateMqtt(index, topics));
}
}
return events;
}
private static TopicCollection createTopics(OperationShape shape) {
if (shape.hasTrait(SubscribeTrait.class)) {
SubscribeTrait trait = shape.expectTrait(SubscribeTrait.class);
List bindings = Collections.singletonList(trait.getTopic());
return new TopicCollection(shape, trait, bindings);
} else if (shape.hasTrait(PublishTrait.class)) {
PublishTrait trait = shape.expectTrait(PublishTrait.class);
List bindings = Collections.singletonList(trait.getTopic());
return new TopicCollection(shape, trait, bindings);
} else {
return null;
}
}
private List validateMqtt(OperationIndex index, TopicCollection topics) {
Set labels = topics.getLabels();
StructureShape input = index.expectInputShape(topics.operation);
List events = new ArrayList<>();
for (MemberShape member : input.getAllMembers().values()) {
if (member.hasTrait(TopicLabelTrait.class)) {
if (labels.contains(member.getMemberName())) {
labels.remove(member.getMemberName());
} else {
events.add(error(member, member.expectTrait(TopicLabelTrait.class), String.format(
"This member is marked with the `smithy.mqtt#topicLabel` trait, but when this member is "
+ "used as part of the input of the `%s` operation, a corresponding label cannot be "
+ "found in the `%s` trait",
topics.operation.getId(),
Trait.getIdiomaticTraitName(topics.trait.toShapeId()))));
}
}
}
if (!labels.isEmpty()) {
events.add(error(topics.operation, topics.trait, String.format(
"The `%s` trait contains the following topic labels that could not be found in the input "
+ "structure of the operation or were not marked with the `smithy.mqtt#topicLabel` trait: [%s]",
Trait.getIdiomaticTraitName(topics.trait.toShapeId()),
ValidationUtils.tickedList(labels))));
}
return events;
}
private static final class TopicCollection {
final List topics;
final OperationShape operation;
final Trait trait;
TopicCollection(OperationShape operation, Trait trait, List topics) {
this.operation = operation;
this.trait = trait;
this.topics = topics;
}
Set getLabels() {
return topics.stream()
.flatMap(topic -> topic.getLabels().stream().map(Topic.Level::getContent))
.collect(Collectors.toSet());
}
}
}