Skip to content

Commit

Permalink
feat(connector-besu): add GetOpenApiSpecV1Endpoint (HTTP GET)
Browse files Browse the repository at this point in the history
The new endpoint can serve up the OpenAPI specification file of the
plugin via a get request without any parameters needed.

Under the hood it uses the re-usable common base endpoint class that
we've recently added to the core package.

The test case at this file path demonstrates how to use it via the API
client:
`packages/cactus-plugin-ledger-connector-besu/src/test/typescript/
unit/get-open-api-spec-v1-connector-besu.test.ts`

Signed-off-by: Peter Somogyvari <[email protected]>
  • Loading branch information
petermetz committed Sep 17, 2023
1 parent 6d68292 commit 76744f0
Show file tree
Hide file tree
Showing 5 changed files with 256 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -949,6 +949,31 @@
}
},
"paths": {
"/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-besu/get-open-api-spec": {
"get": {
"x-hyperledger-cactus": {
"http": {
"verbLowerCase": "get",
"path": "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-besu/get-open-api-spec"
}
},
"operationId": "getOpenApiSpecV1",
"summary": "Retrieves the .json file that contains the OpenAPI specification for the plugin.",
"parameters": [],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "string"
}
}
}
}
}
}
},
"/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-besu/deploy-contract-solidity-bytecode": {
"post": {
"x-hyperledger-cactus": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1423,6 +1423,36 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati
options: localVarRequestOptions,
};
},
/**
*
* @summary Retrieves the .json file that contains the OpenAPI specification for the plugin.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getOpenApiSpecV1: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-besu/get-open-api-spec`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}

const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;



setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};

return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @summary Gets past logs, matching the given options.
Expand Down Expand Up @@ -1679,6 +1709,16 @@ export const DefaultApiFp = function(configuration?: Configuration) {
const localVarAxiosArgs = await localVarAxiosParamCreator.getBlockV1(getBlockV1Request, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
*
* @summary Retrieves the .json file that contains the OpenAPI specification for the plugin.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async getOpenApiSpecV1(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<string>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getOpenApiSpecV1(options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
*
* @summary Gets past logs, matching the given options.
Expand Down Expand Up @@ -1794,6 +1834,15 @@ export const DefaultApiFactory = function (configuration?: Configuration, basePa
getBlockV1(getBlockV1Request?: GetBlockV1Request, options?: any): AxiosPromise<GetBlockV1Response> {
return localVarFp.getBlockV1(getBlockV1Request, options).then((request) => request(axios, basePath));
},
/**
*
* @summary Retrieves the .json file that contains the OpenAPI specification for the plugin.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getOpenApiSpecV1(options?: any): AxiosPromise<string> {
return localVarFp.getOpenApiSpecV1(options).then((request) => request(axios, basePath));
},
/**
*
* @summary Gets past logs, matching the given options.
Expand Down Expand Up @@ -1911,6 +1960,17 @@ export class DefaultApi extends BaseAPI {
return DefaultApiFp(this.configuration).getBlockV1(getBlockV1Request, options).then((request) => request(this.axios, this.basePath));
}

/**
*
* @summary Retrieves the .json file that contains the OpenAPI specification for the plugin.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof DefaultApi
*/
public getOpenApiSpecV1(options?: AxiosRequestConfig) {
return DefaultApiFp(this.configuration).getOpenApiSpecV1(options).then((request) => request(this.axios, this.basePath));
}

/**
*
* @summary Gets past logs, matching the given options.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ import { RunTransactionEndpoint } from "./web-services/run-transaction-endpoint"
import { GetBlockEndpoint } from "./web-services/get-block-v1-endpoint-";
import { GetBesuRecordEndpointV1 } from "./web-services/get-besu-record-endpoint-v1";
import { AbiItem } from "web3-utils";
import {
GetOpenApiSpecV1Endpoint,
IGetOpenApiSpecV1EndpointOptions,
} from "./web-services/get-open-api-spec-v1-endpoint";

export const E_KEYCHAIN_NOT_FOUND = "cactus.connector.besu.keychain_not_found";

Expand All @@ -116,7 +120,8 @@ export class PluginLedgerConnectorBesu
RunTransactionResponse
>,
ICactusPlugin,
IPluginWebService {
IPluginWebService
{
private readonly instanceId: string;
public prometheusExporter: PrometheusExporter;
private readonly log: Logger;
Expand Down Expand Up @@ -288,6 +293,26 @@ export class PluginLedgerConnectorBesu
const endpoint = new GetPrometheusExporterMetricsEndpointV1(opts);
endpoints.push(endpoint);
}
{
const oasPath =
OAS.paths[
"/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-besu/get-open-api-spec"
];

const operationId = oasPath.get.operationId;
const opts: IGetOpenApiSpecV1EndpointOptions = {
oas: OAS,
oasPath,
operationId,
path: oasPath.get["x-hyperledger-cactus"].http.path,
pluginRegistry: this.pluginRegistry,
verbLowerCase: oasPath.get["x-hyperledger-cactus"].http.verbLowerCase,
logLevel: this.options.logLevel,
};
const endpoint = new GetOpenApiSpecV1Endpoint(opts);
endpoints.push(endpoint);
}

this.endpoints = endpoints;
return endpoints;
}
Expand All @@ -296,13 +321,12 @@ export class PluginLedgerConnectorBesu
return `@hyperledger/cactus-plugin-ledger-connector-besu`;
}

public async getConsensusAlgorithmFamily(): Promise<
ConsensusAlgorithmFamily
> {
public async getConsensusAlgorithmFamily(): Promise<ConsensusAlgorithmFamily> {
return ConsensusAlgorithmFamily.Authority;
}
public async hasTransactionFinality(): Promise<boolean> {
const currentConsensusAlgorithmFamily = await this.getConsensusAlgorithmFamily();
const currentConsensusAlgorithmFamily =
await this.getConsensusAlgorithmFamily();

return consensusHasTransactionFinality(currentConsensusAlgorithmFamily);
}
Expand Down Expand Up @@ -451,18 +475,16 @@ export class PluginLedgerConnectorBesu
req.signingCredential.type ==
Web3SigningCredentialType.CactusKeychainRef
) {
const {
keychainEntryKey,
keychainId,
} = req.signingCredential as Web3SigningCredentialCactusKeychainRef;
const { keychainEntryKey, keychainId } =
req.signingCredential as Web3SigningCredentialCactusKeychainRef;

const keychainPlugin = this.pluginRegistry.findOneByKeychainId(
keychainId,
);
const keychainPlugin =
this.pluginRegistry.findOneByKeychainId(keychainId);
privKey = await keychainPlugin?.get(keychainEntryKey);
} else {
privKey = (req.signingCredential as Web3SigningCredentialPrivateKeyHex)
.secret;
privKey = (
req.signingCredential as Web3SigningCredentialPrivateKeyHex
).secret;
}

const fnParams = {
Expand All @@ -476,9 +498,8 @@ export class PluginLedgerConnectorBesu
throw new RuntimeError(`InvalidState: web3Quorum not initialized.`);
}

const privacyGroupId = this.web3Quorum.utils.generatePrivacyGroup(
fnParams,
);
const privacyGroupId =
this.web3Quorum.utils.generatePrivacyGroup(fnParams);
this.log.debug("Generated privacyGroupId: ", privacyGroupId);
callOutput = await this.web3Quorum.priv.call(privacyGroupId, {
to: contractInstance.options.address,
Expand Down Expand Up @@ -670,7 +691,7 @@ export class PluginLedgerConnectorBesu
}

return {
transactionReceipt: (txPoolReceipt as unknown) as Web3TransactionReceipt,
transactionReceipt: txPoolReceipt as unknown as Web3TransactionReceipt,
};
}

Expand All @@ -679,9 +700,8 @@ export class PluginLedgerConnectorBesu
): Promise<RunTransactionResponse> {
const fnTag = `${this.className}#transactPrivateKey()`;
const { transactionConfig, web3SigningCredential } = req;
const {
secret,
} = web3SigningCredential as Web3SigningCredentialPrivateKeyHex;
const { secret } =
web3SigningCredential as Web3SigningCredentialPrivateKeyHex;

// Run transaction to EEA client here if private transaction

Expand Down Expand Up @@ -727,11 +747,8 @@ export class PluginLedgerConnectorBesu
web3SigningCredential,
privateTransactionConfig,
} = req;
const {
ethAccount,
keychainEntryKey,
keychainId,
} = web3SigningCredential as Web3SigningCredentialCactusKeychainRef;
const { ethAccount, keychainEntryKey, keychainId } =
web3SigningCredential as Web3SigningCredentialCactusKeychainRef;

// locate the keychain plugin that has access to the keychain backend
// denoted by the keychainID from the request.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {
GetOpenApiSpecV1EndpointBase,
IGetOpenApiSpecV1EndpointBaseOptions,
} from "@hyperledger/cactus-core";

import { Checks, LogLevelDesc } from "@hyperledger/cactus-common";
import { IWebServiceEndpoint } from "@hyperledger/cactus-core-api";

import OAS from "../../json/openapi.json";

export const OasPathGetOpenApiSpecV1 =
OAS.paths[
"/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-besu/get-open-api-spec"
];

export type OasPathTypeGetOpenApiSpecV1 = typeof OasPathGetOpenApiSpecV1;

export interface IGetOpenApiSpecV1EndpointOptions
extends IGetOpenApiSpecV1EndpointBaseOptions<
typeof OAS,
OasPathTypeGetOpenApiSpecV1
> {
readonly logLevel?: LogLevelDesc;
}

export class GetOpenApiSpecV1Endpoint
extends GetOpenApiSpecV1EndpointBase<typeof OAS, OasPathTypeGetOpenApiSpecV1>
implements IWebServiceEndpoint
{
public get className(): string {
return GetOpenApiSpecV1Endpoint.CLASS_NAME;
}

constructor(public readonly options: IGetOpenApiSpecV1EndpointOptions) {
super(options);
const fnTag = `${this.className}#constructor()`;
Checks.truthy(options, `${fnTag} arg options`);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import {
IListenOptions,
LogLevelDesc,
LoggerProvider,
Servers,
} from "@hyperledger/cactus-common";
import { PluginRegistry } from "@hyperledger/cactus-core";
import { Constants, PluginImportType } from "@hyperledger/cactus-core-api";
import bodyParser from "body-parser";
import express from "express";
import http from "http";
import "jest-extended";
import { AddressInfo } from "net";
import { Server as SocketIoServer } from "socket.io";
import { v4 as uuidv4 } from "uuid";
import {
BesuApiClient,
BesuApiClientOptions,
PluginFactoryLedgerConnector,
PluginLedgerConnectorBesu,
} from "../../../main/typescript/public-api";

describe(__filename, () => {
const logLevel: LogLevelDesc = "TRACE";

const log = LoggerProvider.getOrCreate({
label: __filename,
level: logLevel,
});

const rpcApiHttpHost = "http://127.0.0.1:8000";
const rpcApiWsHost = "ws://127.0.0.1:9000";

const expressApp = express();
expressApp.use(bodyParser.json({ limit: "250mb" }));
const server = http.createServer(expressApp);
let apiClient: BesuApiClient;

afterAll(async () => {
await Servers.shutdown(server);
});

beforeAll(async () => {
const factory = new PluginFactoryLedgerConnector({
pluginImportType: PluginImportType.Local,
});

const connector: PluginLedgerConnectorBesu = await factory.create({
rpcApiHttpHost,
rpcApiWsHost,
logLevel,
instanceId: uuidv4(),
pluginRegistry: new PluginRegistry({ plugins: [] }),
});

const wsApi = new SocketIoServer(server, {
path: Constants.SocketIoConnectionPathV1,
});

await connector.registerWebServices(expressApp, wsApi);

const listenOptions: IListenOptions = {
hostname: "localhost",
port: 0,
server,
};
const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo;
const { address, port } = addressInfo;
const apiHost = `http://${address}:${port}`;

const besuApiClientOptions = new BesuApiClientOptions({
basePath: apiHost,
});
apiClient = new BesuApiClient(besuApiClientOptions);
log.debug("Instantiated BesuApiClient OK");
});

it("Returns a JSON document containing the Open API specification of the plugin.", async () => {
const res1Promise = apiClient.getOpenApiSpecV1();
await expect(res1Promise).resolves.not.toThrow();
const res1 = await res1Promise;
expect(res1.status).toEqual(200);
expect(res1.data).toBeTruthy();
expect(res1.config).toBeTruthy();
expect(res1.config.url).toBeString();
log.debug("Fetched URL OK=%s", res1.config.url);
expect(res1.data).toBeObject();
});
});

0 comments on commit 76744f0

Please sign in to comment.