-
Notifications
You must be signed in to change notification settings - Fork 286
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(cactus-core): add GetOpenApiSpecV1EndpointBase<S, P> class
This is the pre-requisite to finishing the task at #1877 Having this generic endpoint class available will allow us to send in another commit which then uses it to create the Open API spec endpoints in all the plugin classes. Example usage of the generic class looks like this: ```typescript 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`); } } ``` And the associated OpenAPI specification's paths entry: ```json "/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" } } } } } } }, ``` Signed-off-by: Peter Somogyvari <peter.somogyvari@accenture.com>
- Loading branch information
Showing
4 changed files
with
202 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
196 changes: 196 additions & 0 deletions
196
packages/cactus-core/src/main/typescript/web-services/get-open-api-spec-v1-endpoint-base.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
import type { Express, Request, Response } from "express"; | ||
import { RuntimeError } from "run-time-error"; | ||
import { stringify } from "safe-stable-stringify"; | ||
|
||
import { | ||
Logger, | ||
Checks, | ||
LogLevelDesc, | ||
LoggerProvider, | ||
IAsyncProvider, | ||
} from "@hyperledger/cactus-common"; | ||
|
||
import { | ||
IWebServiceEndpoint, | ||
IExpressRequestHandler, | ||
IEndpointAuthzOptions, | ||
} from "@hyperledger/cactus-core-api"; | ||
|
||
import { PluginRegistry } from "../plugin-registry"; | ||
|
||
import { registerWebServiceEndpoint } from "./register-web-service-endpoint"; | ||
|
||
export interface IGetOpenApiSpecV1EndpointBaseOptions<S, P> { | ||
logLevel?: LogLevelDesc; | ||
pluginRegistry: PluginRegistry; | ||
oasPath: P; | ||
oas: S; | ||
path: string; | ||
verbLowerCase: string; | ||
operationId: string; | ||
} | ||
|
||
/** | ||
* A generic base class that plugins can re-use to implement their own endpoints | ||
* which are returning their own OpenAPI specification documents with much less | ||
* boilerplate than otherwise would be needed. | ||
* | ||
* As an example, you can implement a sub-class like this: | ||
* | ||
* ```typescript | ||
* 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`); | ||
* } | ||
* } | ||
* | ||
* ``` | ||
* | ||
* The above code will also need you to update your openapi.json spec file by | ||
* adding a new endpoint matching it (if you skip this step the compiler should | ||
* complain about missing paths) | ||
* | ||
* ```json | ||
* "/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" | ||
* } | ||
* } | ||
* } | ||
* } | ||
* } | ||
* } | ||
* }, | ||
* ``` | ||
*/ | ||
export class GetOpenApiSpecV1EndpointBase<S, P> implements IWebServiceEndpoint { | ||
public static readonly CLASS_NAME = "GetOpenApiSpecV1EndpointBase<S, P>"; | ||
|
||
protected readonly log: Logger; | ||
|
||
public get className(): string { | ||
return GetOpenApiSpecV1EndpointBase.CLASS_NAME; | ||
} | ||
|
||
constructor( | ||
public readonly opts: IGetOpenApiSpecV1EndpointBaseOptions<S, P>, | ||
) { | ||
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(): P { | ||
return this.opts.oasPath; | ||
} | ||
|
||
public getPath(): string { | ||
return this.opts.path; | ||
} | ||
|
||
public getVerbLowerCase(): string { | ||
return this.opts.verbLowerCase; | ||
} | ||
|
||
public getOperationId(): string { | ||
return this.opts.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(); | ||
const reqMeta = `${verbUpper} ${this.getPath()}`; | ||
this.log.debug(reqMeta); | ||
|
||
try { | ||
const { oas } = this.opts; | ||
res.status(200); | ||
res.json(oas); | ||
} catch (ex: unknown) { | ||
const eMsg = `${fnTag} failed to serve request: ${reqMeta}`; | ||
this.log.debug(eMsg, ex); | ||
|
||
const cause = ex instanceof Error ? ex : stringify(ex); | ||
const error = new RuntimeError(eMsg, cause); | ||
|
||
res.status(500).json({ | ||
message: "Internal Server Error", | ||
error, | ||
}); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters