-
Notifications
You must be signed in to change notification settings - Fork 152
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
feat(csharp): add pagination helper methods to C# SDK generator #5187
Conversation
…harp/pagination
* Refactor to take into account different RequestOptions * Generate snippets * Generate reference
Decisions:
|
The |
* Support different types in cursor pagination * Support int and long in offset pagination * Initialize request if null and nested properties towards setCursor and setOffset
Improvements:
For initializing towards cursor and offset property, I assume the types don't have required fields and initialize an empty instance using I'm worried if one of these types does have a required field, at which point the compiler will complain. |
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.
Overall looking great - mostly comments on implementation, and only minor details for the generated code (e.g. comments, etc).
offset: (pagination) => pagination.results.property.valueType, | ||
cursor: (pagination) => pagination.results.property.valueType, | ||
_other: (pagination) => { | ||
throw new Error(`Unsupported pagination type: ${pagination.type}`); |
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.
Prefer switch
w/ assertNever
if possible.
_other: (pagination) => { | ||
throw new Error(`Unsupported pagination type: ${pagination.type}`); | ||
} |
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.
nit: Prefer switch
w/ assertNever
here.
writer.writeTextStatement(")"); | ||
writer.writeTextStatement("return pager"); | ||
}, | ||
cursor: (pagination) => { |
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.
nit: Could we put all of this in a separate function (e.g. generateCursorPagerMethod
).
} | ||
|
||
endpoint.pagination?._visit({ | ||
offset: (pagination) => { |
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.
nit: Could we put all of this in a separate function (e.g. generateOffsetPagerMethod
).
var pager = new CursorPager< | ||
ListUsersBodyCursorPaginationRequest, | ||
RequestOptions?, | ||
ListUsersPaginationResponse, | ||
string, | ||
User | ||
>( | ||
request, | ||
options, | ||
ListWithBodyCursorPaginationAsync, | ||
(request, cursor) => | ||
{ | ||
request.Pagination ??= new(); | ||
request.Pagination.Cursor = cursor; | ||
}, | ||
response => response?.Page?.Next?.StartingAfter, | ||
response => response?.Data?.ToList() | ||
); | ||
return pager; | ||
} |
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.
The generated code looks great 👏
generators/csharp/sdk/src/test-generation/mock-server/MockServerTestGenerator.ts
Show resolved
Hide resolved
public generateEndpointSnippet({ | ||
example, | ||
endpoint, | ||
clientVariableName, | ||
serviceId, | ||
requestOptions, | ||
getResult, | ||
parseDatetimes | ||
}: { | ||
example: ExampleEndpointCall; | ||
endpoint: HttpEndpoint; | ||
clientVariableName: string; | ||
serviceId: ServiceId; | ||
requestOptions?: csharp.CodeBlock; | ||
getResult?: boolean; | ||
parseDatetimes: boolean; | ||
}): csharp.MethodInvocation | undefined { | ||
const additionalEndParameters = requestOptions != null ? [requestOptions] : []; | ||
return this.hasPagination(endpoint) | ||
? this.generateHttpPagerEndpointSnippet({ | ||
example, | ||
endpoint, | ||
clientVariableName, | ||
serviceId, | ||
additionalEndParameters, | ||
getResult, | ||
parseDatetimes | ||
}) | ||
: this.generateHttpEndpointSnippet({ | ||
example, | ||
endpoint, | ||
clientVariableName, | ||
serviceId, | ||
additionalEndParameters, | ||
getResult, | ||
parseDatetimes | ||
}); | ||
} |
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.
Nice 👏
const endpointSignatureInfo = this.getEndpointSignatureInfo({ serviceId, endpoint }); | ||
const parameters = [...endpointSignatureInfo.baseParameters]; | ||
const optionsParamName = this.getRequestOptionsParamNameForEndpoint({ endpoint }); | ||
const requestOptionsParam = this.getRequestOptionsParameter({ endpoint }); | ||
const requestOptionsType = requestOptionsParam.type; | ||
parameters.push(requestOptionsParam); | ||
const itemType = this.getPaginationItemType(endpoint); | ||
const return_ = this.getPagerReturnType(endpoint); | ||
const snippet = this.getHttpPagerMethodSnippet({ endpoint }); |
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.
There's a lot of parameters required here - I wonder if it's be worth rolling out a PaginatedEndpointGenerator
class (or something similar) that could define all these properties as member variables, so that the following implementation could be reduced / a little more concise and readable.
Not totally necessary to do that here, but let me know your thoughts.
I added a shallow clone using the record |
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.
Nice!! 🎉
generators/csharp/sdk/src/endpoint/AbstractEndpointGenerator.ts
Outdated
Show resolved
Hide resolved
generators/csharp/sdk/src/endpoint/AbstractEndpointGenerator.ts
Outdated
Show resolved
Hide resolved
writer.write("var pager = "); | ||
writer.writeNodeStatement( | ||
csharp.instantiateClass({ | ||
classReference: cursorPagerClassReference, | ||
arguments_: [ | ||
csharp.codeblock(requestParam.name), | ||
csharp.codeblock(optionsParamName), | ||
csharp.codeblock(unpagedEndpointMethodName), | ||
csharp.codeblock((writer) => { | ||
writer.writeLine("(request, cursor) => {"); | ||
writer.indent(); | ||
this.initializeNestedObjects(writer, "request", pagination.page); | ||
writer.writeTextStatement(`${this.dotGet("request", pagination.page)} = cursor`); | ||
writer.dedent(); | ||
writer.writeLine("}"); | ||
}), | ||
csharp.codeblock(`response => ${this.nullableDotGet("response", pagination.next)}`), | ||
csharp.codeblock(`response => ${this.nullableDotGet("response", pagination.results)}?.ToList()`) | ||
] | ||
}) | ||
); | ||
writer.writeTextStatement("return pager"); |
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.
nit: Should we just return the pager directly rather than using var
?
writer.write("var pager = "); | ||
writer.writeNodeStatement( | ||
csharp.instantiateClass({ | ||
classReference: offsetPagerClassReference, | ||
arguments_: [ | ||
csharp.codeblock(requestParam.name), | ||
csharp.codeblock(optionsParamName), | ||
csharp.codeblock(unpagedEndpointMethodName), | ||
csharp.codeblock(`request => ${this.nullableDotGet("request", pagination.page)} ?? 0`), | ||
csharp.codeblock((writer) => { | ||
writer.writeLine("(request, offset) => {"); | ||
writer.indent(); | ||
this.initializeNestedObjects(writer, "request", pagination.page); | ||
writer.writeTextStatement(`${this.dotGet("request", pagination.page)} = offset`); | ||
writer.dedent(); | ||
writer.writeLine("}"); | ||
}), | ||
csharp.codeblock( | ||
pagination.step ? `request => ${this.nullableDotGet("request", pagination.step)} ?? 0` : "null" | ||
), | ||
csharp.codeblock(`response => ${this.nullableDotGet("response", pagination.results)}?.ToList()`), | ||
csharp.codeblock( | ||
pagination.hasNextPage | ||
? `response => ${this.nullableDotGet("response", pagination.hasNextPage)}` | ||
: "null" | ||
) | ||
] | ||
}) | ||
); | ||
writer.writeTextStatement("return pager"); |
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.
nit: Same thing with returning directly.
Co-authored-by: Alex McKinney <alexmckinney01@gmail.com>
This feature works, but the code is still a little rough and needs some cleaning.
HttpPagerEndpointGenerator
generates pager methods that use the main endpoint methods for users to paginate over items in a paginated endpoint.AsIs/Page.Template.cs
holds a list of items in a single pageAsIs/Pager.Template.cs
has a public abstractPager<Item>
class for a user to interact with, and two internal implementationsOffsetPager
and CursorPager`. The bulk of the logic to paginate is stored within these implementations, and the remaining parts are passed into the pagers as delegates.