All Downloads are FREE. Search and download functionalities are using the official Maven repository.

software.amazon.smithy.model.knowledge.OperationIndex Maven / Gradle / Ivy

/*
 * Copyright 2021 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.model.knowledge;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.SourceLocation;
import software.amazon.smithy.model.node.ExpectationNotMetException;
import software.amazon.smithy.model.shapes.MemberShape;
import software.amazon.smithy.model.shapes.OperationShape;
import software.amazon.smithy.model.shapes.ServiceShape;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.model.shapes.StructureShape;
import software.amazon.smithy.model.shapes.ToShapeId;
import software.amazon.smithy.model.traits.InputTrait;
import software.amazon.smithy.model.traits.OutputTrait;
import software.amazon.smithy.model.traits.UnitTypeTrait;
import software.amazon.smithy.utils.ListUtils;
import software.amazon.smithy.utils.SetUtils;

/**
 * Index of operation IDs to their resolved input, output, and error
 * structures.
 *
 * 

This index performs no validation that the input, output, and * errors actually reference valid structures. Such operation inputs, * outputs, and errors may be discarded as if they do not exist. */ public final class OperationIndex implements KnowledgeIndex { private final Map inputs = new HashMap<>(); private final Map outputs = new HashMap<>(); private final Map> errors = new HashMap<>(); private final Map> boundInputOperations = new HashMap<>(); private final Map> boundOutputOperations = new HashMap<>(); private final Map> boundErrorShapes = new HashMap<>(); public OperationIndex(Model model) { for (OperationShape operation : model.getOperationShapes()) { getStructure(model, operation.getInputShape()).ifPresent(shape -> { inputs.put(operation.getId(), shape); boundInputOperations.computeIfAbsent(shape.getId(), id -> new HashSet<>()).add(operation); }); getStructure(model, operation.getOutputShape()).ifPresent(shape -> { outputs.put(operation.getId(), shape); boundOutputOperations.computeIfAbsent(shape.getId(), id -> new HashSet<>()).add(operation); }); addErrorsFromShape(model, operation.getId(), operation.getErrors()); } for (ServiceShape service : model.getServiceShapes()) { addErrorsFromShape(model, service.getId(), service.getErrors()); } } private void addErrorsFromShape(Model model, ShapeId source, List errorShapeIds) { List errorShapes = new ArrayList<>(errorShapeIds.size()); Shape sourceShape = model.expectShape(source); for (ShapeId target : errorShapeIds) { model.getShape(target).flatMap(Shape::asStructureShape).ifPresent(errorShapes::add); boundErrorShapes.computeIfAbsent(target, id -> new HashSet<>()).add(sourceShape); } errors.put(source, errorShapes); } public static OperationIndex of(Model model) { return model.getKnowledge(OperationIndex.class, OperationIndex::new); } /** * Gets the optional input structure of an operation, and returns an * empty optional if the input targets {@code smithy.api#Unit}. * * @param operation Operation to get the input structure of. * @return Returns the optional operation input structure. */ public Optional getInput(ToShapeId operation) { return getInputShape(operation).filter(shape -> !shape.getId().equals(UnitTypeTrait.UNIT)); } /** * Gets the optional input structure of an operation. * *

Operations in the model always have input. This operation will * only return an empty optional if the given operation shape cannot * be found in the model or if it is not an operation shape. * * @param operation Operation to get the input structure of. * @return Returns the optional operation input structure. */ public Optional getInputShape(ToShapeId operation) { return Optional.ofNullable(inputs.get(operation.toShapeId())); } /** * Gets the input shape of an operation, and returns Smithy's Unit type * trait if the operation has no meaningful input. * *

In general, this method should be used instead of * {@link #getInputShape(ToShapeId)} when getting the input of operations * that are known to exist. * * @param operation Operation to get the input of. * @return Returns the input shape of the operation. * @throws ExpectationNotMetException if the operation shape cannot be found. */ public StructureShape expectInputShape(ToShapeId operation) { return getInputShape(operation).orElseThrow(() -> new ExpectationNotMetException( "Cannot get the input of `" + operation.toShapeId() + "` because " + "it is not an operation shape in the model.", SourceLocation.NONE)); } /** * Gets the input members of an operation as a map of member names * to {@link MemberShape}. * *

The return map is ordered using the same order defined in * the model. If the operation has no input, an empty map is returned. * * @param operation Operation to get the input members of. * @return Returns the map of members, or an empty map. */ public Map getInputMembers(ToShapeId operation) { return getInputShape(operation) .map(input -> input.getAllMembers()) .orElse(Collections.emptyMap()); } /** * Returns true if the given structure is used as input by any * operation in the model or is marked with the input trait. * * @param structureId Structure to check. * @return Returns true if the structure is used as input. */ public boolean isInputStructure(ToShapeId structureId) { if (structureId instanceof Shape && ((Shape) structureId).hasTrait(InputTrait.class)) { return true; } ShapeId id = structureId.toShapeId(); for (StructureShape shape : inputs.values()) { if (shape.getId().equals(id)) { return true; } } return false; } /** * Gets all the operations that bind the given shape as input. * * @param input The structure that may be used as input. * @return Returns a set of operations that bind the given input shape. */ public Set getInputBindings(ToShapeId input) { return SetUtils.copyOf(boundInputOperations.getOrDefault(input.toShapeId(), Collections.emptySet())); } /** * Gets the optional output structure of an operation, and returns an * empty optional if the output targets {@code smithy.api#Unit}. * * @param operation Operation to get the output structure of. * @return Returns the optional operation output structure. */ public Optional getOutput(ToShapeId operation) { return getOutputShape(operation).filter(shape -> !shape.getId().equals(UnitTypeTrait.UNIT)); } /** * Gets the optional output structure of an operation. * *

Operations in the model always have output. This operation will * only return an empty optional if the given operation shape cannot * be found in the model or if it is not an operation shape. * * @param operation Operation to get the output structure of. * @return Returns the optional operation output structure. */ public Optional getOutputShape(ToShapeId operation) { return Optional.ofNullable(outputs.get(operation.toShapeId())); } /** * Gets the output shape of an operation, and returns Smithy's unit type * trait if the operation has no meaningful output. * *

In general, this method should be used instead of * {@link #getOutputShape(ToShapeId)} when getting the output of operations * that are known to exist. * * @param operation Operation to get the output of. * @return Returns the output shape of the operation. * @throws ExpectationNotMetException if the operation shape cannot be found. */ public StructureShape expectOutputShape(ToShapeId operation) { return getOutputShape(operation).orElseThrow(() -> new ExpectationNotMetException( "Cannot get the output of `" + operation.toShapeId() + "` because " + "it is not an operation shape in the model.", SourceLocation.NONE)); } /** * Gets the output members of an operation as a map of member names * to {@link MemberShape}. * *

The return map is ordered using the same order defined in * the model. If the operation has no output, an empty map is returned. * * @param operation Operation to get the output members of. * @return Returns the map of members, or an empty map. */ public Map getOutputMembers(ToShapeId operation) { return getOutputShape(operation) .map(output -> output.getAllMembers()) .orElse(Collections.emptyMap()); } /** * Returns true if the given structure is used as output by any * operation in the model or is marked with the output trait. * * @param structureId Structure to check. * @return Returns true if the structure is used as output. */ public boolean isOutputStructure(ToShapeId structureId) { if (structureId instanceof Shape && ((Shape) structureId).hasTrait(OutputTrait.class)) { return true; } ShapeId id = structureId.toShapeId(); for (StructureShape shape : outputs.values()) { if (shape.getId().equals(id)) { return true; } } return false; } /** * Gets all the operations that bind the given shape as output. * * @param output The structure that may be used as output. * @return Returns a set of operations that bind the given output shape. */ public Set getOutputBindings(ToShapeId output) { return SetUtils.copyOf(boundOutputOperations.getOrDefault(output.toShapeId(), Collections.emptySet())); } /** * Gets the list of error structures defined on an operation. * *

An empty list is returned if the operation is not found or * has no errors. * * @param operation Operation to get the errors of. * @return Returns the list of error structures, or an empty list. * @see #getErrors(ToShapeId, ToShapeId) to get errors that inherit from a service. */ public List getErrors(ToShapeId operation) { return errors.getOrDefault(operation.toShapeId(), ListUtils.of()); } /** * Gets the list of error structures defined on an operation, * including any common errors inherited from a service shape. * *

An empty list is returned if the operation is not found or * has no errors. * * @param service Service shape to inherit common errors from. * @param operation Operation to get the errors of. * @return Returns the list of error structures, or an empty list. */ public List getErrors(ToShapeId service, ToShapeId operation) { Set result = new LinkedHashSet<>(getErrors(service)); result.addAll(getErrors(operation)); return new ArrayList<>(result); } private Optional getStructure(Model model, ToShapeId id) { return model.getShape(id.toShapeId()).flatMap(Shape::asStructureShape); } /** * Gets all the operations and services that bind the given shape as an error. * * @param error The structure that may be used as an error. * @return Returns a set of operations and services that bind the given error shape. */ public Set getErrorBindings(ToShapeId error) { return SetUtils.copyOf(boundErrorShapes.getOrDefault(error.toShapeId(), Collections.emptySet())); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy