Skip to content

Commit

Permalink
feat: add Additional orchestration capabilities by providing $httpyac (
Browse files Browse the repository at this point in the history
  • Loading branch information
AnWeber committed Mar 15, 2023
1 parent 6b37257 commit 0a6097e
Show file tree
Hide file tree
Showing 29 changed files with 524 additions and 439 deletions.
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
## [Unreleased]
## 6.3.0 (2023-03-15)

### Breaking Changes

- some utils functions are removed and added directly to httpRegion (e.g isGlobalHttpRegion)

### Features

- add Additional orchestration capabilities by providing [`$httpyac`](https://github.com/AnWeber/httpyac/blob/main/src/plugins/javascript/httpyacJsApi.ts) in Script (#405)

### Fixes

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"license": "MIT",
"publisher": "AnWeber",
"description": "HTTP/REST CLI Client for *.http files",
"version": "6.2.1",
"version": "6.3.0",
"homepage": "https://github.com/AnWeber/httpyac",
"repository": {
"type": "git",
Expand Down
4 changes: 2 additions & 2 deletions src/cli/send/send.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ async function execute(fileNames: Array<string>, options: SendOptions): Promise<
if (selection) {
await send(Object.assign({ processedHttpRegions }, context, selection));
jsonOutput[fileProvider.toString(selection.httpFile.fileName)] = [
...processedHttpRegions.filter(obj => !utils.isGlobalHttpRegion(obj)),
...processedHttpRegions.filter(obj => !obj.isGlobal()),
];
} else {
const sendFuncs = httpFiles.map(
Expand All @@ -73,7 +73,7 @@ async function execute(fileNames: Array<string>, options: SendOptions): Promise<
}
await send(Object.assign({ processedHttpRegions }, context, { httpFile }));
jsonOutput[fileProvider.toString(httpFile.fileName)] = [
...processedHttpRegions.filter(obj => !utils.isGlobalHttpRegion(obj)),
...processedHttpRegions.filter(obj => !obj.isGlobal()),
];
processedHttpRegions.length = 0;
}
Expand Down
10 changes: 3 additions & 7 deletions src/httpYacApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export async function send(context: models.SendContext): Promise<boolean> {
async function sendHttpRegion(context: models.HttpRegionSendContext): Promise<boolean> {
const processorContext = await createEmptyProcessorContext(context);
if (await utils.executeGlobalScripts(processorContext)) {
return await utils.processHttpRegionActions(processorContext, true);
return await context.httpRegion.execute(processorContext, true);
}
return false;
}
Expand All @@ -36,11 +36,7 @@ async function sendHttpRegions(context: models.HttpRegionsSendContext): Promise<
context.progress.divider = context.httpRegions.length;
}
for (const httpRegion of context.httpRegions) {
const regionProcessorContext: models.ProcessorContext = {
...processorContext,
httpRegion,
};
if (!(await utils.processHttpRegionActions(regionProcessorContext, true))) {
if (!(await httpRegion.execute(processorContext, true))) {
return false;
}
}
Expand All @@ -54,7 +50,7 @@ async function sendHttpFile(context: models.HttpFileSendContext): Promise<boolea
for (const httpRegion of context.httpFile.httpRegions) {
if (context.httpRegionPredicate && !context.httpRegionPredicate(httpRegion)) {
log.debug(`${httpRegion.symbol.name} disabled by predicate`);
} else if (!utils.isGlobalHttpRegion(httpRegion)) {
} else if (!httpRegion.isGlobal()) {
httpRegions.push(httpRegion);
}
}
Expand Down
File renamed without changes.
4 changes: 3 additions & 1 deletion src/models/httpFile.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { HttpFileHooks } from './httpFileHooks';
import { HttpFileHooks } from './hooks';
import { HttpRegion } from './httpRegion';
import { PathLike } from './pathLike';

Expand All @@ -8,4 +8,6 @@ export interface HttpFile {
readonly hooks: HttpFileHooks;
readonly httpRegions: Array<HttpRegion>;
activeEnvironment?: string[];

findHttpRegion(name: string): HttpRegion | undefined;
}
2 changes: 1 addition & 1 deletion src/models/httpHooksApi.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as utils from '../utils';
import { EnvironmentConfig } from './environmentConfig';
import { FileProvider } from './fileProvider';
import { HttpFileHooks } from './hooks';
import { HttpFile } from './httpFile';
import { HttpFileHooks } from './httpFileHooks';
import { LogHandler } from './logHandler';
import { PathLike } from './pathLike';
import { SessionStore } from './sessionStore';
Expand Down
12 changes: 8 additions & 4 deletions src/models/httpRegion.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { HttpFile } from './httpFile';
import { ExecuteHook, OnRequestHook, OnStreaming, OnResponseHook, ResponseLoggingHook } from './httpFileHooks';
import { ExecuteHook, OnRequestHook, OnStreaming, OnResponseHook, ResponseLoggingHook } from './hooks';
import { Request } from './httpRequest';
import { HttpResponse } from './httpResponse';
import { HttpSymbol } from './httpSymbol';
import { ProcessorContext } from './processorContext';
import { TestResult } from './testResult';
import { Variables } from './variables';

Expand All @@ -15,14 +15,18 @@ export interface ProcessedHttpRegion {
responseRefs?: Array<string>;
}

export type PartialProperty<T, TProperty extends string> = Omit<T, TProperty> & Partial<T>;

export interface HttpRegion extends ProcessedHttpRegion {
variablesPerEnv: Record<string, Variables>;
dependentsPerEnv: Record<string, Array<{ httpRegion: HttpRegion; httpFile: HttpFile }>>;
hooks: {
readonly hooks: {
execute: ExecuteHook;
onRequest: OnRequestHook;
onStreaming: OnStreaming;
onResponse: OnResponseHook;
responseLogging: ResponseLoggingHook;
};
isGlobal(): boolean;
clone(): HttpRegion;
execute(context: PartialProperty<ProcessorContext, 'httpRegion'>, isMainContext?: boolean): Promise<boolean>;
}
2 changes: 1 addition & 1 deletion src/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export * from './environmentConfig';
export * from './fileProvider';
export * from './httpClientProvider';
export * from './httpFile';
export * from './httpFileHooks';
export * from './hooks';
export * from './httpFileStore';
export * from './httpHooksApi';
export * from './httpMethod';
Expand Down
6 changes: 3 additions & 3 deletions src/plugins/core/execute/regionScopedVariablesInterceptor.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as models from '../../../models';
import * as utils from '../../../utils';
import { toEnvironmentKey } from '../../../utils';
import { HookInterceptor, HookTriggerContext } from 'hookpoint';

interface RegionScopedVariableOptions {
Expand All @@ -12,9 +12,9 @@ export class RegionScopedVariablesInterceptor implements HookInterceptor<[models
hookContext: HookTriggerContext<[models.ProcessorContext], boolean | undefined>
): Promise<boolean | undefined> {
const context = hookContext.args[0];
const env = utils.toEnvironmentKey(context.httpFile.activeEnvironment);
const env = toEnvironmentKey(context.httpFile.activeEnvironment);
if (context.config?.useRegionScopedVariables) {
const httpRegions = context.httpFile.httpRegions.filter(obj => utils.isGlobalHttpRegion(obj));
const httpRegions = context.httpFile.httpRegions.filter(obj => obj.isGlobal());
const regionScopedVariables: RegionScopedVariableOptions = context.options;
regionScopedVariables.variables = context.variables;
context.variables = Object.assign({}, context.variables, ...httpRegions.map(obj => obj.variablesPerEnv[env]));
Expand Down
56 changes: 2 additions & 54 deletions src/plugins/core/metaData/importMetaDataHandler.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import * as io from '../../../io';
import * as models from '../../../models';
import * as utils from '../../../utils';

Expand All @@ -10,63 +9,12 @@ export function importMetaDataHandler(type: string, value: string | undefined, c
return false;
}

export interface ImportProcessorContext extends models.ProcessorContext {
options: {
httpFiles?: Array<{ base: models.HttpFile; ref: models.HttpFile }>;
globalScriptsExecuted?: Array<models.HttpFile>;
};
}

class ImportMetaAction {
id = 'import';

constructor(private readonly fileName: string, private readonly httpFileStore: models.HttpFileStore) {}

async process(context: ImportProcessorContext): Promise<boolean> {
const httpFile = await utils.replaceFilePath(this.fileName, context, async (absoluteFileName: models.PathLike) => {
io.log.trace(`parse imported file ${absoluteFileName}`);
if (!context.options.httpFiles) {
context.options.httpFiles = [];
}
const httpFile = context.options.httpFiles.find(obj => obj.ref.fileName === absoluteFileName);
if (httpFile) {
return httpFile.ref;
}
const ref = await this.httpFileStore.getOrCreate(
absoluteFileName,
async () => await io.fileProvider.readFile(absoluteFileName, 'utf-8'),
0,
{
workingDir: context.httpFile.rootDir,
config: context.config,
activeEnvironment: context.httpFile.activeEnvironment,
}
);
context.options.httpFiles.push({ base: context.httpFile, ref });
return ref;
});

if (
httpFile &&
(!context.options.globalScriptsExecuted || context.options.globalScriptsExecuted.indexOf?.(httpFile) < 0)
) {
if (!context.options.globalScriptsExecuted) {
context.options.globalScriptsExecuted = [];
}
io.log.trace(`execute global scripts for import ${httpFile.fileName}`);
const cloneContext: ImportProcessorContext = {
...context,
httpFile,
};
const globResult = await utils.executeGlobalScripts(cloneContext);
if (globResult) {
for (const globRegion of httpFile.httpRegions.filter(utils.isGlobalHttpRegion)) {
utils.registerRegionDependent(context, httpFile, globRegion, context.httpFile, context.httpRegion);
}
context.options.globalScriptsExecuted.push(httpFile);
}
return globResult;
}
return !!httpFile;
async process(context: models.ProcessorContext): Promise<boolean> {
return utils.importHttpFileInContext(this.fileName, this.httpFileStore, context);
}
}
10 changes: 3 additions & 7 deletions src/plugins/core/metaData/loopMetaDataHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,12 +189,8 @@ class LoopMetaInterceptor implements HookInterceptor<[models.ProcessorContext],
}

private createHttpRegionClone(httpRegion: models.HttpRegion, index: number): models.HttpRegion {
return {
...httpRegion,
metaData: {
...httpRegion.metaData,
name: httpRegion.metaData.name ? `${httpRegion.metaData.name}${index}` : undefined,
},
};
const clone = httpRegion.clone();
clone.metaData.name = httpRegion.metaData.name ? `${httpRegion.metaData.name}${index}` : undefined;
return clone;
}
}
61 changes: 9 additions & 52 deletions src/plugins/core/metaData/refMetaDataHandler.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { log } from '../../../io';
import * as models from '../../../models';
import * as utils from '../../../utils';
import { ImportProcessorContext } from './importMetaDataHandler';

export function refMetaDataHandler(type: string, name: string | undefined, context: models.ParserContext): boolean {
if (['ref', 'forceRef'].indexOf(type) >= 0 && name) {
Expand All @@ -27,64 +26,22 @@ class RefMetaAction {

constructor(private readonly data: RefMetaHttpRegionData) {}

async process(context: ImportProcessorContext): Promise<boolean> {
async process(context: models.ProcessorContext): Promise<boolean> {
let result = true;
utils.report(context, `load reference ${this.data.name}`);
let reference = this.findHttpRegionInContext(context.httpFile);
if (!reference) {
reference = this.findHttpRegionInImportedContext(context);
}

const reference = utils.findHttpRegionInContext(this.data.name, context);
if (reference) {
utils.registerRegionDependent(
context,
reference.httpFile,
reference.httpRegion,
context.httpFile,
context.httpRegion
);
const envKey = utils.toEnvironmentKey(context.httpFile.activeEnvironment);
log.trace('import variables', reference.httpRegion.variablesPerEnv[envKey]);
utils.setVariableInContext(reference.httpRegion.variablesPerEnv[envKey], context);
utils.setVariableInContext(reference.variablesPerEnv[envKey], context);
log.trace('import variables', reference.variablesPerEnv[envKey]);
if (this.data.force || utils.isUndefined(context.variables[this.data.name])) {
const refContext = {
...context,
httpFile: reference.httpFile,
httpRegion: reference.httpRegion,
isMainContext: false,
};
const result = await utils.processHttpRegionActions(refContext);
result = await reference.execute(context);
if (result) {
utils.setVariableInContext(refContext.httpRegion.variablesPerEnv[envKey], context);
}
return result;
}
return true;
}
return true;
}

private findHttpRegionInContext(httpFile: models.HttpFile) {
const httpRegion = httpFile.httpRegions.find(
refHttpRegion => refHttpRegion.metaData.name === this.data.name && !refHttpRegion.metaData.disabled
);
if (httpRegion) {
return {
httpRegion,
httpFile,
};
}
return undefined;
}

private findHttpRegionInImportedContext(context: ImportProcessorContext) {
if (context.options.httpFiles) {
for (const { ref } of context.options.httpFiles.filter(obj => obj.base === context.httpFile)) {
const reference = this.findHttpRegionInContext(ref);
if (reference) {
return reference;
// ref to ref variable export
utils.setVariableInContext(reference.variablesPerEnv[envKey], context);
}
}
}
return undefined;
return result;
}
}
42 changes: 42 additions & 0 deletions src/plugins/javascript/httpyacJsApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import * as models from '../../models';
import * as utils from '../../utils';

export class HttpyacJsApi {
constructor(
private readonly context: models.ProcessorContext,
private readonly httpFileStore: models.HttpFileStore
) {}

findHttpRegionInContext(name: string) {
return utils.findHttpRegionInContext(name, this.context);
}

async import(fileName: string) {
return await utils.importHttpFileInContext(fileName, this.httpFileStore, this.context);
}

setVariables(vars: models.Variables) {
utils.setVariableInContext(vars, this.context);
}

async execute(httpRegion: models.HttpRegion | string, vars?: models.Variables): Promise<boolean> {
if (vars) {
utils.setVariableInContext(vars, this.context);
}
let obj: models.HttpRegion | undefined;
if (utils.isString(httpRegion)) {
obj = utils.findHttpRegionInContext(httpRegion, this.context);
} else {
obj = httpRegion;
}
if (obj) {
const result = await obj?.execute(this.context);
if (result) {
const envKey = utils.toEnvironmentKey(this.context.httpFile.activeEnvironment);
utils.setVariableInContext(obj.variablesPerEnv[envKey], this.context);
}
return result;
}
return false;
}
}
Loading

0 comments on commit 0a6097e

Please sign in to comment.