@backstage/backend-openapi-utils 0.6.0-next.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/dist/constants.cjs.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/proxy/setup.cjs.js.map +1 -1
- package/dist/schema/errors.cjs.js.map +1 -1
- package/dist/schema/parameter-validation.cjs.js.map +1 -1
- package/dist/schema/request-body-validation.cjs.js.map +1 -1
- package/dist/schema/response-body-validation.cjs.js.map +1 -1
- package/dist/schema/utils.cjs.js.map +1 -1
- package/dist/schema/validation.cjs.js.map +1 -1
- package/dist/stub.cjs.js.map +1 -1
- package/dist/testUtils.cjs.js.map +1 -1
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @backstage/backend-openapi-utils
|
|
2
2
|
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- c08cbc4: Move Scaffolder API to OpenAPI
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- 3760352: Update `express-openapi-validator` to 5.5.8 to fix security vulnerability in transitive dependency `multer`
|
|
12
|
+
- Updated dependencies
|
|
13
|
+
- @backstage/backend-plugin-api@1.4.2
|
|
14
|
+
|
|
15
|
+
## 0.6.0-next.1
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- 3760352: Update `express-openapi-validator` to 5.5.8 to fix security vulnerability in transitive dependency `multer`
|
|
20
|
+
|
|
3
21
|
## 0.6.0-next.0
|
|
4
22
|
|
|
5
23
|
### Minor Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.cjs.js","sources":["../src/constants.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * The route that all OpenAPI specs should be served from.\n * @public\n */\nexport const OPENAPI_SPEC_ROUTE = '/openapi.json';\n"],"names":[],"mappings":";;AAoBO,MAAM,
|
|
1
|
+
{"version":3,"file":"constants.cjs.js","sources":["../src/constants.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * The route that all OpenAPI specs should be served from.\n * @public\n */\nexport const OPENAPI_SPEC_ROUTE = '/openapi.json';\n"],"names":[],"mappings":";;AAoBO,MAAM,kBAAA,GAAqB;;;;"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { JSONSchema, FromSchema } from 'json-schema-to-ts';
|
|
2
|
-
import {
|
|
2
|
+
import { OpenAPIObject, ReferenceObject, ContentObject, ParameterObject, SchemaObject, ResponseObject, RequestBodyObject } from 'openapi3-ts';
|
|
3
3
|
import core from 'express-serve-static-core';
|
|
4
4
|
import { Router, RequestHandler } from 'express';
|
|
5
5
|
import { middleware } from 'express-openapi-validator';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup.cjs.js","sources":["../../src/proxy/setup.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as mockttp from 'mockttp';\nimport { OpenApiProxyValidator } from '../schema/validation';\nimport getPort from 'get-port';\nimport { Server } from 'http';\n\nexport class Proxy {\n server: mockttp.Mockttp;\n #openRequests: Record<string, mockttp.CompletedRequest> = {};\n requestResponsePairs = new Map<\n mockttp.CompletedRequest,\n mockttp.CompletedResponse\n >();\n validator: OpenApiProxyValidator;\n public forwardTo: { port: number } = { port: 0 };\n express: { server: Server | undefined } = { server: undefined };\n constructor() {\n this.server = mockttp.getLocal();\n this.validator = new OpenApiProxyValidator();\n }\n\n async setup() {\n await this.server.start();\n this.forwardTo.port = await getPort();\n this.server\n .forAnyRequest()\n .thenForwardTo(`http://localhost:${this.forwardTo.port}`);\n await this.server.on('request', request => {\n this.#openRequests[request.id] = request;\n });\n await this.server.on('response', response => {\n const request = this.#openRequests[response.id];\n if (request) {\n this.requestResponsePairs.set(request, response);\n }\n delete this.#openRequests[response.id];\n this.validator.validate(request, response);\n });\n }\n\n async initialize(url: string, server: Server) {\n await this.validator.initialize(`${url}/openapi.json`);\n this.express.server = server;\n }\n\n stop() {\n if (Object.keys(this.#openRequests).length > 0) {\n throw new Error('There are still open requests');\n }\n this.server.stop();\n\n // If this isn't expressly closed, it will cause a jest memory leak warning.\n this.express.server?.close();\n }\n\n get url() {\n return this.server.proxyEnv.HTTP_PROXY;\n }\n}\n"],"names":["mockttp","OpenApiProxyValidator","getPort"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqBO,MAAM,
|
|
1
|
+
{"version":3,"file":"setup.cjs.js","sources":["../../src/proxy/setup.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as mockttp from 'mockttp';\nimport { OpenApiProxyValidator } from '../schema/validation';\nimport getPort from 'get-port';\nimport { Server } from 'http';\n\nexport class Proxy {\n server: mockttp.Mockttp;\n #openRequests: Record<string, mockttp.CompletedRequest> = {};\n requestResponsePairs = new Map<\n mockttp.CompletedRequest,\n mockttp.CompletedResponse\n >();\n validator: OpenApiProxyValidator;\n public forwardTo: { port: number } = { port: 0 };\n express: { server: Server | undefined } = { server: undefined };\n constructor() {\n this.server = mockttp.getLocal();\n this.validator = new OpenApiProxyValidator();\n }\n\n async setup() {\n await this.server.start();\n this.forwardTo.port = await getPort();\n this.server\n .forAnyRequest()\n .thenForwardTo(`http://localhost:${this.forwardTo.port}`);\n await this.server.on('request', request => {\n this.#openRequests[request.id] = request;\n });\n await this.server.on('response', response => {\n const request = this.#openRequests[response.id];\n if (request) {\n this.requestResponsePairs.set(request, response);\n }\n delete this.#openRequests[response.id];\n this.validator.validate(request, response);\n });\n }\n\n async initialize(url: string, server: Server) {\n await this.validator.initialize(`${url}/openapi.json`);\n this.express.server = server;\n }\n\n stop() {\n if (Object.keys(this.#openRequests).length > 0) {\n throw new Error('There are still open requests');\n }\n this.server.stop();\n\n // If this isn't expressly closed, it will cause a jest memory leak warning.\n this.express.server?.close();\n }\n\n get url() {\n return this.server.proxyEnv.HTTP_PROXY;\n }\n}\n"],"names":["mockttp","OpenApiProxyValidator","getPort"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqBO,MAAM,KAAA,CAAM;AAAA,EACjB,MAAA;AAAA,EACA,gBAA0D,EAAC;AAAA,EAC3D,oBAAA,uBAA2B,GAAA,EAGzB;AAAA,EACF,SAAA;AAAA,EACO,SAAA,GAA8B,EAAE,IAAA,EAAM,CAAA,EAAE;AAAA,EAC/C,OAAA,GAA0C,EAAE,MAAA,EAAQ,MAAA,EAAU;AAAA,EAC9D,WAAA,GAAc;AACZ,IAAA,IAAA,CAAK,MAAA,GAASA,mBAAQ,QAAA,EAAS;AAC/B,IAAA,IAAA,CAAK,SAAA,GAAY,IAAIC,gCAAA,EAAsB;AAAA,EAC7C;AAAA,EAEA,MAAM,KAAA,GAAQ;AACZ,IAAA,MAAM,IAAA,CAAK,OAAO,KAAA,EAAM;AACxB,IAAA,IAAA,CAAK,SAAA,CAAU,IAAA,GAAO,MAAMC,wBAAA,EAAQ;AACpC,IAAA,IAAA,CAAK,MAAA,CACF,eAAc,CACd,aAAA,CAAc,oBAAoB,IAAA,CAAK,SAAA,CAAU,IAAI,CAAA,CAAE,CAAA;AAC1D,IAAA,MAAM,IAAA,CAAK,MAAA,CAAO,EAAA,CAAG,SAAA,EAAW,CAAA,OAAA,KAAW;AACzC,MAAA,IAAA,CAAK,aAAA,CAAc,OAAA,CAAQ,EAAE,CAAA,GAAI,OAAA;AAAA,IACnC,CAAC,CAAA;AACD,IAAA,MAAM,IAAA,CAAK,MAAA,CAAO,EAAA,CAAG,UAAA,EAAY,CAAA,QAAA,KAAY;AAC3C,MAAA,MAAM,OAAA,GAAU,IAAA,CAAK,aAAA,CAAc,QAAA,CAAS,EAAE,CAAA;AAC9C,MAAA,IAAI,OAAA,EAAS;AACX,QAAA,IAAA,CAAK,oBAAA,CAAqB,GAAA,CAAI,OAAA,EAAS,QAAQ,CAAA;AAAA,MACjD;AACA,MAAA,OAAO,IAAA,CAAK,aAAA,CAAc,QAAA,CAAS,EAAE,CAAA;AACrC,MAAA,IAAA,CAAK,SAAA,CAAU,QAAA,CAAS,OAAA,EAAS,QAAQ,CAAA;AAAA,IAC3C,CAAC,CAAA;AAAA,EACH;AAAA,EAEA,MAAM,UAAA,CAAW,GAAA,EAAa,MAAA,EAAgB;AAC5C,IAAA,MAAM,IAAA,CAAK,SAAA,CAAU,UAAA,CAAW,CAAA,EAAG,GAAG,CAAA,aAAA,CAAe,CAAA;AACrD,IAAA,IAAA,CAAK,QAAQ,MAAA,GAAS,MAAA;AAAA,EACxB;AAAA,EAEA,IAAA,GAAO;AACL,IAAA,IAAI,OAAO,IAAA,CAAK,IAAA,CAAK,aAAa,CAAA,CAAE,SAAS,CAAA,EAAG;AAC9C,MAAA,MAAM,IAAI,MAAM,+BAA+B,CAAA;AAAA,IACjD;AACA,IAAA,IAAA,CAAK,OAAO,IAAA,EAAK;AAGjB,IAAA,IAAA,CAAK,OAAA,CAAQ,QAAQ,KAAA,EAAM;AAAA,EAC7B;AAAA,EAEA,IAAI,GAAA,GAAM;AACR,IAAA,OAAO,IAAA,CAAK,OAAO,QAAA,CAAS,UAAA;AAAA,EAC9B;AACF;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.cjs.js","sources":["../../src/schema/errors.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Operation } from './types';\nimport { ErrorObject } from 'ajv';\nimport { humanifyAjvError } from './utils';\n\nexport class OperationError extends Error {\n constructor(operation: Operation, message: string) {\n super(\n `[\"${operation.method.toLocaleUpperCase('en-US')} ${\n operation.path\n }\"] ${message}`,\n );\n }\n}\n\nexport class OperationResponseError extends Error {\n constructor(operation: Operation, response: Response, message: string) {\n super(\n `[\"${operation.method.toLocaleUpperCase('en-US')} ${operation.path}\" (${\n response.status\n })]: ${message}`,\n );\n }\n}\n\nexport class OperationParsingError extends OperationError {\n constructor(operation: Operation, type: string, errors: ErrorObject[]) {\n super(\n operation,\n `${type} validation failed.\\n - ${errors\n .map(humanifyAjvError)\n .join('\\n - ')}`,\n );\n }\n}\n\nexport class OperationParsingResponseError extends OperationResponseError {\n constructor(\n operation: Operation,\n response: Response,\n type: string,\n errors: ErrorObject[],\n ) {\n super(\n operation,\n response,\n `${type} validation failed.\\n - ${errors\n .map(humanifyAjvError)\n .join('\\n - ')}`,\n );\n }\n}\n"],"names":["humanifyAjvError"],"mappings":";;;;AAoBO,MAAM,uBAAuB,
|
|
1
|
+
{"version":3,"file":"errors.cjs.js","sources":["../../src/schema/errors.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Operation } from './types';\nimport { ErrorObject } from 'ajv';\nimport { humanifyAjvError } from './utils';\n\nexport class OperationError extends Error {\n constructor(operation: Operation, message: string) {\n super(\n `[\"${operation.method.toLocaleUpperCase('en-US')} ${\n operation.path\n }\"] ${message}`,\n );\n }\n}\n\nexport class OperationResponseError extends Error {\n constructor(operation: Operation, response: Response, message: string) {\n super(\n `[\"${operation.method.toLocaleUpperCase('en-US')} ${operation.path}\" (${\n response.status\n })]: ${message}`,\n );\n }\n}\n\nexport class OperationParsingError extends OperationError {\n constructor(operation: Operation, type: string, errors: ErrorObject[]) {\n super(\n operation,\n `${type} validation failed.\\n - ${errors\n .map(humanifyAjvError)\n .join('\\n - ')}`,\n );\n }\n}\n\nexport class OperationParsingResponseError extends OperationResponseError {\n constructor(\n operation: Operation,\n response: Response,\n type: string,\n errors: ErrorObject[],\n ) {\n super(\n operation,\n response,\n `${type} validation failed.\\n - ${errors\n .map(humanifyAjvError)\n .join('\\n - ')}`,\n );\n }\n}\n"],"names":["humanifyAjvError"],"mappings":";;;;AAoBO,MAAM,uBAAuB,KAAA,CAAM;AAAA,EACxC,WAAA,CAAY,WAAsB,OAAA,EAAiB;AACjD,IAAA,KAAA;AAAA,MACE,CAAA,EAAA,EAAK,SAAA,CAAU,MAAA,CAAO,iBAAA,CAAkB,OAAO,CAAC,CAAA,CAAA,EAC9C,SAAA,CAAU,IACZ,CAAA,GAAA,EAAM,OAAO,CAAA;AAAA,KACf;AAAA,EACF;AACF;AAEO,MAAM,+BAA+B,KAAA,CAAM;AAAA,EAChD,WAAA,CAAY,SAAA,EAAsB,QAAA,EAAoB,OAAA,EAAiB;AACrE,IAAA,KAAA;AAAA,MACE,CAAA,EAAA,EAAK,SAAA,CAAU,MAAA,CAAO,iBAAA,CAAkB,OAAO,CAAC,CAAA,CAAA,EAAI,SAAA,CAAU,IAAI,CAAA,GAAA,EAChE,QAAA,CAAS,MACX,OAAO,OAAO,CAAA;AAAA,KAChB;AAAA,EACF;AACF;AAEO,MAAM,8BAA8B,cAAA,CAAe;AAAA,EACxD,WAAA,CAAY,SAAA,EAAsB,IAAA,EAAc,MAAA,EAAuB;AACrE,IAAA,KAAA;AAAA,MACE,SAAA;AAAA,MACA,GAAG,IAAI,CAAA;AAAA,GAAA,EAA2B,OAC/B,GAAA,CAAIA,sBAAgB,CAAA,CACpB,IAAA,CAAK,OAAO,CAAC,CAAA;AAAA,KAClB;AAAA,EACF;AACF;AAEO,MAAM,sCAAsC,sBAAA,CAAuB;AAAA,EACxE,WAAA,CACE,SAAA,EACA,QAAA,EACA,IAAA,EACA,MAAA,EACA;AACA,IAAA,KAAA;AAAA,MACE,SAAA;AAAA,MACA,QAAA;AAAA,MACA,GAAG,IAAI,CAAA;AAAA,GAAA,EAA2B,OAC/B,GAAA,CAAIA,sBAAgB,CAAA,CACpB,IAAA,CAAK,OAAO,CAAC,CAAA;AAAA,KAClB;AAAA,EACF;AACF;;;;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parameter-validation.cjs.js","sources":["../../src/schema/parameter-validation.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { OpenAPIObject, ParameterObject, SchemaObject } from 'openapi3-ts';\nimport {\n Operation,\n ParserOptions,\n RequestParser,\n Validator,\n ValidatorParams,\n} from './types';\nimport Ajv from 'ajv';\nimport { OperationError, OperationParsingError } from './errors';\nimport { mockttpToFetchRequest } from './utils';\n\ntype ReferencelessSchemaObject = SchemaObject & { $ref?: never };\n\ntype ReferencelessParameterObject = Omit<ParameterObject, 'schema'> & {\n schema: ReferencelessSchemaObject;\n};\n\nclass BaseParameterParser {\n ajv: Ajv;\n operation: Operation;\n parameters: Record<string, ReferencelessParameterObject> = {};\n constructor(\n parameterIn: string,\n operation: Operation,\n options: ParserOptions,\n ) {\n this.ajv = options.ajv;\n this.operation = operation;\n const { schema, path, method } = operation;\n const parameters = schema.parameters || [];\n for (const parameter of parameters) {\n if ('$ref' in parameter) {\n throw new Error(\n `[(${method}) ${path}] Reference objects are not supported`,\n );\n }\n\n if (!parameter.schema) {\n throw new OperationError(\n operation,\n 'Schema not found for path parameter',\n );\n }\n if ('$ref' in parameter.schema) {\n throw new OperationError(\n this.operation,\n 'Reference objects are not supported for parameters',\n );\n }\n if (parameter.in === parameterIn) {\n this.parameters[parameter.name] =\n parameter as ReferencelessParameterObject;\n }\n }\n }\n\n /**\n * Attempt to transform a string value to its expected type, this allows Ajv to perform validation. As these are parameters,\n * support for edge cases like nested type casting is not currently supported.\n * @param value\n * @param schema\n * @returns\n */\n optimisticallyParseValue(value: string, schema: SchemaObject) {\n if (schema.type === 'integer') {\n return parseInt(value, 10);\n }\n if (schema.type === 'number') {\n return parseFloat(value);\n }\n if (schema.type === 'boolean') {\n if (['true', 'false'].includes(value)) {\n return value === 'true';\n }\n throw new Error('Invalid boolean value must be either \"true\" or \"false\"');\n }\n return value;\n }\n}\n\nexport class QueryParameterParser\n extends BaseParameterParser\n implements RequestParser<Record<string, any>>\n{\n constructor(operation: Operation, options: ParserOptions) {\n super('query', operation, options);\n }\n async parse(request: Request) {\n const { searchParams } = new URL(request.url);\n const remainingQueryParameters = new Set<string>(searchParams.keys());\n const queryParameters: Record<string, any> = {};\n\n let parameterIterator = Object.entries(this.parameters);\n\n const isFormExplode = (parameter: ReferencelessParameterObject) => {\n return (\n parameter.schema?.type === 'object' &&\n (parameter.style === 'form' || !parameter.style) &&\n parameter.explode\n );\n };\n\n const regularParameters = parameterIterator.filter(\n ([_, parameter]) => !isFormExplode(parameter),\n );\n\n const formExplodeParameters = parameterIterator.filter(([_, parameter]) =>\n isFormExplode(parameter),\n );\n\n if (formExplodeParameters.length > 1) {\n throw new OperationError(\n this.operation,\n 'Ambiguous query parameters, you cannot have 2 form explode parameters',\n );\n }\n\n // Sort the parameters so that form explode parameters are processed last.\n parameterIterator = [...regularParameters, ...formExplodeParameters];\n\n for (const [name, parameter] of parameterIterator) {\n if (!parameter.schema) {\n throw new OperationError(\n this.operation,\n 'Schema not found for query parameter',\n );\n }\n if ('$ref' in parameter.schema) {\n throw new OperationError(\n this.operation,\n 'Reference objects are not supported for parameters',\n );\n }\n // eslint-disable-next-line prefer-const\n let [param, indices]: [any | null, string[]] = this.#findQueryParameters(\n this.parameters,\n remainingQueryParameters,\n searchParams,\n name,\n );\n if (!!param) {\n indices.forEach(index => remainingQueryParameters.delete(index));\n }\n\n // The query parameters can be either a single value or an array of values, try to wrangle them into the expected format if they're not explicitly an array.\n if (parameter.schema.type !== 'array' && Array.isArray(param)) {\n param = param.length > 0 ? param[0] : undefined;\n }\n if (\n parameter.required &&\n !indices.some(index => searchParams.has(index))\n ) {\n throw new OperationError(\n this.operation,\n `Required query parameter ${name} not found`,\n );\n } else if (!param && !parameter.required) {\n continue;\n }\n if (param) {\n // We do this here because all query parameters are strings but the schema will expect the real value.\n param = this.optimisticallyParseValue(param, parameter.schema);\n }\n const validate = this.ajv.compile(parameter.schema);\n const valid = validate(param);\n if (!valid) {\n throw new OperationParsingError(\n this.operation,\n 'Query parameter',\n validate.errors!,\n );\n }\n queryParameters[name] = param;\n }\n if (remainingQueryParameters.size > 0) {\n throw new OperationError(\n this.operation,\n `Unexpected query parameters: ${Array.from(\n remainingQueryParameters,\n ).join(', ')}`,\n );\n }\n return queryParameters;\n }\n\n #findQueryParameters(\n parameters: Record<string, ParameterObject>,\n remainingQueryParameters: Set<string>,\n searchParams: URLSearchParams,\n name: string,\n ): [any | null, string[]] {\n const parameter = parameters[name];\n const schema = parameter.schema as SchemaObject;\n\n // Since getAll will return an empty array if the key is not found, we need to check if the key exists first.\n const getIfExists = (key: string) =>\n searchParams.has(key) ? searchParams.getAll(key) : null;\n\n if (schema.type === 'array') {\n // Form is the default array format.\n if (\n parameter.style === 'form' ||\n typeof parameter.style === 'undefined'\n ) {\n // As is explode = true.\n if (parameter.explode || typeof parameter.explode === 'undefined') {\n // Support for qs explode format. Every value is stored as a separate query parameter.\n if (!searchParams.has(name) && searchParams.has(`${name}[0]`)) {\n const values: string[] = [];\n const indices: string[] = [];\n let index = 0;\n while (searchParams.has(`${name}[${index}]`)) {\n values.push(searchParams.get(`${name}[${index}]`)!);\n indices.push(`${name}[${index}]`);\n index++;\n }\n return [values, indices];\n }\n // If not qs format, grab all values with the same name from search params.\n return [getIfExists(name), [name]];\n }\n // Add support for qs non-standard array format. This is helpful for search-backend, since that uses qs still.\n if (!searchParams.has(name) && searchParams.has(`${name}[]`)) {\n return [searchParams.get(`${name}[]`)?.split(','), [`${name}[]`]];\n }\n // Non-explode arrays should be comma separated.\n if (searchParams.has(name) && searchParams.getAll(name).length > 1) {\n throw new OperationError(\n this.operation,\n 'Arrays must be comma separated in non-explode mode',\n );\n }\n return [searchParams.get(name)?.split(','), [name]];\n } else if (parameter.style === 'spaceDelimited') {\n return [searchParams.get(name)?.split(' '), [name]];\n } else if (parameter.style === 'pipeDelimited') {\n return [searchParams.get(name)?.split('|'), [name]];\n }\n throw new OperationError(\n this.operation,\n 'Unsupported style for array parameter',\n );\n }\n if (schema.type === 'object') {\n // Form is the default object format.\n if (\n parameter.style === 'form' ||\n typeof parameter.style === 'undefined'\n ) {\n if (parameter.explode) {\n // Object form/explode is a collection of disjoint keys, there's no mapping for what they are so we collect all of them.\n // This means we need to run this as the last query parameter that is processed.\n const obj: Record<string, string> = {};\n const indices: string[] = [];\n for (const [key, value] of searchParams.entries()) {\n // Have we processed this query parameter as part of another parameter parsing? If not, consider it to be a part of this object.\n if (!remainingQueryParameters.has(key)) {\n continue;\n }\n indices.push(key);\n obj[key] = value;\n }\n return [obj, indices];\n }\n // For non-explode, the schema is comma separated key,value \"pairs\", so filter=key1,value1,key2,value2 would parse to {key1: value1, key2: value2}.\n const obj: Record<string, string> = {};\n const value = searchParams.get(name);\n if (value) {\n const parts = value.split(',');\n if (parts.length % 2 !== 0) {\n throw new OperationError(\n this.operation,\n 'Invalid object query parameter, must have an even number of key-value pairs',\n );\n }\n for (let i = 0; i < parts.length; i += 2) {\n obj[parts[i]] = parts[i + 1];\n }\n }\n return [obj, [name]];\n } else if (parameter.style === 'deepObject') {\n // Deep object is a nested object structure, so we need to parse the keys to build the object.\n // example: ?filter[key1]=value1&filter[key2]=value2 => { key1: value1, key2: value2 }\n const obj: Record<string, any> = {};\n const indices: string[] = [];\n for (const [key, value] of searchParams.entries()) {\n if (key.startsWith(`${name}[`)) {\n indices.push(key);\n const parts = key.split('[');\n let currentLayer = obj;\n for (let partIndex = 1; partIndex < parts.length - 1; partIndex++) {\n const part = parts[partIndex];\n if (!part.includes(']')) {\n throw new OperationError(\n this.operation,\n `Invalid object parameter, missing closing bracket for key \"${key}\"`,\n );\n }\n const objKey = part.split(']')[0];\n if (!currentLayer[objKey]) {\n currentLayer[objKey] = {};\n }\n currentLayer = currentLayer[objKey];\n }\n const lastPart = parts[parts.length - 1];\n if (!lastPart.includes(']')) {\n throw new OperationError(\n this.operation,\n `Invalid object parameter, missing closing bracket for key \"${key}\"`,\n );\n }\n currentLayer[lastPart.split(']')[0]] = value;\n }\n }\n return [obj, indices];\n }\n throw new OperationError(\n this.operation,\n `Unsupported style for object parameter, \"${parameter.style}\"`,\n );\n }\n // For everything else, just return the value.\n return [getIfExists(name), [name]];\n }\n}\n\nexport class HeaderParameterParser\n extends BaseParameterParser\n implements RequestParser<Record<string, any>>\n{\n constructor(operation: Operation, options: ParserOptions) {\n super('header', operation, options);\n }\n async parse(request: Request) {\n const headerParameters: Record<string, any> = {};\n for (const [name, parameter] of Object.entries(this.parameters)) {\n const header = request.headers.get(name);\n if (!header) {\n if (parameter.required) {\n throw new OperationError(\n this.operation,\n `Header parameter ${name} not found`,\n );\n }\n continue;\n }\n if (!parameter.schema) {\n throw new OperationError(\n this.operation,\n 'Schema not found for header parameter',\n );\n }\n if ('$ref' in parameter.schema) {\n throw new OperationError(\n this.operation,\n 'Reference objects are not supported for parameters',\n );\n }\n const validate = this.ajv.compile(parameter.schema);\n const valid = validate(header);\n\n if (!valid) {\n throw new OperationParsingError(\n this.operation,\n 'Header parameter',\n validate.errors!,\n );\n }\n headerParameters[name] = header;\n }\n return headerParameters;\n }\n}\n\nexport class PathParameterParser\n extends BaseParameterParser\n implements RequestParser<Record<string, any>>\n{\n constructor(operation: Operation, options: ParserOptions) {\n super('path', operation, options);\n }\n async parse(request: Request) {\n const { pathname } = new URL(request.url);\n const params = PathParameterParser.parsePath({\n operation: this.operation,\n path: pathname,\n schema: this.operation.path,\n });\n const pathParameters: Record<string, any> = {};\n for (const [name, parameter] of Object.entries(this.parameters)) {\n let param: string | number | boolean = params[name];\n if (!param && parameter.required) {\n throw new OperationError(\n this.operation,\n `Path parameter ${name} not found`,\n );\n } else if (!params[name] && !parameter.required) {\n continue;\n }\n\n if (param) {\n param = this.optimisticallyParseValue(param, parameter.schema);\n }\n\n const validate = this.ajv.compile(parameter.schema);\n const valid = validate(param);\n\n if (!valid) {\n throw new OperationParsingError(\n this.operation,\n 'Path parameter',\n validate.errors!,\n );\n }\n pathParameters[name] = param;\n }\n return pathParameters;\n }\n\n static parsePath({\n operation,\n schema,\n path,\n }: {\n operation: Operation;\n schema: string;\n path: string;\n }) {\n const parts = path.split('/');\n const pathParts = schema.split('/');\n if (parts.length !== pathParts.length) {\n throw new OperationError(operation, 'Path parts do not match');\n }\n const params: Record<string, string> = {};\n for (let i = 0; i < parts.length; i++) {\n if (pathParts[i] === parts[i]) {\n continue;\n }\n if (pathParts[i].startsWith('{') && pathParts[i].endsWith('}')) {\n params[pathParts[i].slice(1, -1)] = parts[i];\n continue;\n }\n break;\n }\n return params;\n }\n}\n\nexport class ParameterValidator implements Validator {\n schema: OpenAPIObject;\n cache: Record<string, any> = {};\n constructor(schema: OpenAPIObject) {\n this.schema = schema;\n }\n\n async validate({ pair: { request, response }, operation }: ValidatorParams) {\n if (response.statusCode === 400) {\n // If the response is a 400, then the request is invalid and we shouldn't validate the parameters\n return;\n }\n\n const ajv = new Ajv();\n const queryParser = new QueryParameterParser(operation, { ajv });\n const headerParser = new HeaderParameterParser(operation, { ajv });\n const pathParser = new PathParameterParser(operation, { ajv });\n\n const fetchRequest = mockttpToFetchRequest(request);\n\n await Promise.all([\n queryParser.parse(fetchRequest),\n headerParser.parse(fetchRequest),\n pathParser.parse(fetchRequest),\n ]);\n }\n}\n"],"names":["OperationError","OperationParsingError","obj","value","Ajv","mockttpToFetchRequest"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"parameter-validation.cjs.js","sources":["../../src/schema/parameter-validation.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { OpenAPIObject, ParameterObject, SchemaObject } from 'openapi3-ts';\nimport {\n Operation,\n ParserOptions,\n RequestParser,\n Validator,\n ValidatorParams,\n} from './types';\nimport Ajv from 'ajv';\nimport { OperationError, OperationParsingError } from './errors';\nimport { mockttpToFetchRequest } from './utils';\n\ntype ReferencelessSchemaObject = SchemaObject & { $ref?: never };\n\ntype ReferencelessParameterObject = Omit<ParameterObject, 'schema'> & {\n schema: ReferencelessSchemaObject;\n};\n\nclass BaseParameterParser {\n ajv: Ajv;\n operation: Operation;\n parameters: Record<string, ReferencelessParameterObject> = {};\n constructor(\n parameterIn: string,\n operation: Operation,\n options: ParserOptions,\n ) {\n this.ajv = options.ajv;\n this.operation = operation;\n const { schema, path, method } = operation;\n const parameters = schema.parameters || [];\n for (const parameter of parameters) {\n if ('$ref' in parameter) {\n throw new Error(\n `[(${method}) ${path}] Reference objects are not supported`,\n );\n }\n\n if (!parameter.schema) {\n throw new OperationError(\n operation,\n 'Schema not found for path parameter',\n );\n }\n if ('$ref' in parameter.schema) {\n throw new OperationError(\n this.operation,\n 'Reference objects are not supported for parameters',\n );\n }\n if (parameter.in === parameterIn) {\n this.parameters[parameter.name] =\n parameter as ReferencelessParameterObject;\n }\n }\n }\n\n /**\n * Attempt to transform a string value to its expected type, this allows Ajv to perform validation. As these are parameters,\n * support for edge cases like nested type casting is not currently supported.\n * @param value\n * @param schema\n * @returns\n */\n optimisticallyParseValue(value: string, schema: SchemaObject) {\n if (schema.type === 'integer') {\n return parseInt(value, 10);\n }\n if (schema.type === 'number') {\n return parseFloat(value);\n }\n if (schema.type === 'boolean') {\n if (['true', 'false'].includes(value)) {\n return value === 'true';\n }\n throw new Error('Invalid boolean value must be either \"true\" or \"false\"');\n }\n return value;\n }\n}\n\nexport class QueryParameterParser\n extends BaseParameterParser\n implements RequestParser<Record<string, any>>\n{\n constructor(operation: Operation, options: ParserOptions) {\n super('query', operation, options);\n }\n async parse(request: Request) {\n const { searchParams } = new URL(request.url);\n const remainingQueryParameters = new Set<string>(searchParams.keys());\n const queryParameters: Record<string, any> = {};\n\n let parameterIterator = Object.entries(this.parameters);\n\n const isFormExplode = (parameter: ReferencelessParameterObject) => {\n return (\n parameter.schema?.type === 'object' &&\n (parameter.style === 'form' || !parameter.style) &&\n parameter.explode\n );\n };\n\n const regularParameters = parameterIterator.filter(\n ([_, parameter]) => !isFormExplode(parameter),\n );\n\n const formExplodeParameters = parameterIterator.filter(([_, parameter]) =>\n isFormExplode(parameter),\n );\n\n if (formExplodeParameters.length > 1) {\n throw new OperationError(\n this.operation,\n 'Ambiguous query parameters, you cannot have 2 form explode parameters',\n );\n }\n\n // Sort the parameters so that form explode parameters are processed last.\n parameterIterator = [...regularParameters, ...formExplodeParameters];\n\n for (const [name, parameter] of parameterIterator) {\n if (!parameter.schema) {\n throw new OperationError(\n this.operation,\n 'Schema not found for query parameter',\n );\n }\n if ('$ref' in parameter.schema) {\n throw new OperationError(\n this.operation,\n 'Reference objects are not supported for parameters',\n );\n }\n // eslint-disable-next-line prefer-const\n let [param, indices]: [any | null, string[]] = this.#findQueryParameters(\n this.parameters,\n remainingQueryParameters,\n searchParams,\n name,\n );\n if (!!param) {\n indices.forEach(index => remainingQueryParameters.delete(index));\n }\n\n // The query parameters can be either a single value or an array of values, try to wrangle them into the expected format if they're not explicitly an array.\n if (parameter.schema.type !== 'array' && Array.isArray(param)) {\n param = param.length > 0 ? param[0] : undefined;\n }\n if (\n parameter.required &&\n !indices.some(index => searchParams.has(index))\n ) {\n throw new OperationError(\n this.operation,\n `Required query parameter ${name} not found`,\n );\n } else if (!param && !parameter.required) {\n continue;\n }\n if (param) {\n // We do this here because all query parameters are strings but the schema will expect the real value.\n param = this.optimisticallyParseValue(param, parameter.schema);\n }\n const validate = this.ajv.compile(parameter.schema);\n const valid = validate(param);\n if (!valid) {\n throw new OperationParsingError(\n this.operation,\n 'Query parameter',\n validate.errors!,\n );\n }\n queryParameters[name] = param;\n }\n if (remainingQueryParameters.size > 0) {\n throw new OperationError(\n this.operation,\n `Unexpected query parameters: ${Array.from(\n remainingQueryParameters,\n ).join(', ')}`,\n );\n }\n return queryParameters;\n }\n\n #findQueryParameters(\n parameters: Record<string, ParameterObject>,\n remainingQueryParameters: Set<string>,\n searchParams: URLSearchParams,\n name: string,\n ): [any | null, string[]] {\n const parameter = parameters[name];\n const schema = parameter.schema as SchemaObject;\n\n // Since getAll will return an empty array if the key is not found, we need to check if the key exists first.\n const getIfExists = (key: string) =>\n searchParams.has(key) ? searchParams.getAll(key) : null;\n\n if (schema.type === 'array') {\n // Form is the default array format.\n if (\n parameter.style === 'form' ||\n typeof parameter.style === 'undefined'\n ) {\n // As is explode = true.\n if (parameter.explode || typeof parameter.explode === 'undefined') {\n // Support for qs explode format. Every value is stored as a separate query parameter.\n if (!searchParams.has(name) && searchParams.has(`${name}[0]`)) {\n const values: string[] = [];\n const indices: string[] = [];\n let index = 0;\n while (searchParams.has(`${name}[${index}]`)) {\n values.push(searchParams.get(`${name}[${index}]`)!);\n indices.push(`${name}[${index}]`);\n index++;\n }\n return [values, indices];\n }\n // If not qs format, grab all values with the same name from search params.\n return [getIfExists(name), [name]];\n }\n // Add support for qs non-standard array format. This is helpful for search-backend, since that uses qs still.\n if (!searchParams.has(name) && searchParams.has(`${name}[]`)) {\n return [searchParams.get(`${name}[]`)?.split(','), [`${name}[]`]];\n }\n // Non-explode arrays should be comma separated.\n if (searchParams.has(name) && searchParams.getAll(name).length > 1) {\n throw new OperationError(\n this.operation,\n 'Arrays must be comma separated in non-explode mode',\n );\n }\n return [searchParams.get(name)?.split(','), [name]];\n } else if (parameter.style === 'spaceDelimited') {\n return [searchParams.get(name)?.split(' '), [name]];\n } else if (parameter.style === 'pipeDelimited') {\n return [searchParams.get(name)?.split('|'), [name]];\n }\n throw new OperationError(\n this.operation,\n 'Unsupported style for array parameter',\n );\n }\n if (schema.type === 'object') {\n // Form is the default object format.\n if (\n parameter.style === 'form' ||\n typeof parameter.style === 'undefined'\n ) {\n if (parameter.explode) {\n // Object form/explode is a collection of disjoint keys, there's no mapping for what they are so we collect all of them.\n // This means we need to run this as the last query parameter that is processed.\n const obj: Record<string, string> = {};\n const indices: string[] = [];\n for (const [key, value] of searchParams.entries()) {\n // Have we processed this query parameter as part of another parameter parsing? If not, consider it to be a part of this object.\n if (!remainingQueryParameters.has(key)) {\n continue;\n }\n indices.push(key);\n obj[key] = value;\n }\n return [obj, indices];\n }\n // For non-explode, the schema is comma separated key,value \"pairs\", so filter=key1,value1,key2,value2 would parse to {key1: value1, key2: value2}.\n const obj: Record<string, string> = {};\n const value = searchParams.get(name);\n if (value) {\n const parts = value.split(',');\n if (parts.length % 2 !== 0) {\n throw new OperationError(\n this.operation,\n 'Invalid object query parameter, must have an even number of key-value pairs',\n );\n }\n for (let i = 0; i < parts.length; i += 2) {\n obj[parts[i]] = parts[i + 1];\n }\n }\n return [obj, [name]];\n } else if (parameter.style === 'deepObject') {\n // Deep object is a nested object structure, so we need to parse the keys to build the object.\n // example: ?filter[key1]=value1&filter[key2]=value2 => { key1: value1, key2: value2 }\n const obj: Record<string, any> = {};\n const indices: string[] = [];\n for (const [key, value] of searchParams.entries()) {\n if (key.startsWith(`${name}[`)) {\n indices.push(key);\n const parts = key.split('[');\n let currentLayer = obj;\n for (let partIndex = 1; partIndex < parts.length - 1; partIndex++) {\n const part = parts[partIndex];\n if (!part.includes(']')) {\n throw new OperationError(\n this.operation,\n `Invalid object parameter, missing closing bracket for key \"${key}\"`,\n );\n }\n const objKey = part.split(']')[0];\n if (!currentLayer[objKey]) {\n currentLayer[objKey] = {};\n }\n currentLayer = currentLayer[objKey];\n }\n const lastPart = parts[parts.length - 1];\n if (!lastPart.includes(']')) {\n throw new OperationError(\n this.operation,\n `Invalid object parameter, missing closing bracket for key \"${key}\"`,\n );\n }\n currentLayer[lastPart.split(']')[0]] = value;\n }\n }\n return [obj, indices];\n }\n throw new OperationError(\n this.operation,\n `Unsupported style for object parameter, \"${parameter.style}\"`,\n );\n }\n // For everything else, just return the value.\n return [getIfExists(name), [name]];\n }\n}\n\nexport class HeaderParameterParser\n extends BaseParameterParser\n implements RequestParser<Record<string, any>>\n{\n constructor(operation: Operation, options: ParserOptions) {\n super('header', operation, options);\n }\n async parse(request: Request) {\n const headerParameters: Record<string, any> = {};\n for (const [name, parameter] of Object.entries(this.parameters)) {\n const header = request.headers.get(name);\n if (!header) {\n if (parameter.required) {\n throw new OperationError(\n this.operation,\n `Header parameter ${name} not found`,\n );\n }\n continue;\n }\n if (!parameter.schema) {\n throw new OperationError(\n this.operation,\n 'Schema not found for header parameter',\n );\n }\n if ('$ref' in parameter.schema) {\n throw new OperationError(\n this.operation,\n 'Reference objects are not supported for parameters',\n );\n }\n const validate = this.ajv.compile(parameter.schema);\n const valid = validate(header);\n\n if (!valid) {\n throw new OperationParsingError(\n this.operation,\n 'Header parameter',\n validate.errors!,\n );\n }\n headerParameters[name] = header;\n }\n return headerParameters;\n }\n}\n\nexport class PathParameterParser\n extends BaseParameterParser\n implements RequestParser<Record<string, any>>\n{\n constructor(operation: Operation, options: ParserOptions) {\n super('path', operation, options);\n }\n async parse(request: Request) {\n const { pathname } = new URL(request.url);\n const params = PathParameterParser.parsePath({\n operation: this.operation,\n path: pathname,\n schema: this.operation.path,\n });\n const pathParameters: Record<string, any> = {};\n for (const [name, parameter] of Object.entries(this.parameters)) {\n let param: string | number | boolean = params[name];\n if (!param && parameter.required) {\n throw new OperationError(\n this.operation,\n `Path parameter ${name} not found`,\n );\n } else if (!params[name] && !parameter.required) {\n continue;\n }\n\n if (param) {\n param = this.optimisticallyParseValue(param, parameter.schema);\n }\n\n const validate = this.ajv.compile(parameter.schema);\n const valid = validate(param);\n\n if (!valid) {\n throw new OperationParsingError(\n this.operation,\n 'Path parameter',\n validate.errors!,\n );\n }\n pathParameters[name] = param;\n }\n return pathParameters;\n }\n\n static parsePath({\n operation,\n schema,\n path,\n }: {\n operation: Operation;\n schema: string;\n path: string;\n }) {\n const parts = path.split('/');\n const pathParts = schema.split('/');\n if (parts.length !== pathParts.length) {\n throw new OperationError(operation, 'Path parts do not match');\n }\n const params: Record<string, string> = {};\n for (let i = 0; i < parts.length; i++) {\n if (pathParts[i] === parts[i]) {\n continue;\n }\n if (pathParts[i].startsWith('{') && pathParts[i].endsWith('}')) {\n params[pathParts[i].slice(1, -1)] = parts[i];\n continue;\n }\n break;\n }\n return params;\n }\n}\n\nexport class ParameterValidator implements Validator {\n schema: OpenAPIObject;\n cache: Record<string, any> = {};\n constructor(schema: OpenAPIObject) {\n this.schema = schema;\n }\n\n async validate({ pair: { request, response }, operation }: ValidatorParams) {\n if (response.statusCode === 400) {\n // If the response is a 400, then the request is invalid and we shouldn't validate the parameters\n return;\n }\n\n const ajv = new Ajv();\n const queryParser = new QueryParameterParser(operation, { ajv });\n const headerParser = new HeaderParameterParser(operation, { ajv });\n const pathParser = new PathParameterParser(operation, { ajv });\n\n const fetchRequest = mockttpToFetchRequest(request);\n\n await Promise.all([\n queryParser.parse(fetchRequest),\n headerParser.parse(fetchRequest),\n pathParser.parse(fetchRequest),\n ]);\n }\n}\n"],"names":["OperationError","OperationParsingError","obj","value","Ajv","mockttpToFetchRequest"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request-body-validation.cjs.js","sources":["../../src/schema/request-body-validation.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { JsonObject } from '@backstage/types';\nimport { Operation, ParserOptions, RequestParser } from './types';\nimport { ValidateFunction } from 'ajv';\nimport { OperationError, OperationParsingError } from './errors';\nimport { RequestBodyObject, SchemaObject } from 'openapi3-ts';\n\nclass DisabledRequestBodyParser\n implements RequestParser<JsonObject | undefined>\n{\n operation: Operation;\n constructor(operation: Operation) {\n this.operation = operation;\n }\n async parse(request: Request): Promise<JsonObject | undefined> {\n const bodyText = await request.text();\n if (bodyText?.length) {\n throw new OperationError(\n this.operation,\n 'Received a body but no schema was found',\n );\n }\n return undefined;\n }\n}\nexport class RequestBodyParser\n implements RequestParser<JsonObject | undefined>\n{\n operation: Operation;\n disabled: boolean = false;\n validate!: ValidateFunction;\n schema!: SchemaObject;\n requestBodySchema!: RequestBodyObject;\n\n static fromOperation(operation: Operation, options: ParserOptions) {\n return operation.schema.requestBody\n ? new RequestBodyParser(operation, options)\n : new DisabledRequestBodyParser(operation);\n }\n\n constructor(operation: Operation, options: ParserOptions) {\n this.operation = operation;\n const { schema: operationSchema } = this.operation;\n const requestBody = operationSchema.requestBody;\n\n if (!requestBody) {\n throw new OperationError(\n this.operation,\n 'No request body found in operation',\n );\n }\n\n if ('$ref' in requestBody!) {\n throw new OperationError(\n this.operation,\n 'Reference objects are not supported',\n );\n }\n if (!requestBody!.content) {\n throw new OperationError(\n this.operation,\n 'No content found in request body',\n );\n }\n const contentTypes = requestBody!.content;\n const jsonContentType = Object.keys(contentTypes).find(contentType =>\n contentType.split(';').includes('application/json'),\n );\n if (!jsonContentType) {\n throw new OperationError(\n this.operation,\n 'No application/json content type found in request body',\n );\n }\n const schema = requestBody!.content[jsonContentType].schema;\n if (!schema) {\n throw new OperationError(\n this.operation,\n 'No JSON schema found in request body',\n );\n }\n if ('$ref' in schema) {\n throw new OperationError(\n this.operation,\n 'Reference objects are not supported',\n );\n }\n this.validate = options.ajv.compile(schema);\n this.schema = schema;\n this.requestBodySchema = requestBody;\n }\n async parse(request: Request): Promise<JsonObject | undefined> {\n const bodyText = await request.text();\n if (this.requestBodySchema.required && !bodyText?.length) {\n throw new OperationError(\n this.operation,\n `No request body found for ${request.url}`,\n );\n }\n\n const contentType =\n request.headers.get('content-type') || 'application/json';\n if (!contentType.split(';').includes('application/json')) {\n throw new OperationError(\n this.operation,\n 'Content type is not application/json',\n );\n }\n const body = (await request.json()) as JsonObject;\n const valid = this.validate(body);\n if (!valid) {\n throw new OperationParsingError(\n this.operation,\n `Request body`,\n this.validate.errors!,\n );\n }\n return body;\n }\n}\n"],"names":["OperationError","OperationParsingError"],"mappings":";;;;AAsBA,MAAM,
|
|
1
|
+
{"version":3,"file":"request-body-validation.cjs.js","sources":["../../src/schema/request-body-validation.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { JsonObject } from '@backstage/types';\nimport { Operation, ParserOptions, RequestParser } from './types';\nimport { ValidateFunction } from 'ajv';\nimport { OperationError, OperationParsingError } from './errors';\nimport { RequestBodyObject, SchemaObject } from 'openapi3-ts';\n\nclass DisabledRequestBodyParser\n implements RequestParser<JsonObject | undefined>\n{\n operation: Operation;\n constructor(operation: Operation) {\n this.operation = operation;\n }\n async parse(request: Request): Promise<JsonObject | undefined> {\n const bodyText = await request.text();\n if (bodyText?.length) {\n throw new OperationError(\n this.operation,\n 'Received a body but no schema was found',\n );\n }\n return undefined;\n }\n}\nexport class RequestBodyParser\n implements RequestParser<JsonObject | undefined>\n{\n operation: Operation;\n disabled: boolean = false;\n validate!: ValidateFunction;\n schema!: SchemaObject;\n requestBodySchema!: RequestBodyObject;\n\n static fromOperation(operation: Operation, options: ParserOptions) {\n return operation.schema.requestBody\n ? new RequestBodyParser(operation, options)\n : new DisabledRequestBodyParser(operation);\n }\n\n constructor(operation: Operation, options: ParserOptions) {\n this.operation = operation;\n const { schema: operationSchema } = this.operation;\n const requestBody = operationSchema.requestBody;\n\n if (!requestBody) {\n throw new OperationError(\n this.operation,\n 'No request body found in operation',\n );\n }\n\n if ('$ref' in requestBody!) {\n throw new OperationError(\n this.operation,\n 'Reference objects are not supported',\n );\n }\n if (!requestBody!.content) {\n throw new OperationError(\n this.operation,\n 'No content found in request body',\n );\n }\n const contentTypes = requestBody!.content;\n const jsonContentType = Object.keys(contentTypes).find(contentType =>\n contentType.split(';').includes('application/json'),\n );\n if (!jsonContentType) {\n throw new OperationError(\n this.operation,\n 'No application/json content type found in request body',\n );\n }\n const schema = requestBody!.content[jsonContentType].schema;\n if (!schema) {\n throw new OperationError(\n this.operation,\n 'No JSON schema found in request body',\n );\n }\n if ('$ref' in schema) {\n throw new OperationError(\n this.operation,\n 'Reference objects are not supported',\n );\n }\n this.validate = options.ajv.compile(schema);\n this.schema = schema;\n this.requestBodySchema = requestBody;\n }\n async parse(request: Request): Promise<JsonObject | undefined> {\n const bodyText = await request.text();\n if (this.requestBodySchema.required && !bodyText?.length) {\n throw new OperationError(\n this.operation,\n `No request body found for ${request.url}`,\n );\n }\n\n const contentType =\n request.headers.get('content-type') || 'application/json';\n if (!contentType.split(';').includes('application/json')) {\n throw new OperationError(\n this.operation,\n 'Content type is not application/json',\n );\n }\n const body = (await request.json()) as JsonObject;\n const valid = this.validate(body);\n if (!valid) {\n throw new OperationParsingError(\n this.operation,\n `Request body`,\n this.validate.errors!,\n );\n }\n return body;\n }\n}\n"],"names":["OperationError","OperationParsingError"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"response-body-validation.cjs.js","sources":["../../src/schema/response-body-validation.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { JsonObject } from '@backstage/types';\nimport { Operation, ParserOptions, ResponseParser } from './types';\nimport {\n OperationError,\n OperationParsingResponseError,\n OperationResponseError,\n} from './errors';\nimport Ajv from 'ajv';\nimport { OperationObject, ResponseObject } from 'openapi3-ts';\n\nclass DisabledResponseBodyParser\n implements ResponseParser<JsonObject | undefined>\n{\n operation: Operation;\n constructor(operation: Operation) {\n this.operation = operation;\n }\n async parse(response: Response): Promise<JsonObject | undefined> {\n const body = await response.text();\n if (body?.length) {\n throw new OperationError(\n this.operation,\n 'Received a body but no schema was found',\n );\n }\n return undefined;\n }\n}\n\nexport class ResponseBodyParser\n implements ResponseParser<JsonObject | undefined>\n{\n operation: Operation;\n ajv: Ajv;\n\n static fromOperation(operation: Operation, options: ParserOptions) {\n return operation.schema.responses &&\n Object.keys(operation.schema.responses).length\n ? new ResponseBodyParser(operation, options)\n : new DisabledResponseBodyParser(operation);\n }\n\n constructor(operation: Operation, options: ParserOptions) {\n this.operation = operation;\n this.ajv = options.ajv;\n const responseSchemas = operation.schema.responses;\n for (const [statusCode, schema] of Object.entries(responseSchemas)) {\n const contentTypes = schema.content;\n if (!contentTypes) {\n // Skip responses without content, eg 204 No Content.\n continue;\n }\n const jsonContentType = Object.keys(contentTypes).find(contentType =>\n contentType.split(';').includes('application/json'),\n );\n if (!jsonContentType) {\n throw new OperationError(\n this.operation,\n `No application/json content type found in response for status code ${statusCode}`,\n );\n } else if ('$ref' in contentTypes[jsonContentType].schema) {\n throw new OperationError(\n this.operation,\n 'Reference objects are not supported',\n );\n }\n }\n }\n\n async parse(response: Response): Promise<JsonObject | undefined> {\n const body = await response.text();\n const responseSchema = this.findResponseSchema(\n this.operation.schema,\n response,\n );\n if (!responseSchema?.content && !body?.length) {\n // If there is no content in the response schema and no body in the response, then the response is valid.\n // eg 204 No Content\n return undefined;\n }\n if (!responseSchema) {\n throw new OperationResponseError(\n this.operation,\n response,\n `No schema found.`,\n );\n }\n\n const contentTypes = responseSchema.content;\n if (!contentTypes && body?.length) {\n throw new OperationResponseError(\n this.operation,\n response,\n 'Received a body but no schema was found',\n );\n }\n const jsonContentType = Object.keys(contentTypes ?? {}).find(contentType =>\n contentType.split(';').includes('application/json'),\n );\n if (!jsonContentType) {\n throw new OperationResponseError(\n this.operation,\n response,\n 'No application/json content type found in response',\n );\n }\n const schema = responseSchema.content![jsonContentType].schema;\n // This is a bit of type laziness. Ideally, this would be a type-narrowing function, but I wasn't able to get the types to work.\n if (!schema) {\n throw new OperationError(this.operation, 'No schema found in response');\n }\n if ('$ref' in schema) {\n throw new OperationResponseError(\n this.operation,\n response,\n 'Reference objects are not supported',\n );\n }\n\n if (!schema.required && !body?.length) {\n throw new OperationResponseError(\n this.operation,\n response,\n 'Response body is required but missing',\n );\n } else if (!schema.required && !body?.length) {\n // If there is no content in the response schema and no body in the response, then the response is valid\n return undefined;\n }\n\n const validate = this.ajv.compile(schema);\n const jsonBody = (await response.json()) as JsonObject;\n const valid = validate(jsonBody);\n if (!valid) {\n throw new OperationParsingResponseError(\n this.operation,\n response,\n 'Response body',\n validate.errors!,\n );\n }\n return jsonBody;\n }\n\n private findResponseSchema(\n operationSchema: OperationObject,\n { status }: Response,\n ): ResponseObject | undefined {\n return (\n operationSchema.responses?.[status] ?? operationSchema.responses?.default\n );\n }\n}\n"],"names":["OperationError","OperationResponseError","OperationParsingResponseError"],"mappings":";;;;AA0BA,MAAM,
|
|
1
|
+
{"version":3,"file":"response-body-validation.cjs.js","sources":["../../src/schema/response-body-validation.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { JsonObject } from '@backstage/types';\nimport { Operation, ParserOptions, ResponseParser } from './types';\nimport {\n OperationError,\n OperationParsingResponseError,\n OperationResponseError,\n} from './errors';\nimport Ajv from 'ajv';\nimport { OperationObject, ResponseObject } from 'openapi3-ts';\n\nclass DisabledResponseBodyParser\n implements ResponseParser<JsonObject | undefined>\n{\n operation: Operation;\n constructor(operation: Operation) {\n this.operation = operation;\n }\n async parse(response: Response): Promise<JsonObject | undefined> {\n const body = await response.text();\n if (body?.length) {\n throw new OperationError(\n this.operation,\n 'Received a body but no schema was found',\n );\n }\n return undefined;\n }\n}\n\nexport class ResponseBodyParser\n implements ResponseParser<JsonObject | undefined>\n{\n operation: Operation;\n ajv: Ajv;\n\n static fromOperation(operation: Operation, options: ParserOptions) {\n return operation.schema.responses &&\n Object.keys(operation.schema.responses).length\n ? new ResponseBodyParser(operation, options)\n : new DisabledResponseBodyParser(operation);\n }\n\n constructor(operation: Operation, options: ParserOptions) {\n this.operation = operation;\n this.ajv = options.ajv;\n const responseSchemas = operation.schema.responses;\n for (const [statusCode, schema] of Object.entries(responseSchemas)) {\n const contentTypes = schema.content;\n if (!contentTypes) {\n // Skip responses without content, eg 204 No Content.\n continue;\n }\n const jsonContentType = Object.keys(contentTypes).find(contentType =>\n contentType.split(';').includes('application/json'),\n );\n if (!jsonContentType) {\n throw new OperationError(\n this.operation,\n `No application/json content type found in response for status code ${statusCode}`,\n );\n } else if ('$ref' in contentTypes[jsonContentType].schema) {\n throw new OperationError(\n this.operation,\n 'Reference objects are not supported',\n );\n }\n }\n }\n\n async parse(response: Response): Promise<JsonObject | undefined> {\n const body = await response.text();\n const responseSchema = this.findResponseSchema(\n this.operation.schema,\n response,\n );\n if (!responseSchema?.content && !body?.length) {\n // If there is no content in the response schema and no body in the response, then the response is valid.\n // eg 204 No Content\n return undefined;\n }\n if (!responseSchema) {\n throw new OperationResponseError(\n this.operation,\n response,\n `No schema found.`,\n );\n }\n\n const contentTypes = responseSchema.content;\n if (!contentTypes && body?.length) {\n throw new OperationResponseError(\n this.operation,\n response,\n 'Received a body but no schema was found',\n );\n }\n const jsonContentType = Object.keys(contentTypes ?? {}).find(contentType =>\n contentType.split(';').includes('application/json'),\n );\n if (!jsonContentType) {\n throw new OperationResponseError(\n this.operation,\n response,\n 'No application/json content type found in response',\n );\n }\n const schema = responseSchema.content![jsonContentType].schema;\n // This is a bit of type laziness. Ideally, this would be a type-narrowing function, but I wasn't able to get the types to work.\n if (!schema) {\n throw new OperationError(this.operation, 'No schema found in response');\n }\n if ('$ref' in schema) {\n throw new OperationResponseError(\n this.operation,\n response,\n 'Reference objects are not supported',\n );\n }\n\n if (!schema.required && !body?.length) {\n throw new OperationResponseError(\n this.operation,\n response,\n 'Response body is required but missing',\n );\n } else if (!schema.required && !body?.length) {\n // If there is no content in the response schema and no body in the response, then the response is valid\n return undefined;\n }\n\n const validate = this.ajv.compile(schema);\n const jsonBody = (await response.json()) as JsonObject;\n const valid = validate(jsonBody);\n if (!valid) {\n throw new OperationParsingResponseError(\n this.operation,\n response,\n 'Response body',\n validate.errors!,\n );\n }\n return jsonBody;\n }\n\n private findResponseSchema(\n operationSchema: OperationObject,\n { status }: Response,\n ): ResponseObject | undefined {\n return (\n operationSchema.responses?.[status] ?? operationSchema.responses?.default\n );\n }\n}\n"],"names":["OperationError","OperationResponseError","OperationParsingResponseError"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.cjs.js","sources":["../../src/schema/utils.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { CompletedRequest, CompletedResponse } from 'mockttp';\nimport { ErrorObject } from 'ajv';\n\nexport function mockttpToFetchRequest(request: CompletedRequest) {\n const headers = new Headers(request.rawHeaders);\n return {\n url: request.url,\n method: request.method,\n headers,\n json: () => request.body.getJson(),\n text: () => request.body.getText(),\n } as Request;\n}\nexport function mockttpToFetchResponse(response: CompletedResponse) {\n const headers = new Headers(response.rawHeaders);\n return {\n status: response.statusCode,\n headers,\n json: () => response.body?.getJson(),\n text: () => response.body?.getText(),\n } as Response;\n}\n\nexport function humanifyAjvError(error: ErrorObject) {\n switch (error.keyword) {\n case 'required':\n return `The \"${error.params.missingProperty}\" property is required`;\n case 'type':\n return `${\n error.instancePath ? `\"${error.instancePath}\"` : 'Value'\n } should be of type ${error.params.type}`;\n case 'additionalProperties':\n return `The \"${error.params.additionalProperty}\" property is not allowed`;\n default:\n return error.message;\n }\n}\n"],"names":[],"mappings":";;AAkBO,SAAS,sBAAsB,
|
|
1
|
+
{"version":3,"file":"utils.cjs.js","sources":["../../src/schema/utils.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { CompletedRequest, CompletedResponse } from 'mockttp';\nimport { ErrorObject } from 'ajv';\n\nexport function mockttpToFetchRequest(request: CompletedRequest) {\n const headers = new Headers(request.rawHeaders);\n return {\n url: request.url,\n method: request.method,\n headers,\n json: () => request.body.getJson(),\n text: () => request.body.getText(),\n } as Request;\n}\nexport function mockttpToFetchResponse(response: CompletedResponse) {\n const headers = new Headers(response.rawHeaders);\n return {\n status: response.statusCode,\n headers,\n json: () => response.body?.getJson(),\n text: () => response.body?.getText(),\n } as Response;\n}\n\nexport function humanifyAjvError(error: ErrorObject) {\n switch (error.keyword) {\n case 'required':\n return `The \"${error.params.missingProperty}\" property is required`;\n case 'type':\n return `${\n error.instancePath ? `\"${error.instancePath}\"` : 'Value'\n } should be of type ${error.params.type}`;\n case 'additionalProperties':\n return `The \"${error.params.additionalProperty}\" property is not allowed`;\n default:\n return error.message;\n }\n}\n"],"names":[],"mappings":";;AAkBO,SAAS,sBAAsB,OAAA,EAA2B;AAC/D,EAAA,MAAM,OAAA,GAAU,IAAI,OAAA,CAAQ,OAAA,CAAQ,UAAU,CAAA;AAC9C,EAAA,OAAO;AAAA,IACL,KAAK,OAAA,CAAQ,GAAA;AAAA,IACb,QAAQ,OAAA,CAAQ,MAAA;AAAA,IAChB,OAAA;AAAA,IACA,IAAA,EAAM,MAAM,OAAA,CAAQ,IAAA,CAAK,OAAA,EAAQ;AAAA,IACjC,IAAA,EAAM,MAAM,OAAA,CAAQ,IAAA,CAAK,OAAA;AAAQ,GACnC;AACF;AACO,SAAS,uBAAuB,QAAA,EAA6B;AAClE,EAAA,MAAM,OAAA,GAAU,IAAI,OAAA,CAAQ,QAAA,CAAS,UAAU,CAAA;AAC/C,EAAA,OAAO;AAAA,IACL,QAAQ,QAAA,CAAS,UAAA;AAAA,IACjB,OAAA;AAAA,IACA,IAAA,EAAM,MAAM,QAAA,CAAS,IAAA,EAAM,OAAA,EAAQ;AAAA,IACnC,IAAA,EAAM,MAAM,QAAA,CAAS,IAAA,EAAM,OAAA;AAAQ,GACrC;AACF;AAEO,SAAS,iBAAiB,KAAA,EAAoB;AACnD,EAAA,QAAQ,MAAM,OAAA;AAAS,IACrB,KAAK,UAAA;AACH,MAAA,OAAO,CAAA,KAAA,EAAQ,KAAA,CAAM,MAAA,CAAO,eAAe,CAAA,sBAAA,CAAA;AAAA,IAC7C,KAAK,MAAA;AACH,MAAA,OAAO,CAAA,EACL,KAAA,CAAM,YAAA,GAAe,CAAA,CAAA,EAAI,KAAA,CAAM,YAAY,CAAA,CAAA,CAAA,GAAM,OACnD,CAAA,mBAAA,EAAsB,KAAA,CAAM,MAAA,CAAO,IAAI,CAAA,CAAA;AAAA,IACzC,KAAK,sBAAA;AACH,MAAA,OAAO,CAAA,KAAA,EAAQ,KAAA,CAAM,MAAA,CAAO,kBAAkB,CAAA,yBAAA,CAAA;AAAA,IAChD;AACE,MAAA,OAAO,KAAA,CAAM,OAAA;AAAA;AAEnB;;;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.cjs.js","sources":["../../src/schema/validation.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { CompletedRequest, CompletedResponse } from 'mockttp';\nimport { OpenAPIObject, OperationObject } from 'openapi3-ts';\nimport Ajv from 'ajv';\nimport Parser from '@apidevtools/swagger-parser';\nimport { Operation, Validator, ValidatorParams } from './types';\nimport { ParameterValidator } from './parameter-validation';\nimport { OperationError } from './errors';\nimport { RequestBodyParser } from './request-body-validation';\nimport { mockttpToFetchRequest, mockttpToFetchResponse } from './utils';\nimport { ResponseBodyParser } from './response-body-validation';\n\nconst ajv = new Ajv({ allErrors: true });\n\nclass RequestBodyValidator implements Validator {\n schema: OpenAPIObject;\n constructor(schema: OpenAPIObject) {\n this.schema = schema;\n }\n\n async validate({ pair, operation }: ValidatorParams) {\n const { request, response } = pair;\n if (response.statusCode === 400) {\n // If the response is a 400, then the request is invalid and we shouldn't validate the parameters\n return;\n }\n\n // NOTE: There may be a worthwhile optimization here to cache these results to avoid re-parsing the schema for every request. As is, I don't think this is a big deal.\n const parser = RequestBodyParser.fromOperation(operation, { ajv });\n const fetchRequest = mockttpToFetchRequest(request);\n await parser.parse(fetchRequest);\n }\n}\n\nclass ResponseBodyValidator implements Validator {\n schema: OpenAPIObject;\n constructor(schema: OpenAPIObject) {\n this.schema = schema;\n }\n\n async validate({ pair, operation }: ValidatorParams) {\n const { response } = pair;\n // NOTE: There may be a worthwhile optimization here to cache these results to avoid re-parsing the schema for every request. As is, I don't think this is a big deal.\n const parser = ResponseBodyParser.fromOperation(operation, { ajv });\n const fetchResponse = mockttpToFetchResponse(response);\n await parser.parse(fetchResponse);\n }\n}\n\n/**\n * Find an operation in an OpenAPI schema that matches a request. This is done by comparing the request URL to the paths in the schema.\n * @param openApiSchema - The OpenAPI schema to search for the operation in.\n * @param request - The request to find the operation for.\n * @returns A tuple of the path and the operation object that matches the request.\n */\nexport function findOperationByRequest(\n openApiSchema: OpenAPIObject,\n request: CompletedRequest,\n): [string, OperationObject] | undefined {\n const { url } = request;\n const { pathname } = new URL(url);\n\n const parts = pathname.split('/');\n for (const [path, schema] of Object.entries(openApiSchema.paths)) {\n const pathParts = path.split('/');\n if (parts.length !== pathParts.length) {\n continue;\n }\n let found = true;\n for (let i = 0; i < parts.length; i++) {\n if (pathParts[i] === parts[i]) {\n continue;\n }\n // If the path part is a parameter, we can count it as a match. eg /api/{id} will match /api/1\n if (pathParts[i].startsWith('{') && pathParts[i].endsWith('}')) {\n continue;\n }\n found = false;\n break;\n }\n if (!found) {\n continue;\n }\n let matchingOperationType: OperationObject | undefined = undefined;\n for (const [operationType, operation] of Object.entries(schema)) {\n if (operationType === request.method.toLowerCase()) {\n matchingOperationType = operation as OperationObject;\n break;\n }\n }\n if (!matchingOperationType) {\n continue;\n }\n return [path, matchingOperationType];\n }\n\n return undefined;\n}\n\nexport class OpenApiProxyValidator {\n schema: OpenAPIObject | undefined;\n validators: Validator[] | undefined;\n\n async initialize(url: string) {\n this.schema = (await Parser.dereference(url)) as unknown as OpenAPIObject;\n this.validators = [\n new ParameterValidator(this.schema),\n new RequestBodyValidator(this.schema),\n new ResponseBodyValidator(this.schema),\n ];\n }\n\n async validate(request: CompletedRequest, response: CompletedResponse) {\n const operationPathTuple = findOperationByRequest(this.schema!, request);\n if (!operationPathTuple) {\n throw new OperationError(\n { path: request.path, method: request.method } as Operation,\n `No operation schema found for ${request.url}`,\n );\n }\n\n const [path, operationSchema] = operationPathTuple;\n const operation = { path, method: request.method, schema: operationSchema };\n\n const validators = this.validators!;\n await Promise.all(\n validators.map(validator =>\n validator.validate({\n pair: { request, response },\n operation,\n }),\n ),\n );\n }\n}\n"],"names":["Ajv","RequestBodyParser","mockttpToFetchRequest","ResponseBodyParser","mockttpToFetchResponse","Parser","ParameterValidator","OperationError"],"mappings":";;;;;;;;;;;;;;;AA0BA,MAAM,MAAM,IAAIA,oBAAA,CAAI,EAAE,SAAA,EAAW,MAAM,CAAA;AAEvC,MAAM,
|
|
1
|
+
{"version":3,"file":"validation.cjs.js","sources":["../../src/schema/validation.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { CompletedRequest, CompletedResponse } from 'mockttp';\nimport { OpenAPIObject, OperationObject } from 'openapi3-ts';\nimport Ajv from 'ajv';\nimport Parser from '@apidevtools/swagger-parser';\nimport { Operation, Validator, ValidatorParams } from './types';\nimport { ParameterValidator } from './parameter-validation';\nimport { OperationError } from './errors';\nimport { RequestBodyParser } from './request-body-validation';\nimport { mockttpToFetchRequest, mockttpToFetchResponse } from './utils';\nimport { ResponseBodyParser } from './response-body-validation';\n\nconst ajv = new Ajv({ allErrors: true });\n\nclass RequestBodyValidator implements Validator {\n schema: OpenAPIObject;\n constructor(schema: OpenAPIObject) {\n this.schema = schema;\n }\n\n async validate({ pair, operation }: ValidatorParams) {\n const { request, response } = pair;\n if (response.statusCode === 400) {\n // If the response is a 400, then the request is invalid and we shouldn't validate the parameters\n return;\n }\n\n // NOTE: There may be a worthwhile optimization here to cache these results to avoid re-parsing the schema for every request. As is, I don't think this is a big deal.\n const parser = RequestBodyParser.fromOperation(operation, { ajv });\n const fetchRequest = mockttpToFetchRequest(request);\n await parser.parse(fetchRequest);\n }\n}\n\nclass ResponseBodyValidator implements Validator {\n schema: OpenAPIObject;\n constructor(schema: OpenAPIObject) {\n this.schema = schema;\n }\n\n async validate({ pair, operation }: ValidatorParams) {\n const { response } = pair;\n // NOTE: There may be a worthwhile optimization here to cache these results to avoid re-parsing the schema for every request. As is, I don't think this is a big deal.\n const parser = ResponseBodyParser.fromOperation(operation, { ajv });\n const fetchResponse = mockttpToFetchResponse(response);\n await parser.parse(fetchResponse);\n }\n}\n\n/**\n * Find an operation in an OpenAPI schema that matches a request. This is done by comparing the request URL to the paths in the schema.\n * @param openApiSchema - The OpenAPI schema to search for the operation in.\n * @param request - The request to find the operation for.\n * @returns A tuple of the path and the operation object that matches the request.\n */\nexport function findOperationByRequest(\n openApiSchema: OpenAPIObject,\n request: CompletedRequest,\n): [string, OperationObject] | undefined {\n const { url } = request;\n const { pathname } = new URL(url);\n\n const parts = pathname.split('/');\n for (const [path, schema] of Object.entries(openApiSchema.paths)) {\n const pathParts = path.split('/');\n if (parts.length !== pathParts.length) {\n continue;\n }\n let found = true;\n for (let i = 0; i < parts.length; i++) {\n if (pathParts[i] === parts[i]) {\n continue;\n }\n // If the path part is a parameter, we can count it as a match. eg /api/{id} will match /api/1\n if (pathParts[i].startsWith('{') && pathParts[i].endsWith('}')) {\n continue;\n }\n found = false;\n break;\n }\n if (!found) {\n continue;\n }\n let matchingOperationType: OperationObject | undefined = undefined;\n for (const [operationType, operation] of Object.entries(schema)) {\n if (operationType === request.method.toLowerCase()) {\n matchingOperationType = operation as OperationObject;\n break;\n }\n }\n if (!matchingOperationType) {\n continue;\n }\n return [path, matchingOperationType];\n }\n\n return undefined;\n}\n\nexport class OpenApiProxyValidator {\n schema: OpenAPIObject | undefined;\n validators: Validator[] | undefined;\n\n async initialize(url: string) {\n this.schema = (await Parser.dereference(url)) as unknown as OpenAPIObject;\n this.validators = [\n new ParameterValidator(this.schema),\n new RequestBodyValidator(this.schema),\n new ResponseBodyValidator(this.schema),\n ];\n }\n\n async validate(request: CompletedRequest, response: CompletedResponse) {\n const operationPathTuple = findOperationByRequest(this.schema!, request);\n if (!operationPathTuple) {\n throw new OperationError(\n { path: request.path, method: request.method } as Operation,\n `No operation schema found for ${request.url}`,\n );\n }\n\n const [path, operationSchema] = operationPathTuple;\n const operation = { path, method: request.method, schema: operationSchema };\n\n const validators = this.validators!;\n await Promise.all(\n validators.map(validator =>\n validator.validate({\n pair: { request, response },\n operation,\n }),\n ),\n );\n }\n}\n"],"names":["Ajv","RequestBodyParser","mockttpToFetchRequest","ResponseBodyParser","mockttpToFetchResponse","Parser","ParameterValidator","OperationError"],"mappings":"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"}
|
package/dist/stub.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stub.cjs.js","sources":["../src/stub.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport PromiseRouter from 'express-promise-router';\nimport { ApiRouter, TypedRouter } from './router';\nimport { EndpointMap, RequiredDoc } from './types';\nimport {\n ErrorRequestHandler,\n RequestHandler,\n NextFunction,\n Request,\n Response,\n json,\n Router,\n} from 'express';\nimport { InputError } from '@backstage/errors';\nimport { middleware as OpenApiValidator } from 'express-openapi-validator';\nimport { OPENAPI_SPEC_ROUTE } from './constants';\nimport { isErrorResult, merge } from 'openapi-merge';\n\ntype PropertyOverrideRequest = Request & {\n [key: symbol]: string;\n};\n\nconst baseUrlSymbol = Symbol();\nconst originalUrlSymbol = Symbol();\n\nfunction validatorErrorTransformer(): ErrorRequestHandler {\n return (error: Error, _: Request, _2: Response, next: NextFunction) => {\n next(new InputError(error.message));\n };\n}\n\nexport function getDefaultRouterMiddleware() {\n return [json()];\n}\n\n/**\n * Given a base url for a plugin, find the given OpenAPI spec for that plugin.\n * @param baseUrl - Plugin base url.\n * @returns OpenAPI spec route for the base url.\n * @public\n */\nexport function getOpenApiSpecRoute(baseUrl: string) {\n return `${baseUrl}${OPENAPI_SPEC_ROUTE}`;\n}\n\n/**\n * Create a router with validation middleware. This is used by typing methods to create an\n * \"OpenAPI router\" with all of the expected validation + metadata.\n * @param spec - Your OpenAPI spec imported as a JSON object.\n * @param validatorOptions - `openapi-express-validator` options to override the defaults.\n * @returns A new express router with validation middleware.\n */\nfunction createRouterWithValidation(\n spec: RequiredDoc,\n options?: {\n validatorOptions?: Partial<Parameters<typeof OpenApiValidator>['0']>;\n middleware?: RequestHandler[];\n },\n): Router {\n const router = PromiseRouter();\n router.use(options?.middleware || getDefaultRouterMiddleware());\n\n /**\n * Middleware to setup the routing for OpenApiValidator. OpenApiValidator expects `req.originalUrl`\n * and `req.baseUrl` to be the full path. We adjust them here to basically be nothing and then\n * revive the old values in the last function in this method. We could instead update `req.path`\n * but that might affect the routing and I'd rather not.\n *\n * TODO: I opened https://github.com/cdimascio/express-openapi-validator/issues/843\n * to track this on the middleware side, but there was a similar ticket, https://github.com/cdimascio/express-openapi-validator/issues/113\n * that has had minimal activity. If that changes, update this to use a new option on their side.\n */\n router.use((req: Request, _, next) => {\n /**\n * Express typings are weird. They don't recognize PropertyOverrideRequest as a valid\n * Request child and try to overload as PathParams. Just cast it here, since we know\n * what we're doing.\n */\n const customRequest = req as PropertyOverrideRequest;\n customRequest[baseUrlSymbol] = customRequest.baseUrl;\n customRequest.baseUrl = '';\n customRequest[originalUrlSymbol] = customRequest.originalUrl;\n customRequest.originalUrl = customRequest.url;\n next();\n });\n\n // TODO: Handle errors by converting from OpenApiValidator errors to known @backstage/errors errors.\n router.use(\n OpenApiValidator({\n validateRequests: {\n coerceTypes: false,\n allowUnknownQueryParameters: false,\n },\n ignoreUndocumented: true,\n validateResponses: false,\n ...options?.validatorOptions,\n apiSpec: spec as any,\n }),\n );\n\n /**\n * Revert `req.baseUrl` and `req.originalUrl` changes. This ensures that any further usage\n * of these variables will be unchanged.\n */\n router.use((req: Request, _, next) => {\n const customRequest = req as PropertyOverrideRequest;\n customRequest.baseUrl = customRequest[baseUrlSymbol];\n customRequest.originalUrl = customRequest[originalUrlSymbol];\n delete customRequest[baseUrlSymbol];\n delete customRequest[originalUrlSymbol];\n next();\n });\n\n // Any errors from the middleware get through here.\n router.use(validatorErrorTransformer());\n\n router.get(OPENAPI_SPEC_ROUTE, async (req, res) => {\n const mergeOutput = merge([\n {\n oas: spec as any,\n pathModification: {\n /**\n * Get the route that this OpenAPI spec is hosted on. The other\n * approach of using the discovery API increases the router constructor\n * significantly and since we're just looking for path and not full URL,\n * this works.\n *\n * If we wanted to add a list of servers, there may be a case for adding\n * discovery API to get an exhaustive list of upstream servers, but that's\n * also not currently supported.\n */\n prepend: req.originalUrl.replace(OPENAPI_SPEC_ROUTE, ''),\n },\n },\n ]);\n if (isErrorResult(mergeOutput)) {\n throw new InputError('Invalid spec defined');\n }\n res.json(mergeOutput.output);\n });\n return router;\n}\n\n/**\n * Create a new OpenAPI router with some default middleware.\n * @param spec - Your OpenAPI spec imported as a JSON object.\n * @param validatorOptions - `openapi-express-validator` options to override the defaults.\n * @returns A new express router with validation middleware.\n * @public\n */\nexport function createValidatedOpenApiRouter<T extends RequiredDoc>(\n spec: T,\n options?: {\n validatorOptions?: Partial<Parameters<typeof OpenApiValidator>['0']>;\n middleware?: RequestHandler[];\n },\n) {\n return createRouterWithValidation(spec, options) as ApiRouter<typeof spec>;\n}\n\n/**\n * Create a new OpenAPI router with some default middleware.\n * @param spec - Your OpenAPI spec imported as a JSON object.\n * @param validatorOptions - `openapi-express-validator` options to override the defaults.\n * @returns A new express router with validation middleware.\n * @public\n */\nexport function createValidatedOpenApiRouterFromGeneratedEndpointMap<\n T extends EndpointMap,\n>(\n spec: RequiredDoc,\n options?: {\n validatorOptions?: Partial<Parameters<typeof OpenApiValidator>['0']>;\n middleware?: RequestHandler[];\n },\n) {\n return createRouterWithValidation(spec, options) as TypedRouter<T>;\n}\n"],"names":["InputError","json","OPENAPI_SPEC_ROUTE","PromiseRouter","OpenApiValidator","merge","isErrorResult"],"mappings":";;;;;;;;;;;;;AAqCA,MAAM,gBAAgB,
|
|
1
|
+
{"version":3,"file":"stub.cjs.js","sources":["../src/stub.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport PromiseRouter from 'express-promise-router';\nimport { ApiRouter, TypedRouter } from './router';\nimport { EndpointMap, RequiredDoc } from './types';\nimport {\n ErrorRequestHandler,\n RequestHandler,\n NextFunction,\n Request,\n Response,\n json,\n Router,\n} from 'express';\nimport { InputError } from '@backstage/errors';\nimport { middleware as OpenApiValidator } from 'express-openapi-validator';\nimport { OPENAPI_SPEC_ROUTE } from './constants';\nimport { isErrorResult, merge } from 'openapi-merge';\n\ntype PropertyOverrideRequest = Request & {\n [key: symbol]: string;\n};\n\nconst baseUrlSymbol = Symbol();\nconst originalUrlSymbol = Symbol();\n\nfunction validatorErrorTransformer(): ErrorRequestHandler {\n return (error: Error, _: Request, _2: Response, next: NextFunction) => {\n next(new InputError(error.message));\n };\n}\n\nexport function getDefaultRouterMiddleware() {\n return [json()];\n}\n\n/**\n * Given a base url for a plugin, find the given OpenAPI spec for that plugin.\n * @param baseUrl - Plugin base url.\n * @returns OpenAPI spec route for the base url.\n * @public\n */\nexport function getOpenApiSpecRoute(baseUrl: string) {\n return `${baseUrl}${OPENAPI_SPEC_ROUTE}`;\n}\n\n/**\n * Create a router with validation middleware. This is used by typing methods to create an\n * \"OpenAPI router\" with all of the expected validation + metadata.\n * @param spec - Your OpenAPI spec imported as a JSON object.\n * @param validatorOptions - `openapi-express-validator` options to override the defaults.\n * @returns A new express router with validation middleware.\n */\nfunction createRouterWithValidation(\n spec: RequiredDoc,\n options?: {\n validatorOptions?: Partial<Parameters<typeof OpenApiValidator>['0']>;\n middleware?: RequestHandler[];\n },\n): Router {\n const router = PromiseRouter();\n router.use(options?.middleware || getDefaultRouterMiddleware());\n\n /**\n * Middleware to setup the routing for OpenApiValidator. OpenApiValidator expects `req.originalUrl`\n * and `req.baseUrl` to be the full path. We adjust them here to basically be nothing and then\n * revive the old values in the last function in this method. We could instead update `req.path`\n * but that might affect the routing and I'd rather not.\n *\n * TODO: I opened https://github.com/cdimascio/express-openapi-validator/issues/843\n * to track this on the middleware side, but there was a similar ticket, https://github.com/cdimascio/express-openapi-validator/issues/113\n * that has had minimal activity. If that changes, update this to use a new option on their side.\n */\n router.use((req: Request, _, next) => {\n /**\n * Express typings are weird. They don't recognize PropertyOverrideRequest as a valid\n * Request child and try to overload as PathParams. Just cast it here, since we know\n * what we're doing.\n */\n const customRequest = req as PropertyOverrideRequest;\n customRequest[baseUrlSymbol] = customRequest.baseUrl;\n customRequest.baseUrl = '';\n customRequest[originalUrlSymbol] = customRequest.originalUrl;\n customRequest.originalUrl = customRequest.url;\n next();\n });\n\n // TODO: Handle errors by converting from OpenApiValidator errors to known @backstage/errors errors.\n router.use(\n OpenApiValidator({\n validateRequests: {\n coerceTypes: false,\n allowUnknownQueryParameters: false,\n },\n ignoreUndocumented: true,\n validateResponses: false,\n ...options?.validatorOptions,\n apiSpec: spec as any,\n }),\n );\n\n /**\n * Revert `req.baseUrl` and `req.originalUrl` changes. This ensures that any further usage\n * of these variables will be unchanged.\n */\n router.use((req: Request, _, next) => {\n const customRequest = req as PropertyOverrideRequest;\n customRequest.baseUrl = customRequest[baseUrlSymbol];\n customRequest.originalUrl = customRequest[originalUrlSymbol];\n delete customRequest[baseUrlSymbol];\n delete customRequest[originalUrlSymbol];\n next();\n });\n\n // Any errors from the middleware get through here.\n router.use(validatorErrorTransformer());\n\n router.get(OPENAPI_SPEC_ROUTE, async (req, res) => {\n const mergeOutput = merge([\n {\n oas: spec as any,\n pathModification: {\n /**\n * Get the route that this OpenAPI spec is hosted on. The other\n * approach of using the discovery API increases the router constructor\n * significantly and since we're just looking for path and not full URL,\n * this works.\n *\n * If we wanted to add a list of servers, there may be a case for adding\n * discovery API to get an exhaustive list of upstream servers, but that's\n * also not currently supported.\n */\n prepend: req.originalUrl.replace(OPENAPI_SPEC_ROUTE, ''),\n },\n },\n ]);\n if (isErrorResult(mergeOutput)) {\n throw new InputError('Invalid spec defined');\n }\n res.json(mergeOutput.output);\n });\n return router;\n}\n\n/**\n * Create a new OpenAPI router with some default middleware.\n * @param spec - Your OpenAPI spec imported as a JSON object.\n * @param validatorOptions - `openapi-express-validator` options to override the defaults.\n * @returns A new express router with validation middleware.\n * @public\n */\nexport function createValidatedOpenApiRouter<T extends RequiredDoc>(\n spec: T,\n options?: {\n validatorOptions?: Partial<Parameters<typeof OpenApiValidator>['0']>;\n middleware?: RequestHandler[];\n },\n) {\n return createRouterWithValidation(spec, options) as ApiRouter<typeof spec>;\n}\n\n/**\n * Create a new OpenAPI router with some default middleware.\n * @param spec - Your OpenAPI spec imported as a JSON object.\n * @param validatorOptions - `openapi-express-validator` options to override the defaults.\n * @returns A new express router with validation middleware.\n * @public\n */\nexport function createValidatedOpenApiRouterFromGeneratedEndpointMap<\n T extends EndpointMap,\n>(\n spec: RequiredDoc,\n options?: {\n validatorOptions?: Partial<Parameters<typeof OpenApiValidator>['0']>;\n middleware?: RequestHandler[];\n },\n) {\n return createRouterWithValidation(spec, options) as TypedRouter<T>;\n}\n"],"names":["InputError","json","OPENAPI_SPEC_ROUTE","PromiseRouter","OpenApiValidator","merge","isErrorResult"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testUtils.cjs.js","sources":["../src/testUtils.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { Express } from 'express';\nimport { Server } from 'http';\nimport { Proxy } from './proxy/setup';\n\nconst proxiesToCleanup: Proxy[] = [];\n\n/**\n * !!! THIS CURRENTLY ONLY SUPPORTS SUPERTEST !!!\n * Setup a server with a custom OpenAPI proxy. This proxy will capture all requests and responses and make sure they\n * conform to the spec.\n * @param app - express server, needed to ensure we have the correct ports for the proxy.\n * @returns - a configured HTTP server that should be used with supertest.\n * @public\n */\nexport async function wrapServer(app: Express): Promise<Server> {\n const proxy = new Proxy();\n proxiesToCleanup.push(proxy);\n await proxy.setup();\n\n const server = app.listen(proxy.forwardTo.port);\n await proxy.initialize(`http://localhost:${proxy.forwardTo.port}`, server);\n\n return { ...server, address: () => new URL(proxy.url) } as any;\n}\n\nlet registered = false;\nfunction registerHooks() {\n if (typeof afterAll !== 'function' || typeof beforeAll !== 'function') {\n return;\n }\n if (registered) {\n return;\n }\n registered = true;\n\n afterAll(() => {\n for (const proxy of proxiesToCleanup) {\n proxy.stop();\n }\n });\n}\n\nregisterHooks();\n\n/**\n * !!! THIS CURRENTLY ONLY SUPPORTS SUPERTEST !!!\n * Running against supertest, we need some way to hit the optic proxy. This ensures that\n * that happens at runtime when in the context of a `yarn optic capture` command.\n * @param app - Express router that would be passed to supertest's `request`.\n * @returns A wrapper around the express router (or the router untouched) that still works with supertest.\n * @public\n */\nexport const wrapInOpenApiTestServer = (app: Express): Server | Express => {\n if (process.env.OPTIC_PROXY) {\n const server = app.listen(+process.env.PORT!);\n return {\n ...server,\n address: () => new URL(process.env.OPTIC_PROXY!),\n } as any;\n }\n return app;\n};\n"],"names":["Proxy"],"mappings":";;;;AAmBA,MAAM,mBAA4B,EAAC;AAUnC,eAAsB,WAAW,
|
|
1
|
+
{"version":3,"file":"testUtils.cjs.js","sources":["../src/testUtils.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { Express } from 'express';\nimport { Server } from 'http';\nimport { Proxy } from './proxy/setup';\n\nconst proxiesToCleanup: Proxy[] = [];\n\n/**\n * !!! THIS CURRENTLY ONLY SUPPORTS SUPERTEST !!!\n * Setup a server with a custom OpenAPI proxy. This proxy will capture all requests and responses and make sure they\n * conform to the spec.\n * @param app - express server, needed to ensure we have the correct ports for the proxy.\n * @returns - a configured HTTP server that should be used with supertest.\n * @public\n */\nexport async function wrapServer(app: Express): Promise<Server> {\n const proxy = new Proxy();\n proxiesToCleanup.push(proxy);\n await proxy.setup();\n\n const server = app.listen(proxy.forwardTo.port);\n await proxy.initialize(`http://localhost:${proxy.forwardTo.port}`, server);\n\n return { ...server, address: () => new URL(proxy.url) } as any;\n}\n\nlet registered = false;\nfunction registerHooks() {\n if (typeof afterAll !== 'function' || typeof beforeAll !== 'function') {\n return;\n }\n if (registered) {\n return;\n }\n registered = true;\n\n afterAll(() => {\n for (const proxy of proxiesToCleanup) {\n proxy.stop();\n }\n });\n}\n\nregisterHooks();\n\n/**\n * !!! THIS CURRENTLY ONLY SUPPORTS SUPERTEST !!!\n * Running against supertest, we need some way to hit the optic proxy. This ensures that\n * that happens at runtime when in the context of a `yarn optic capture` command.\n * @param app - Express router that would be passed to supertest's `request`.\n * @returns A wrapper around the express router (or the router untouched) that still works with supertest.\n * @public\n */\nexport const wrapInOpenApiTestServer = (app: Express): Server | Express => {\n if (process.env.OPTIC_PROXY) {\n const server = app.listen(+process.env.PORT!);\n return {\n ...server,\n address: () => new URL(process.env.OPTIC_PROXY!),\n } as any;\n }\n return app;\n};\n"],"names":["Proxy"],"mappings":";;;;AAmBA,MAAM,mBAA4B,EAAC;AAUnC,eAAsB,WAAW,GAAA,EAA+B;AAC9D,EAAA,MAAM,KAAA,GAAQ,IAAIA,WAAA,EAAM;AACxB,EAAA,gBAAA,CAAiB,KAAK,KAAK,CAAA;AAC3B,EAAA,MAAM,MAAM,KAAA,EAAM;AAElB,EAAA,MAAM,MAAA,GAAS,GAAA,CAAI,MAAA,CAAO,KAAA,CAAM,UAAU,IAAI,CAAA;AAC9C,EAAA,MAAM,MAAM,UAAA,CAAW,CAAA,iBAAA,EAAoB,MAAM,SAAA,CAAU,IAAI,IAAI,MAAM,CAAA;AAEzE,EAAA,OAAO,EAAE,GAAG,MAAA,EAAQ,OAAA,EAAS,MAAM,IAAI,GAAA,CAAI,KAAA,CAAM,GAAG,CAAA,EAAE;AACxD;AAEA,IAAI,UAAA,GAAa,KAAA;AACjB,SAAS,aAAA,GAAgB;AACvB,EAAA,IAAI,OAAO,QAAA,KAAa,UAAA,IAAc,OAAO,cAAc,UAAA,EAAY;AACrE,IAAA;AAAA,EACF;AACA,EAAA,IAAI,UAAA,EAAY;AACd,IAAA;AAAA,EACF;AACA,EAAA,UAAA,GAAa,IAAA;AAEb,EAAA,QAAA,CAAS,MAAM;AACb,IAAA,KAAA,MAAW,SAAS,gBAAA,EAAkB;AACpC,MAAA,KAAA,CAAM,IAAA,EAAK;AAAA,IACb;AAAA,EACF,CAAC,CAAA;AACH;AAEA,aAAA,EAAc;AAUP,MAAM,uBAAA,GAA0B,CAAC,GAAA,KAAmC;AACzE,EAAA,IAAI,OAAA,CAAQ,IAAI,WAAA,EAAa;AAC3B,IAAA,MAAM,SAAS,GAAA,CAAI,MAAA,CAAO,CAAC,OAAA,CAAQ,IAAI,IAAK,CAAA;AAC5C,IAAA,OAAO;AAAA,MACL,GAAG,MAAA;AAAA,MACH,SAAS,MAAM,IAAI,GAAA,CAAI,OAAA,CAAQ,IAAI,WAAY;AAAA,KACjD;AAAA,EACF;AACA,EAAA,OAAO,GAAA;AACT;;;;;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backstage/backend-openapi-utils",
|
|
3
|
-
"version": "0.6.0
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "OpenAPI typescript support.",
|
|
5
5
|
"backstage": {
|
|
6
6
|
"role": "node-library"
|
|
@@ -54,14 +54,14 @@
|
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@apidevtools/swagger-parser": "^10.1.0",
|
|
57
|
-
"@backstage/backend-plugin-api": "1.4.2
|
|
58
|
-
"@backstage/errors": "1.2.7",
|
|
59
|
-
"@backstage/types": "1.2.1",
|
|
57
|
+
"@backstage/backend-plugin-api": "^1.4.2",
|
|
58
|
+
"@backstage/errors": "^1.2.7",
|
|
59
|
+
"@backstage/types": "^1.2.1",
|
|
60
60
|
"@types/express": "^4.17.6",
|
|
61
61
|
"@types/express-serve-static-core": "^4.17.5",
|
|
62
62
|
"ajv": "^8.16.0",
|
|
63
63
|
"express": "^4.17.1",
|
|
64
|
-
"express-openapi-validator": "^5.
|
|
64
|
+
"express-openapi-validator": "^5.5.8",
|
|
65
65
|
"express-promise-router": "^4.1.0",
|
|
66
66
|
"get-port": "^5.1.1",
|
|
67
67
|
"json-schema-to-ts": "^3.0.0",
|
|
@@ -71,8 +71,8 @@
|
|
|
71
71
|
"openapi3-ts": "^3.1.2"
|
|
72
72
|
},
|
|
73
73
|
"devDependencies": {
|
|
74
|
-
"@backstage/cli": "0.
|
|
75
|
-
"@backstage/test-utils": "1.7.11
|
|
74
|
+
"@backstage/cli": "^0.34.0",
|
|
75
|
+
"@backstage/test-utils": "^1.7.11",
|
|
76
76
|
"msw": "^1.0.0",
|
|
77
77
|
"supertest": "^7.0.0"
|
|
78
78
|
}
|