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

Adds waiter support to smithy-go #237

Merged
merged 23 commits into from
Dec 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
152e733
draft for waiter support
skotambkar Dec 1, 2020
5a80be1
fix dep import, codegen and code styling issues
skotambkar Dec 1, 2020
30a4e83
additional waiter changes and fixes to support mutliple acceptors
skotambkar Dec 1, 2020
1ba08af
update waiter util to export utils and remove hand-written waiter mid…
skotambkar Dec 2, 2020
81faf82
update codegen to generate waiter specific middleware
skotambkar Dec 2, 2020
e18ca4f
simplify waiter state mutator and fix issue with multi acceptor waiters
skotambkar Dec 2, 2020
53751d0
more model driven testing, and fine-tuning waiters for different comp…
skotambkar Dec 2, 2020
ecdac4f
make comparators case sensitive, update unmodeled error handling and …
skotambkar Dec 2, 2020
96d63bb
move sleep with context method to smithy time package
skotambkar Dec 3, 2020
3adc0fc
feedback and change of design
skotambkar Dec 3, 2020
cc1c95c
update compute delay waiter util
skotambkar Dec 3, 2020
06ecf6c
minor fix
skotambkar Dec 3, 2020
48917fb
update waiter util, naming and add tests
skotambkar Dec 3, 2020
865d004
feedback around naming of java class and go package
skotambkar Dec 4, 2020
6f95047
updates jitter and delay computation for waiter
skotambkar Dec 5, 2020
0ff9d3f
update to use change delay computation function
skotambkar Dec 5, 2020
c4c9a5d
fix operation interface generator for incorrect exceptions
skotambkar Dec 5, 2020
b8cdbaa
update waiter compute delay util and adds waiter logger middleware
skotambkar Dec 8, 2020
3806a96
update java code as per feedback
skotambkar Dec 8, 2020
23a9719
use paginationIndex for paginator interface generation
skotambkar Dec 9, 2020
6cc9c96
doc update
skotambkar Dec 9, 2020
2bbbd84
feedback around remainingTime computation
skotambkar Dec 9, 2020
1d741e4
update waiter call workflow
skotambkar Dec 9, 2020
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
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": {
skotambkar marked this conversation as resolved.
Show resolved Hide resolved
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);
skotambkar marked this conversation as resolved.
Show resolved Hide resolved

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