software.amazon.awssdk.codegen.docs.PaginationDocs Maven / Gradle / Ivy
/*
* Copyright 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.awssdk.codegen.docs;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.TypeName;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
import software.amazon.awssdk.codegen.model.intermediate.OperationModel;
import software.amazon.awssdk.codegen.model.service.PaginatorDefinition;
import software.amazon.awssdk.codegen.poet.PoetExtension;
import software.amazon.awssdk.codegen.utils.PaginatorUtils;
import software.amazon.awssdk.utils.async.SequentialSubscriber;
public class PaginationDocs {
private static final String SUBSCRIBE_METHOD_NAME = "subscribe";
private final OperationModel operationModel;
private final PoetExtension poetExtensions;
private final PaginatorDefinition paginatorDefinition;
public PaginationDocs(IntermediateModel intermediateModel, OperationModel operationModel,
PaginatorDefinition paginatorDefinition) {
this.operationModel = operationModel;
this.poetExtensions = new PoetExtension(intermediateModel);
this.paginatorDefinition = paginatorDefinition;
}
/**
* Constructs additional documentation on the client operation that is appended to the service documentation.
*
* TODO Add a link to our developer guide which will have more details and sample code. Write a blog post too.
*/
public String getDocsForSyncOperation() {
return CodeBlock.builder()
.add("This is a variant of {@link #$L($T)} operation. "
+ "The return type is a custom iterable that can be used to iterate through all the pages. "
+ "SDK will internally handle making service calls for you.\n
",
operationModel.getMethodName(), requestType())
.add("\nWhen this operation is called, a custom iterable is returned but no service calls are "
+ "made yet. So there is no guarantee that the request is valid. As you iterate "
+ "through the iterable, SDK will start lazily loading response pages by making service calls until "
+ "there are no pages left or your iteration stops. If there are errors in your request, you will "
+ "see the failures only after you start iterating through the iterable.\n
")
.add(getSyncCodeSnippets())
.build()
.toString();
}
/**
* Constructs javadocs for the generated response classes in a paginated operation.
* @param clientInterface A java poet {@link ClassName} type of the sync client interface
*/
public String getDocsForSyncResponseClass(ClassName clientInterface) {
return CodeBlock.builder()
.add("Represents the output for the {@link $T#$L($T)} operation which is a paginated operation."
+ " This class is an iterable of {@link $T} that can be used to iterate through all the "
+ "response pages of the operation.
",
clientInterface, getPaginatedMethodName(), requestType(), syncResponsePageType())
.add("When the operation is called, an instance of this class is returned. At this point, "
+ "no service calls are made yet and so there is no guarantee that the request is valid. "
+ "As you iterate through the iterable, SDK will start lazily loading response pages by making "
+ "service calls until there are no pages left or your iteration stops. If there are errors in your "
+ "request, you will see the failures only after you start iterating through the iterable.
")
.add(getSyncCodeSnippets())
.build()
.toString();
}
/**
* Constructs additional documentation on the async client operation that is appended to the service documentation.
*/
public String getDocsForAsyncOperation() {
return CodeBlock.builder()
.add("This is a variant of {@link #$L($T)} operation. "
+ "The return type is a custom publisher that can be subscribed to request a stream of response "
+ "pages. SDK will internally handle making service calls for you.\n
",
operationModel.getMethodName(), requestType())
.add("When the operation is called, an instance of this class is returned. At this point, "
+ "no service calls are made yet and so there is no guarantee that the request is valid. "
+ "If there are errors in your request, you will see the failures only after you start streaming "
+ "the data. The subscribe method should be called as a request to start streaming data. "
+ "For more info, see {@link $T#$L($T)}. Each call to the subscribe method will result in a new "
+ "{@link $T} i.e., a new contract to stream data from the starting request.
",
getPublisherType(), SUBSCRIBE_METHOD_NAME, getSubscriberType(), getSubscriptionType())
.add(getAsyncCodeSnippets())
.build()
.toString();
}
/**
* Constructs javadocs for the generated response classes of a paginated operation in Async client.
* @param clientInterface A java poet {@link ClassName} type of the Async client interface
*/
public String getDocsForAsyncResponseClass(ClassName clientInterface) {
return CodeBlock.builder()
.add("Represents the output for the {@link $T#$L($T)} operation which is a paginated operation."
+ " This class is a type of {@link $T} which can be used to provide a sequence of {@link $T} "
+ "response pages as per demand from the subscriber.
",
clientInterface, getPaginatedMethodName(), requestType(), getPublisherType(),
syncResponsePageType())
.add("When the operation is called, an instance of this class is returned. At this point, "
+ "no service calls are made yet and so there is no guarantee that the request is valid. "
+ "If there are errors in your request, you will see the failures only after you start streaming "
+ "the data. The subscribe method should be called as a request to start streaming data. "
+ "For more info, see {@link $T#$L($T)}. Each call to the subscribe method will result in a new "
+ "{@link $T} i.e., a new contract to stream data from the starting request.
",
getPublisherType(), SUBSCRIBE_METHOD_NAME, getSubscriberType(), getSubscriptionType())
.add(getAsyncCodeSnippets())
.build()
.toString();
}
private String getSyncCodeSnippets() {
CodeBlock callOperationOnClient = CodeBlock.builder()
.addStatement("$T responses = client.$L(request)", syncPaginatedResponseType(),
getPaginatedMethodName())
.build();
return CodeBlock.builder()
.add("\n\nThe following are few ways to iterate through the response pages:
")
.add("1) Using a Stream")
.add(buildCode(CodeBlock.builder()
.add(callOperationOnClient)
.addStatement("responses.stream().forEach(....)")
.build()))
.add("\n\n2) Using For loop")
.add(buildCode(CodeBlock.builder()
.add(callOperationOnClient)
.beginControlFlow("for ($T response : responses)", syncResponsePageType())
.addStatement(" // do something")
.endControlFlow()
.build()))
.add("\n\n3) Use iterator directly")
.add(buildCode(CodeBlock.builder()
.add(callOperationOnClient)
.addStatement("responses.iterator().forEachRemaining(....)")
.build()))
.add(noteAboutLimitConfigurationMethod())
.add(noteAboutSyncNonPaginatedMethod())
.build()
.toString();
}
private String getAsyncCodeSnippets() {
CodeBlock callOperationOnClient = CodeBlock.builder()
.addStatement("$T publisher = client.$L(request)",
asyncPaginatedResponseType(),
getPaginatedMethodName())
.build();
return CodeBlock.builder()
.add("\n\nThe following are few ways to use the response class:
")
.add("1) Using the subscribe helper method",
TypeName.get(SequentialSubscriber.class))
.add(buildCode(CodeBlock.builder()
.add(callOperationOnClient)
.add(CodeBlock.builder()
.addStatement("CompletableFuture future = publisher"
+ ".subscribe(res -> "
+ "{ // Do something with the response })")
.addStatement("future.get()")
.build())
.build()))
.add("\n\n2) Using a custom subscriber")
.add(buildCode(CodeBlock.builder()
.add(callOperationOnClient)
.add("publisher.subscribe(new Subscriber<$T>() {\n\n", syncResponsePageType())
.addStatement("public void onSubscribe($T subscription) { //... }",
getSubscriberType())
.add("\n\n")
.addStatement("public void onNext($T response) { //... }", syncResponsePageType())
.add("});")
.build()))
.add("As the response is a publisher, it can work well with third party reactive streams implementations "
+ "like RxJava2.")
.add(noteAboutLimitConfigurationMethod())
.add(noteAboutSyncNonPaginatedMethod())
.build()
.toString();
}
private CodeBlock buildCode(CodeBlock codeSnippet) {
return CodeBlock.builder()
.add("{@code\n")
.add(codeSnippet)
.add("}")
.build();
}
/**
* @return Method name for the sync paginated operation
*/
private String getPaginatedMethodName() {
return PaginatorUtils.getPaginatedMethodName(operationModel.getMethodName());
}
/**
* @return A Poet {@link ClassName} for the sync operation request type.
*
* Example: For ListTables operation, it will be "ListTablesRequest" class.
*/
private ClassName requestType() {
return poetExtensions.getModelClass(operationModel.getInput().getVariableType());
}
/**
* @return A Poet {@link ClassName} for the return type of sync non-paginated operation.
*
* Example: For ListTables operation, it will be "ListTablesResponse" class.
*/
private ClassName syncResponsePageType() {
return poetExtensions.getModelClass(operationModel.getReturnType().getReturnType());
}
/**
* @return A Poet {@link ClassName} for the return type of sync paginated operation.
*/
private ClassName syncPaginatedResponseType() {
return poetExtensions.getResponseClassForPaginatedSyncOperation(operationModel.getOperationName());
}
/**
* @return A Poet {@link ClassName} for the return type of Async paginated operation.
*/
private ClassName asyncPaginatedResponseType() {
return poetExtensions.getResponseClassForPaginatedAsyncOperation(operationModel.getOperationName());
}
private String getPaginatorLimitKeyName() {
return paginatorDefinition != null ? paginatorDefinition.getLimitKey() : "";
}
private CodeBlock noteAboutLimitConfigurationMethod() {
return CodeBlock.builder()
.add("\nPlease notice that the configuration of $L won't limit the number of results "
+ "you get with the paginator. It only limits the number of results in each page.
",
getPaginatorLimitKeyName())
.build();
}
private CodeBlock noteAboutSyncNonPaginatedMethod() {
return CodeBlock.builder()
.add("\nNote: If you prefer to have control on service calls, use the {@link #$L($T)} operation."
+ "
", operationModel.getMethodName(), requestType())
.build();
}
/**
* @return A Poet {@link ClassName} for the reactive streams {@link Publisher}.
*/
private ClassName getPublisherType() {
return ClassName.get(Publisher.class);
}
/**
* @return A Poet {@link ClassName} for the reactive streams {@link Subscriber}.
*/
private ClassName getSubscriberType() {
return ClassName.get(Subscriber.class);
}
/**
* @return A Poet {@link ClassName} for the reactive streams {@link Subscription}.
*/
private ClassName getSubscriptionType() {
return ClassName.get(Subscription.class);
}
}