import { davError, davResponse, numberOfMatchesWithinLimitsError } from "@/worker/dav/http"; import { buildMultistatus, type Propstat } from "@/worker/dav/multistatus"; import { readReportDepth } from "@/worker/dav/readers"; import { davResultError, multigetMultistatus, syncCollectionMultistatus } from "@/worker/dav/responses"; import type { DavRequestContext } from "@/worker/dav/runtime"; import type { DavOperationResult } from "@/worker/dav/collection-object"; type CollectionReportOperation = "sync" | "multiget" | "query"; type ReportDepth = "0" | "1" | "infinity"; export interface CollectionReportAdapter { invalidBodyMessage: string; validatePath(context: DavRequestContext, path: TPath): Response | null; validateReportPath?(context: DavRequestContext, path: TPath, report: TReport): Response | null; parseReport(xml: string, maxBytes: number): TReport; operationForReport(report: TReport): CollectionReportOperation; depthFallback(report: TReport): ReportDepth; sync( context: DavRequestContext, path: TPath, report: TReport, ): Promise< DavOperationResult<{ resources: TResource[]; changes: { href: string; changeType: string }[]; syncToken: string; truncated: boolean; }> >; multiget( context: DavRequestContext, path: TPath, report: TReport, ): Promise>; query( context: DavRequestContext, path: TPath, report: TReport, ): Promise>; propstatsForResource(context: DavRequestContext, path: TPath, report: TReport, resource: TResource): Propstat[]; } export async function handleCollectionReport( context: DavRequestContext, path: TPath, maxXmlBytes: number, adapter: CollectionReportAdapter, ): Promise { const pathResponse = adapter.validatePath(context, path); if (pathResponse) return pathResponse; let report: TReport; try { report = adapter.parseReport(await context.c.req.text(), maxXmlBytes); } catch (cause) { return davError(400, cause instanceof Error ? cause.message : adapter.invalidBodyMessage); } const depth = readReportDepth(context.c, adapter.depthFallback(report)); if (!depth.ok) return depth.response; const reportPathResponse = adapter.validateReportPath?.(context, path, report); if (reportPathResponse) return reportPathResponse; const operation = adapter.operationForReport(report); if (operation === "sync") { return syncCollectionReport(await adapter.sync(context, path, report), (resource) => adapter.propstatsForResource(context, path, report, resource), ); } if (operation === "multiget") { return multigetReport(await adapter.multiget(context, path, report), (resource) => adapter.propstatsForResource(context, path, report, resource), ); } if (depth.value === "0") return davResponse(buildMultistatus([]), 207); return queryReport(await adapter.query(context, path, report), (resource) => adapter.propstatsForResource(context, path, report, resource), ); } export function syncCollectionReport( result: DavOperationResult<{ resources: TResource[]; changes: { href: string; changeType: string }[]; syncToken: string; truncated: boolean; }>, propstatsForResource: (resource: TResource) => Propstat[], ): Response { return result.ok ? syncCollectionMultistatus(result, propstatsForResource) : davResultError(result); } export function multigetReport( result: DavOperationResult<{ requested: { href: string; resource: TResource | null }[] }>, propstatsForResource: (resource: TResource) => Propstat[], ): Response { return result.ok ? multigetMultistatus(result.requested, propstatsForResource) : davResultError(result); } export function queryReport( result: DavOperationResult<{ resources: TResource[]; truncated: boolean }>, propstatsForResource: (resource: TResource) => Propstat[], ): Response { if (!result.ok) return davResultError(result); if (result.truncated) return numberOfMatchesWithinLimitsError(); return davResponse( buildMultistatus( result.resources.map((resource) => ({ href: resource.href, propstats: propstatsForResource(resource), })), ), 207, ); }