Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cmd-api-server): aggregate swagger.json endpoints #16

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"cccg",
"cccs",
"ccep",
"cccs",
"ccid",
"celo",
"cids",
Expand Down Expand Up @@ -62,6 +63,7 @@
"fidm",
"flowdb",
"fsouza",
"Fuzzer",
"genproto",
"GETHKEYCHAINPASSWORD",
"ghcr",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"tools:check-missing-node-deps": "TS_NODE_PROJECT=tools/tsconfig.json node --experimental-json-modules --trace-deprecation --experimental-modules --abort-on-uncaught-exception --loader ts-node/esm --experimental-specifier-resolution=node ./tools/custom-checks/check-missing-node-deps.ts",
"generate-api-server-config": "node ./tools/generate-api-server-config.js",
"sync-ts-config": "TS_NODE_PROJECT=tools/tsconfig.json node --experimental-json-modules --loader ts-node/esm ./tools/sync-npm-deps-to-tsc-projects.ts",
"start:api-server": "node ./packages/cactus-cmd-api-server/dist/lib/main/typescript/cmd/cactus-api.js --config-file=.config.json",
"start:api-server": "node --max-http-header-size=4194304 ./packages/cactus-cmd-api-server/dist/lib/main/typescript/cmd/cactus-api.js --config-file=.config.json",
"start:example-supply-chain": "yarn build:dev && cd ./examples/cactus-example-supply-chain-backend/ && yarn && yarn start",
"start:example-carbon-accounting": "CONFIG_FILE=examples/cactus-example-carbon-accounting-backend/example-config.json node examples/cactus-example-carbon-accounting-backend/dist/lib/main/typescript/carbon-accounting-app-cli.js",
"start:example-cbdc-bridging-app": "node -r ts-node/register examples/cactus-example-cbdc-bridging-backend/dist/lib/main/typescript/cbdc-bridging-app-cli.js dotenv_config_path=examples/cactus-example-cbdc-bridging-backend/process.env",
Expand Down
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 {};
}
}
Loading
Loading