private void writePaginator()

in codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/Paginators.java [97:310]


    private void writePaginator(
            GoWriter writer,
            Model model,
            SymbolProvider symbolProvider,
            PaginationInfo paginationInfo,
            Symbol interfaceSymbol,
            Symbol paginatorSymbol,
            Symbol optionsSymbol
    ) {
        var inputMember = symbolProvider.toMemberName(paginationInfo.getInputTokenMember());

        var operation = paginationInfo.getOperation();
        var pagingExtensionTrait = operation.getTrait(PagingExtensionTrait.class);

        var operationSymbol = symbolProvider.toSymbol(operation);
        var inputSymbol = symbolProvider.toSymbol(paginationInfo.getInput());
        var inputTokenSymbol = symbolProvider.toSymbol(paginationInfo.getInputTokenMember());
        var inputTokenShape = model.expectShape(paginationInfo.getInputTokenMember().getTarget());

        GoPointableIndex pointableIndex = GoPointableIndex.of(model);

        writer.pushState();
        writer.putContext("paginator", paginatorSymbol);
        writer.putContext("options", optionsSymbol);
        writer.putContext("client", interfaceSymbol);
        writer.putContext("input", inputSymbol);
        writer.putContext("token", inputTokenSymbol);
        writer.putContext("inputMember", inputMember);

        writer.writeDocs(String.format("%s is a paginator for %s", paginatorSymbol, operationSymbol.getName()));
        writer.write("""
                     type $paginator:T struct {
                         options $options:T
                         client $client:T
                         params $input:P
                         nextToken $token:P
                         firstPage bool
                     }
                     """);

        Symbol newPagiantor = SymbolUtils.createValueSymbolBuilder(String.format("New%s",
                paginatorSymbol.getName())).build();

        writer.putContext("newPaginator", newPagiantor);

        writer.writeDocs(String.format("%s returns a new %s", newPagiantor.getName(), paginatorSymbol.getName()));
        writer.openBlock("func $newPaginator:T(client $client:T, params $input:P, "
                         + "optFns ...func($options:P)) $paginator:P {", "}",
                () -> {
                    writer.write("""
                                 if params == nil {
                                     params = &$input:T{}
                                 }

                                 options := $options:T{}""");
                    paginationInfo.getPageSizeMember().ifPresent(memberShape -> {
                        GoValueAccessUtils.writeIfNonZeroValueMember(model, symbolProvider, writer, memberShape,
                                "params", op -> {
                                    op = CodegenUtils.getAsValueIfDereferencable(pointableIndex, memberShape, op);
                                    writer.write("options.Limit = $L", op);
                                });

                    });
                    writer.write("""

                                 for _, fn := range optFns {
                                     fn(&options)
                                 }

                                 return &$paginator:T{
                                     options: options,
                                     client: client,
                                     params: params,
                                     firstPage: true,
                                     nextToken: params.$inputMember:L,
                                 }""");
                }).write("");

        writer.writeDocs("HasMorePages returns a boolean indicating whether more pages are available");
        writer.openBlock("func (p $paginator:P) HasMorePages() bool {", "}", () -> {
            writer.writeInline("return p.firstPage || ");
            Runnable checkNotNil = () -> writer.writeInline("p.nextToken != nil");
            if (inputTokenShape.getType() == ShapeType.STRING) {
                writer.writeInline("(");
                checkNotNil.run();
                writer.write(" && len(*p.nextToken) != 0 )");
            } else {
                checkNotNil.run();
            }
        }).write("");

        var contextSymbol = SymbolUtils.createValueSymbolBuilder("Context", SmithyGoDependency.CONTEXT)
                .build();
        var outputSymbol = symbolProvider.toSymbol(paginationInfo.getOutput());
        var pageSizeMember = paginationInfo.getPageSizeMember();

        writer.putContext("context", contextSymbol);
        writer.putContext("output", outputSymbol);

        writer.writeDocs(String.format("NextPage retrieves the next %s page.", operationSymbol.getName()));
        writer.openBlock("func (p $paginator:P) NextPage(ctx $context:T, optFns ...func(*Options)) "
                         + "($output:P, error) {", "}",
                () -> {
                    writer.putContext("errorf", SymbolUtils.createValueSymbolBuilder("Errorf",
                            SmithyGoDependency.FMT).build());
                    writer.write("""
                                 if !p.HasMorePages() {
                                     return nil, $errorf:T("no more pages available")
                                 }

                                 params := *p.params
                                 params.$inputMember:L = p.nextToken
                                 """);

                    pageSizeMember.ifPresent(memberShape -> {
                        if (pointableIndex.isPointable(memberShape)) {
                            writer.write("""
                                         var limit $P
                                         if p.options.Limit > 0 {
                                             limit = &p.options.Limit
                                         }
                                         params.$L = limit
                                         """,
                                    symbolProvider.toSymbol(memberShape),
                                    symbolProvider.toMemberName(memberShape));
                        } else {
                            writer.write("params.$L = p.options.Limit", symbolProvider.toMemberName(memberShape))
                                    .write("");
                        }
                    });

                    var optFns = GoWriter.ChainWritable.of(
                            getAdditionalClientOptions().stream()
                                    .map(it -> goTemplate("$T,", it))
                                    .toList()
                    ).compose(false);
                    writer.write("""
                                 optFns = append([]func(*Options) {
                                     $W
                                 }, optFns...)
                                 result, err := p.client.$L(ctx, &params, optFns...)
                                 if err != nil {
                                     return nil, err
                                 }
                                 p.firstPage = false
                                 """, optFns, operationSymbol.getName());

                    var outputMemberPath = paginationInfo.getOutputTokenMemberPath();
                    var tokenMember = outputMemberPath.get(outputMemberPath.size() - 1);
                    Consumer<String> setNextTokenFromOutput = (container) -> {
                        writer.write("p.nextToken = $L", container + "."
                                                         + symbolProvider.toMemberName(tokenMember));
                    };

                    for (int i = outputMemberPath.size() - 2; i >= 0; i--) {
                        var memberShape = outputMemberPath.get(i);
                        Consumer<String> inner = setNextTokenFromOutput;
                        setNextTokenFromOutput = (container) -> {
                            GoValueAccessUtils.writeIfNonZeroValueMember(model, symbolProvider, writer, memberShape,
                                    container, inner);
                        };
                    }

                    {
                        final Consumer<String> inner = setNextTokenFromOutput;
                        setNextTokenFromOutput = s -> {
                            if (outputMemberPath.size() > 1) {
                                writer.write("p.nextToken = nil");
                            }
                            inner.accept(s);
                        };
                    }

                    {
                        final Consumer<String> setToken = setNextTokenFromOutput;
                        writer.write("prevToken := p.nextToken");
                        Optional<MemberShape> moreResults = pagingExtensionTrait
                                .flatMap(PagingExtensionTrait::getMoreResults);

                        if (moreResults.isPresent()) {
                            MemberShape memberShape = moreResults.get();
                            model.expectShape(memberShape.getTarget(), BooleanShape.class); // Must be boolean
                            writer.write("p.nextToken = nil");
                            String memberName = symbolProvider.toMemberName(memberShape);
                            if (pointableIndex.isNillable(memberShape.getTarget())) {
                                writer.openBlock("if result.$L != nil && *result.$L {", "}", memberName, memberName,
                                        () -> setToken.accept("result"));
                            } else {
                                writer.openBlock("if result.$L {", "}", memberName, () -> setToken.accept("result"));
                            }
                        } else {
                            setToken.accept("result");
                        }
                    }
                    writer.write("");

                    if (inputTokenShape.isStringShape()) {
                        writer.write("""
                                     if p.options.StopOnDuplicateToken &&
                                         prevToken != nil &&
                                         p.nextToken != nil &&
                                         *prevToken == *p.nextToken {
                                         p.nextToken = nil
                                     }
                                     """);
                    } else {
                        writer.write("_ = prevToken").write("");
                    }

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

        writer.popState();
    }