import type { Children } from "@alloy-js/core";
import { code, List, refkey } from "@alloy-js/core";
import * as ts from "@alloy-js/typescript";
import { useTsp } from "@typespec/emitter-framework";
import { FunctionDeclaration } from "@typespec/emitter-framework/typescript";
import type { HttpOperation } from "@typespec/http";
import * as cl from "@typespec/http-client";
import { reportDiagnostic } from "../../../lib.js";
import { getClientcontextDeclarationRef } from "../../client-context/client-context-declaration.jsx";
import { OperationOptionsDeclaration } from "../../operation-options.jsx";
import { getOperationParameters } from "../../operation-parameters.jsx";
import {
getBuildPagedAsyncIteratorRefkey,
getPagedAsyncIterableIteratorRefkey,
} from "../../static-helpers/paging-helper.jsx";
import { getPageItemTypeName } from "../paging/page-item.jsx";
import { getPageResponseTypeRefkey, PageResponseDeclaration } from "../paging/page-response.jsx";
import {
getPageSettingProperties,
getPageSettingsTypeRefkey,
PageSettingsDeclaration,
} from "../paging/page-settings.jsx";
import { HttpRequestSend } from "../paging/request-send.jsx";
import {
getHttpRequestDeserializeRefkey,
HttpResponseDeserialize,
} from "../paging/response-deserialize.jsx";
import type { PagingDetail } from "../paging/util.js";
import { extractPagingDetail } from "../paging/util.js";
import type { OperationHandler } from "../types.js";
export const PaginatedOperationHandler: OperationHandler = {
canHandle(httpOperation: HttpOperation): boolean {
const { $ } = useTsp();
const pagingMetadata = $.operation.getPagingMetadata(httpOperation.operation);
return pagingMetadata !== undefined;
},
handle(httpOperation: HttpOperation): Children {
const { $ } = useTsp();
const clientLibrary = cl.useClientLibrary();
const client = clientLibrary.getClientForOperation(httpOperation);
if (!client) {
reportDiagnostic($.program, {
code: "client-not-found",
target: httpOperation.operation,
});
return;
}
const pagingOperation = $.operation.getPagingMetadata(httpOperation.operation)!;
const responseRefkey = refkey(httpOperation, "http-response");
const operationRefkey = refkey(httpOperation.operation);
const returnType = code`${getPagedAsyncIterableIteratorRefkey()}<${getPageItemTypeName(pagingOperation)},${getPageResponseTypeRefkey(httpOperation)},${getPageSettingsTypeRefkey(httpOperation)}>`;
const clientContextInterfaceRef = getClientcontextDeclarationRef(client);
const optionsRefkey = refkey();
const signatureParams: ts.ParameterDescriptor[] = [
{
name: "client",
type: clientContextInterfaceRef,
refkey: refkey(client, "paging-operation-client-param"),
},
...getOperationParameters(httpOperation, optionsRefkey),
];
const pagingDetail = extractPagingDetail(httpOperation, pagingOperation);
// Exclude from operation options and include them into PageSettings
const excludes = getPageSettingProperties(pagingOperation).map((p) => p.property);
return (
<>
{code`
return ${getBuildPagedAsyncIteratorRefkey}<${getPageItemTypeName(pagingOperation)}, ${getPageResponseTypeRefkey(httpOperation)}, ${getPageSettingsTypeRefkey(httpOperation)}>({${getElementsRefkey(httpOperation)}, ${getPagedResponseFunctionRefkey(httpOperation)}: ${getPagedResponseFunctionRefkey(httpOperation)} as any});`}
>
);
},
};
interface GetPagedResponseProps {
httpOperation: HttpOperation;
pageDetail: PagingDetail;
operationParams: ts.ParameterDescriptor[];
}
function getPagedResponseFunctionRefkey(httpOperation: HttpOperation) {
return refkey(httpOperation, "get-paged-response");
}
function GetPagedResponse(props: GetPagedResponseProps) {
const pagingDetail = props.pageDetail;
const httpOperation = props.httpOperation;
const params: ts.ParameterDescriptor[] = [
{ name: "nextToken", type: "string", optional: true, refkey: refkey() },
{
name: "settings",
type: getPageSettingsTypeRefkey(httpOperation),
optional: true,
refkey: refkey(),
},
];
// TODO: template can't resolve the ref key so fallback to useTSNamePolicy
const namePolicy = ts.useTSNamePolicy();
const functionName = namePolicy.getName(httpOperation.operation.name + "Send", "function");
const sendParamStr = props.operationParams
.map((param) => param.name)
.slice(0, -1)
.join(", ");
return (
{code`
${
props.pageDetail.pattern === "nextLink"
? `
let response: PathUncheckedResponse;
if (nextToken) {
response = await client.pathUnchecked(nextToken).get();
} else {
const combinedOptions = { ...options, ...settings };
response = await ${functionName}(${sendParamStr}, combinedOptions);
} `
: `
const combinedOptions = {...options, ...settings};
${
pagingDetail.input?.nextToken
? `
if (nextToken) {
combinedOptions.${pagingDetail.input?.nextToken} = nextToken;
}`
: ``
}
const response = await ${functionName}(${sendParamStr}, combinedOptions);
`
}
return {
pagedResponse: await ${getHttpRequestDeserializeRefkey(httpOperation)}(response, options),
nextToken: ${
pagingDetail.output.nextToken
? `response.${pagingDetail.output.nextToken.position}["${pagingDetail.output.nextToken.name}"]`
: `undefined`
},
};
`}
);
}
interface GetElementsProps {
httpOperation: HttpOperation;
pageDetail: PagingDetail;
}
function getElementsRefkey(httpOperation: HttpOperation) {
return refkey(httpOperation, "get-elements");
}
function GetElements(props: GetElementsProps) {
const pagingDetail = props.pageDetail;
const httpOperation = props.httpOperation;
const params = [
{
name: "response",
type: getPageResponseTypeRefkey(httpOperation),
refkey: refkey(),
},
];
return (
{code`
return response.${pagingDetail.output.items};
`}
);
}