private void generateRetryable()

in codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/Waiters.java [487:648]


    private void generateRetryable(
            Model model,
            SymbolProvider symbolProvider,
            GoWriter writer,
            ServiceShape serviceShape,
            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 inputSymbol = symbolProvider.toSymbol(inputShape);
        Symbol outputSymbol = symbolProvider.toSymbol(outputShape);

        writer.write("");
        writer.openBlock("func $L(ctx context.Context, input $P, output $P, err error) (bool, error) {",
                "}", generateRetryableName(waiterName), inputSymbol, outputSymbol, () -> {
                    waiter.getAcceptors().forEach(acceptor -> {
                        writer.write("");
                        // scope each acceptor to avoid name collisions
                        Matcher matcher = acceptor.getMatcher();
                        switch (matcher.getMemberName()) {
                            case "output":
                                writer.addUseImports(SmithyGoDependency.GO_JMESPATH);
                                writer.addUseImports(SmithyGoDependency.FMT);

                                Matcher.OutputMember outputMember = (Matcher.OutputMember) matcher;
                                String path = outputMember.getValue().getPath();
                                String expectedValue = outputMember.getValue().getExpected();
                                PathComparator comparator = outputMember.getValue().getComparator();
                                writer.openBlock("if err == nil {", "}", () -> {
                                    writer.write("pathValue, err :=  jmespath.Search($S, output)", path);
                                    writer.openBlock("if err != nil {", "}", () -> {
                                        writer.write(
                                                "return false, "
                                                        + "fmt.Errorf(\"error evaluating waiter state: %w\", err)");
                                    }).write("");
                                    writer.write("expectedValue := $S", expectedValue);

                                    if (comparator == PathComparator.BOOLEAN_EQUALS) {
                                        writeWaiterComparator(writer, acceptor, comparator, null, "pathValue",
                                                "expectedValue");
                                    } else {
                                        String[] pathMembers = path.split("\\.");
                                        Shape targetShape = outputShape;
                                        for (int i = 0; i < pathMembers.length; i++) {
                                            MemberShape member = getComparedMember(model, targetShape, pathMembers[i]);
                                            if (member == null) {
                                                targetShape = null;
                                                break;
                                            }
                                            targetShape = model.expectShape(member.getTarget());
                                        }

                                        if (targetShape == null) {
                                            writeWaiterComparator(writer, acceptor, comparator, null, "pathValue",
                                                    "expectedValue");
                                        } else {
                                            Symbol targetSymbol = symbolProvider.toSymbol(targetShape);
                                            writeWaiterComparator(writer, acceptor, comparator, targetSymbol,
                                                    "pathValue",
                                                    "expectedValue");
                                        }
                                    }
                                });
                                break;

                            case "inputOutput":
                                writer.addUseImports(SmithyGoDependency.GO_JMESPATH);
                                writer.addUseImports(SmithyGoDependency.FMT);

                                Matcher.InputOutputMember ioMember = (Matcher.InputOutputMember) matcher;
                                path = ioMember.getValue().getPath();
                                expectedValue = ioMember.getValue().getExpected();
                                comparator = ioMember.getValue().getComparator();
                                writer.openBlock("if err == nil {", "}", () -> {
                                    writer.openBlock("pathValue, err :=  jmespath.Search($S, &struct{",
                                            "})", path, () -> {
                                                writer.write("Input $P \n Output $P \n }{", inputSymbol,
                                                        outputSymbol);
                                                writer.write("Input: input, \n Output: output, \n");
                                            });
                                    writer.openBlock("if err != nil {", "}", () -> {
                                        writer.write(
                                                "return false, "
                                                        + "fmt.Errorf(\"error evaluating waiter state: %w\", err)");
                                    });
                                    writer.write("");
                                    writer.write("expectedValue := $S", expectedValue);
                                    writeWaiterComparator(writer, acceptor, comparator, outputSymbol, "pathValue",
                                            "expectedValue");
                                });
                                break;

                            case "success":
                                Matcher.SuccessMember successMember = (Matcher.SuccessMember) matcher;
                                writer.openBlock("if err == nil {", "}",
                                        () -> {
                                            writeMatchedAcceptorReturn(writer, acceptor);
                                        });
                                break;

                            case "errorType":
                                Matcher.ErrorTypeMember errorTypeMember = (Matcher.ErrorTypeMember) matcher;
                                String errorType = errorTypeMember.getValue();

                                writer.openBlock("if err != nil {", "}", () -> {

                                    // identify if this is a modeled error shape
                                    Optional<ShapeId> errorShapeId = operationShape.getErrors().stream().filter(
                                            shapeId -> {
                                                return shapeId.getName(serviceShape).equalsIgnoreCase(errorType);
                                            }).findFirst();

                                    // if modeled error shape
                                    if (errorShapeId.isPresent()) {
                                        Shape errorShape = model.expectShape(errorShapeId.get());
                                        Symbol modeledErrorSymbol = symbolProvider.toSymbol(errorShape);
                                        writer.addUseImports(SmithyGoDependency.ERRORS);
                                        writer.write("var errorType *$T", modeledErrorSymbol);
                                        writer.openBlock("if errors.As(err, &errorType) {", "}", () -> {
                                            writeMatchedAcceptorReturn(writer, acceptor);
                                        });
                                    } else {
                                        // fall back to un-modeled error shape matching
                                        writer.addUseImports(SmithyGoDependency.SMITHY);
                                        writer.addUseImports(SmithyGoDependency.ERRORS);

                                        // assert unmodeled error to smithy's API error
                                        writer.write("var apiErr smithy.APIError");
                                        writer.write("ok := errors.As(err, &apiErr)");
                                        writer.openBlock("if !ok {", "}", () -> {
                                            writer.write("return false, "
                                                    + "fmt.Errorf(\"expected err to be of type smithy.APIError, "
                                                    + "got %w\", err)");
                                        });
                                        writer.write("");

                                        writer.openBlock("if $S == apiErr.ErrorCode() {", "}",
                                                errorType, () -> {
                                                    writeMatchedAcceptorReturn(writer, acceptor);
                                                });
                                    }
                                });
                                break;

                            default:
                                throw new CodegenException(
                                        String.format("unknown waiter state : %v", matcher.getMemberName())
                                );
                        }
                    });

                    writer.write("");
                    writer.write("return true, nil");
                });
    }