Skip to content

Commit

Permalink
Merge pull request #237 from awslabs/waiter_impl
Browse files Browse the repository at this point in the history
Adds waiter support to smithy-go
  • Loading branch information
skotambkar authored Dec 10, 2020
2 parents 525ee12 + 1d741e4 commit a679708
Show file tree
Hide file tree
Showing 12 changed files with 1,214 additions and 30 deletions.
80 changes: 80 additions & 0 deletions codegen/smithy-go-codegen-test/model/main.smithy
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ namespace example.weather

use smithy.test#httpRequestTests
use smithy.test#httpResponseTests
use smithy.waiters#waitable

/// Provides weather forecasts.
@fakeProtocol
Expand Down Expand Up @@ -36,6 +37,56 @@ resource CityImage {
string CityId

@readonly
@waitable(
CityExists: {
description: "Waits until a city has been created",
acceptors: [
// Fail-fast if the thing transitions to a "failed" state.
{
state: "failure",
matcher: {
errorType: "NoSuchResource"
}
},
// Fail-fast if the thing transitions to a "failed" state.
{
state: "failure",
matcher: {
errorType: "UnModeledError"
}
},
// Succeed when the city image value is not empty i.e. enters into a "success" state.
{
state: "success",
matcher: {
success: true
}
},
// Retry if city id input is of same length as city name in output
{
state: "retry",
matcher: {
inputOutput: {
path: "length(input.cityId) == length(output.name)",
comparator: "booleanEquals",
expected: "true",
}
}
},
// Success if city name in output is seattle
{
state: "success",
matcher: {
output: {
path: "name",
comparator: "stringEquals",
expected: "seattle",
}
}
}
]
}
)
@http(method: "GET", uri: "/cities/{cityId}")
operation GetCity {
input: GetCityInput,
Expand Down Expand Up @@ -178,6 +229,35 @@ apply NoSuchResource @httpResponseTests([
// return truncated results.
@readonly
@paginated(items: "items")
@waitable(
"ListContainsCity": {
description: "Wait until ListCities operation response matches a given state",
acceptors: [
// failure in case all items returned match to seattle
{
state: "failure",
matcher: {
output: {
path: "items",
comparator: "allStringEquals",
expected: "seattle",
}
}
},
// success in case any items returned match to NewYork
{
state: "success",
matcher: {
output: {
path: "items",
comparator: "anyStringEquals",
expected: "NewYork",
}
}
}
]
}
)
@http(method: "GET", uri: "/cities")
operation ListCities {
input: ListCitiesInput,
Expand Down
1 change: 1 addition & 0 deletions codegen/smithy-go-codegen/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ extra["moduleName"] = "software.amazon.smithy.go.codegen"

dependencies {
api("software.amazon.smithy:smithy-codegen-core:[1.3.0,2.0.0[")
implementation("software.amazon.smithy:smithy-waiters:[1.4.0,2.0.0[")
compile("com.atlassian.commonmark:commonmark:0.15.2")
api("org.jsoup:jsoup:1.13.1")
implementation("software.amazon.smithy:smithy-protocol-test-traits:[1.3.0,2.0.0[")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,16 @@ public final class SmithyGoDependency {
public static final GoDependency SMITHY_RAND = smithy("rand", "smithyrand");
public static final GoDependency SMITHY_TESTING = smithy("testing", "smithytesting");
public static final GoDependency SMITHY_XML = smithy("xml", "smithyxml");
public static final GoDependency SMITHY_WAITERS = smithy("waiter", "smithywaiter");

public static final GoDependency GO_CMP = goCmp("cmp");
public static final GoDependency GO_CMP_OPTIONS = goCmp("cmp/cmpopts");

public static final GoDependency GO_JMESPATH = goJmespath(null);

private static final String SMITHY_SOURCE_PATH = "github.com/awslabs/smithy-go";
private static final String GO_CMP_SOURCE_PATH = "github.com/google/go-cmp";
private static final String GO_JMESPATH_SOURCE_PATH = "github.com/jmespath/go-jmespath";

private SmithyGoDependency() {
}
Expand Down Expand Up @@ -94,6 +98,10 @@ private static GoDependency goCmp(String relativePath) {
return relativePackage(GO_CMP_SOURCE_PATH, relativePath, Versions.GO_CMP, null);
}

private static GoDependency goJmespath(String relativePath) {
return relativePackage(GO_JMESPATH_SOURCE_PATH, relativePath, Versions.GO_JMESPATH, null);
}

private static GoDependency relativePackage(
String moduleImportPath,
String relativePath,
Expand All @@ -110,6 +118,7 @@ private static GoDependency relativePackage(
private static final class Versions {
private static final String GO_STDLIB = "1.15";
private static final String GO_CMP = "v0.5.4";
private static final String SMITHY_GO = "v0.4.0";
private static final String SMITHY_GO = "v0.4.1-0.20201208232924-b8cdbaa577ff";
private static final String GO_JMESPATH = "v0.4.0";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
* Copyright 2020 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.go.codegen.integration;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import software.amazon.smithy.codegen.core.Symbol;
import software.amazon.smithy.codegen.core.SymbolProvider;
import software.amazon.smithy.go.codegen.GoDelegator;
import software.amazon.smithy.go.codegen.GoSettings;
import software.amazon.smithy.go.codegen.GoWriter;
import software.amazon.smithy.go.codegen.SmithyGoDependency;
import software.amazon.smithy.go.codegen.SymbolUtils;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.knowledge.PaginatedIndex;
import software.amazon.smithy.model.knowledge.TopDownIndex;
import software.amazon.smithy.model.shapes.OperationShape;
import software.amazon.smithy.model.shapes.ServiceShape;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.waiters.WaitableTrait;

/**
* Generates API client Interfaces as per API operation.
*/
public class OperationInterfaceGenerator implements GoIntegration {

private static Map<ShapeId, Set<ShapeId>> mapOfClientInterfaceOperations = new HashMap<>();

/**
* Returns name of an API client interface.
*
* @param operationSymbol Symbol of operation shape for which Api client interface is being generated.
* @return name of the interface.
*/
public static String getApiClientInterfaceName(
Symbol operationSymbol
) {
return String.format("%sAPIClient", operationSymbol.getName());
}

@Override
public void processFinalizedModel(
GoSettings settings,
Model model
) {
ServiceShape serviceShape = settings.getService(model);
TopDownIndex topDownIndex = TopDownIndex.of(model);
PaginatedIndex paginatedIndex = PaginatedIndex.of(model);

Set<ShapeId> listOfClientInterfaceOperations = new TreeSet<>();

// fetch operations for which paginators are generated
topDownIndex.getContainedOperations(serviceShape).stream()
.map(operationShape -> paginatedIndex.getPaginationInfo(serviceShape, operationShape))
.filter(Optional::isPresent)
.map(Optional::get)
.forEach(paginationInfo -> listOfClientInterfaceOperations.add(paginationInfo.getOperation().getId()));

// fetch operations for which waitable trait is applied
topDownIndex.getContainedOperations(serviceShape).stream()
.filter(operationShape -> operationShape.hasTrait(WaitableTrait.class))
.forEach(operationShape -> listOfClientInterfaceOperations.add(operationShape.getId()));

if (!listOfClientInterfaceOperations.isEmpty()) {
mapOfClientInterfaceOperations.put(serviceShape.getId(), listOfClientInterfaceOperations);
}
}

@Override
public void writeAdditionalFiles(
GoSettings settings,
Model model,
SymbolProvider symbolProvider,
GoDelegator goDelegator
) {
ShapeId serviceId = settings.getService(model).getId();

if (mapOfClientInterfaceOperations.containsKey(serviceId)) {
Set<ShapeId> listOfClientInterfaceOperations = mapOfClientInterfaceOperations.get(serviceId);
listOfClientInterfaceOperations.stream().forEach(shapeId -> {
OperationShape operationShape = model.expectShape(shapeId, OperationShape.class);
goDelegator.useShapeWriter(operationShape, writer -> {
generateApiClientInterface(writer, model, symbolProvider, operationShape);
});
});
}
}

private void generateApiClientInterface(
GoWriter writer,
Model model,
SymbolProvider symbolProvider,
OperationShape operationShape
) {
Symbol contextSymbol = SymbolUtils.createValueSymbolBuilder("Context", SmithyGoDependency.CONTEXT)
.build();

Symbol operationSymbol = symbolProvider.toSymbol(operationShape);

Symbol interfaceSymbol = SymbolUtils.createValueSymbolBuilder(getApiClientInterfaceName(operationSymbol))
.build();

Symbol inputSymbol = symbolProvider.toSymbol(model.expectShape(operationShape.getInput().get()));
Symbol outputSymbol = symbolProvider.toSymbol(model.expectShape(operationShape.getOutput().get()));

writer.writeDocs(String.format("%s is a client that implements the %s operation.",
interfaceSymbol.getName(), operationSymbol.getName()));
writer.openBlock("type $T interface {", "}", interfaceSymbol, () -> {
writer.write("$L($T, $P, ...func(*Options)) ($P, error)", operationSymbol.getName(), contextSymbol,
inputSymbol, outputSymbol);
});
writer.write("");
writer.write("var _ $T = (*Client)(nil)", interfaceSymbol);
writer.write("");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,14 @@ private void generateOperationPaginator(
) {
Symbol operationSymbol = symbolProvider.toSymbol(paginationInfo.getOperation());

Symbol interfaceSymbol = SymbolUtils.createValueSymbolBuilder(String.format("%sAPIClient",
operationSymbol.getName())).build();
Symbol interfaceSymbol = SymbolUtils.createValueSymbolBuilder(
OperationInterfaceGenerator.getApiClientInterfaceName(operationSymbol)
).build();
Symbol paginatorSymbol = SymbolUtils.createPointableSymbolBuilder(String.format("%sPaginator",
operationSymbol.getName())).build();
Symbol optionsSymbol = SymbolUtils.createPointableSymbolBuilder(String.format("%sOptions",
paginatorSymbol.getName())).build();

writeClientOperationInterface(writer, symbolProvider, paginationInfo, interfaceSymbol);
writePaginatorOptions(writer, model, symbolProvider, paginationInfo, operationSymbol, optionsSymbol);
writePaginator(writer, model, symbolProvider, paginationInfo, interfaceSymbol, paginatorSymbol, optionsSymbol);
}
Expand Down Expand Up @@ -249,30 +249,4 @@ private void writePaginatorOptions(
});
writer.write("");
}

private void writeClientOperationInterface(
GoWriter writer,
SymbolProvider symbolProvider,
PaginationInfo paginationInfo,
Symbol interfaceSymbol
) {
Symbol contextSymbol = SymbolUtils.createValueSymbolBuilder("Context", SmithyGoDependency.CONTEXT)
.build();

Symbol operationSymbol = symbolProvider.toSymbol(paginationInfo.getOperation());
Symbol inputSymbol = symbolProvider.toSymbol(paginationInfo.getInput());
Symbol outputSymbol = symbolProvider.toSymbol(paginationInfo.getOutput());

writer.writeDocs(String.format("%s is a client that implements the %s operation.",
interfaceSymbol.getName(), operationSymbol.getName()));
writer.openBlock("type $T interface {", "}", interfaceSymbol, () -> {
writer.write("$L($T, $P, ...func(*Options)) ($P, error)", operationSymbol.getName(), contextSymbol,
inputSymbol, outputSymbol);
});
writer.write("");
writer.write("var _ $T = (*Client)(nil)", interfaceSymbol);
writer.write("");
}


}
Loading

0 comments on commit a679708

Please sign in to comment.