Skip to content

Commit

Permalink
feat(cmd-api-server): aggregate swagger.json endpoints
Browse files Browse the repository at this point in the history
WORK IN PROGRESS

Fixes hyperledger#431

Signed-off-by: Peter Somogyvari <peter.somogyvari@accenture.com>
  • Loading branch information
petermetz committed Sep 21, 2023
1 parent b7ad571 commit 27f8623
Show file tree
Hide file tree
Showing 20 changed files with 793 additions and 17 deletions.
24 changes: 24 additions & 0 deletions packages/cactus-cmd-api-server/src/main/json/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,30 @@
}
}
}
},
"/api/v1/api-server/get-aggregate-openapi-json": {
"get": {
"summary": "Returns the combined openapi.json document of all plugins currently installed in the API server.",
"description": "The various distinct openapi.json documents (formerly called swagger.json) are flattened into a single one.",
"x-hyperledger-cactus": {
"http": {
"verbLowerCase": "get",
"path": "/api/v1/api-server/get-aggregate-openapi-json"
}
},
"operationId": "getAggregateOpenapiJsonV1",
"parameters": [],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {}
}
}
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ All URIs are relative to *http://localhost*

Class | Method | HTTP request | Description
------------ | ------------- | ------------- | -------------
*DefaultApi* | [**getAggregateOpenapiJsonV1**](docs/DefaultApi.md#getaggregateopenapijsonv1) | **GET** /api/v1/api-server/get-aggregate-openapi-json | Returns the combined openapi.json document of all plugins currently installed in the API server.
*DefaultApi* | [**getHealthCheckV1**](docs/DefaultApi.md#gethealthcheckv1) | **GET** /api/v1/api-server/healthcheck | Can be used to verify liveness of an API server instance
*DefaultApi* | [**getOpenApiSpecV1**](docs/DefaultApi.md#getopenapispecv1) | **GET** /api/v1/api-server/get-open-api-spec |
*DefaultApi* | [**getPrometheusMetricsV1**](docs/DefaultApi.md#getprometheusmetricsv1) | **GET** /api/v1/api-server/get-prometheus-exporter-metrics | Get the Prometheus Metrics
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,74 @@ class DefaultApi(basePath: kotlin.String = defaultBasePath, client: OkHttpClient
}
}

/**
* Returns the combined openapi.json document of all plugins currently installed in the API server.
* The various distinct openapi.json documents (formerly called swagger.json) are flattened into a single one.
* @return kotlin.Any
* @throws IllegalStateException If the request is not correctly configured
* @throws IOException Rethrows the OkHttp execute method exception
* @throws UnsupportedOperationException If the API returns an informational or redirection response
* @throws ClientException If the API returns a client error response
* @throws ServerException If the API returns a server error response
*/
@Suppress("UNCHECKED_CAST")
@Throws(IllegalStateException::class, IOException::class, UnsupportedOperationException::class, ClientException::class, ServerException::class)
fun getAggregateOpenapiJsonV1() : kotlin.Any {
val localVarResponse = getAggregateOpenapiJsonV1WithHttpInfo()

return when (localVarResponse.responseType) {
ResponseType.Success -> (localVarResponse as Success<*>).data as kotlin.Any
ResponseType.Informational -> throw UnsupportedOperationException("Client does not support Informational responses.")
ResponseType.Redirection -> throw UnsupportedOperationException("Client does not support Redirection responses.")
ResponseType.ClientError -> {
val localVarError = localVarResponse as ClientError<*>
throw ClientException("Client error : ${localVarError.statusCode} ${localVarError.message.orEmpty()}", localVarError.statusCode, localVarResponse)
}
ResponseType.ServerError -> {
val localVarError = localVarResponse as ServerError<*>
throw ServerException("Server error : ${localVarError.statusCode} ${localVarError.message.orEmpty()}", localVarError.statusCode, localVarResponse)
}
}
}

/**
* Returns the combined openapi.json document of all plugins currently installed in the API server.
* The various distinct openapi.json documents (formerly called swagger.json) are flattened into a single one.
* @return ApiResponse<kotlin.Any?>
* @throws IllegalStateException If the request is not correctly configured
* @throws IOException Rethrows the OkHttp execute method exception
*/
@Suppress("UNCHECKED_CAST")
@Throws(IllegalStateException::class, IOException::class)
fun getAggregateOpenapiJsonV1WithHttpInfo() : ApiResponse<kotlin.Any?> {
val localVariableConfig = getAggregateOpenapiJsonV1RequestConfig()

return request<Unit, kotlin.Any>(
localVariableConfig
)
}

/**
* To obtain the request config of the operation getAggregateOpenapiJsonV1
*
* @return RequestConfig
*/
fun getAggregateOpenapiJsonV1RequestConfig() : RequestConfig<Unit> {
val localVariableBody = null
val localVariableQuery: MultiValueMap = mutableMapOf()
val localVariableHeaders: MutableMap<String, String> = mutableMapOf()
localVariableHeaders["Accept"] = "application/json"

return RequestConfig(
method = RequestMethod.GET,
path = "/api/v1/api-server/get-aggregate-openapi-json",
query = localVariableQuery,
headers = localVariableHeaders,
requiresAuthentication = false,
body = localVariableBody
)
}

/**
* Can be used to verify liveness of an API server instance
* Returns the current timestamp of the API server as proof of health/liveness
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ syntax = "proto3";
package org.hyperledger.cactus.cmd_api_server;

import "google/protobuf/empty.proto";
import "models/any_type_pb.proto";
import "models/health_check_response_pb.proto";

service DefaultService {
rpc GetAggregateOpenapiJsonV1 (google.protobuf.Empty) returns (AnyTypePB);

rpc GetHealthCheckV1 (google.protobuf.Empty) returns (HealthCheckResponsePB);

rpc GetOpenApiSpecV1 (google.protobuf.Empty) returns (GetOpenApiSpecV1Response);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -380,8 +380,8 @@ export class ApiServer {
pluginImport: PluginImport,
): Promise<void> {
const fnTag = `ApiServer#installPluginPackage()`;
const pkgName = pluginImport.options.packageSrc
? pluginImport.options.packageSrc
const pkgName = pluginImport.pluginPkgInstallSource
? pluginImport.pluginPkgInstallSource
: pluginImport.packageName;

const instanceId = pluginImport.options.instanceId;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,36 @@ export type WatchHealthcheckV1 = typeof WatchHealthcheckV1[keyof typeof WatchHea
*/
export const DefaultApiAxiosParamCreator = function (configuration?: Configuration) {
return {
/**
* The various distinct openapi.json documents (formerly called swagger.json) are flattened into a single one.
* @summary Returns the combined openapi.json document of all plugins currently installed in the API server.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getAggregateOpenapiJsonV1: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/api/v1/api-server/get-aggregate-openapi-json`;
// 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,
};
},
/**
* Returns the current timestamp of the API server as proof of health/liveness
* @summary Can be used to verify liveness of an API server instance
Expand Down Expand Up @@ -208,6 +238,16 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati
export const DefaultApiFp = function(configuration?: Configuration) {
const localVarAxiosParamCreator = DefaultApiAxiosParamCreator(configuration)
return {
/**
* The various distinct openapi.json documents (formerly called swagger.json) are flattened into a single one.
* @summary Returns the combined openapi.json document of all plugins currently installed in the API server.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async getAggregateOpenapiJsonV1(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<any>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getAggregateOpenapiJsonV1(options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
* Returns the current timestamp of the API server as proof of health/liveness
* @summary Can be used to verify liveness of an API server instance
Expand Down Expand Up @@ -247,6 +287,15 @@ export const DefaultApiFp = function(configuration?: Configuration) {
export const DefaultApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
const localVarFp = DefaultApiFp(configuration)
return {
/**
* The various distinct openapi.json documents (formerly called swagger.json) are flattened into a single one.
* @summary Returns the combined openapi.json document of all plugins currently installed in the API server.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getAggregateOpenapiJsonV1(options?: any): AxiosPromise<any> {
return localVarFp.getAggregateOpenapiJsonV1(options).then((request) => request(axios, basePath));
},
/**
* Returns the current timestamp of the API server as proof of health/liveness
* @summary Can be used to verify liveness of an API server instance
Expand Down Expand Up @@ -283,6 +332,17 @@ export const DefaultApiFactory = function (configuration?: Configuration, basePa
* @extends {BaseAPI}
*/
export class DefaultApi extends BaseAPI {
/**
* The various distinct openapi.json documents (formerly called swagger.json) are flattened into a single one.
* @summary Returns the combined openapi.json document of all plugins currently installed in the API server.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof DefaultApi
*/
public getAggregateOpenapiJsonV1(options?: AxiosRequestConfig) {
return DefaultApiFp(this.configuration).getAggregateOpenapiJsonV1(options).then((request) => request(this.axios, this.basePath));
}

/**
* Returns the current timestamp of the API server as proof of health/liveness
* @summary Can be used to verify liveness of an API server instance
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { OpenAPIV3 } from "express-openapi-validator/dist/framework/types";

import type { PluginRegistry } from "@hyperledger/cactus-core";
import type { ICactusPlugin } from "@hyperledger/cactus-core-api";
import type { IPluginWebService } from "@hyperledger/cactus-core-api";
import { isIPluginWebService } from "@hyperledger/cactus-core-api";
import { Checks } from "@hyperledger/cactus-common";

export async function collectOpenapiJsonDocs(
pr: PluginRegistry,
): Promise<OpenAPIV3.Document[]> {
Checks.truthy(pr, `collectOpenapiJsonDocs() pr (PluginRegistry)`);

const openApiJsonDocsPromises = pr
.getPlugins()
.filter((pluginInstance) => isIPluginWebService(pluginInstance))
.map(async (plugin: ICactusPlugin) => {
const webSvc = plugin as IPluginWebService;
const openApiJson = (await webSvc.getOpenApiSpec()) as OpenAPIV3.Document;
return openApiJson;
});

const openApiJsonDocs = await Promise.all(openApiJsonDocsPromises);

// Filter out falsy results where the plugin did not return anything.
return openApiJsonDocs.filter((d) => !!d);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { Express, Request, Response } from "express";
import HttpStatus from "http-status-codes";

import {
Logger,
Checks,
LogLevelDesc,
LoggerProvider,
IAsyncProvider,
} from "@hyperledger/cactus-common";

import {
IWebServiceEndpoint,
IExpressRequestHandler,
IEndpointAuthzOptions,
} from "@hyperledger/cactus-core-api";

import {
PluginRegistry,
registerWebServiceEndpoint,
} from "@hyperledger/cactus-core";

import OAS from "../../../json/openapi.json";
import { collectOpenapiJsonDocs } from "./collect-openapi-json-docs";

export interface IGetAggregateOpenapiJsonEndpointV1Options {
logLevel?: LogLevelDesc;
pluginRegistry: PluginRegistry;
}

export class GetAggregateOpenapiJsonEndpointV1 implements IWebServiceEndpoint {
public static readonly CLASS_NAME = "GetAggregateOpenapiJsonEndpointV1";

private readonly log: Logger;

public get className(): string {
return GetAggregateOpenapiJsonEndpointV1.CLASS_NAME;
}

constructor(public readonly opts: IGetAggregateOpenapiJsonEndpointV1Options) {
const fnTag = `${this.className}#constructor()`;
Checks.truthy(opts, `${fnTag} arg options`);
Checks.truthy(opts.pluginRegistry, `${fnTag} arg options.pluginRegistry`);

const level = this.opts.logLevel || "INFO";
const label = this.className;
this.log = LoggerProvider.getOrCreate({ level, label });
}

public getExpressRequestHandler(): IExpressRequestHandler {
return this.handleRequest.bind(this);
}

public get oasPath(): typeof OAS.paths["/api/v1/api-server/get-aggregate-openapi-json"] {
return OAS.paths["/api/v1/api-server/get-aggregate-openapi-json"];
}

public getPath(): string {
return this.oasPath.get["x-hyperledger-cactus"].http.path;
}

public getVerbLowerCase(): string {
return this.oasPath.get["x-hyperledger-cactus"].http.verbLowerCase;
}

public getOperationId(): string {
return this.oasPath.get.operationId;
}

public async registerExpress(
expressApp: Express,
): Promise<IWebServiceEndpoint> {
await registerWebServiceEndpoint(expressApp, this);
return this;
}

getAuthorizationOptionsProvider(): IAsyncProvider<IEndpointAuthzOptions> {
// TODO: make this an injectable dependency in the constructor
return {
get: async () => ({
isProtected: true,
requiredRoles: [],
}),
};
}

async handleRequest(req: Request, res: Response): Promise<void> {
const fnTag = `${this.className}#handleRequest()`;
const verbUpper = this.getVerbLowerCase().toUpperCase();
this.log.debug(`${verbUpper} ${this.getPath()}`);

try {
const resBody = await collectOpenapiJsonDocs(this.opts.pluginRegistry);
res.status(HttpStatus.OK);
res.json(resBody);
} catch (ex) {
this.log.error(`${fnTag} failed to serve contract deploy request`, ex);
res.status(HttpStatus.INTERNAL_SERVER_ERROR);
res.statusMessage = ex.message;
res.json({ error: ex.stack });
}
}

public async getAggregateOpenapiJson(): Promise<unknown> {
return {};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ test("can install plugins at runtime with specified version based on imports", a
packageName: "@hyperledger/cactus-dummy-package",
type: PluginImportType.Local,
action: PluginImportAction.Install,
pluginPkgInstallSource:
"https://gitpkg.now.sh/hyperledger/cactus/packages/cactus-cmd-api-server/src/test/resources/cactus-dummy-package?main",
options: {
instanceId: uuidv4(),
keychainId: uuidv4(),
logLevel,
packageSrc:
"https://gitpkg.now.sh/hyperledger/cactus/packages/cactus-cmd-api-server/src/test/resources/cactus-dummy-package?main",
},
},
];
Expand Down
7 changes: 7 additions & 0 deletions packages/cactus-core-api/src/main/json/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@
"maxLength": 1024,
"nullable": false
},
"pluginPkgInstallSource": {
"type": "string",
"description": "If specified, it overrides the installation source of the package from the npm registry configured to be a folder on the local file-system. This is useful for testing how a plugin package behaves when loaded into the API server without having to publish it to a registry first.",
"minLength": 1,
"maxLength": 1024,
"nullable": false
},
"type": {
"nullable": false,
"description": "",
Expand Down
Loading

0 comments on commit 27f8623

Please sign in to comment.