Public: anyone can read this; the crew can change it.

The API's "not found" and "authenticate" sentences live in one place, read by respond.ts and proposal tokens #5596

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

mason/forge:mason/5583-the-api-s-not-found-and-authenticate-sen → main 1 commit fork

  • Merged. mason/5583-the-api-s-not-found-and-authenticate-sen was merged into main as 3787925.

Review

Sign in to review this change.
3 files changed +38 −6
src/actions/proposal_tokens.ts +2 −4
@@ -16,15 +16,13 @@ import {
1616 import { canWrite, getRepo, type Repo } from "../core/repos.ts";
1717 import type { User } from "../core/users.ts";
1818 import { err, ok, type Result } from "../kernel/result.ts";
19 import { NOT_FOUND_WORDS, UNAUTHORIZED_WORDS } from "../http/respond.ts";
1920
2021 export interface ProposalTokenError {
2122 readonly code: "unauthorized" | "not_found" | "invalid_child" | "invalid_origin";
2223 readonly message: string;
2324 }
2425
25 /** The API's own words for a repository that is missing or not yours. */
26 export const NOT_FOUND_WORDS = "no such resource here, or it is not visible to you";
27
2826 const fail = (code: ProposalTokenError["code"], message: string) =>
2927 err<ProposalTokenError>({ code, message });
3028
@@ -36,7 +34,7 @@ function mine(
3634 actor: User | undefined,
3735 repo: Repo | undefined,
3836 ): Result<{ repo: Repo; actor: User }, ProposalTokenError> {
39 if (!actor) return fail("unauthorized", "authenticate with a bearer token or account credentials");
37 if (!actor) return fail("unauthorized", UNAUTHORIZED_WORDS);
4038 if (!repo || !canWrite(app.db, repo, actor)) return fail("not_found", NOT_FOUND_WORDS);
4139 return ok({ repo, actor });
4240 }
src/http/respond.ts +7 −2
@@ -34,6 +34,11 @@ export const apiError = (
3434 message: string,
3535 ): Response => json({ error: code, message }, status);
3636
37 /** The API's own words for a resource that is missing or not visible to you. */
38 export const NOT_FOUND_WORDS = "no such resource here, or it is not visible to you";
39 /** The API's own words for a request that carries no credential. */
40 export const UNAUTHORIZED_WORDS = "authenticate with a bearer token or account credentials";
41
3742 // `not_found` deliberately conflates "no such resource" with "not visible to
3843 // you" so a private repo never leaks its existence — the message keeps that
3944 // ambiguity intact.
@@ -41,13 +46,13 @@ export const notFound = () =>
4146 apiError(
4247 "not_found",
4348 404,
44 "no such resource here, or it is not visible to you",
49 NOT_FOUND_WORDS,
4550 );
4651 export const unauthorized = () =>
4752 apiError(
4853 "unauthorized",
4954 401,
50 "authenticate with a bearer token or account credentials",
55 UNAUTHORIZED_WORDS,
5156 );
5257 // A password door past the sign-in windows (`core/signin-window.ts`).
5358 export const tooManyAttempts = (retryAfter: number) =>
test/respond-words.test.ts added +29
@@ -0,0 +1,29 @@
1 /**
2 * The API's "not found" and "authenticate" sentences are said from one place,
3 * `src/http/respond.ts`; the files that say them import them rather than
4 * typing a copy that could drift.
5 */
6
7 import { describe, expect, test } from "bun:test";
8 import { NOT_FOUND_WORDS, UNAUTHORIZED_WORDS } from "../src/http/respond.ts";
9
10 const SENTENCES = { NOT_FOUND_WORDS, UNAUTHORIZED_WORDS };
11 const READERS = ["src/http/respond.ts", "src/actions/proposal_tokens.ts"];
12 const root = new URL("..", import.meta.url).pathname;
13
14 describe("respond words", () => {
15 for (const [name, words] of Object.entries(SENTENCES)) {
16 test(`${name} is a sentence`, () => {
17 expect(words.trim().length).toBeGreaterThan(0);
18 });
19
20 for (const file of READERS) {
21 test(`${file} does not type ${name} out`, async () => {
22 const source = await Bun.file(root + file).text();
23 const typed = source.split(`"${words}"`).length - 1;
24 // respond.ts holds the one definition; every other reader has none.
25 expect(typed).toBe(file === "src/http/respond.ts" ? 1 : 0);
26 });
27 }
28 }
29 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs