import { isPermanentDenial } from './fetchFailureMessage'; /** * A LIST THAT WAS REFUSED IS NOT A LIST THAT IS EMPTY. * * Measured on a patient's page for a practice they cannot be shown: six reads answered 403, three of them * rendered the refusal honestly, and the other three turned it into an assertion about the world. A * practice with four practitioners read as "No providers listed." A patient's own appointments read as * "No appointments at this organization." The locations block rendered nothing at all, which is the same * false assertion with less to read. * * Each of those three sections caught the error, discarded it, and fell through to the branch that means * "the server answered and there was nothing in it". That branch is correct copy for a genuinely empty * list and it stays; what was missing is that a refusal never reaches it. * * THE RULE: a refusal renders as a denial naming what is denied, never as a failure or as empty data, * because empty data is a false statement about the patient's own record. * * ITS SIBLINGS, AND WHY THIS IS NOT ONE OF THEM. `fetchFailureMessage` answers "a load failed" for a * surface that has a message line to put a sentence on. `serverRefusalMessage` answers "a WRITE was * refused". `agreementStatusFailure` answers the same question this does and is the file this one was * generalised out of - it now delegates here, so the split lives in one place and the agreement copy * stays its own, because a document's status and a list of people are not the same subject. */ export type SectionReadFailure = 'denied' | 'error'; /** * Which of the two this was. * * ONLY A PERMANENT DENIAL IS RECLASSIFIED. A 5xx, a timeout and a dropped connection stay retryable and * keep their Try again, which is the control that stops this becoming the same defect pointing the other * way: telling somebody with a bad connection that they are not allowed to see their own appointments * would be worse than what it replaces. */ export const sectionReadFailure = (error: unknown): SectionReadFailure => { const status = (error as { status?: unknown } | null | undefined)?.status; return typeof status === 'number' && isPermanentDenial(status) ? 'denied' : 'error'; }; /** * What a refused section says, in the three parts the agreement denial on the same page already uses. * * IT NAMES WHAT COULD NOT BE READ. "This" is not a denial; "the providers at Great River Health Center" * is. That is the whole of the rule, and it is why the subject is a noun phrase supplied by the caller * rather than a word this file guesses at. * * IT SAYS THE RETRY IS ABSENT ON PURPOSE, because a control that silently disappears reads as a bug to * the person who saw it a moment ago. * * IT DOES NOT GUESS AT WHY. The refusal arrives as a bare 403 and this surface cannot tell a practice's * configuration from a gap in the patient's own session, so it says what happened and where to get the * answer. Naming the practice as the one who withheld it would be an accusation we have no evidence for. * * @param subject what could not be shown, already a noun phrase - e.g. `'the providers at Great River'` * @param practice the practice to ask, named again because the closing sentence is an instruction * @param whatTheyCanTell what that practice can answer, with no trailing punctuation */ export const sectionReadDeniedMessage = ( subject: string, practice: string, whatTheyCanTell: string, ): string => `We are not allowed to show you ${subject}. ` + 'That is a refusal rather than a loading problem, so trying again will not change it. ' + `Ask ${practice} directly, and they can tell you ${whatTheyCanTell}.`;