Skip to content
This repository was archived by the owner on Jul 18, 2025. It is now read-only.

Commit c4d05ad

Browse files
author
Peter Somogyvari
committed
feat(cmd-api-server): aggregate swagger.json endpoints
WORK IN PROGRESS Fixes hyperledger-cacti#431 Signed-off-by: Peter Somogyvari <peter.somogyvari@accenture.com>
1 parent 175ebac commit c4d05ad

28 files changed

Lines changed: 1439 additions & 72 deletions

File tree

.cspell.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"cccg",
2727
"cccs",
2828
"ccep",
29+
"cccs",
2930
"ccid",
3031
"celo",
3132
"cids",
@@ -51,6 +52,7 @@
5152
"fidm",
5253
"flowdb",
5354
"fsouza",
55+
"Fuzzer",
5456
"GETHKEYCHAINPASSWORD",
5557
"ghcr",
5658
"gopath",

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
"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",
4343
"generate-api-server-config": "node ./tools/generate-api-server-config.js",
4444
"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",
45-
"start:api-server": "node ./packages/cactus-cmd-api-server/dist/lib/main/typescript/cmd/cactus-api.js --config-file=.config.json",
45+
"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",
4646
"start:example-supply-chain": "yarn build:dev && cd ./examples/supply-chain-app/ && yarn --no-lockfile && yarn start",
4747
"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",
4848
"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",

packages/cactus-cmd-api-server/src/main/json/openapi.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,30 @@
154154
}
155155
}
156156
}
157+
},
158+
"/api/v1/api-server/get-aggregate-openapi-json": {
159+
"get": {
160+
"summary": "Returns the combined openapi.json document of all plugins currently installed in the API server.",
161+
"description": "The various distinct openapi.json documents (formerly called swagger.json) are flattened into a single one.",
162+
"x-hyperledger-cactus": {
163+
"http": {
164+
"verbLowerCase": "get",
165+
"path": "/api/v1/api-server/get-aggregate-openapi-json"
166+
}
167+
},
168+
"operationId": "getAggregateOpenapiJsonV1",
169+
"parameters": [],
170+
"responses": {
171+
"200": {
172+
"description": "OK",
173+
"content": {
174+
"application/json": {
175+
"schema": {}
176+
}
177+
}
178+
}
179+
}
180+
}
157181
}
158182
}
159183
}

packages/cactus-cmd-api-server/src/main/kotlin/generated/openapi/kotlin-client/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ All URIs are relative to *http://localhost*
4444

4545
Class | Method | HTTP request | Description
4646
------------ | ------------- | ------------- | -------------
47+
*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.
4748
*DefaultApi* | [**getHealthCheckV1**](docs/DefaultApi.md#gethealthcheckv1) | **GET** /api/v1/api-server/healthcheck | Can be used to verify liveness of an API server instance
4849
*DefaultApi* | [**getOpenApiSpecV1**](docs/DefaultApi.md#getopenapispecv1) | **GET** /api/v1/api-server/get-open-api-spec |
4950
*DefaultApi* | [**getPrometheusMetricsV1**](docs/DefaultApi.md#getprometheusmetricsv1) | **GET** /api/v1/api-server/get-prometheus-exporter-metrics | Get the Prometheus Metrics

packages/cactus-cmd-api-server/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/apis/DefaultApi.kt

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,74 @@ class DefaultApi(basePath: kotlin.String = defaultBasePath, client: OkHttpClient
4545
}
4646
}
4747

48+
/**
49+
* Returns the combined openapi.json document of all plugins currently installed in the API server.
50+
* The various distinct openapi.json documents (formerly called swagger.json) are flattened into a single one.
51+
* @return kotlin.Any
52+
* @throws IllegalStateException If the request is not correctly configured
53+
* @throws IOException Rethrows the OkHttp execute method exception
54+
* @throws UnsupportedOperationException If the API returns an informational or redirection response
55+
* @throws ClientException If the API returns a client error response
56+
* @throws ServerException If the API returns a server error response
57+
*/
58+
@Suppress("UNCHECKED_CAST")
59+
@Throws(IllegalStateException::class, IOException::class, UnsupportedOperationException::class, ClientException::class, ServerException::class)
60+
fun getAggregateOpenapiJsonV1() : kotlin.Any {
61+
val localVarResponse = getAggregateOpenapiJsonV1WithHttpInfo()
62+
63+
return when (localVarResponse.responseType) {
64+
ResponseType.Success -> (localVarResponse as Success<*>).data as kotlin.Any
65+
ResponseType.Informational -> throw UnsupportedOperationException("Client does not support Informational responses.")
66+
ResponseType.Redirection -> throw UnsupportedOperationException("Client does not support Redirection responses.")
67+
ResponseType.ClientError -> {
68+
val localVarError = localVarResponse as ClientError<*>
69+
throw ClientException("Client error : ${localVarError.statusCode} ${localVarError.message.orEmpty()}", localVarError.statusCode, localVarResponse)
70+
}
71+
ResponseType.ServerError -> {
72+
val localVarError = localVarResponse as ServerError<*>
73+
throw ServerException("Server error : ${localVarError.statusCode} ${localVarError.message.orEmpty()}", localVarError.statusCode, localVarResponse)
74+
}
75+
}
76+
}
77+
78+
/**
79+
* Returns the combined openapi.json document of all plugins currently installed in the API server.
80+
* The various distinct openapi.json documents (formerly called swagger.json) are flattened into a single one.
81+
* @return ApiResponse<kotlin.Any?>
82+
* @throws IllegalStateException If the request is not correctly configured
83+
* @throws IOException Rethrows the OkHttp execute method exception
84+
*/
85+
@Suppress("UNCHECKED_CAST")
86+
@Throws(IllegalStateException::class, IOException::class)
87+
fun getAggregateOpenapiJsonV1WithHttpInfo() : ApiResponse<kotlin.Any?> {
88+
val localVariableConfig = getAggregateOpenapiJsonV1RequestConfig()
89+
90+
return request<Unit, kotlin.Any>(
91+
localVariableConfig
92+
)
93+
}
94+
95+
/**
96+
* To obtain the request config of the operation getAggregateOpenapiJsonV1
97+
*
98+
* @return RequestConfig
99+
*/
100+
fun getAggregateOpenapiJsonV1RequestConfig() : RequestConfig<Unit> {
101+
val localVariableBody = null
102+
val localVariableQuery: MultiValueMap = mutableMapOf()
103+
val localVariableHeaders: MutableMap<String, String> = mutableMapOf()
104+
localVariableHeaders["Accept"] = "application/json"
105+
106+
return RequestConfig(
107+
method = RequestMethod.GET,
108+
path = "/api/v1/api-server/get-aggregate-openapi-json",
109+
query = localVariableQuery,
110+
headers = localVariableHeaders,
111+
requiresAuthentication = false,
112+
body = localVariableBody
113+
)
114+
}
115+
48116
/**
49117
* Can be used to verify liveness of an API server instance
50118
* Returns the current timestamp of the API server as proof of health/liveness

packages/cactus-cmd-api-server/src/main/proto/generated/openapi/services/default_service.proto

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@ syntax = "proto3";
1313
package org.hyperledger.cactus.cmd_api_server;
1414

1515
import "google/protobuf/empty.proto";
16+
import "models/any_type_pb.proto";
1617
import "models/health_check_response_pb.proto";
1718

1819
service DefaultService {
20+
rpc GetAggregateOpenapiJsonV1 (google.protobuf.Empty) returns (AnyTypePB);
21+
1922
rpc GetHealthCheckV1 (google.protobuf.Empty) returns (HealthCheckResponsePB);
2023

2124
rpc GetOpenApiSpecV1 (google.protobuf.Empty) returns (GetOpenApiSpecV1Response);

packages/cactus-cmd-api-server/src/main/typescript/api-server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -380,8 +380,8 @@ export class ApiServer {
380380
pluginImport: PluginImport,
381381
): Promise<void> {
382382
const fnTag = `ApiServer#installPluginPackage()`;
383-
const pkgName = pluginImport.options.packageSrc
384-
? pluginImport.options.packageSrc
383+
const pkgName = pluginImport.pluginPkgInstallSource
384+
? pluginImport.pluginPkgInstallSource
385385
: pluginImport.packageName;
386386

387387
const instanceId = pluginImport.options.instanceId;

packages/cactus-cmd-api-server/src/main/typescript/generated/openapi/typescript-axios/api.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,36 @@ export type WatchHealthcheckV1 = typeof WatchHealthcheckV1[keyof typeof WatchHea
109109
*/
110110
export const DefaultApiAxiosParamCreator = function (configuration?: Configuration) {
111111
return {
112+
/**
113+
* The various distinct openapi.json documents (formerly called swagger.json) are flattened into a single one.
114+
* @summary Returns the combined openapi.json document of all plugins currently installed in the API server.
115+
* @param {*} [options] Override http request option.
116+
* @throws {RequiredError}
117+
*/
118+
getAggregateOpenapiJsonV1: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
119+
const localVarPath = `/api/v1/api-server/get-aggregate-openapi-json`;
120+
// use dummy base URL string because the URL constructor only accepts absolute URLs.
121+
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
122+
let baseOptions;
123+
if (configuration) {
124+
baseOptions = configuration.baseOptions;
125+
}
126+
127+
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
128+
const localVarHeaderParameter = {} as any;
129+
const localVarQueryParameter = {} as any;
130+
131+
132+
133+
setSearchParams(localVarUrlObj, localVarQueryParameter);
134+
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
135+
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
136+
137+
return {
138+
url: toPathString(localVarUrlObj),
139+
options: localVarRequestOptions,
140+
};
141+
},
112142
/**
113143
* Returns the current timestamp of the API server as proof of health/liveness
114144
* @summary Can be used to verify liveness of an API server instance
@@ -208,6 +238,16 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati
208238
export const DefaultApiFp = function(configuration?: Configuration) {
209239
const localVarAxiosParamCreator = DefaultApiAxiosParamCreator(configuration)
210240
return {
241+
/**
242+
* The various distinct openapi.json documents (formerly called swagger.json) are flattened into a single one.
243+
* @summary Returns the combined openapi.json document of all plugins currently installed in the API server.
244+
* @param {*} [options] Override http request option.
245+
* @throws {RequiredError}
246+
*/
247+
async getAggregateOpenapiJsonV1(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<any>> {
248+
const localVarAxiosArgs = await localVarAxiosParamCreator.getAggregateOpenapiJsonV1(options);
249+
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
250+
},
211251
/**
212252
* Returns the current timestamp of the API server as proof of health/liveness
213253
* @summary Can be used to verify liveness of an API server instance
@@ -247,6 +287,15 @@ export const DefaultApiFp = function(configuration?: Configuration) {
247287
export const DefaultApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
248288
const localVarFp = DefaultApiFp(configuration)
249289
return {
290+
/**
291+
* The various distinct openapi.json documents (formerly called swagger.json) are flattened into a single one.
292+
* @summary Returns the combined openapi.json document of all plugins currently installed in the API server.
293+
* @param {*} [options] Override http request option.
294+
* @throws {RequiredError}
295+
*/
296+
getAggregateOpenapiJsonV1(options?: any): AxiosPromise<any> {
297+
return localVarFp.getAggregateOpenapiJsonV1(options).then((request) => request(axios, basePath));
298+
},
250299
/**
251300
* Returns the current timestamp of the API server as proof of health/liveness
252301
* @summary Can be used to verify liveness of an API server instance
@@ -283,6 +332,17 @@ export const DefaultApiFactory = function (configuration?: Configuration, basePa
283332
* @extends {BaseAPI}
284333
*/
285334
export class DefaultApi extends BaseAPI {
335+
/**
336+
* The various distinct openapi.json documents (formerly called swagger.json) are flattened into a single one.
337+
* @summary Returns the combined openapi.json document of all plugins currently installed in the API server.
338+
* @param {*} [options] Override http request option.
339+
* @throws {RequiredError}
340+
* @memberof DefaultApi
341+
*/
342+
public getAggregateOpenapiJsonV1(options?: AxiosRequestConfig) {
343+
return DefaultApiFp(this.configuration).getAggregateOpenapiJsonV1(options).then((request) => request(this.axios, this.basePath));
344+
}
345+
286346
/**
287347
* Returns the current timestamp of the API server as proof of health/liveness
288348
* @summary Can be used to verify liveness of an API server instance
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import type { OpenAPIV3 } from "express-openapi-validator/dist/framework/types";
2+
3+
import type { PluginRegistry } from "@hyperledger/cactus-core";
4+
import type { ICactusPlugin } from "@hyperledger/cactus-core-api";
5+
import type { IPluginWebService } from "@hyperledger/cactus-core-api";
6+
import { isIPluginWebService } from "@hyperledger/cactus-core-api";
7+
import { Checks } from "@hyperledger/cactus-common";
8+
9+
export async function collectOpenapiJsonDocs(
10+
pr: PluginRegistry,
11+
): Promise<OpenAPIV3.Document[]> {
12+
Checks.truthy(pr, `collectOpenapiJsonDocs() pr (PluginRegistry)`);
13+
14+
const openApiJsonDocsPromises = pr
15+
.getPlugins()
16+
.filter((pluginInstance) => isIPluginWebService(pluginInstance))
17+
.map(async (plugin: ICactusPlugin) => {
18+
const webSvc = plugin as IPluginWebService;
19+
const openApiJson = (await webSvc.getOpenApiSpec()) as OpenAPIV3.Document;
20+
return openApiJson;
21+
});
22+
23+
const openApiJsonDocs = await Promise.all(openApiJsonDocsPromises);
24+
25+
// Filter out falsy results where the plugin did not return anything.
26+
return openApiJsonDocs.filter((d) => !!d);
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { Express, Request, Response } from "express";
2+
import HttpStatus from "http-status-codes";
3+
4+
import {
5+
Logger,
6+
Checks,
7+
LogLevelDesc,
8+
LoggerProvider,
9+
IAsyncProvider,
10+
} from "@hyperledger/cactus-common";
11+
12+
import {
13+
IWebServiceEndpoint,
14+
IExpressRequestHandler,
15+
IEndpointAuthzOptions,
16+
} from "@hyperledger/cactus-core-api";
17+
18+
import {
19+
PluginRegistry,
20+
registerWebServiceEndpoint,
21+
} from "@hyperledger/cactus-core";
22+
23+
import OAS from "../../../json/openapi.json";
24+
import { collectOpenapiJsonDocs } from "./collect-openapi-json-docs";
25+
26+
export interface IGetAggregateOpenapiJsonEndpointV1Options {
27+
logLevel?: LogLevelDesc;
28+
pluginRegistry: PluginRegistry;
29+
}
30+
31+
export class GetAggregateOpenapiJsonEndpointV1 implements IWebServiceEndpoint {
32+
public static readonly CLASS_NAME = "GetAggregateOpenapiJsonEndpointV1";
33+
34+
private readonly log: Logger;
35+
36+
public get className(): string {
37+
return GetAggregateOpenapiJsonEndpointV1.CLASS_NAME;
38+
}
39+
40+
constructor(public readonly opts: IGetAggregateOpenapiJsonEndpointV1Options) {
41+
const fnTag = `${this.className}#constructor()`;
42+
Checks.truthy(opts, `${fnTag} arg options`);
43+
Checks.truthy(opts.pluginRegistry, `${fnTag} arg options.pluginRegistry`);
44+
45+
const level = this.opts.logLevel || "INFO";
46+
const label = this.className;
47+
this.log = LoggerProvider.getOrCreate({ level, label });
48+
}
49+
50+
public getExpressRequestHandler(): IExpressRequestHandler {
51+
return this.handleRequest.bind(this);
52+
}
53+
54+
public get oasPath(): typeof OAS.paths["/api/v1/api-server/get-aggregate-openapi-json"] {
55+
return OAS.paths["/api/v1/api-server/get-aggregate-openapi-json"];
56+
}
57+
58+
public getPath(): string {
59+
return this.oasPath.get["x-hyperledger-cactus"].http.path;
60+
}
61+
62+
public getVerbLowerCase(): string {
63+
return this.oasPath.get["x-hyperledger-cactus"].http.verbLowerCase;
64+
}
65+
66+
public getOperationId(): string {
67+
return this.oasPath.get.operationId;
68+
}
69+
70+
public async registerExpress(
71+
expressApp: Express,
72+
): Promise<IWebServiceEndpoint> {
73+
await registerWebServiceEndpoint(expressApp, this);
74+
return this;
75+
}
76+
77+
getAuthorizationOptionsProvider(): IAsyncProvider<IEndpointAuthzOptions> {
78+
// TODO: make this an injectable dependency in the constructor
79+
return {
80+
get: async () => ({
81+
isProtected: true,
82+
requiredRoles: [],
83+
}),
84+
};
85+
}
86+
87+
async handleRequest(req: Request, res: Response): Promise<void> {
88+
const fnTag = `${this.className}#handleRequest()`;
89+
const verbUpper = this.getVerbLowerCase().toUpperCase();
90+
this.log.debug(`${verbUpper} ${this.getPath()}`);
91+
92+
try {
93+
const resBody = await collectOpenapiJsonDocs(this.opts.pluginRegistry);
94+
res.status(HttpStatus.OK);
95+
res.json(resBody);
96+
} catch (ex) {
97+
this.log.error(`${fnTag} failed to serve contract deploy request`, ex);
98+
res.status(HttpStatus.INTERNAL_SERVER_ERROR);
99+
res.statusMessage = ex.message;
100+
res.json({ error: ex.stack });
101+
}
102+
}
103+
104+
public async getAggregateOpenapiJson(): Promise<unknown> {
105+
return {};
106+
}
107+
}

0 commit comments

Comments
 (0)