Skip to content

Commit 765b617

Browse files
fixed issue with persisted document execution; enhanced some services, tests, etc. (#67)
* fixed issue with persisted document execution; enhanced some services, tests, etc. * fixed unecessary exports
1 parent 3840f58 commit 765b617

File tree

8 files changed

+690
-199
lines changed

8 files changed

+690
-199
lines changed

src/commands/executeStepZenRequest.ts

Lines changed: 23 additions & 193 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,15 @@ import * as vscode from "vscode";
22
import * as fs from "fs";
33
import * as path from "path";
44
import * as os from "os";
5-
import * as https from "https";
65
import { resolveStepZenProjectRoot } from "../utils/stepzenProject";
76
import { clearResultsPanel, openResultsPanel } from "../panels/resultsPanel";
87
import { summariseDiagnostics, publishDiagnostics } from "../utils/runtimeDiagnostics";
98
import { runtimeDiag } from "../extension";
10-
import { UI, TIMEOUTS, TEMP_FILE_PATTERNS, FILE_PATTERNS } from "../utils/constants";
9+
import { UI, TIMEOUTS, TEMP_FILE_PATTERNS } from "../utils/constants";
1110
import { services } from "../services";
1211
import { StepZenResponse, StepZenDiagnostic } from "../types";
13-
import { ValidationError, NetworkError, handleError } from "../errors";
12+
import { ValidationError, handleError } from "../errors";
1413

15-
/* CLEANUP - DELETE WHEN SAFE
16-
// Export utility functions for use in other files
17-
export {
18-
createTempGraphQLFile,
19-
cleanupLater
20-
};
21-
*/
2214

2315
/**
2416
* Creates a temporary GraphQL file with the provided query content
@@ -93,33 +85,17 @@ function cleanupLater(filePath: string) {
9385
*/
9486
export async function executeStepZenRequest(options: {
9587
queryText?: string;
96-
documentId?: string;
88+
documentContent?: string;
9789
operationName?: string;
9890
varArgs?: string[];
9991
}): Promise<void> {
100-
// Validate options object
101-
if (!options || typeof options !== 'object') {
102-
handleError(new ValidationError("Invalid request options provided", "INVALID_OPTIONS"));
103-
return;
104-
}
105-
106-
const { queryText, documentId, operationName, varArgs = [] } = options;
107-
108-
// Validate at least one of queryText or documentId is provided and valid
109-
if (documentId === undefined && (!queryText || typeof queryText !== 'string')) {
110-
handleError(new ValidationError("Invalid request: either documentId or queryText must be provided", "MISSING_QUERY"));
111-
return;
112-
}
113-
114-
// Validate operationName if provided
115-
if (operationName !== undefined && typeof operationName !== 'string') {
116-
handleError(new ValidationError("Invalid operation name provided", "INVALID_OPERATION_NAME"));
117-
return;
118-
}
92+
const { queryText, documentContent, operationName, varArgs = [] } = options;
11993

120-
// Validate varArgs is an array
121-
if (!Array.isArray(varArgs)) {
122-
handleError(new ValidationError("Invalid variable arguments: expected an array", "INVALID_VAR_ARGS"));
94+
// Validate request options using the request service
95+
try {
96+
services.request.validateRequestOptions({ queryText, documentContent, operationName, varArgs });
97+
} catch (err) {
98+
handleError(err);
12399
return;
124100
}
125101

@@ -136,90 +112,13 @@ export async function executeStepZenRequest(options: {
136112
const debugLevel = cfg.get<number>("request.debugLevel", 1);
137113

138114
// For persisted documents, we need to make an HTTP request directly
139-
if (documentId) {
115+
if (documentContent) {
140116
try {
141-
// Get StepZen config to build the endpoint URL
142-
const configPath = path.join(projectRoot, FILE_PATTERNS.CONFIG_FILE);
143-
services.logger.debug(`Looking for config file at: ${configPath}`);
144-
145-
// Verify config file exists
146-
if (!fs.existsSync(configPath)) {
147-
handleError(new ValidationError(
148-
`StepZen configuration file not found at: ${configPath}`,
149-
"CONFIG_NOT_FOUND"
150-
));
151-
return;
152-
}
153-
154-
let endpoint: string;
155-
let apiKey: string;
117+
// Load endpoint configuration using the request service
118+
const endpointConfig = await services.request.loadEndpointConfig(projectRoot);
156119

157-
try {
158-
const configContent = fs.readFileSync(configPath, "utf8");
159-
160-
if (!configContent) {
161-
handleError(new ValidationError("StepZen configuration file is empty", "EMPTY_CONFIG"));
162-
return;
163-
}
164-
165-
const config = JSON.parse(configContent);
166-
167-
if (!config || !config.endpoint) {
168-
handleError(new ValidationError("Invalid StepZen configuration: missing endpoint", "MISSING_ENDPOINT"));
169-
return;
170-
}
171-
172-
endpoint = config.endpoint;
173-
apiKey = config.apiKey || "";
174-
} catch (err) {
175-
handleError(new ValidationError(
176-
"Failed to parse StepZen configuration file",
177-
"CONFIG_PARSE_ERROR",
178-
err
179-
));
180-
return;
181-
}
182-
183-
// Parse endpoint to extract account and domain
184-
const endpointParts = endpoint.split("/");
185-
if (endpointParts.length < 2) {
186-
handleError(new ValidationError(
187-
`Invalid StepZen endpoint format: ${endpoint}`,
188-
"INVALID_ENDPOINT_FORMAT"
189-
));
190-
return;
191-
}
192-
193-
// Construct the GraphQL endpoint URL
194-
const graphqlUrl = `https://${endpoint}/graphql`;
195-
196-
// Prepare variables from varArgs
197-
let variables: Record<string, string> = {};
198-
for (let i = 0; i < varArgs.length; i += 2) {
199-
if (varArgs[i] === "--var" && i + 1 < varArgs.length) {
200-
const [name, value] = varArgs[i + 1].split("=");
201-
variables[name] = value;
202-
} else if (varArgs[i] === "--var-file" && i + 1 < varArgs.length) {
203-
try {
204-
const fileContent = fs.readFileSync(varArgs[i + 1], "utf8");
205-
variables = JSON.parse(fileContent);
206-
} catch (err) {
207-
handleError(new ValidationError(
208-
"Failed to read variables file",
209-
"VAR_FILE_READ_ERROR",
210-
err
211-
));
212-
return;
213-
}
214-
}
215-
}
216-
217-
// Prepare the request body
218-
const requestBody = {
219-
documentId,
220-
operationName,
221-
variables
222-
};
120+
// Parse variables using the request service
121+
const { variables } = services.request.parseVariables(varArgs);
223122

224123
// Show a progress notification
225124
await vscode.window.withProgress(
@@ -229,53 +128,13 @@ export async function executeStepZenRequest(options: {
229128
cancellable: false
230129
},
231130
async () => {
232-
services.logger.info("Making HTTP request to StepZen API for persisted document");
233-
// Use Node.js https module to make the request
234-
const result = await new Promise<StepZenResponse>((resolve, reject) => {
235-
const postData = JSON.stringify(requestBody);
236-
237-
const options = {
238-
method: 'POST',
239-
headers: {
240-
'Content-Type': 'application/json',
241-
'Content-Length': Buffer.byteLength(postData),
242-
'Authorization': apiKey ? `Apikey ${apiKey}` : '',
243-
'stepzen-debug-level': debugLevel.toString(),
244-
}
245-
};
246-
247-
const req = https.request(graphqlUrl, options, (res) => {
248-
let responseData = '';
249-
250-
res.on('data', (chunk) => {
251-
responseData += chunk;
252-
});
253-
254-
res.on('end', () => {
255-
try {
256-
const json = JSON.parse(responseData);
257-
resolve(json);
258-
} catch (err) {
259-
reject(new ValidationError(
260-
"Failed to parse StepZen response",
261-
"RESPONSE_PARSE_ERROR",
262-
err
263-
));
264-
}
265-
});
266-
});
267-
268-
req.on('error', (err) => {
269-
reject(new NetworkError(
270-
"Failed to connect to StepZen API",
271-
"API_CONNECTION_ERROR",
272-
err
273-
));
274-
});
275-
276-
req.write(postData);
277-
req.end();
278-
});
131+
// Execute the persisted document request using the request service
132+
const result = await services.request.executePersistedDocumentRequest(
133+
endpointConfig,
134+
documentContent,
135+
variables,
136+
operationName
137+
);
279138

280139
// Process results
281140
const rawDiags = (result.extensions?.stepzen?.diagnostics ?? []) as StepZenDiagnostic[];
@@ -355,37 +214,8 @@ export async function executeStepZenRequest(options: {
355214
},
356215
async () => {
357216
try {
358-
// Parse varArgs into variables object
359-
const variables: Record<string, any> = {};
360-
for (let i = 0; i < varArgs.length; i += 2) {
361-
if (varArgs[i] === "--var" && i + 1 < varArgs.length) {
362-
const [name, value] = varArgs[i + 1].split("=");
363-
if (name && value !== undefined) {
364-
variables[name] = value;
365-
services.logger.debug(`Setting variable ${name}=${value}`);
366-
} else {
367-
services.logger.warn(`Invalid variable format: ${varArgs[i + 1]}`);
368-
}
369-
} else if (varArgs[i] === "--var-file" && i + 1 < varArgs.length) {
370-
try {
371-
const varFilePath = varArgs[i + 1];
372-
services.logger.debug(`Reading variables from file: ${varFilePath}`);
373-
if (!fs.existsSync(varFilePath)) {
374-
throw new ValidationError(`Variables file not found: ${varFilePath}`, "VAR_FILE_NOT_FOUND");
375-
}
376-
const fileContent = fs.readFileSync(varFilePath, "utf8");
377-
const fileVars = JSON.parse(fileContent);
378-
services.logger.debug(`Loaded ${Object.keys(fileVars).length} variables from file`);
379-
Object.assign(variables, fileVars);
380-
} catch (err) {
381-
throw new ValidationError(
382-
"Failed to read variables file",
383-
"VAR_FILE_READ_ERROR",
384-
err
385-
);
386-
}
387-
}
388-
}
217+
// Parse variables using the request service
218+
const { variables } = services.request.parseVariables(varArgs);
389219

390220
// Use the CLI service to execute the request
391221
services.logger.info(`Executing StepZen request${operationName ? ` for operation "${operationName}"` : ' (anonymous operation)'} with debug level ${debugLevel}`);

src/commands/runRequest.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -371,9 +371,9 @@ export async function runPersisted(documentId: string, operationName: string) {
371371
return; // user cancelled
372372
}
373373

374-
// Execute using document ID approach
374+
// Execute using persisted document approach with the full document content
375375
await executeStepZenRequest({
376-
documentId,
376+
documentContent: content,
377377
operationName,
378378
varArgs
379379
});

src/services/cli.ts

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,90 @@ export class StepzenCliService {
160160
throw error;
161161
}
162162
}
163+
164+
/**
165+
* Get the API key from StepZen CLI
166+
*
167+
* @returns Promise resolving to the API key
168+
* @throws CliError if the operation fails
169+
*/
170+
async getApiKey(): Promise<string> {
171+
try {
172+
logger.debug('Retrieving API key from StepZen CLI');
173+
174+
const result = await this.spawnProcessWithOutput(['whoami', '--apikey']);
175+
const apiKey = result.trim();
176+
177+
if (!apiKey) {
178+
throw new CliError("Empty API key returned from StepZen CLI", "EMPTY_API_KEY");
179+
}
180+
181+
logger.debug("Successfully retrieved API key from CLI");
182+
return apiKey;
183+
} catch (err) {
184+
throw new CliError(
185+
"Failed to retrieve API key from StepZen CLI",
186+
"API_KEY_RETRIEVAL_FAILED",
187+
err
188+
);
189+
}
190+
}
191+
192+
/**
193+
* Get the account name from StepZen CLI
194+
*
195+
* @returns Promise resolving to the account name
196+
* @throws CliError if the operation fails
197+
*/
198+
async getAccount(): Promise<string> {
199+
try {
200+
logger.debug('Retrieving account from StepZen CLI');
201+
202+
const result = await this.spawnProcessWithOutput(['whoami', '--account']);
203+
const account = result.trim();
204+
205+
if (!account) {
206+
throw new CliError("Empty account returned from StepZen CLI", "EMPTY_ACCOUNT");
207+
}
208+
209+
logger.debug("Successfully retrieved account from CLI");
210+
return account;
211+
} catch (err) {
212+
throw new CliError(
213+
"Failed to retrieve account from StepZen CLI",
214+
"ACCOUNT_RETRIEVAL_FAILED",
215+
err
216+
);
217+
}
218+
}
219+
220+
/**
221+
* Get the domain from StepZen CLI
222+
*
223+
* @returns Promise resolving to the domain
224+
* @throws CliError if the operation fails
225+
*/
226+
async getDomain(): Promise<string> {
227+
try {
228+
logger.debug('Retrieving domain from StepZen CLI');
229+
230+
const result = await this.spawnProcessWithOutput(['whoami', '--domain']);
231+
const domain = result.trim();
232+
233+
if (!domain) {
234+
throw new CliError("Empty domain returned from StepZen CLI", "EMPTY_DOMAIN");
235+
}
236+
237+
logger.debug("Successfully retrieved domain from CLI");
238+
return domain;
239+
} catch (err) {
240+
throw new CliError(
241+
"Failed to retrieve domain from StepZen CLI",
242+
"DOMAIN_RETRIEVAL_FAILED",
243+
err
244+
);
245+
}
246+
}
163247

164248
// TODO: CLEANUP
165249
// /**
@@ -228,7 +312,7 @@ export class StepzenCliService {
228312
* @returns Promise resolving to the captured stdout
229313
* @throws CliError if the process fails
230314
*/
231-
private async spawnProcessWithOutput(
315+
public async spawnProcessWithOutput(
232316
args: string[] = [],
233317
options: cp.SpawnOptions = {}
234318
): Promise<string> {

src/services/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { StepzenCliService } from './cli';
22
import { Logger, logger } from './logger';
33
import { ProjectResolver } from './projectResolver';
44
import { SchemaIndexService } from './SchemaIndexService';
5+
import { RequestService } from './request';
56

67
/**
78
* Service registry for dependency injection of application services
@@ -12,6 +13,7 @@ export interface ServiceRegistry {
1213
logger: Logger;
1314
projectResolver: ProjectResolver;
1415
schemaIndex: SchemaIndexService;
16+
request: RequestService;
1517
}
1618

1719
/**
@@ -22,6 +24,7 @@ export const services: ServiceRegistry = {
2224
logger,
2325
projectResolver: new ProjectResolver(logger),
2426
schemaIndex: new SchemaIndexService(),
27+
request: new RequestService(logger),
2528
};
2629

2730
/**

0 commit comments

Comments
 (0)