-
Notifications
You must be signed in to change notification settings - Fork 856
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support Async pagination #419
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,13 +17,20 @@ | |
|
||
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.poet.PoetExtensions; | ||
import software.amazon.awssdk.codegen.utils.PaginatorUtils; | ||
import software.amazon.awssdk.core.pagination.async.SequentialSubscriber; | ||
|
||
public class PaginationDocs { | ||
|
||
private static final String SUBSCRIBE_METHOD_NAME = "subscribe"; | ||
|
||
private final OperationModel operationModel; | ||
private final PoetExtensions poetExtensions; | ||
|
||
|
@@ -62,7 +69,7 @@ public String getDocsForSyncResponseClass(ClassName clientInterface) { | |
.add("<p>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.</p>", | ||
clientInterface, getSyncPaginatedMethodName(), requestType(), syncResponsePageType()) | ||
clientInterface, getPaginatedMethodName(), requestType(), syncResponsePageType()) | ||
.add("<p>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 " | ||
|
@@ -73,10 +80,54 @@ clientInterface, getSyncPaginatedMethodName(), requestType(), syncResponsePageTy | |
.toString(); | ||
} | ||
|
||
/** | ||
* Constructs additional documentation on the async client operation that is appended to the service documentation. | ||
*/ | ||
public String getDocsForAsyncOperation() { | ||
return CodeBlock.builder() | ||
.add("<p>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</p>", | ||
operationModel.getMethodName(), requestType()) | ||
.add("<p>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.</p>", | ||
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("<p>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.</p>", | ||
clientInterface, getPaginatedMethodName(), requestType(), getPublisherType(), | ||
syncResponsePageType()) | ||
.add("<p>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.</p>", | ||
getPublisherType(), SUBSCRIBE_METHOD_NAME, getSubscriberType(), getSubscriptionType()) | ||
.add(getAsyncCodeSnippets()) | ||
.build() | ||
.toString(); | ||
} | ||
|
||
private String getSyncCodeSnippets() { | ||
CodeBlock callOperationOnClient = CodeBlock.builder() | ||
.addStatement("$T responses = client.$L(request)", syncPaginatedResponseType(), | ||
getSyncPaginatedMethodName()) | ||
getPaginatedMethodName()) | ||
.build(); | ||
|
||
return CodeBlock.builder() | ||
|
@@ -103,20 +154,56 @@ private String getSyncCodeSnippets() { | |
.toString(); | ||
} | ||
|
||
private String getAsyncCodeSnippets() { | ||
CodeBlock callOperationOnClient = CodeBlock.builder() | ||
.addStatement("$T publisher = client.$L(request)", | ||
asyncPaginatedResponseType(), | ||
getPaginatedMethodName()) | ||
.build(); | ||
|
||
return CodeBlock.builder() | ||
.add("\n\n<p>The following are few ways to use the response class:</p>") | ||
.add("1) Using the forEach helper method", | ||
TypeName.get(SequentialSubscriber.class)) | ||
.add(buildCode(CodeBlock.builder() | ||
.add(callOperationOnClient) | ||
.add(CodeBlock.builder() | ||
.addStatement("CompletableFuture<Void> future = publisher" | ||
+ ".forEach(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(noteAboutSyncNonPaginatedMethod()) | ||
.build() | ||
.toString(); | ||
} | ||
|
||
private CodeBlock buildCode(CodeBlock codeSnippet) { | ||
return CodeBlock.builder() | ||
.add("<pre>{@code\n") | ||
.add(codeSnippet) | ||
.add("}</pre>") | ||
.build(); | ||
|
||
} | ||
|
||
/** | ||
* @return Method name for the sync paginated operation | ||
*/ | ||
private String getSyncPaginatedMethodName() { | ||
return PaginatorUtils.getSyncMethodName(operationModel.getMethodName()); | ||
private String getPaginatedMethodName() { | ||
return PaginatorUtils.getPaginatedMethodName(operationModel.getMethodName()); | ||
} | ||
|
||
/** | ||
|
@@ -140,14 +227,42 @@ private ClassName syncResponsePageType() { | |
/** | ||
* @return A Poet {@link ClassName} for the return type of sync paginated operation. | ||
*/ | ||
public ClassName syncPaginatedResponseType() { | ||
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 CodeBlock noteAboutSyncNonPaginatedMethod() { | ||
return CodeBlock.builder() | ||
.add("\n<p><b>Note: If you prefer to have control on service calls, use the {@link #$L($T)} operation." | ||
+ "</b></p>", operationModel.getMethodName(), requestType()) | ||
.build(); | ||
} | ||
|
||
/** | ||
* @return A Poet {@link ClassName} for the reactive streams {@link Publisher}. | ||
*/ | ||
private ClassName getPublisherType() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we really need this and the one below in separate methods? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These are used 2-3 places in the class. I thought it would be better to have them in separate method rather than calling the ClassName.get() method. |
||
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); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -67,11 +67,22 @@ public ClassName getClientClass(String className) { | |
* @return A Poet {@link ClassName} for the response type of a paginated operation in the base service package. | ||
* | ||
* Example: If operationName is "ListTables", then the response type of the paginated operation | ||
* will be "ListTablesPaginator" class in the base service package. | ||
* will be "ListTablesIterable" class. | ||
*/ | ||
@ReviewBeforeRelease("Naming of response shape for paginated APIs") | ||
public ClassName getResponseClassForPaginatedSyncOperation(String operationName) { | ||
return ClassName.get(model.getMetadata().getFullPaginatorsPackageName(), operationName + "Paginator"); | ||
return ClassName.get(model.getMetadata().getFullPaginatorsPackageName(), operationName + "Iterable"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why the change in name here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The method ends with "Paginator", so didn't want same name for response type. For example, This also conveys the return type is a Iterable. It also matches the convention for async method. |
||
} | ||
|
||
/** | ||
* @param operationName Name of the operation | ||
* @return A Poet {@link ClassName} for the response type of a async paginated operation in the base service package. | ||
* | ||
* Example: If operationName is "ListTables", then the async response type of the paginated operation | ||
* will be "ListTablesPublisher" class. | ||
*/ | ||
@ReviewBeforeRelease("Naming of response shape for paginated APIs") | ||
public ClassName getResponseClassForPaginatedAsyncOperation(String operationName) { | ||
return ClassName.get(model.getMetadata().getFullPaginatorsPackageName(), operationName + "Publisher"); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the future here be fulfilled with the last response? Would that be useful or confusing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where would you do it? Can you show me sample code