private void generateWaiterInvokerWithOutput()

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);
                });
    }