Retire allowFhirApi and resourceTypes off the share-link response

iOS green-lit removal on 2026-07-30 (their decoder treats both as
optional, confirmed at ShareLinkDTO.swift:105-106) and confirmed no
shipped build breaks. Drops RETIRED_FHIR_SCOPE_FIELDS and its spread
from the create and list responses, removes the two properties and
their required-list entries from ShareLinkCreated/ShareLinkList in
the OpenAPI source, and regenerates the spec. The request side was
already closed off by .strict() in an earlier pass; this finishes it
on the response side.

Route tests now assert the fields are absent from both responses
instead of asserting the constants they used to carry.
This commit is contained in:
Marc-André Bombeck committed 2026-08-01 11:38:16 +02:00
1 parent fb1bf710e6
commit 5767e0330b
5 files changed
+12 -71

No files matched your search

-16
View File
@@ -16518,12 +16518,6 @@ components:
type: boolean
needsReselection:
type: boolean
allowFhirApi:
type: boolean
resourceTypes:
type: array
items:
type: string
expiresAt:
type: string
createdAt:
@@ -16562,8 +16556,6 @@ components:
- documentCount
- documentOnly
- needsReselection
- allowFhirApi
- resourceTypes
- expiresAt
- createdAt
- revokedAt
@@ -16616,12 +16608,6 @@ components:
type: boolean
needsReselection:
type: boolean
allowFhirApi:
type: boolean
resourceTypes:
type: array
items:
type: string
expiresAt:
type: string
createdAt:
@@ -16647,8 +16633,6 @@ components:
- documentCount
- documentOnly
- needsReselection
- allowFhirApi
- resourceTypes
- expiresAt
- createdAt
- revokedAt
+11 -20
View File
@@ -374,8 +374,6 @@ describe("DELETE /api/share-links/[id] — revoke", () => {
* Mutation checks:
* - drop the `SHARE_LINK_FORBIDDEN_LEAVES` check in the route → "refuses the
* insurance leaf by name" goes red.
* - drop `RETIRED_FHIR_SCOPE_FIELDS` from the create response → "keeps the
* retired scope fields on the response" goes red.
* - make `needsReselection` always false → "flags a link whose frozen scope
* predates the selection model" goes red.
*/
@@ -433,21 +431,17 @@ describe("POST /api/share-links — the frozen scope", () => {
expect(createArg.data.sectionsJson).toEqual({ v: 2, leaves: [] });
});
it("keeps the retired scope fields on the response", async () => {
// Response-only, serving constants. The request refuses them and the
// columns are gone; they stay here until the native client confirms it
// does not decode them.
it("the retired FHIR scope fields are gone from the create response", async () => {
// #70 — the native client confirmed removal breaks no shipped decoder.
vi.mocked(prisma.clinicianShareLink.create).mockResolvedValue(
storedRow() as never,
);
const res = await POST(
postReq(validBody({ selection: { v: 2, leaves: ["WEIGHT"] } })),
);
const body = (await res.json()) as {
data: { allowFhirApi: boolean; resourceTypes: string[] };
};
expect(body.data.allowFhirApi).toBe(false);
expect(body.data.resourceTypes).toEqual([]);
const body = (await res.json()) as { data: Record<string, unknown> };
expect(body.data).not.toHaveProperty("allowFhirApi");
expect(body.data).not.toHaveProperty("resourceTypes");
});
});
@@ -465,12 +459,9 @@ describe("GET /api/share-links — the pick-again state", () => {
const res = await GET();
const body = (await res.json()) as {
data: {
shareLinks: Array<{
id: string;
needsReselection: boolean;
allowFhirApi: boolean;
resourceTypes: string[];
}>;
shareLinks: Array<
{ id: string; needsReselection: boolean } & Record<string, unknown>
>;
};
};
const byId = new Map(body.data.shareLinks.map((l) => [l.id, l]));
@@ -479,8 +470,8 @@ describe("GET /api/share-links — the pick-again state", () => {
// A documents-only link carried no report scope to begin with, so it was
// never revoked and is not asked to pick again.
expect(byId.get("docs")!.needsReselection).toBe(false);
// The retired fields ride the list response too.
expect(byId.get("current")!.allowFhirApi).toBe(false);
expect(byId.get("current")!.resourceTypes).toEqual([]);
// The retired FHIR scope fields are gone from the list response too.
expect(byId.get("current")).not.toHaveProperty("allowFhirApi");
expect(byId.get("current")).not.toHaveProperty("resourceTypes");
});
});
+1 -21
View File
@@ -87,22 +87,6 @@ const shareLinkSummarySelect = {
_count: { select: { documents: true } },
} as const;
/**
* RETIRED, kept on the response only.
*
* These two fields described a scoped FHIR face for a share link that was
* never built. The request schema refuses them — sending either is a 422 —
* their columns are dropped, and nothing reads or honours them. They stay in
* the create and list responses, serving the constant `false` and `[]`,
* because the native client was given this response shape and a missing
* non-optional key fails a whole Swift decode. They come out once #70 in the
* native repository confirms the client does not decode them.
*/
const RETIRED_FHIR_SCOPE_FIELDS = {
allowFhirApi: false,
resourceTypes: [] as string[],
} as const;
/** Project a stored row to the safe owner-facing shape (never the token). */
function toSummary(row: {
id: string;
@@ -279,7 +263,6 @@ export const POST = apiHandler(async (request: NextRequest) => {
return apiSuccess(
{
...toSummary(created),
...RETIRED_FHIR_SCOPE_FIELDS,
token: rawToken,
passphrase: rawPassphrase,
shareUrl,
@@ -300,9 +283,6 @@ export const GET = apiHandler(async () => {
});
return apiSuccess({
shareLinks: rows.map((row) => ({
...toSummary(row),
...RETIRED_FHIR_SCOPE_FIELDS,
})),
shareLinks: rows.map((row) => toSummary(row)),
});
});
@@ -83,14 +83,6 @@ export interface ShareLinkSummary {
* hashes). `qrUrl` carries the passphrase in the URL fragment (`#k=`).
*/
export interface ShareLinkCreated extends ShareLinkSummary {
/**
* RETIRED, response-only. They configured a FHIR face that was never built;
* the request refuses them and the columns are gone. They stay here because
* the native client was given this shape and a missing non-optional key
* fails a whole Swift decode — see #70 in the native repository.
*/
allowFhirApi: boolean;
resourceTypes: string[];
token: string;
passphrase: string;
shareUrl: string;
-6
View File
@@ -36,12 +36,6 @@ const shareLinkSummary = z.object({
// link was revoked on upgrade; the sharing list surfaces this so the owner
// knows to re-mint rather than wondering why a working link stopped.
needsReselection: z.boolean(),
// RETIRED, response-only, serving the constants `false` and `[]`. They
// configured a FHIR face that was never built; the request refuses them and
// their columns are gone. They stay on the response until the native client
// confirms it does not decode them.
allowFhirApi: z.boolean(),
resourceTypes: z.array(z.string()),
expiresAt: z.string(),
createdAt: z.string(),
revokedAt: z.string().nullable(),