From c2f2d470684606b627ba436e244dfbffe95eddd7 Mon Sep 17 00:00:00 2001 From: Sean McGrail Date: Thu, 5 Nov 2020 14:59:38 -0800 Subject: [PATCH 1/6] PaginationTrait Support --- .../go/codegen/integration/Paginators.java | 254 ++++++++++++++++++ ...mithy.go.codegen.integration.GoIntegration | 1 + 2 files changed, 255 insertions(+) create mode 100644 codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/Paginators.java diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/Paginators.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/Paginators.java new file mode 100644 index 000000000..1e95599ef --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/Paginators.java @@ -0,0 +1,254 @@ +/* + * 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.List; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import software.amazon.smithy.codegen.core.Symbol; +import software.amazon.smithy.codegen.core.SymbolProvider; +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.go.codegen.TriConsumer; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.knowledge.PaginatedIndex; +import software.amazon.smithy.model.knowledge.PaginationInfo; +import software.amazon.smithy.model.knowledge.TopDownIndex; +import software.amazon.smithy.model.shapes.MemberShape; +import software.amazon.smithy.model.shapes.ServiceShape; +import software.amazon.smithy.model.traits.DocumentationTrait; + +/** + * Implements support for PaginatedTrait. + */ +public class Paginators implements GoIntegration { + @Override + public void writeAdditionalFiles( + GoSettings settings, + Model model, + SymbolProvider symbolProvider, + TriConsumer> writerFactory + ) { + ServiceShape serviceShape = settings.getService(model); + + PaginatedIndex paginatedIndex = PaginatedIndex.of(model); + + TopDownIndex topDownIndex = TopDownIndex.of(model); + + List paginationInfos = topDownIndex.getContainedOperations(serviceShape).stream() + .map(operationShape -> paginatedIndex.getPaginationInfo(serviceShape, operationShape)) + .filter(Optional::isPresent) + .map(paginationInfo -> paginationInfo.get()) + .collect(Collectors.toList()); + + if (paginationInfos.size() == 0) { + return; + } + + writerFactory.accept("paginators.go", settings.getModuleName(), writer -> { + paginationInfos.forEach((paginationInfo) -> { + generateOperationPaginator(model, symbolProvider, writer, paginationInfo); + }); + }); + } + + private void generateOperationPaginator( + Model model, + SymbolProvider symbolProvider, + GoWriter writer, + PaginationInfo paginationInfo + ) { + Symbol operationSymbol = symbolProvider.toSymbol(paginationInfo.getOperation()); + Symbol inputSymbol = symbolProvider.toSymbol(paginationInfo.getInput()); + Symbol outputSymbol = symbolProvider.toSymbol(paginationInfo.getOutput()); + Optional pageSizeMember = paginationInfo.getPageSizeMember(); + + Symbol interfaceSymbol = SymbolUtils.createValueSymbolBuilder(String.format("%sAPIClient", + operationSymbol.getName())).build(); + Symbol paginatorSymbol = SymbolUtils.createPointableSymbolBuilder(String.format("%sPaginator", + operationSymbol.getName())).build(); + Symbol optionsSymbol = SymbolUtils.createPointableSymbolBuilder(String.format("%sOptions", + paginatorSymbol.getName())).build(); + + writeClientOperationInterface(writer, operationSymbol, inputSymbol, outputSymbol, interfaceSymbol); + writePaginatorOptions(model, symbolProvider, writer, operationSymbol, pageSizeMember, optionsSymbol); + writePaginator(symbolProvider, writer, paginationInfo, operationSymbol, inputSymbol, outputSymbol, + pageSizeMember, interfaceSymbol, paginatorSymbol, optionsSymbol); + } + + private void writePaginator( + SymbolProvider symbolProvider, + GoWriter writer, + PaginationInfo paginationInfo, + Symbol operationSymbol, + Symbol inputSymbol, + Symbol outputSymbol, + Optional pageSizeMember, + Symbol interfaceSymbol, + Symbol paginatorSymbol, + Symbol optionsSymbol + ) { + String inputMember = symbolProvider.toMemberName(paginationInfo.getInputTokenMember()); + + writer.writeDocs(String.format("%s is a paginator for %s", paginatorSymbol, operationSymbol)); + writer.openBlock("type $T struct {", "}", paginatorSymbol, () -> { + writer.write("options $T", optionsSymbol); + writer.write("client $T", interfaceSymbol); + writer.write("params $P", inputSymbol); + writer.write("nextToken *string"); + writer.write("firstPage bool"); + writer.write("done bool"); + }); + writer.write(""); + + Symbol newPagiantor = SymbolUtils.createValueSymbolBuilder(String.format("New%s", + paginatorSymbol.getName())).build(); + writer.writeDocs(String.format("%s returns a new %s", newPagiantor.getName(), paginatorSymbol.getName())); + writer.openBlock("func $T(client $T, params $P, optFns ...func($P)) $P {", "}", + newPagiantor, interfaceSymbol, inputSymbol, optionsSymbol, paginatorSymbol, () -> { + writer.write("options := $T{}", optionsSymbol); + writer.openBlock("for _, fn := range optFns {", "}", () -> { + writer.write("fn(&options)"); + }); + + writer.openBlock("return &$T{", "}", paginatorSymbol, () -> { + writer.write("options: options,"); + writer.write("client: client,"); + writer.write("params: params,"); + writer.write("firstPage: true,"); + }); + }); + writer.write(""); + + writer.writeDocs("HasMorePages returns a boolean indicating whether more pages are available"); + writer.openBlock("func (p $P) HasMorePages() bool {", "}", paginatorSymbol, () -> { + writer.openBlock("if p.done {", "}", () -> writer.write("return false")); + writer.write("return p.firstPage || (p.nextToken != nil && len(*p.nextToken) > 0)"); + }); + writer.write(""); + + Symbol contextSymbol = SymbolUtils.createValueSymbolBuilder("Context", SmithyGoDependency.CONTEXT) + .build(); + writer.writeDocs(String.format("NextPage retrieves the next %s page.", operationSymbol.getName())); + writer.openBlock("func (p $P) NextPage(ctx $T, optFns ...func(*Options)) ($P, error) {", "}", + paginatorSymbol, contextSymbol, outputSymbol, () -> { + writer.addUseImports(SmithyGoDependency.FMT); + writer.openBlock("if !p.HasMorePages() {", "}", () -> { + writer.write("return nil, fmt.Errorf(\"no more pages available\")"); + }); + writer.write(""); + writer.write("params := *p.params"); + writer.write("params.$L = p.nextToken", inputMember); + pageSizeMember.ifPresent(memberShape -> writer.write("params.$L = p.options.Limit", + symbolProvider.toMemberName(memberShape))); + + writer.write("result, err := p.client.$L(ctx, ¶ms, optFns...)", + operationSymbol.getName()); + writer.openBlock("if err != nil {", "}", () -> { + writer.write("return nil, err"); + }); + writer.write("p.firstPage = false"); + + StringBuilder nilGuard = new StringBuilder(); + StringBuilder outputPath = new StringBuilder("result"); + + List outputMemberPath = paginationInfo.getOutputTokenPath(); + for (int i = 0; i < outputMemberPath.size(); i++) { + MemberShape memberShape = outputMemberPath.get(i); + outputPath.append("."); + outputPath.append(symbolProvider.toMemberName(memberShape)); + + // Don't need to check the last member here as we won't dereference it + if (i != outputMemberPath.size() - 1) { + if (i != 0) { + nilGuard.append(" && "); + } + nilGuard.append(outputPath); + nilGuard.append(" != nil"); + } + } + + Runnable setToken = () -> { + writer.write("p.nextToken = $L", outputPath); + }; + if (nilGuard.length() > 0) { + writer.openBlock("if $L {", "}", nilGuard.toString(), setToken::run); + } else { + setToken.run(); + } + + writer.openBlock("if p.options.StopOnDuplicateToken && " + + "params.$L != nil && p.nextToken != nil && " + + "*params.$L == *p.nextToken {", "", inputMember, inputMember, () -> { + writer.write("p.done = true"); + writer.openBlock("} else {", "}", () -> writer.write("p.done = false")); + }); + + writer.write(""); + writer.write("return result, nil"); + }); + } + + private void writePaginatorOptions( + Model model, + SymbolProvider symbolProvider, + GoWriter writer, + Symbol operationSymbol, + Optional pageSizeMember, + Symbol optionsSymbol + ) { + writer.writeDocs(String.format("%s is the paginator options for %s", optionsSymbol.getName(), + operationSymbol.getName())); + writer.openBlock("type $T struct {", "}", optionsSymbol, () -> { + pageSizeMember.ifPresent(memberShape -> { + MemberShape limitShape = pageSizeMember.get(); + limitShape.getMemberTrait(model, DocumentationTrait.class).ifPresent(documentationTrait -> { + writer.writeDocs(documentationTrait.getValue()); + }); + writer.write("Limit $P", symbolProvider.toSymbol(limitShape)); + writer.write(""); + }); + writer.writeDocs("Set to true if pagination should stop if the service returns " + + "the same taken passed to it"); + writer.write("StopOnDuplicateToken bool"); + }); + writer.write(""); + } + + private void writeClientOperationInterface( + GoWriter writer, + Symbol operationSymbol, + Symbol inputSymbol, + Symbol outputSymbol, + Symbol interfaceSymbol + ) { + Symbol contextSymbol = SymbolUtils.createValueSymbolBuilder("Context", SmithyGoDependency.CONTEXT) + .build(); + + 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(""); + } +} diff --git a/codegen/smithy-go-codegen/src/main/resources/META-INF/services/software.amazon.smithy.go.codegen.integration.GoIntegration b/codegen/smithy-go-codegen/src/main/resources/META-INF/services/software.amazon.smithy.go.codegen.integration.GoIntegration index abeabe7d5..1c670e6da 100644 --- a/codegen/smithy-go-codegen/src/main/resources/META-INF/services/software.amazon.smithy.go.codegen.integration.GoIntegration +++ b/codegen/smithy-go-codegen/src/main/resources/META-INF/services/software.amazon.smithy.go.codegen.integration.GoIntegration @@ -4,3 +4,4 @@ software.amazon.smithy.go.codegen.integration.AddChecksumRequiredMiddleware software.amazon.smithy.go.codegen.integration.RequiresLengthTraitSupport software.amazon.smithy.go.codegen.integration.EndpointHostPrefixMiddleware software.amazon.smithy.go.codegen.integration.ClientLogger +software.amazon.smithy.go.codegen.integration.Paginators From 32d9b254adb0aa06497097ca8759e1c1ab997253 Mon Sep 17 00:00:00 2001 From: Sean McGrail Date: Wed, 11 Nov 2020 14:56:10 -0800 Subject: [PATCH 2/6] Address Feedback --- .../go/codegen/integration/Paginators.java | 74 +++++++++---------- 1 file changed, 35 insertions(+), 39 deletions(-) diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/Paginators.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/Paginators.java index 1e95599ef..3ca2a025e 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/Paginators.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/Paginators.java @@ -17,15 +17,13 @@ import java.util.List; import java.util.Optional; -import java.util.function.Consumer; -import java.util.stream.Collectors; 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.go.codegen.TriConsumer; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.knowledge.PaginatedIndex; import software.amazon.smithy.model.knowledge.PaginationInfo; @@ -43,7 +41,7 @@ public void writeAdditionalFiles( GoSettings settings, Model model, SymbolProvider symbolProvider, - TriConsumer> writerFactory + GoDelegator goDelegator ) { ServiceShape serviceShape = settings.getService(model); @@ -51,21 +49,15 @@ public void writeAdditionalFiles( TopDownIndex topDownIndex = TopDownIndex.of(model); - List paginationInfos = topDownIndex.getContainedOperations(serviceShape).stream() + topDownIndex.getContainedOperations(serviceShape).stream() .map(operationShape -> paginatedIndex.getPaginationInfo(serviceShape, operationShape)) .filter(Optional::isPresent) - .map(paginationInfo -> paginationInfo.get()) - .collect(Collectors.toList()); - - if (paginationInfos.size() == 0) { - return; - } - - writerFactory.accept("paginators.go", settings.getModuleName(), writer -> { - paginationInfos.forEach((paginationInfo) -> { - generateOperationPaginator(model, symbolProvider, writer, paginationInfo); - }); - }); + .map(Optional::get) + .forEach(paginationInfo -> { + goDelegator.useShapeWriter(paginationInfo.getOperation(), writer -> { + generateOperationPaginator(model, symbolProvider, writer, paginationInfo); + }); + }); } private void generateOperationPaginator( @@ -76,6 +68,7 @@ private void generateOperationPaginator( ) { Symbol operationSymbol = symbolProvider.toSymbol(paginationInfo.getOperation()); Symbol inputSymbol = symbolProvider.toSymbol(paginationInfo.getInput()); + Symbol inputTokenSymbol = symbolProvider.toSymbol(paginationInfo.getInputTokenMember()); Symbol outputSymbol = symbolProvider.toSymbol(paginationInfo.getOutput()); Optional pageSizeMember = paginationInfo.getPageSizeMember(); @@ -87,9 +80,10 @@ private void generateOperationPaginator( paginatorSymbol.getName())).build(); writeClientOperationInterface(writer, operationSymbol, inputSymbol, outputSymbol, interfaceSymbol); - writePaginatorOptions(model, symbolProvider, writer, operationSymbol, pageSizeMember, optionsSymbol); - writePaginator(symbolProvider, writer, paginationInfo, operationSymbol, inputSymbol, outputSymbol, - pageSizeMember, interfaceSymbol, paginatorSymbol, optionsSymbol); + writePaginatorOptions(model, symbolProvider, writer, operationSymbol, pageSizeMember.orElse(null), + optionsSymbol); + writePaginator(symbolProvider, writer, paginationInfo, operationSymbol, inputSymbol, inputTokenSymbol, + outputSymbol, pageSizeMember.orElse(null), interfaceSymbol, paginatorSymbol, optionsSymbol); } private void writePaginator( @@ -98,8 +92,9 @@ private void writePaginator( PaginationInfo paginationInfo, Symbol operationSymbol, Symbol inputSymbol, + Symbol inputTokenSymbol, Symbol outputSymbol, - Optional pageSizeMember, + MemberShape pageSizeMember, Symbol interfaceSymbol, Symbol paginatorSymbol, Symbol optionsSymbol @@ -111,9 +106,8 @@ private void writePaginator( writer.write("options $T", optionsSymbol); writer.write("client $T", interfaceSymbol); writer.write("params $P", inputSymbol); - writer.write("nextToken *string"); + writer.write("nextToken $P", inputTokenSymbol); writer.write("firstPage bool"); - writer.write("done bool"); }); writer.write(""); @@ -126,7 +120,9 @@ private void writePaginator( writer.openBlock("for _, fn := range optFns {", "}", () -> { writer.write("fn(&options)"); }); - + writer.write(""); + writer.openBlock("if params == nil {", "}", () -> writer.write("params = &$T{}", inputSymbol)); + writer.write(""); writer.openBlock("return &$T{", "}", paginatorSymbol, () -> { writer.write("options: options,"); writer.write("client: client,"); @@ -138,8 +134,7 @@ private void writePaginator( writer.writeDocs("HasMorePages returns a boolean indicating whether more pages are available"); writer.openBlock("func (p $P) HasMorePages() bool {", "}", paginatorSymbol, () -> { - writer.openBlock("if p.done {", "}", () -> writer.write("return false")); - writer.write("return p.firstPage || (p.nextToken != nil && len(*p.nextToken) > 0)"); + writer.write("return p.firstPage || p.nextToken != nil"); }); writer.write(""); @@ -155,8 +150,9 @@ private void writePaginator( writer.write(""); writer.write("params := *p.params"); writer.write("params.$L = p.nextToken", inputMember); - pageSizeMember.ifPresent(memberShape -> writer.write("params.$L = p.options.Limit", - symbolProvider.toMemberName(memberShape))); + if (pageSizeMember != null) { + writer.write("params.$L = p.options.Limit", symbolProvider.toMemberName(pageSizeMember)); + } writer.write("result, err := p.client.$L(ctx, ¶ms, optFns...)", operationSymbol.getName()); @@ -195,9 +191,8 @@ private void writePaginator( writer.openBlock("if p.options.StopOnDuplicateToken && " + "params.$L != nil && p.nextToken != nil && " - + "*params.$L == *p.nextToken {", "", inputMember, inputMember, () -> { - writer.write("p.done = true"); - writer.openBlock("} else {", "}", () -> writer.write("p.done = false")); + + "*params.$L == *p.nextToken {", "}", inputMember, inputMember, () -> { + writer.write("p.nextToken = nil"); }); writer.write(""); @@ -210,22 +205,21 @@ private void writePaginatorOptions( SymbolProvider symbolProvider, GoWriter writer, Symbol operationSymbol, - Optional pageSizeMember, + MemberShape pageSizeMember, Symbol optionsSymbol ) { writer.writeDocs(String.format("%s is the paginator options for %s", optionsSymbol.getName(), operationSymbol.getName())); writer.openBlock("type $T struct {", "}", optionsSymbol, () -> { - pageSizeMember.ifPresent(memberShape -> { - MemberShape limitShape = pageSizeMember.get(); - limitShape.getMemberTrait(model, DocumentationTrait.class).ifPresent(documentationTrait -> { + if (pageSizeMember != null) { + pageSizeMember.getMemberTrait(model, DocumentationTrait.class).ifPresent(documentationTrait -> { writer.writeDocs(documentationTrait.getValue()); }); - writer.write("Limit $P", symbolProvider.toSymbol(limitShape)); + writer.write("Limit $P", symbolProvider.toSymbol(pageSizeMember)); writer.write(""); - }); - writer.writeDocs("Set to true if pagination should stop if the service returns " - + "the same taken passed to it"); + } + writer.writeDocs("Set to true if pagination should stop if the service returns a pagination token that " + + "matches the most recent token provided to the service."); writer.write("StopOnDuplicateToken bool"); }); writer.write(""); @@ -251,4 +245,6 @@ private void writeClientOperationInterface( writer.write("var _ $T = (*Client)(nil)", interfaceSymbol); writer.write(""); } + + } From fc456344d9bab1a1123e7241402d5286e6115f9b Mon Sep 17 00:00:00 2001 From: Sean McGrail Date: Thu, 12 Nov 2020 14:13:49 -0800 Subject: [PATCH 3/6] Address @jasdel feedback --- .../go/codegen/integration/Paginators.java | 79 +++++++++++-------- 1 file changed, 44 insertions(+), 35 deletions(-) diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/Paginators.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/Paginators.java index 3ca2a025e..ce987cfe1 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/Paginators.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/Paginators.java @@ -67,10 +67,6 @@ private void generateOperationPaginator( PaginationInfo paginationInfo ) { Symbol operationSymbol = symbolProvider.toSymbol(paginationInfo.getOperation()); - Symbol inputSymbol = symbolProvider.toSymbol(paginationInfo.getInput()); - Symbol inputTokenSymbol = symbolProvider.toSymbol(paginationInfo.getInputTokenMember()); - Symbol outputSymbol = symbolProvider.toSymbol(paginationInfo.getOutput()); - Optional pageSizeMember = paginationInfo.getPageSizeMember(); Symbol interfaceSymbol = SymbolUtils.createValueSymbolBuilder(String.format("%sAPIClient", operationSymbol.getName())).build(); @@ -79,28 +75,26 @@ private void generateOperationPaginator( Symbol optionsSymbol = SymbolUtils.createPointableSymbolBuilder(String.format("%sOptions", paginatorSymbol.getName())).build(); - writeClientOperationInterface(writer, operationSymbol, inputSymbol, outputSymbol, interfaceSymbol); - writePaginatorOptions(model, symbolProvider, writer, operationSymbol, pageSizeMember.orElse(null), - optionsSymbol); - writePaginator(symbolProvider, writer, paginationInfo, operationSymbol, inputSymbol, inputTokenSymbol, - outputSymbol, pageSizeMember.orElse(null), interfaceSymbol, paginatorSymbol, optionsSymbol); + writeClientOperationInterface(writer, symbolProvider, paginationInfo, interfaceSymbol); + writePaginatorOptions(writer, model, symbolProvider, paginationInfo, operationSymbol, optionsSymbol); + writePaginator(writer, model, symbolProvider, paginationInfo, interfaceSymbol, paginatorSymbol, optionsSymbol); } private void writePaginator( - SymbolProvider symbolProvider, GoWriter writer, + Model model, + SymbolProvider symbolProvider, PaginationInfo paginationInfo, - Symbol operationSymbol, - Symbol inputSymbol, - Symbol inputTokenSymbol, - Symbol outputSymbol, - MemberShape pageSizeMember, Symbol interfaceSymbol, Symbol paginatorSymbol, Symbol optionsSymbol ) { String inputMember = symbolProvider.toMemberName(paginationInfo.getInputTokenMember()); + Symbol operationSymbol = symbolProvider.toSymbol(paginationInfo.getOperation()); + Symbol inputSymbol = symbolProvider.toSymbol(paginationInfo.getInput()); + Symbol inputTokenSymbol = symbolProvider.toSymbol(paginationInfo.getInputTokenMember()); + writer.writeDocs(String.format("%s is a paginator for %s", paginatorSymbol, operationSymbol)); writer.openBlock("type $T struct {", "}", paginatorSymbol, () -> { writer.write("options $T", optionsSymbol); @@ -140,6 +134,9 @@ private void writePaginator( Symbol contextSymbol = SymbolUtils.createValueSymbolBuilder("Context", SmithyGoDependency.CONTEXT) .build(); + Symbol outputSymbol = symbolProvider.toSymbol(paginationInfo.getOutput()); + Optional pageSizeMember = paginationInfo.getPageSizeMember(); + writer.writeDocs(String.format("NextPage retrieves the next %s page.", operationSymbol.getName())); writer.openBlock("func (p $P) NextPage(ctx $T, optFns ...func(*Options)) ($P, error) {", "}", paginatorSymbol, contextSymbol, outputSymbol, () -> { @@ -150,9 +147,10 @@ private void writePaginator( writer.write(""); writer.write("params := *p.params"); writer.write("params.$L = p.nextToken", inputMember); - if (pageSizeMember != null) { - writer.write("params.$L = p.options.Limit", symbolProvider.toMemberName(pageSizeMember)); - } + + pageSizeMember.ifPresent(memberShape -> { + writer.write("params.$L = p.options.Limit", symbolProvider.toMemberName(pageSizeMember.get())); + }); writer.write("result, err := p.client.$L(ctx, ¶ms, optFns...)", operationSymbol.getName()); @@ -164,7 +162,7 @@ private void writePaginator( StringBuilder nilGuard = new StringBuilder(); StringBuilder outputPath = new StringBuilder("result"); - List outputMemberPath = paginationInfo.getOutputTokenPath(); + List outputMemberPath = paginationInfo.getOutputTokenMemberPath(); for (int i = 0; i < outputMemberPath.size(); i++) { MemberShape memberShape = outputMemberPath.get(i); outputPath.append("."); @@ -180,20 +178,26 @@ private void writePaginator( } } + writer.write("prevToken := p.nextToken"); Runnable setToken = () -> { writer.write("p.nextToken = $L", outputPath); }; if (nilGuard.length() > 0) { + writer.write("p.nextToken = nil"); writer.openBlock("if $L {", "}", nilGuard.toString(), setToken::run); } else { setToken.run(); } - writer.openBlock("if p.options.StopOnDuplicateToken && " - + "params.$L != nil && p.nextToken != nil && " - + "*params.$L == *p.nextToken {", "}", inputMember, inputMember, () -> { - writer.write("p.nextToken = nil"); - }); + if (model.expectShape(paginationInfo.getInputTokenMember().getTarget()).isStringShape()) { + writer.openBlock("if p.options.StopOnDuplicateToken && " + + "prevToken != nil && p.nextToken != nil && " + + "*prevToken == *p.nextToken {", "}", () -> { + writer.write("p.nextToken = nil"); + }); + } else { + writer.write("_ = prevToken"); + } writer.write(""); writer.write("return result, nil"); @@ -201,40 +205,45 @@ private void writePaginator( } private void writePaginatorOptions( + GoWriter writer, Model model, SymbolProvider symbolProvider, - GoWriter writer, + PaginationInfo paginationInfo, Symbol operationSymbol, - MemberShape pageSizeMember, Symbol optionsSymbol ) { writer.writeDocs(String.format("%s is the paginator options for %s", optionsSymbol.getName(), operationSymbol.getName())); writer.openBlock("type $T struct {", "}", optionsSymbol, () -> { - if (pageSizeMember != null) { - pageSizeMember.getMemberTrait(model, DocumentationTrait.class).ifPresent(documentationTrait -> { + paginationInfo.getPageSizeMember().ifPresent(memberShape -> { + memberShape.getMemberTrait(model, DocumentationTrait.class).ifPresent(documentationTrait -> { writer.writeDocs(documentationTrait.getValue()); }); - writer.write("Limit $P", symbolProvider.toSymbol(pageSizeMember)); + writer.write("Limit $P", symbolProvider.toSymbol(memberShape)); writer.write(""); + }); + if (model.expectShape(paginationInfo.getInputTokenMember().getTarget()).isStringShape()) { + writer.writeDocs("Set to true if pagination should stop if the service returns a pagination token that " + + "matches the most recent token provided to the service."); + writer.write("StopOnDuplicateToken bool"); } - writer.writeDocs("Set to true if pagination should stop if the service returns a pagination token that " - + "matches the most recent token provided to the service."); - writer.write("StopOnDuplicateToken bool"); }); writer.write(""); } private void writeClientOperationInterface( GoWriter writer, - Symbol operationSymbol, - Symbol inputSymbol, - Symbol outputSymbol, + 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, () -> { From e97ac029a7ee6ddb31a725286b2399928b0d2a59 Mon Sep 17 00:00:00 2001 From: Sean McGrail Date: Tue, 17 Nov 2020 14:08:38 -0800 Subject: [PATCH 4/6] Update GoValueAccessUtils to not require ProtocolGenerator Context --- .../smithy/go/codegen/GoValueAccessUtils.java | 155 ++++++++------- .../HttpBindingProtocolGenerator.java | 178 +++++++++--------- 2 files changed, 173 insertions(+), 160 deletions(-) diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoValueAccessUtils.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoValueAccessUtils.java index f741b47d0..bd659693b 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoValueAccessUtils.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoValueAccessUtils.java @@ -18,8 +18,9 @@ package software.amazon.smithy.go.codegen; import java.util.function.Consumer; -import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; +import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.knowledge.GoPointableIndex; +import software.amazon.smithy.model.Model; import software.amazon.smithy.model.shapes.CollectionShape; import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.Shape; @@ -35,32 +36,32 @@ private GoValueAccessUtils() { /** * Writes non-zero conditional checks around a lambda specific to the member shape type. - * + *

* Note: Collections and map member values by default will not have individual checks on member values. To check * not empty strings set the ignoreEmptyString to false. * - * @param context generation context - * @param writer go writer - * @param member API shape member to determine wrapping check with - * @param operand string of text with access to value + * @param model smithy model + * @param writer go writer + * @param member API shape member to determine wrapping check with + * @param operand string of text with access to value * @param ignoreEmptyString if empty strings also checked - * @param lambda lambda to run + * @param lambda lambda to run */ public static void writeIfNonZeroValue( - ProtocolGenerator.GenerationContext context, + Model model, GoWriter writer, MemberShape member, String operand, Boolean ignoreEmptyString, Runnable lambda ) { - Shape targetShape = context.getModel().expectShape(member.getTarget()); - Shape container = context.getModel().expectShape(member.getContainer()); + Shape targetShape = model.expectShape(member.getTarget()); + Shape container = model.expectShape(member.getContainer()); // default to empty block for variable scoping with not value check. String check = "{"; - if (GoPointableIndex.of(context.getModel()).isNillable(member)) { + if (GoPointableIndex.of(model).isNillable(member)) { if (!ignoreEmptyString && targetShape.getType() == ShapeType.STRING) { check = String.format("if %s != nil && len(*%s) > 0 {", operand, operand); } else { @@ -88,112 +89,116 @@ public static void writeIfNonZeroValue( /** * Writes non-zero conditional checks around a lambda specific to the member shape type. - * + *

* Ignores empty strings of string pointers, and nested within list and maps. * - * @param context generation context - * @param writer go writer - * @param member API shape member to determine wrapping check with + * @param model smithy model + * @param writer go writer + * @param member API shape member to determine wrapping check with * @param operand string of text with access to value - * @param lambda lambda to run + * @param lambda lambda to run */ public static void writeIfNonZeroValue( - ProtocolGenerator.GenerationContext context, + Model model, GoWriter writer, MemberShape member, String operand, Runnable lambda ) { - writeIfNonZeroValue(context, writer, member, operand, true, lambda); + writeIfNonZeroValue(model, writer, member, operand, true, lambda); } /** * Writes non-zero conditional check around a lambda specific to a member of a container. - * + *

* Ignores empty strings of string pointers, and members nested within list and maps. * - * @param context generation context - * @param writer go writer - * @param member API shape member to determine wrapping check with - * @param container operand of source member is a part of. - * @param lambda lambda to run + * @param model smithy model + * @param symbolProvider symbol provider + * @param writer go writer + * @param member API shape member to determine wrapping check with + * @param container operand of source member is a part of. + * @param lambda lambda to run */ public static void writeIfNonZeroValueMember( - ProtocolGenerator.GenerationContext context, + Model model, + SymbolProvider symbolProvider, GoWriter writer, MemberShape member, String container, Consumer lambda ) { - String memberName = context.getSymbolProvider().toMemberName(member); + String memberName = symbolProvider.toMemberName(member); String operand = container + "." + memberName; - writeIfNonZeroValue(context, writer, member, operand, true, () -> { + writeIfNonZeroValue(model, writer, member, operand, true, () -> { lambda.accept(operand); }); } /** * Writes non-zero conditional check around a lambda specific to a member of a container. - * + *

* Note: Collections and map member values by default will not have individual checks on member values. To check * not empty strings set the ignoreEmptyString to false. * - * @param context generation context - * @param writer go writer - * @param member API shape member to determine wrapping check with - * @param container operand of source member is a part of. + * @param model smithy model + * @param symbolProvider symbol provider + * @param writer go writer + * @param member API shape member to determine wrapping check with + * @param container operand of source member is a part of. * @param ignoreEmptyString if empty strings also checked - * @param lambda lambda to run + * @param lambda lambda to run */ public static void writeIfNonZeroValueMember( - ProtocolGenerator.GenerationContext context, + Model model, + SymbolProvider symbolProvider, GoWriter writer, MemberShape member, String container, boolean ignoreEmptyString, Consumer lambda ) { - String memberName = context.getSymbolProvider().toMemberName(member); + String memberName = symbolProvider.toMemberName(member); String operand = container + "." + memberName; - writeIfNonZeroValue(context, writer, member, operand, ignoreEmptyString, () -> { + writeIfNonZeroValue(model, writer, member, operand, ignoreEmptyString, () -> { lambda.accept(operand); }); } /** * Writes zero conditional checks around a lambda specific to the member shape type. - * + *

* Members with containers of Collection and map shapes, will ignore the lambda block * and not call it. Optionally will ignore empty strings based on the ignoreEmptyString flag. - * + *

* Non-nillable shapes other than Enum, Boolean, and Number will ignore the lambda block. Optionally will ignore * empty strings based on the ignoreEmptyString flag. - * + *

* Note: Collections and map member values by default will not have individual checks on member values. To check * for empty strings set the ignoreEmptyString to false. * - * @param context generation context - * @param writer go writer - * @param member API shape member to determine wrapping check with - * @param operand string of text with access to value + * @param model smithy model + * @param writer go writer + * @param member API shape member to determine wrapping check with + * @param operand string of text with access to value * @param ignoreEmptyString if empty strings also checked - * @param lambda lambda to run + * @param lambda lambda to run */ public static void writeIfZeroValue( - ProtocolGenerator.GenerationContext context, + Model model, GoWriter writer, MemberShape member, String operand, Boolean ignoreEmptyString, Runnable lambda ) { - Shape targetShape = context.getModel().expectShape(member.getTarget()); - Shape container = context.getModel().expectShape(member.getContainer()); + Shape targetShape = model.expectShape(member.getTarget()); + Shape container = model.expectShape(member.getContainer()); String check = "{"; - if (GoPointableIndex.of(context.getModel()).isNillable(member)) { + if (GoPointableIndex.of(model).isNillable(member)) { if (!ignoreEmptyString && targetShape.getType() == ShapeType.STRING) { check = String.format("if %s == nil || len(*%s) == 0 {", operand, operand); } else { @@ -230,75 +235,79 @@ public static void writeIfZeroValue( /** * Writes zero conditional checks around a lambda specific to the member shape type. - * + *

* Ignores empty strings of string pointers, and members nested within list and maps. * - * @param context generation context - * @param writer go writer - * @param member API shape member to determine wrapping check with + * @param model smithy model + * @param writer go writer + * @param member API shape member to determine wrapping check with * @param operand string of text with access to value - * @param lambda lambda to run + * @param lambda lambda to run */ public static void writeIfZeroValue( - ProtocolGenerator.GenerationContext context, + Model model, GoWriter writer, MemberShape member, String operand, Runnable lambda ) { - writeIfZeroValue(context, writer, member, operand, true, lambda); + writeIfZeroValue(model, writer, member, operand, true, lambda); } /** * Writes zero conditional check around a lambda specific to a member of a container. - * + *

* Ignores empty strings of string pointers, and members nested within list and maps. * - * @param context generation context - * @param writer go writer - * @param member API shape member to determine wrapping check with - * @param container operand of source member is a part of. - * @param lambda lambda to run + * @param model smithy model + * @param symbolProvider symbol provider + * @param writer go writer + * @param member API shape member to determine wrapping check with + * @param container operand of source member is a part of. + * @param lambda lambda to run */ public static void writeIfZeroValueMember( - ProtocolGenerator.GenerationContext context, + Model model, + SymbolProvider symbolProvider, GoWriter writer, MemberShape member, String container, Consumer lambda ) { - String memberName = context.getSymbolProvider().toMemberName(member); + String memberName = symbolProvider.toMemberName(member); String operand = container + "." + memberName; - writeIfZeroValue(context, writer, member, operand, () -> { + writeIfZeroValue(model, writer, member, operand, () -> { lambda.accept(operand); }); } /** * Writes zero conditional check around a lambda specific to a member of a container. - * + *

* Ignores empty strings of string pointers, and members nested within list and maps. * - * @param context generation context - * @param writer go writer - * @param member API shape member to determine wrapping check with - * @param container operand of source member is a part of. + * @param model smithy model + * @param symbolProvider symbol provider + * @param writer go writer + * @param member API shape member to determine wrapping check with + * @param container operand of source member is a part of. * @param ignoreEmptyString if empty strings also checked - * @param lambda lambda to run + * @param lambda lambda to run */ public static void writeIfZeroValueMember( - ProtocolGenerator.GenerationContext context, + Model model, + SymbolProvider symbolProvider, GoWriter writer, MemberShape member, String container, boolean ignoreEmptyString, Consumer lambda ) { - String memberName = context.getSymbolProvider().toMemberName(member); + String memberName = symbolProvider.toMemberName(member); String operand = container + "." + memberName; - writeIfZeroValue(context, writer, member, operand, ignoreEmptyString, () -> { + writeIfZeroValue(model, writer, member, operand, ignoreEmptyString, () -> { lambda.accept(operand); }); } diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/HttpBindingProtocolGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/HttpBindingProtocolGenerator.java index 17a9cc49a..8a3e3f906 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/HttpBindingProtocolGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/HttpBindingProtocolGenerator.java @@ -390,33 +390,34 @@ protected void writeMiddlewarePayloadSerializerDelegator( Model model = context.getModel(); Shape payloadShape = model.expectShape(memberShape.getTarget()); - GoValueAccessUtils.writeIfNonZeroValueMember(context, writer, memberShape, "input", (s) -> { - writer.openBlock("if !restEncoder.HasHeader(\"Content-Type\") {", "}", () -> { - writer.write("restEncoder.SetHeader(\"Content-Type\").String($S)", - getPayloadShapeMediaType(payloadShape)); - }); - writer.write(""); + GoValueAccessUtils.writeIfNonZeroValueMember(context.getModel(), context.getSymbolProvider(), writer, + memberShape, "input", (s) -> { + writer.openBlock("if !restEncoder.HasHeader(\"Content-Type\") {", "}", () -> { + writer.write("restEncoder.SetHeader(\"Content-Type\").String($S)", + getPayloadShapeMediaType(payloadShape)); + }); + writer.write(""); - if (payloadShape.hasTrait(StreamingTrait.class)) { - writer.write("payload := $L", s); + if (payloadShape.hasTrait(StreamingTrait.class)) { + writer.write("payload := $L", s); - } else if (payloadShape.isBlobShape()) { - writer.addUseImports(SmithyGoDependency.BYTES); - writer.write("payload := bytes.NewReader($L)", s); + } else if (payloadShape.isBlobShape()) { + writer.addUseImports(SmithyGoDependency.BYTES); + writer.write("payload := bytes.NewReader($L)", s); - } else if (payloadShape.isStringShape()) { - writer.addUseImports(SmithyGoDependency.STRINGS); - writer.write("payload := strings.NewReader(*$L)", s); + } else if (payloadShape.isStringShape()) { + writer.addUseImports(SmithyGoDependency.STRINGS); + writer.write("payload := strings.NewReader(*$L)", s); - } else { - writeMiddlewarePayloadAsDocumentSerializerDelegator(context, memberShape, s); - } + } else { + writeMiddlewarePayloadAsDocumentSerializerDelegator(context, memberShape, s); + } - writer.openBlock("if request, err = request.SetStream(payload); err != nil {", "}", - () -> { - writer.write("return out, metadata, &smithy.SerializationError{Err: err}"); - }); - }); + writer.openBlock("if request, err = request.SetStream(payload); err != nil {", "}", + () -> { + writer.write("return out, metadata, &smithy.SerializationError{Err: err}"); + }); + }); } /** @@ -665,75 +666,78 @@ private void writeHttpBindingMember( // return an error if member shape targets location label, but is unset. if (location.equals(HttpBinding.Location.LABEL)) { // labels must always be set to be serialized on URI, and non empty strings, - GoValueAccessUtils.writeIfZeroValueMember(context, writer, memberShape, "v", false, operand -> { - writer.addUseImports(SmithyGoDependency.SMITHY); - writer.write("return &smithy.SerializationError { " - + "Err: fmt.Errorf(\"input member $L must not be empty\")}", - memberShape.getMemberName()); - }); + GoValueAccessUtils.writeIfZeroValueMember(context.getModel(), context.getSymbolProvider(), writer, + memberShape, "v", false, operand -> { + writer.addUseImports(SmithyGoDependency.SMITHY); + writer.write("return &smithy.SerializationError { " + + "Err: fmt.Errorf(\"input member $L must not be empty\")}", + memberShape.getMemberName()); + }); } boolean allowZeroStrings = location != HttpBinding.Location.HEADER; - GoValueAccessUtils.writeIfNonZeroValueMember(context, writer, memberShape, "v", allowZeroStrings, (operand) -> { - final String locationName = binding.getLocationName().isEmpty() - ? memberShape.getMemberName() : binding.getLocationName(); - switch (location) { - case HEADER: - writer.write("locationName := $S", getCanonicalHeader(locationName)); - writeHeaderBinding(context, memberShape, operand, location, "locationName", "encoder"); - break; - case PREFIX_HEADERS: - MemberShape valueMemberShape = model.expectShape(targetShape.getId(), - MapShape.class).getValue(); - Shape valueMemberTarget = model.expectShape(valueMemberShape.getTarget()); - - if (targetShape.getType() != ShapeType.MAP) { - throw new CodegenException("Unexpected prefix headers target shape " - + valueMemberTarget.getType() + ", " + valueMemberShape.getId()); - } - - writer.write("hv := encoder.Headers($S)", getCanonicalHeader(locationName)); - writer.addUseImports(SmithyGoDependency.NET_HTTP); - writer.openBlock("for mapKey, mapVal := range $L {", "}", operand, () -> { - GoValueAccessUtils.writeIfNonZeroValue(context, writer, valueMemberShape, "mapVal", false, - () -> { - writeHeaderBinding(context, valueMemberShape, "mapVal", location, - "http.CanonicalHeaderKey(mapKey)", "hv"); - }); - }); - break; - case LABEL: - writeHttpBindingSetter(context, writer, memberShape, location, operand, (w, s) -> { - w.openBlock("if err := encoder.SetURI($S).$L; err != nil {", "}", locationName, s, - () -> { - w.write("return err"); + GoValueAccessUtils.writeIfNonZeroValueMember(context.getModel(), context.getSymbolProvider(), writer, + memberShape, "v", allowZeroStrings, (operand) -> { + final String locationName = binding.getLocationName().isEmpty() + ? memberShape.getMemberName() : binding.getLocationName(); + switch (location) { + case HEADER: + writer.write("locationName := $S", getCanonicalHeader(locationName)); + writeHeaderBinding(context, memberShape, operand, location, "locationName", "encoder"); + break; + case PREFIX_HEADERS: + MemberShape valueMemberShape = model.expectShape(targetShape.getId(), + MapShape.class).getValue(); + Shape valueMemberTarget = model.expectShape(valueMemberShape.getTarget()); + + if (targetShape.getType() != ShapeType.MAP) { + throw new CodegenException("Unexpected prefix headers target shape " + + valueMemberTarget.getType() + ", " + valueMemberShape.getId()); + } + + writer.write("hv := encoder.Headers($S)", getCanonicalHeader(locationName)); + writer.addUseImports(SmithyGoDependency.NET_HTTP); + writer.openBlock("for mapKey, mapVal := range $L {", "}", operand, () -> { + GoValueAccessUtils.writeIfNonZeroValue(context.getModel(), writer, valueMemberShape, + "mapVal", false, () -> { + writeHeaderBinding(context, valueMemberShape, "mapVal", location, + "http.CanonicalHeaderKey(mapKey)", "hv"); + }); + }); + break; + case LABEL: + writeHttpBindingSetter(context, writer, memberShape, location, operand, (w, s) -> { + w.openBlock("if err := encoder.SetURI($S).$L; err != nil {", "}", locationName, s, + () -> { + w.write("return err"); + }); + }); + break; + case QUERY: + if (targetShape instanceof CollectionShape) { + MemberShape collectionMember = CodegenUtils.expectCollectionShape(targetShape) + .getMember(); + writer.openBlock("for i := range $L {", "}", operand, () -> { + GoValueAccessUtils.writeIfZeroValue(context.getModel(), writer, collectionMember, + operand + "[i]", () -> { + writer.write("continue"); + }); + writeHttpBindingSetter(context, writer, collectionMember, location, operand + "[i]", + (w, s) -> { + w.writeInline("encoder.AddQuery($S).$L", locationName, s); + }); }); - }); - break; - case QUERY: - if (targetShape instanceof CollectionShape) { - MemberShape collectionMember = CodegenUtils.expectCollectionShape(targetShape).getMember(); - writer.openBlock("for i := range $L {", "}", operand, () -> { - GoValueAccessUtils.writeIfZeroValue(context, writer, collectionMember, - operand + "[i]", () -> { - writer.write("continue"); - }); - writeHttpBindingSetter(context, writer, collectionMember, location, operand + "[i]", - (w, s) -> { - w.writeInline("encoder.AddQuery($S).$L", locationName, s); - }); - }); - } else { - writeHttpBindingSetter(context, writer, memberShape, location, operand, - (w, s) -> w.writeInline( - "encoder.SetQuery($S).$L", locationName, s)); + } else { + writeHttpBindingSetter(context, writer, memberShape, location, operand, + (w, s) -> w.writeInline( + "encoder.SetQuery($S).$L", locationName, s)); + } + break; + default: + throw new CodegenException("unexpected http binding found"); } - break; - default: - throw new CodegenException("unexpected http binding found"); - } - }); + }); } private void writeHeaderBinding( @@ -760,8 +764,8 @@ private void writeHeaderBinding( writer.openBlock("for i := range $L {", "}", operand, () -> { // Only set non-empty non-nil header values String indexedOperand = operand + "[i]"; - GoValueAccessUtils.writeIfNonZeroValue(context, writer, collectionMemberShape, indexedOperand, false, - () -> { + GoValueAccessUtils.writeIfNonZeroValue(context.getModel(), writer, collectionMemberShape, indexedOperand, + false, () -> { String op = conditionallyBase64Encode(writer, targetShape, indexedOperand); writeHttpBindingSetter(context, writer, collectionMemberShape, location, op, (w, s) -> { w.writeInline("$L.AddHeader($L).$L", dest, locationName, s); From 6d8a1b0c90cb793a4ae49e252e653f89b1f46876 Mon Sep 17 00:00:00 2001 From: Sean McGrail Date: Tue, 17 Nov 2020 14:09:11 -0800 Subject: [PATCH 5/6] Update Paginator Codegen to Utilize GoValueAccessUtils --- .../go/codegen/integration/Paginators.java | 42 ++++++++----------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/Paginators.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/Paginators.java index ce987cfe1..135e95b35 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/Paginators.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/Paginators.java @@ -17,10 +17,12 @@ import java.util.List; import java.util.Optional; +import java.util.function.Consumer; 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.GoValueAccessUtils; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.go.codegen.SymbolUtils; @@ -158,36 +160,28 @@ private void writePaginator( writer.write("return nil, err"); }); writer.write("p.firstPage = false"); - - StringBuilder nilGuard = new StringBuilder(); - StringBuilder outputPath = new StringBuilder("result"); + writer.write(""); List outputMemberPath = paginationInfo.getOutputTokenMemberPath(); - for (int i = 0; i < outputMemberPath.size(); i++) { - MemberShape memberShape = outputMemberPath.get(i); - outputPath.append("."); - outputPath.append(symbolProvider.toMemberName(memberShape)); + MemberShape tokenMember = outputMemberPath.get(outputMemberPath.size() - 1); + Consumer setToken = (container) -> { + writer.write("p.nextToken = $L", container + "." + + symbolProvider.toMemberName(tokenMember)); + }; - // Don't need to check the last member here as we won't dereference it - if (i != outputMemberPath.size() - 1) { - if (i != 0) { - nilGuard.append(" && "); - } - nilGuard.append(outputPath); - nilGuard.append(" != nil"); - } + for (int i = outputMemberPath.size() - 2; i >= 0; i--) { + MemberShape memberShape = outputMemberPath.get(i); + Consumer inner = setToken; + setToken = (container) -> { + GoValueAccessUtils.writeIfNonZeroValueMember(model, symbolProvider, writer, memberShape, + container, inner); + }; } writer.write("prevToken := p.nextToken"); - Runnable setToken = () -> { - writer.write("p.nextToken = $L", outputPath); - }; - if (nilGuard.length() > 0) { - writer.write("p.nextToken = nil"); - writer.openBlock("if $L {", "}", nilGuard.toString(), setToken::run); - } else { - setToken.run(); - } + writer.write("p.nextToken = nil"); + setToken.accept("result"); + writer.write(""); if (model.expectShape(paginationInfo.getInputTokenMember().getTarget()).isStringShape()) { writer.openBlock("if p.options.StopOnDuplicateToken && " From 54de852c88f6e9ff928bf658f4c9f8c41579c6c7 Mon Sep 17 00:00:00 2001 From: Sean McGrail Date: Tue, 17 Nov 2020 16:41:42 -0800 Subject: [PATCH 6/6] Updated to always treat page limit as unboxed for pagiantor options. --- .../go/codegen/integration/Paginators.java | 35 ++++++++++++++++--- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/Paginators.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/Paginators.java index 135e95b35..89a88788b 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/Paginators.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/Paginators.java @@ -20,12 +20,14 @@ import java.util.function.Consumer; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolProvider; +import software.amazon.smithy.go.codegen.CodegenUtils; import software.amazon.smithy.go.codegen.GoDelegator; import software.amazon.smithy.go.codegen.GoSettings; import software.amazon.smithy.go.codegen.GoValueAccessUtils; 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.go.codegen.knowledge.GoPointableIndex; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.knowledge.PaginatedIndex; import software.amazon.smithy.model.knowledge.PaginationInfo; @@ -97,7 +99,9 @@ private void writePaginator( Symbol inputSymbol = symbolProvider.toSymbol(paginationInfo.getInput()); Symbol inputTokenSymbol = symbolProvider.toSymbol(paginationInfo.getInputTokenMember()); - writer.writeDocs(String.format("%s is a paginator for %s", paginatorSymbol, operationSymbol)); + GoPointableIndex pointableIndex = GoPointableIndex.of(model); + + writer.writeDocs(String.format("%s is a paginator for %s", paginatorSymbol, operationSymbol.getName())); writer.openBlock("type $T struct {", "}", paginatorSymbol, () -> { writer.write("options $T", optionsSymbol); writer.write("client $T", interfaceSymbol); @@ -113,6 +117,15 @@ private void writePaginator( writer.openBlock("func $T(client $T, params $P, optFns ...func($P)) $P {", "}", newPagiantor, interfaceSymbol, inputSymbol, optionsSymbol, paginatorSymbol, () -> { writer.write("options := $T{}", optionsSymbol); + 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(""); writer.openBlock("for _, fn := range optFns {", "}", () -> { writer.write("fn(&options)"); }); @@ -146,14 +159,24 @@ private void writePaginator( writer.openBlock("if !p.HasMorePages() {", "}", () -> { writer.write("return nil, fmt.Errorf(\"no more pages available\")"); }); + writer.write(""); writer.write("params := *p.params"); writer.write("params.$L = p.nextToken", inputMember); - pageSizeMember.ifPresent(memberShape -> { - writer.write("params.$L = p.options.Limit", symbolProvider.toMemberName(pageSizeMember.get())); + writer.write(""); + if (pointableIndex.isPointable(model.expectShape(memberShape.getTarget()))) { + writer.write("var limit $P", symbolProvider.toSymbol(memberShape)); + writer.openBlock("if p.options.Limit > 0 {", "}", () -> { + writer.write("limit = &p.options.Limit"); + }); + writer.openBlock("params.$L = limit", symbolProvider.toMemberName(memberShape)); + } else { + writer.openBlock("params.$L = p.options.Limit", symbolProvider.toMemberName(memberShape)); + } }); + writer.write(""); writer.write("result, err := p.client.$L(ctx, ¶ms, optFns...)", operationSymbol.getName()); writer.openBlock("if err != nil {", "}", () -> { @@ -179,7 +202,9 @@ private void writePaginator( } writer.write("prevToken := p.nextToken"); - writer.write("p.nextToken = nil"); + if (outputMemberPath.size() > 1) { + writer.write("p.nextToken = nil"); + } setToken.accept("result"); writer.write(""); @@ -213,7 +238,7 @@ private void writePaginatorOptions( memberShape.getMemberTrait(model, DocumentationTrait.class).ifPresent(documentationTrait -> { writer.writeDocs(documentationTrait.getValue()); }); - writer.write("Limit $P", symbolProvider.toSymbol(memberShape)); + writer.write("Limit $T", symbolProvider.toSymbol(memberShape)); writer.write(""); }); if (model.expectShape(paginationInfo.getInputTokenMember().getTarget()).isStringShape()) {