Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions lib/api-client/api-path-scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@
* the generated client can use it without pulling in `@sentry/api/client`.
*/

/**
* The path every Sentry API operation starts with, as the OpenAPI spec
* spells it. A self-hosted install may serve it under a path prefix.
*/
export const API_PREFIX = "/api/0";

const [API_SEGMENT, VERSION_SEGMENT] = API_PREFIX.slice(1).split("/");

/** The organization, and project when present, an API path targets. */
export type ApiPathScope = { organization: string; project?: string };

Expand All @@ -16,15 +24,18 @@ function isApiPathIdentifier(segment: string | undefined): segment is string {
return segment !== undefined && API_PATH_IDENTIFIER.test(segment);
}

/** Path segments after `/api/0/`, or undefined for a URL outside the API. */
/** Path segments after {@link API_PREFIX}, or undefined for a URL outside the API. */
function apiPathSegments(urlOrPath: string): string[] | undefined {
const isUrl = URL.canParse(urlOrPath);
const path = isUrl
? new URL(urlOrPath).pathname
: (urlOrPath.split(/[?#]/, 1)[0] ?? "");
const segments = path.split("/").filter((segment) => segment.length > 0);
for (let index = 0; index < segments.length - 1; index += 1) {
if (segments[index] === "api" && segments[index + 1] === "0") {
if (
segments[index] === API_SEGMENT &&
segments[index + 1] === VERSION_SEGMENT
) {
return segments.slice(index + 2);
}
}
Expand Down
9 changes: 7 additions & 2 deletions lib/api-client/api-path.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { API_PREFIX } from "./api-path-scope.js";
import { UserInputError } from "./errors.js";

export { type ApiPathScope, parseApiPathScope } from "./api-path-scope.js";
export {
API_PREFIX,
type ApiPathScope,
parseApiPathScope,
} from "./api-path-scope.js";

/**
* Encode one Sentry API path identifier without letting it become a URL path.
Expand All @@ -26,7 +31,7 @@ export function buildSentryApiUrl(baseUrl: string, endpoint: string): string {
while (end > 0 && baseUrl.charAt(end - 1) === "/") {
end -= 1;
}
return `${baseUrl.slice(0, end)}/api/0/${path}`;
return `${baseUrl.slice(0, end)}${API_PREFIX}/${path}`;
}

/**
Expand Down
4 changes: 1 addition & 3 deletions lib/api-client/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
parseSentryLinkHeader,
type SentryClient,
} from "@sentry/api";
import { buildSentryApiUrl } from "./api-path.js";
import { API_PREFIX, buildSentryApiUrl } from "./api-path.js";
import {
type ApiError,
ApiValidationError,
Expand Down Expand Up @@ -105,8 +105,6 @@ const NETWORK_ERROR_MESSAGES: Record<string, string> = {
ECONNRESET: "Connection reset. Try again in a moment.",
};

const API_PREFIX = "/api/0";

/** Network failures that mean the host is wrong or unreachable. */
const CONFIGURATION_ERROR_CODES = new Set([
"ENOTFOUND",
Expand Down
6 changes: 4 additions & 2 deletions lib/api-client/response-cache/invalidation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@
* query-param variant cached under that path).
*/

const API_V0_SEGMENT = "/api/0/";
import { API_PREFIX } from "../api-path-scope.js";

const API_V0_SEGMENT = `${API_PREFIX}/`;
const TRAILING_SLASH_RE = /\/$/;

/**
Expand Down Expand Up @@ -80,7 +82,7 @@ const CROSS_ENDPOINT_RULES: CrossEndpointRule[] = [
{
match: /^organizations\/[^/]+\/issues\/([^/]+)\/?$/,
extraAbsolute: ([, issueId], { apiBaseUrl }) => [
`${apiBaseUrl.replace(TRAILING_SLASH_RE, "")}/api/0/issues/${issueId}/`,
`${apiBaseUrl.replace(TRAILING_SLASH_RE, "")}${API_V0_SEGMENT}issues/${issueId}/`,
],
},
];
Expand Down
6 changes: 6 additions & 0 deletions test/operations.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { describe, expect, test } from "vitest";
import { createClient } from "@hey-api/openapi-ts";
import { API_PREFIX } from "@sentry/api/client";
import { operations } from "@sentry/api/operations";
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
Expand Down Expand Up @@ -151,4 +152,9 @@ describe("operation discovery", () => {
const source = readFileSync(new URL("../src/sdk.gen.ts", import.meta.url), "utf8");
expect(operations).toEqual(extractOperations(original, source));
});

test("starts every path in Sentry's spec with the exported API prefix", () => {
const original = JSON.parse(readFileSync(new URL("../openapi-derefed.json", import.meta.url), "utf8"));
expect(Object.keys(original.paths).filter(path => !path.startsWith(`${API_PREFIX}/`))).toEqual([]);
});
});
Loading