in codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/Waiters2.java [332:498]
private void generateWaiterInvokerWithOutput(
Model model,
SymbolProvider symbolProvider,
GoWriter writer,
OperationShape operationShape,
String waiterName,
Waiter waiter
) {
StructureShape inputShape = model.expectShape(
operationShape.getInput().get(), StructureShape.class
);
StructureShape outputShape = model.expectShape(
operationShape.getOutput().get(), StructureShape.class
);
Symbol operationSymbol = symbolProvider.toSymbol(operationShape);
Symbol inputSymbol = symbolProvider.toSymbol(inputShape);
Symbol outputSymbol = symbolProvider.toSymbol(outputShape);
Symbol waiterOptionsSymbol = SymbolUtils.createPointableSymbolBuilder(
generateWaiterOptionsName(waiterName)
).build();
Symbol clientSymbol = SymbolUtils.createPointableSymbolBuilder(
generateWaiterClientName(waiterName)
).build();
writer.write("");
writer.addUseImports(SmithyGoDependency.CONTEXT);
writer.addUseImports(SmithyGoDependency.TIME);
writer.writeDocs(
String.format(
"%s calls the waiter function for %s waiter and returns the output of the successful "
+ "operation. The maxWaitDur is the maximum wait duration the waiter will wait. The "
+ "maxWaitDur is required and must be greater than zero.",
WAITER_INVOKER_WITH_OUTPUT_FUNCTION_NAME, waiterName)
);
writer.openBlock(
"func (w $P) $L(ctx context.Context, params $P, maxWaitDur time.Duration, optFns ...func($P)) "
+ "($P, error) {",
"}",
clientSymbol, WAITER_INVOKER_WITH_OUTPUT_FUNCTION_NAME, inputSymbol, waiterOptionsSymbol, outputSymbol,
() -> {
writer.openBlock("if maxWaitDur <= 0 {", "}", () -> {
writer.addUseImports(SmithyGoDependency.FMT);
writer.write(
"return nil, fmt.Errorf(\"maximum wait time for waiter must be greater than zero\")"
);
}).write("");
writer.write("options := w.options");
writer.openBlock("for _, fn := range optFns {",
"}", () -> {
writer.write("fn(&options)");
});
writer.write("");
// validate values for MaxDelay from options
writer.openBlock("if options.MaxDelay <= 0 {", "}", () -> {
writer.write("options.MaxDelay = $L * time.Second", waiter.getMaxDelay());
});
writer.write("");
// validate that MinDelay is lesser than or equal to resolved MaxDelay
writer.openBlock("if options.MinDelay > options.MaxDelay {", "}", () -> {
writer.addUseImports(SmithyGoDependency.FMT);
writer.write("return nil, fmt.Errorf(\"minimum waiter delay %v must be lesser than or equal to "
+ "maximum waiter delay of %v.\", options.MinDelay, options.MaxDelay)");
}).write("");
writer.addUseImports(SmithyGoDependency.CONTEXT);
writer.write("ctx, cancelFn := context.WithTimeout(ctx, maxWaitDur)");
writer.write("defer cancelFn()");
writer.write("");
Symbol loggerMiddleware = SymbolUtils.createValueSymbolBuilder(
"Logger", SmithyGoDependency.SMITHY_WAITERS
).build();
writer.write("logger := $T{}", loggerMiddleware);
writer.write("remainingTime := maxWaitDur").write("");
writer.write("var attempt int64");
writer.openBlock("for {", "}", () -> {
writer.write("");
writer.write("attempt++");
writer.write("apiOptions := options.APIOptions");
writer.write("start := time.Now()").write("");
// add waiter logger middleware to log an attempt, if LogWaitAttempts is enabled.
writer.openBlock("if options.LogWaitAttempts {", "}", () -> {
writer.write("logger.Attempt = attempt");
writer.write(
"apiOptions = append([]func(*middleware.Stack) error{}, options.APIOptions...)");
writer.write("apiOptions = append(apiOptions, logger.AddLogger)");
}).write("");
// make a request
var baseOpts = GoWriter.ChainWritable.of(
getAdditionalClientOptions().stream()
.map(it -> goTemplate("$T,", it))
.toList()
).compose(false);
writer.openBlock("out, err := w.client.$T(ctx, params, func (o *Options) { ", "})",
operationSymbol, () -> {
writer.write("""
baseOpts := []func(*Options) {
$W
}""", baseOpts);
writer.write("o.APIOptions = append(o.APIOptions, apiOptions...)");
writer.write("""
for _, opt := range baseOpts {
opt(o)
}
for _, opt := range options.ClientOptions {
opt(o)
}""");
});
writer.write("");
// handle response and identify waiter state
writer.write("retryable, err := options.Retryable(ctx, params, out, err)");
writer.write("if err != nil { return nil, err }");
writer.write("if !retryable { return out, nil }").write("");
// update remaining time
writer.write("remainingTime -= time.Since(start)");
// check if next iteration is possible
writer.openBlock("if remainingTime < options.MinDelay || remainingTime <= 0 {", "}", () -> {
writer.write("break");
});
writer.write("");
// handle retry delay computation, sleep.
Symbol computeDelaySymbol = SymbolUtils.createValueSymbolBuilder(
"ComputeDelay", SmithyGoDependency.SMITHY_WAITERS
).build();
writer.writeDocs("compute exponential backoff between waiter retries");
writer.openBlock("delay, err := $T(", ")", computeDelaySymbol, () -> {
writer.write("attempt, options.MinDelay, options.MaxDelay, remainingTime,");
});
writer.addUseImports(SmithyGoDependency.FMT);
writer.write(
"if err != nil { return nil, fmt.Errorf(\"error computing waiter delay, %w\", err)}");
writer.write("");
// update remaining time as per computed delay
writer.write("remainingTime -= delay");
// sleep for delay
Symbol sleepWithContextSymbol = SymbolUtils.createValueSymbolBuilder(
"SleepWithContext", SmithyGoDependency.SMITHY_TIME
).build();
writer.writeDocs("sleep for the delay amount before invoking a request");
writer.openBlock("if err := $T(ctx, delay); err != nil {", "}", sleepWithContextSymbol,
() -> {
writer.write(
"return nil, fmt.Errorf(\"request cancelled while waiting, %w\", err)");
});
});
writer.write("return nil, fmt.Errorf(\"exceeded max wait time for $L waiter\")", waiterName);
});
}