Skip to content

Commit 0e14e47

Browse files
committed
feat: add ContextExpressionClient and centralize route handling
- Introduced ContextExpressionClient to encapsulate API calls for \/resolveContextExpression\, applying settings, timeout and logging - Updated contextHelp.ts to consume the new client instead of creating SourceControlApi manually - Added routes.ts with BASE_PATH and resolveContextExpression route - Updated extension.ts to import CCS commands/providers from barrel - Adapted SourceControlApi client to use centralized routes (only resolveContextExpression for now)
1 parent 2a9a3d9 commit 0e14e47

File tree

5 files changed

+97
-45
lines changed

5 files changed

+97
-45
lines changed

src/ccs/commands/contextHelp.ts

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
import * as path from "path";
22
import * as vscode from "vscode";
33

4-
import { AtelierAPI } from "../../api";
4+
import { ContextExpressionClient } from "../sourcecontrol/contextExpressionClient";
55
import { handleError } from "../../utils";
6-
import { SourceControlApi } from "../sourcecontrol/client";
76

8-
interface ResolveContextExpressionResponse {
9-
status?: string;
10-
textExpression?: string;
11-
message?: string;
12-
}
7+
const sharedClient = new ContextExpressionClient();
138

149
export async function resolveContextExpression(): Promise<void> {
1510
const editor = vscode.window.activeTextEditor;
@@ -28,23 +23,11 @@ export async function resolveContextExpression(): Promise<void> {
2823
}
2924

3025
const routine = path.basename(document.fileName);
31-
const api = new AtelierAPI(document.uri);
32-
33-
let sourceControlApi: SourceControlApi;
34-
try {
35-
sourceControlApi = SourceControlApi.fromAtelierApi(api);
36-
} catch (error) {
37-
void vscode.window.showErrorMessage(error instanceof Error ? error.message : String(error));
38-
return;
39-
}
4026

4127
try {
42-
const response = await sourceControlApi.post<ResolveContextExpressionResponse>("/resolveContextExpression", {
43-
routine,
44-
contextExpression,
45-
});
28+
const response = await sharedClient.resolve(document, { routine, contextExpression });
29+
const data = response ?? {};
4630

47-
const data = response.data ?? {};
4831
if (typeof data.status === "string" && data.status.toLowerCase() === "success" && data.textExpression) {
4932
const eol = document.eol === vscode.EndOfLine.CRLF ? "\r\n" : "\n";
5033
const textExpression = data.textExpression.replace(/\r?\n/g, eol);

src/ccs/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
1+
export { getCcsSettings, isFlagEnabled, type CcsSettings } from "./config/settings";
2+
export { logDebug, logError, logInfo, logWarn } from "./core/logging";
13
export { SourceControlApi } from "./sourcecontrol/client";
24
export { resolveContextExpression } from "./commands/contextHelp";
5+
export { ContextExpressionClient } from "./sourcecontrol/contextExpressionClient";

src/ccs/sourcecontrol/client.ts

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";
2-
import * as https from "https";
3-
import * as vscode from "vscode";
1+
import { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";
2+
43
import { AtelierAPI } from "../../api";
4+
import { getCcsSettings } from "../config/settings";
5+
import { createHttpClient } from "../core/http";
6+
import { logDebug } from "../core/logging";
7+
import { BASE_PATH } from "./routes";
58

69
export class SourceControlApi {
710
private readonly client: AxiosInstance;
@@ -18,35 +21,37 @@ export class SourceControlApi {
1821
}
1922

2023
const normalizedPrefix = pathPrefix ? (pathPrefix.startsWith("/") ? pathPrefix : `/${pathPrefix}`) : "";
21-
const baseUrl = `${useHttps ? "https" : "http"}://${host}:${port}${encodeURI(normalizedPrefix)}`;
22-
23-
const httpsAgent = new https.Agent({
24-
rejectUnauthorized: vscode.workspace.getConfiguration("http").get("proxyStrictSSL"),
25-
});
26-
27-
const client = axios.create({
28-
baseURL: `${baseUrl}/api/sourcecontrol/vscode`,
29-
headers: {
30-
"Content-Type": "application/json",
31-
},
32-
httpsAgent,
33-
auth:
34-
typeof username === "string" && typeof password === "string"
35-
? {
36-
username,
37-
password,
38-
}
39-
: undefined,
24+
const trimmedPrefix = normalizedPrefix.endsWith("/") ? normalizedPrefix.slice(0, -1) : normalizedPrefix;
25+
const encodedPrefix = encodeURI(trimmedPrefix);
26+
const protocol = useHttps ? "https" : "http";
27+
const defaultBaseUrl = `${protocol}://${host}:${port}${encodedPrefix}${BASE_PATH}`;
28+
29+
const { endpoint, requestTimeout } = getCcsSettings();
30+
const baseURL = endpoint ?? defaultBaseUrl;
31+
const auth =
32+
typeof username === "string" && typeof password === "string"
33+
? {
34+
username,
35+
password,
36+
}
37+
: undefined;
38+
39+
logDebug("Creating SourceControl API client", { baseURL, hasAuth: Boolean(auth) });
40+
41+
const client = createHttpClient({
42+
baseURL,
43+
auth,
44+
defaultTimeout: requestTimeout,
4045
});
4146

4247
return new SourceControlApi(client);
4348
}
4449

4550
public post<T = unknown, R = AxiosResponse<T>>(
46-
endpoint: string,
51+
route: string,
4752
data?: unknown,
4853
config?: AxiosRequestConfig<unknown>
4954
): Promise<R> {
50-
return this.client.post<T, R>(endpoint, data, config);
55+
return this.client.post<T, R>(route, data, config);
5156
}
5257
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import * as vscode from "vscode";
2+
3+
import { AtelierAPI } from "../../api";
4+
import { getCcsSettings } from "../config/settings";
5+
import { logDebug } from "../core/logging";
6+
import { ResolveContextExpressionResponse } from "../core/types";
7+
import { SourceControlApi } from "./client";
8+
import { ROUTES } from "./routes";
9+
10+
interface ResolveContextExpressionPayload {
11+
routine: string;
12+
contextExpression: string;
13+
}
14+
15+
export class ContextExpressionClient {
16+
private readonly apiFactory: (api: AtelierAPI) => SourceControlApi;
17+
18+
public constructor(apiFactory: (api: AtelierAPI) => SourceControlApi = SourceControlApi.fromAtelierApi) {
19+
this.apiFactory = apiFactory;
20+
}
21+
22+
public async resolve(
23+
document: vscode.TextDocument,
24+
payload: ResolveContextExpressionPayload
25+
): Promise<ResolveContextExpressionResponse> {
26+
const api = new AtelierAPI(document.uri);
27+
28+
let sourceControlApi: SourceControlApi;
29+
try {
30+
sourceControlApi = this.apiFactory(api);
31+
} catch (error) {
32+
logDebug("Failed to create SourceControl API client for context expression", error);
33+
throw error;
34+
}
35+
36+
const { requestTimeout } = getCcsSettings();
37+
38+
try {
39+
const response = await sourceControlApi.post<ResolveContextExpressionResponse>(
40+
ROUTES.resolveContextExpression(),
41+
payload,
42+
{
43+
timeout: requestTimeout,
44+
validateStatus: (status) => status >= 200 && status < 300,
45+
}
46+
);
47+
48+
return response.data ?? {};
49+
} catch (error) {
50+
logDebug("Context expression resolution failed", error);
51+
throw error;
52+
}
53+
}
54+
}

src/ccs/sourcecontrol/routes.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export const BASE_PATH = "/api/sourcecontrol/vscode" as const;
2+
3+
export const ROUTES = {
4+
resolveContextExpression: () => `/resolveContextExpression`,
5+
} as const;
6+
7+
export type RouteKey = keyof typeof ROUTES;

0 commit comments

Comments
 (0)