Conversation
Add @inversifyjs/http-openapi-validation with: - Validate parameter decorator - OpenApiValidationPipe for v3.1 and v3.2 - Ajv-based schema validation with format support - Content-type resolution from request headers - Subpath exports for v3.1 (default) and v3.2
Add end-to-end integration tests for: - v3.1 body validation (valid/invalid requests) - v3.2 body validation (valid/invalid requests) - Content-type fallback (single content type) - Content-type ambiguity error (multiple types)
Add changesets for: - @inversifyjs/http-core (minor): new public exports - @inversifyjs/http-open-api (minor): getter + metadata exports - @inversifyjs/http-openapi-validation (minor): new package
- add validation-docs/openapi/ with introduction and API pages - add code examples for OpenApiValidationPipe and Validate - add integration test for OpenAPI validation code example - add http-open-api, http-openapi-validation devDeps to examples
🦋 Changeset detectedLatest commit: cb350a7 The changes in this PR will be included in the next version bump. This PR includes changesets to release 15 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughAdds a new package Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Client
participant Decorator as `@ValidatedBody` (handler)
participant Controller as Controller (parameter)
participant Pipe as OpenApiValidationPipe
participant OpenApi as SwaggerUiProvider.openApiObject
participant Ajv as Ajv Validator
Client->>Decorator: HTTP request (body + headers)
Decorator-->>Controller: BodyValidationInputParam (body, method, url, contentType, type)
Controller->>Pipe: execute(awaited input, metadata)
Pipe->>OpenApi: lookup operation/requestBody (path + method)
alt content-type missing
Pipe->>OpenApi: infer content-type from operation.requestBody.content
end
Pipe->>Ajv: getSchema(jsonPointer)
alt validator found
Pipe->>Ajv: validate(input.body)
alt valid
Pipe-->>Controller: return validated body
else invalid
Pipe-->>Controller: throw InversifyValidationError
end
else validator missing
Pipe-->>Controller: throw InversifyValidationError (missing schema)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 12
🧹 Nitpick comments (4)
packages/framework/http/libraries/open-api/src/metadata/calculations/v3Dot1/getControllerOpenApiMetadata.ts (1)
6-13: Consider adding JSDoc documentation for this public API.This function is part of the package's public API surface. Adding JSDoc documentation would improve developer experience by providing inline documentation about the function's purpose, parameters, and return value.
📝 Suggested JSDoc
+/** + * Retrieves OpenAPI metadata for a controller. + * + * `@param` target - The controller class to retrieve metadata from + * `@returns` The controller's OpenAPI metadata if present, otherwise undefined + */ export function getControllerOpenApiMetadata( target: object, ): ControllerOpenApiMetadata | undefined {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/framework/http/libraries/open-api/src/metadata/calculations/v3Dot1/getControllerOpenApiMetadata.ts` around lines 6 - 13, Add JSDoc for the exported getControllerOpenApiMetadata function: document its purpose (retrieves controller OpenAPI metadata from the target via reflect metadata), describe the parameter `target: object` (the controller/class whose metadata is read), mention the return type `ControllerOpenApiMetadata | undefined` and what undefined means (no metadata found), and reference the internal helpers/keys used (`getOwnReflectMetadata`, `controllerOpenApiMetadataReflectKey`) for clarity; place the JSDoc immediately above the getControllerOpenApiMetadata declaration and keep it concise and consistent with other public API docs in the package.packages/framework/http/libraries/open-api/src/metadata/calculations/v3Dot2/getControllerOpenApiMetadata.ts (1)
6-13: Consider adding JSDoc documentation for this public API.This function is part of the package's public API surface. Adding JSDoc documentation would improve developer experience and maintain consistency with the v3Dot1 counterpart.
📝 Suggested JSDoc
+/** + * Retrieves OpenAPI metadata for a controller. + * + * `@param` target - The controller class to retrieve metadata from + * `@returns` The controller's OpenAPI metadata if present, otherwise undefined + */ export function getControllerOpenApiMetadata( target: object, ): ControllerOpenApiMetadata | undefined {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/framework/http/libraries/open-api/src/metadata/calculations/v3Dot2/getControllerOpenApiMetadata.ts` around lines 6 - 13, Add a JSDoc comment for the public function getControllerOpenApiMetadata describing its purpose (retrieves controller OpenAPI metadata via reflect metadata), document the parameter target (object) and the return type ControllerOpenApiMetadata | undefined, and mention that it uses getOwnReflectMetadata with controllerOpenApiMetadataReflectKey; follow the style and wording used in the v3Dot1 counterpart to keep consistency across versions.packages/framework/http/libraries/open-api/src/metadata/calculations/v3Dot1/getControllerOpenApiMetadata.spec.ts (1)
11-70: Align this spec with the repository’s required test structure/fixtures.The assertions are good, but the unit test shape should follow the required 4-layer
describehierarchy and fixture-class pattern instead of ad-hocletfixtures inbeforeAll.As per coding guidelines,
**/*.spec.tsmust use the four-layer describe structure (Class → Method → Input → Flow scopes), and**/*.{spec,int.spec}.tsshould use reusable fixture classes with static methods.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/framework/http/libraries/open-api/src/metadata/calculations/v3Dot1/getControllerOpenApiMetadata.spec.ts` around lines 11 - 70, Refactor the spec for getControllerOpenApiMetadata to follow the repository’s 4-layer describe hierarchy (Class → Method → Input → Flow) and replace ad-hoc let fixtures with a reusable fixture class that exposes static factory methods; specifically, wrap tests in nested describes for the target class, the getControllerOpenApiMetadata method, the input case (metadata present vs undefined), and the execution flow, and create a fixture class (e.g., ControllerFixture) with static methods to produce targetFixture and metadataFixture; update test bodies to call vitest.mocked(getOwnReflectMetadata).mockReturnValueOnce(...) and assert calls and returns against controllerOpenApiMetadataReflectKey as before, but using the new fixture class and describe structure to comply with the required pattern.packages/framework/http/libraries/openapi-validation/src/v3Dot2/pipes/OpenApiValidationPipe.ts (1)
35-233: Consider extracting shared logic between v3.1 and v3.2 implementations.The v3Dot1 and v3Dot2
OpenApiValidationPipeimplementations are nearly identical, differing only in import paths, type names, andSCHEMA_ID. A generic base class or shared utility functions could reduce duplication and simplify maintenance.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/framework/http/libraries/openapi-validation/src/v3Dot2/pipes/OpenApiValidationPipe.ts` around lines 35 - 233, The two implementations of OpenApiValidationPipe (v3Dot1 and v3Dot2) duplicate logic; extract the common behavior into a shared base class or utilities and have the version-specific classes only supply differences (types, import SCHEMA_ID, and content-type mapping). Concretely, create a BaseOpenApiValidationPipe that contains execute, `#getOrInitAjv`, and `#resolveContentType` logic using generic type parameters or abstract getters for the OpenAPI object type and SCHEMA_ID, then refactor the v3Dot2 OpenApiValidationPipe to extend BaseOpenApiValidationPipe and override/implement only the version-specific pieces (e.g., providing this.#openApiObject type, SCHEMA_ID constant, and any type aliases), doing the same for v3Dot1 so both implementations reuse the shared logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.changeset/config.json:
- Line 12: The package "@inversifyjs/http-openapi-validation" in the fixed group
has version 0.1.0 which mismatches the group's 3.0.0; open the package.json for
the `@inversifyjs/http-openapi-validation` package and change the "version" field
from "0.1.0" to "3.0.0" so it matches the other fixed-group packages referenced
in .changeset/config.json, then verify the package name
"@inversifyjs/http-openapi-validation" is unchanged and commit the updated
package.json.
In
`@packages/docs/tools/inversify-validation-code-examples/src/examples/v1/openApiValidation/apiIntroduction.int.spec.ts`:
- Around line 15-16: The afterAll teardown assumes server was set by beforeAll
and will throw if beforeAll failed early; update the afterAll blocks that
reference server (e.g., the server variable used alongside beforeAll/afterAll)
to guard the teardown by checking the server is truthy before calling its
close/shutdown method (e.g., if (server) await server.close()), and apply the
same guard to the other afterAll at the other occurrence (lines referenced
around 47-49) so teardown does not raise a secondary error when setup failed.
In
`@packages/framework/http/libraries/open-api/src/openApi/services/v3Dot1/SwaggerUiProvider.spec.ts`:
- Around line 71-87: The test suite for SwaggerUiProvider.openApiObject must
follow the Class → Method → Input → Flow describe structure and use the 'when
called, and [condition]' naming pattern; update the nested describes so the
outermost is SwaggerUiProvider, the next targets .openApiObject, then an
input-level describe (e.g., 'with optionsFixture'), and the flow-level describe
should be 'when called, and [condition]' (or simply 'when called, and no special
options' if nothing else), and keep the existing assertions using result and
optionsFixture.api.openApiObject; ensure the describe strings reference
SwaggerUiProvider and .openApiObject so the test name aligns with project
conventions.
In
`@packages/framework/http/libraries/open-api/src/openApi/services/v3Dot1/SwaggerUiProvider.ts`:
- Around line 70-72: The openApiObject getter currently returns
this.#options.api.openApiObject without verifying initialization; add a guard
using the provider's internal flag (this.#provided) to throw a clear error if
openApiObject is accessed before provide() has been called. Update the
SwaggerUiProvider class's openApiObject getter to check this.#provided and throw
a descriptive Error (e.g., "OpenAPI object not provided — call provide() first")
when false, otherwise return this.#options.api.openApiObject.
In
`@packages/framework/http/libraries/open-api/src/openApi/services/v3Dot2/SwaggerUiProvider.spec.ts`:
- Around line 71-87: The test for the SwaggerUiProvider.openApiObject getter
must follow the four-layer describe convention (Class → Method → Input → Flow);
update the spec so the top-level describe names the class (SwaggerUiProvider),
the next describes the 'openApiObject' getter, add an Input-level describe for
the fixture/input context (e.g. 'when options contains api.openApiObject'
referencing optionsFixture), and change the Flow-level describe to a descriptive
condition (e.g. 'when called and the option is present') so the it() assertion
remains the same (expect(result).toBe(optionsFixture.api.openApiObject)); keep
references to SwaggerUiProvider, openApiObject, and optionsFixture to locate the
code.
In `@packages/framework/http/libraries/openapi-validation/package.json`:
- Around line 26-27: The package.json's devDependencies entry for ajv
("devDependencies.ajv") is pinned to 8.17.1 which does not satisfy the declared
peerDependencies.ajv range (^8.18.0); update devDependencies.ajv to a version
that matches the peer range (e.g., ^8.18.0 or a specific 8.18.x/8.19.x release)
so local tests run against a supported AJV version and the peer constraint is
honored.
- Around line 12-14: The package.json pins two internal packages to fixed
versions; change the dependencies for "@inversifyjs/prototype-utils" and
"@inversifyjs/reflect-metadata-utils" to use the workspace protocol
(workspace:*) like "@inversifyjs/validation-common" so they reference the
monorepo packages; update the dependency values for the exact keys
"@inversifyjs/prototype-utils" and "@inversifyjs/reflect-metadata-utils" to
"workspace:*".
In
`@packages/framework/http/libraries/openapi-validation/src/v3Dot1/pipes/OpenApiValidationPipe.int.spec.ts`:
- Around line 285-297: The test description in OpenApiValidationPipe.int.spec.ts
is incorrect: the describe currently says "no Content-Type header" but the test
sends a 'Content-Type': 'application/json' header and actually exercises the
case where the pipe has no requestContentTypeProvider and falls back to the
OpenAPI single declared content type; update the describe string to accurately
reflect this (e.g., mention "requestContentTypeProvider undefined / falls back
to single declared content type" or "with single content type and Content-Type
header present") so the description matches the behavior exercised by the test
that uses the POST /users request and the response variable.
In
`@packages/framework/http/libraries/openapi-validation/src/v3Dot1/pipes/OpenApiValidationPipe.ts`:
- Around line 206-217: The content-type check in OpenApiValidationPipe (inside
the rawContentType !== undefined branch) compares the lowercased request media
type (baseContentType) against declaredContentTypes in their original casing
which can cause mismatches; normalize declaredContentTypes by mapping each entry
to a trimmed, lowercased form (e.g., normalizedDeclared =
declaredContentTypes.map(...) ) and use
normalizedDeclared.includes(baseContentType) for the comparison, then throw the
same InversifyValidationError (InversifyValidationErrorKind.validationFailed) if
not included and keep returning baseContentType.
In
`@packages/framework/http/libraries/openapi-validation/src/v3Dot2/pipes/OpenApiValidationPipe.int.spec.ts`:
- Around line 58-91: The promise returned for bootstrapping the Server currently
only accepts a resolve callback and throws inside the httpServer.listen
callback, which won't reject the outer Promise and lacks an 'error' listener for
listen/start failures; update the Promise signature to accept both resolve and
reject, replace the throw when address is null/string with reject(new
Error(...)), attach an httpServer.once('error', reject) before calling
httpServer.listen and remove that listener (or use httpServer.off) once the
listen callback succeeds so startup errors properly reject the outer Promise,
and ensure any listeners are cleaned up on successful resolve.
In
`@packages/framework/http/libraries/openapi-validation/src/v3Dot2/pipes/OpenApiValidationPipe.spec.ts`:
- Around line 30-49: The test claims "parameter metadata is undefined" but the
mock returns an empty array; change the mocked return of
getControllerMethodParameterMetadataList from [] to undefined
(vitest.mocked(getControllerMethodParameterMetadataList).mockReturnValueOnce(undefined))
so the OpenApiValidationPipe branch for undefined metadata is exercised; update
any related expectations/assertions in this spec (OpenApiValidationPipe.spec.ts)
to reflect the undefined-path behavior if needed.
In
`@packages/framework/http/libraries/openapi-validation/src/v3Dot2/pipes/OpenApiValidationPipe.ts`:
- Around line 206-217: OpenApiValidationPipe currently lowercases the
request-derived baseContentType but compares it against declaredContentTypes
which retain original casing, causing mismatches for differently cased media
types; update the check in the validation block (the code that computes
baseMediaType/baseContentType and uses declaredContentTypes.includes(...)) to
perform a case-insensitive comparison by normalizing declaredContentTypes (e.g.,
map to lowercase/trim) or by comparing baseContentType to each declared entry
lowercased, and keep the thrown InversifyValidationError behavior but use the
normalized values when matching.
---
Nitpick comments:
In
`@packages/framework/http/libraries/open-api/src/metadata/calculations/v3Dot1/getControllerOpenApiMetadata.spec.ts`:
- Around line 11-70: Refactor the spec for getControllerOpenApiMetadata to
follow the repository’s 4-layer describe hierarchy (Class → Method → Input →
Flow) and replace ad-hoc let fixtures with a reusable fixture class that exposes
static factory methods; specifically, wrap tests in nested describes for the
target class, the getControllerOpenApiMetadata method, the input case (metadata
present vs undefined), and the execution flow, and create a fixture class (e.g.,
ControllerFixture) with static methods to produce targetFixture and
metadataFixture; update test bodies to call
vitest.mocked(getOwnReflectMetadata).mockReturnValueOnce(...) and assert calls
and returns against controllerOpenApiMetadataReflectKey as before, but using the
new fixture class and describe structure to comply with the required pattern.
In
`@packages/framework/http/libraries/open-api/src/metadata/calculations/v3Dot1/getControllerOpenApiMetadata.ts`:
- Around line 6-13: Add JSDoc for the exported getControllerOpenApiMetadata
function: document its purpose (retrieves controller OpenAPI metadata from the
target via reflect metadata), describe the parameter `target: object` (the
controller/class whose metadata is read), mention the return type
`ControllerOpenApiMetadata | undefined` and what undefined means (no metadata
found), and reference the internal helpers/keys used (`getOwnReflectMetadata`,
`controllerOpenApiMetadataReflectKey`) for clarity; place the JSDoc immediately
above the getControllerOpenApiMetadata declaration and keep it concise and
consistent with other public API docs in the package.
In
`@packages/framework/http/libraries/open-api/src/metadata/calculations/v3Dot2/getControllerOpenApiMetadata.ts`:
- Around line 6-13: Add a JSDoc comment for the public function
getControllerOpenApiMetadata describing its purpose (retrieves controller
OpenAPI metadata via reflect metadata), document the parameter target (object)
and the return type ControllerOpenApiMetadata | undefined, and mention that it
uses getOwnReflectMetadata with controllerOpenApiMetadataReflectKey; follow the
style and wording used in the v3Dot1 counterpart to keep consistency across
versions.
In
`@packages/framework/http/libraries/openapi-validation/src/v3Dot2/pipes/OpenApiValidationPipe.ts`:
- Around line 35-233: The two implementations of OpenApiValidationPipe (v3Dot1
and v3Dot2) duplicate logic; extract the common behavior into a shared base
class or utilities and have the version-specific classes only supply differences
(types, import SCHEMA_ID, and content-type mapping). Concretely, create a
BaseOpenApiValidationPipe that contains execute, `#getOrInitAjv`, and
`#resolveContentType` logic using generic type parameters or abstract getters for
the OpenAPI object type and SCHEMA_ID, then refactor the v3Dot2
OpenApiValidationPipe to extend BaseOpenApiValidationPipe and override/implement
only the version-specific pieces (e.g., providing this.#openApiObject type,
SCHEMA_ID constant, and any type aliases), doing the same for v3Dot1 so both
implementations reuse the shared logic.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 729742ee-fad4-4e73-a0e5-414c85d0733f
📒 Files selected for processing (44)
.changeset/add-openapi-body-validation-http-core.md.changeset/add-openapi-body-validation-http-open-api.md.changeset/add-openapi-body-validation-new-package.md.changeset/config.jsonpackages/docs/services/inversify-framework-site/blog/2026-04-04-openapi-body-validation/index.mdxpackages/docs/services/inversify-framework-site/validation-docs/openapi/_category_.jsonpackages/docs/services/inversify-framework-site/validation-docs/openapi/api.mdxpackages/docs/services/inversify-framework-site/validation-docs/openapi/introduction.mdxpackages/docs/tools/inversify-validation-code-examples/package.jsonpackages/docs/tools/inversify-validation-code-examples/src/examples/v1/openApiValidation/apiIntroduction.int.spec.tspackages/docs/tools/inversify-validation-code-examples/src/examples/v1/openApiValidation/apiIntroduction.tspackages/docs/tools/inversify-validation-code-examples/src/examples/v1/openApiValidation/apiOpenApiValidationPipe.tspackages/docs/tools/inversify-validation-code-examples/src/examples/v1/openApiValidation/apiValidateDecorator.tspackages/framework/http/libraries/core/src/index.tspackages/framework/http/libraries/open-api/src/index.tspackages/framework/http/libraries/open-api/src/metadata/calculations/v3Dot1/getControllerOpenApiMetadata.spec.tspackages/framework/http/libraries/open-api/src/metadata/calculations/v3Dot1/getControllerOpenApiMetadata.tspackages/framework/http/libraries/open-api/src/metadata/calculations/v3Dot2/getControllerOpenApiMetadata.spec.tspackages/framework/http/libraries/open-api/src/metadata/calculations/v3Dot2/getControllerOpenApiMetadata.tspackages/framework/http/libraries/open-api/src/openApi/services/v3Dot1/SwaggerUiProvider.spec.tspackages/framework/http/libraries/open-api/src/openApi/services/v3Dot1/SwaggerUiProvider.tspackages/framework/http/libraries/open-api/src/openApi/services/v3Dot2/SwaggerUiProvider.spec.tspackages/framework/http/libraries/open-api/src/openApi/services/v3Dot2/SwaggerUiProvider.tspackages/framework/http/libraries/open-api/src/v3Dot2.tspackages/framework/http/libraries/openapi-validation/.gitignorepackages/framework/http/libraries/openapi-validation/.lintstagedrc.jsonpackages/framework/http/libraries/openapi-validation/eslint.config.mjspackages/framework/http/libraries/openapi-validation/package.jsonpackages/framework/http/libraries/openapi-validation/prettier.config.mjspackages/framework/http/libraries/openapi-validation/src/common/decorators/Validate.spec.tspackages/framework/http/libraries/openapi-validation/src/common/decorators/Validate.tspackages/framework/http/libraries/openapi-validation/src/common/reflectMetadata/openApiValidationMetadataReflectKey.tspackages/framework/http/libraries/openapi-validation/src/index.tspackages/framework/http/libraries/openapi-validation/src/v3Dot1/pipes/OpenApiValidationPipe.int.spec.tspackages/framework/http/libraries/openapi-validation/src/v3Dot1/pipes/OpenApiValidationPipe.spec.tspackages/framework/http/libraries/openapi-validation/src/v3Dot1/pipes/OpenApiValidationPipe.tspackages/framework/http/libraries/openapi-validation/src/v3Dot2.tspackages/framework/http/libraries/openapi-validation/src/v3Dot2/pipes/OpenApiValidationPipe.int.spec.tspackages/framework/http/libraries/openapi-validation/src/v3Dot2/pipes/OpenApiValidationPipe.spec.tspackages/framework/http/libraries/openapi-validation/src/v3Dot2/pipes/OpenApiValidationPipe.tspackages/framework/http/libraries/openapi-validation/stryker.config.mjspackages/framework/http/libraries/openapi-validation/tsconfig.esm.jsonpackages/framework/http/libraries/openapi-validation/tsconfig.jsonpackages/framework/http/libraries/openapi-validation/vitest.config.mjs
...rsify-validation-code-examples/src/examples/v1/openApiValidation/apiIntroduction.int.spec.ts
Show resolved
Hide resolved
...ages/framework/http/libraries/open-api/src/openApi/services/v3Dot1/SwaggerUiProvider.spec.ts
Show resolved
Hide resolved
| public get openApiObject(): OpenApi3Dot1Object { | ||
| return this.#options.api.openApiObject; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify whether openApiObject is consumed before provide() in call sites.
# Expected: no cases where swaggerProvider.openApiObject is passed into validators before swaggerProvider.provide(container).
rg -n -C6 --type=ts --type=tsx 'swaggerProvider\.openApiObject|swaggerProvider\.provide\('Repository: inversify/monorepo
Length of output: 89
🏁 Script executed:
#!/bin/bash
# Re-run with correct rg syntax - rg doesn't support 'tsx' as separate type
rg -n -C6 'swaggerProvider\.openApiObject|swaggerProvider\.provide\(' --type tsRepository: inversify/monorepo
Length of output: 20225
🏁 Script executed:
#!/bin/bash
# Read the SwaggerUiProvider.ts file to understand the implementation
cat -n packages/framework/http/libraries/open-api/src/openApi/services/v3Dot1/SwaggerUiProvider.tsRepository: inversify/monorepo
Length of output: 10932
🏁 Script executed:
#!/bin/bash
# Search for openApiObject getter and provide() method in the file
rg -n -A5 -B5 'openApiObject|provide\(' packages/framework/http/libraries/open-api/src/openApi/services/v3Dot1/SwaggerUiProvider.ts --type tsRepository: inversify/monorepo
Length of output: 1006
Add a guard to prevent accessing the OpenAPI spec before initialization.
The openApiObject getter at line 70 lacks a check for the #provided state. While all current call sites properly invoke provide() before accessing the getter, adding a guard improves defensive programming and prevents misuse:
Proposed fix
public get openApiObject(): OpenApi3Dot1Object {
+ if (!this.#provided) {
+ throw new Error('Cannot access openApiObject before provide()');
+ }
+
return this.#options.api.openApiObject;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public get openApiObject(): OpenApi3Dot1Object { | |
| return this.#options.api.openApiObject; | |
| } | |
| public get openApiObject(): OpenApi3Dot1Object { | |
| if (!this.#provided) { | |
| throw new Error('Cannot access openApiObject before provide()'); | |
| } | |
| return this.#options.api.openApiObject; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@packages/framework/http/libraries/open-api/src/openApi/services/v3Dot1/SwaggerUiProvider.ts`
around lines 70 - 72, The openApiObject getter currently returns
this.#options.api.openApiObject without verifying initialization; add a guard
using the provider's internal flag (this.#provided) to throw a clear error if
openApiObject is accessed before provide() has been called. Update the
SwaggerUiProvider class's openApiObject getter to check this.#provided and throw
a descriptive Error (e.g., "OpenAPI object not provided — call provide() first")
when false, otherwise return this.#options.api.openApiObject.
...ages/framework/http/libraries/open-api/src/openApi/services/v3Dot2/SwaggerUiProvider.spec.ts
Show resolved
Hide resolved
...p/libraries/openapi-validation/src/validation/pipes/v3Dot1/OpenApiValidationPipe.int.spec.ts
Outdated
Show resolved
Hide resolved
| if (rawContentType !== undefined) { | ||
| const baseMediaType: string = rawContentType.split(';')[0] ?? ''; | ||
| const baseContentType: string = baseMediaType.trim().toLowerCase(); | ||
|
|
||
| if (!declaredContentTypes.includes(baseContentType)) { | ||
| throw new InversifyValidationError( | ||
| InversifyValidationErrorKind.validationFailed, | ||
| `Unsupported content type: ${baseContentType}. Supported: ${declaredContentTypes.join(', ')}`, | ||
| ); | ||
| } | ||
|
|
||
| return baseContentType; |
There was a problem hiding this comment.
Content-type matching may fail due to case sensitivity.
Same issue as in the v3Dot2 implementation: the case-insensitive comparison between the request's content type (lowercased) and declared content types (original case) may fail for specs with mixed-case media types.
Proposed fix for case-insensitive comparison
const rawContentType: string | undefined =
this.#requestContentTypeProvider?.();
if (rawContentType !== undefined) {
const baseMediaType: string = rawContentType.split(';')[0] ?? '';
const baseContentType: string = baseMediaType.trim().toLowerCase();
- if (!declaredContentTypes.includes(baseContentType)) {
+ const matchedContentType: string | undefined = declaredContentTypes.find(
+ (ct: string): boolean => ct.toLowerCase() === baseContentType,
+ );
+
+ if (matchedContentType === undefined) {
throw new InversifyValidationError(
InversifyValidationErrorKind.validationFailed,
`Unsupported content type: ${baseContentType}. Supported: ${declaredContentTypes.join(', ')}`,
);
}
- return baseContentType;
+ return matchedContentType;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (rawContentType !== undefined) { | |
| const baseMediaType: string = rawContentType.split(';')[0] ?? ''; | |
| const baseContentType: string = baseMediaType.trim().toLowerCase(); | |
| if (!declaredContentTypes.includes(baseContentType)) { | |
| throw new InversifyValidationError( | |
| InversifyValidationErrorKind.validationFailed, | |
| `Unsupported content type: ${baseContentType}. Supported: ${declaredContentTypes.join(', ')}`, | |
| ); | |
| } | |
| return baseContentType; | |
| if (rawContentType !== undefined) { | |
| const baseMediaType: string = rawContentType.split(';')[0] ?? ''; | |
| const baseContentType: string = baseMediaType.trim().toLowerCase(); | |
| const matchedContentType: string | undefined = declaredContentTypes.find( | |
| (ct: string): boolean => ct.toLowerCase() === baseContentType, | |
| ); | |
| if (matchedContentType === undefined) { | |
| throw new InversifyValidationError( | |
| InversifyValidationErrorKind.validationFailed, | |
| `Unsupported content type: ${baseContentType}. Supported: ${declaredContentTypes.join(', ')}`, | |
| ); | |
| } | |
| return matchedContentType; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@packages/framework/http/libraries/openapi-validation/src/v3Dot1/pipes/OpenApiValidationPipe.ts`
around lines 206 - 217, The content-type check in OpenApiValidationPipe (inside
the rawContentType !== undefined branch) compares the lowercased request media
type (baseContentType) against declaredContentTypes in their original casing
which can cause mismatches; normalize declaredContentTypes by mapping each entry
to a trimmed, lowercased form (e.g., normalizedDeclared =
declaredContentTypes.map(...) ) and use
normalizedDeclared.includes(baseContentType) for the comparison, then throw the
same InversifyValidationError (InversifyValidationErrorKind.validationFailed) if
not included and keep returning baseContentType.
| return new Promise<Server>( | ||
| (resolve: (value: Server | PromiseLike<Server>) => void) => { | ||
| httpServer.listen(0, '127.0.0.1', () => { | ||
| const address: AddressInfo | string | null = httpServer.address(); | ||
|
|
||
| if (address === null || typeof address === 'string') { | ||
| throw new Error('Failed to get server address'); | ||
| } | ||
|
|
||
| const server: Server = { | ||
| host: address.address, | ||
| port: address.port, | ||
| shutdown: async (): Promise<void> => { | ||
| await new Promise<void>( | ||
| ( | ||
| resolve: (value: void | PromiseLike<void>) => void, | ||
| reject: (reason?: unknown) => void, | ||
| ) => { | ||
| httpServer.close((error: Error | undefined) => { | ||
| if (error !== undefined) { | ||
| reject(error); | ||
| } else { | ||
| resolve(); | ||
| } | ||
| }); | ||
| }, | ||
| ); | ||
| }, | ||
| }; | ||
|
|
||
| resolve(server); | ||
| }); | ||
| }, | ||
| ); |
There was a problem hiding this comment.
Server bootstrap promise can hang or crash on startup failures
Line 63 throws inside an async listen callback, which does not safely reject the outer Promise, and there is no error listener for listen failures. This can cause flaky hangs/crashes in the integration suite.
Proposed fix
- return new Promise<Server>(
- (resolve: (value: Server | PromiseLike<Server>) => void) => {
- httpServer.listen(0, '127.0.0.1', () => {
+ return new Promise<Server>(
+ (
+ resolve: (value: Server | PromiseLike<Server>) => void,
+ reject: (reason?: unknown) => void,
+ ) => {
+ const onError = (error: Error): void => {
+ reject(error);
+ };
+
+ httpServer.once('error', onError);
+ httpServer.listen(0, '127.0.0.1', () => {
+ httpServer.off('error', onError);
const address: AddressInfo | string | null = httpServer.address();
if (address === null || typeof address === 'string') {
- throw new Error('Failed to get server address');
+ reject(new Error('Failed to get server address'));
+ return;
}
const server: Server = {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@packages/framework/http/libraries/openapi-validation/src/v3Dot2/pipes/OpenApiValidationPipe.int.spec.ts`
around lines 58 - 91, The promise returned for bootstrapping the Server
currently only accepts a resolve callback and throws inside the
httpServer.listen callback, which won't reject the outer Promise and lacks an
'error' listener for listen/start failures; update the Promise signature to
accept both resolve and reject, replace the throw when address is null/string
with reject(new Error(...)), attach an httpServer.once('error', reject) before
calling httpServer.listen and remove that listener (or use httpServer.off) once
the listen callback succeeds so startup errors properly reject the outer
Promise, and ensure any listeners are cleaned up on successful resolve.
| describe('when called, and parameter metadata is undefined', () => { | ||
| let pipe: OpenApiValidationPipe; | ||
| let inputFixture: unknown; | ||
| let metadataFixture: PipeMetadata; | ||
| let result: unknown; | ||
|
|
||
| beforeAll(() => { | ||
| inputFixture = { name: 'test' }; | ||
| metadataFixture = { | ||
| methodName: 'create', | ||
| parameterIndex: 0, | ||
| targetClass: class {} as NewableFunction, | ||
| }; | ||
|
|
||
| pipe = new OpenApiValidationPipe({} as OpenApi3Dot2Object); | ||
|
|
||
| vitest | ||
| .mocked(getControllerMethodParameterMetadataList) | ||
| .mockReturnValueOnce([]); | ||
|
|
There was a problem hiding this comment.
Test case doesn’t exercise the branch its title describes
Line 30 says the metadata is undefined, but Line 48 returns []. This scenario currently validates the empty-list path, not the undefined path.
Proposed fix
- vitest
- .mocked(getControllerMethodParameterMetadataList)
- .mockReturnValueOnce([]);
+ vitest
+ .mocked(getControllerMethodParameterMetadataList)
+ .mockReturnValueOnce(undefined);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| describe('when called, and parameter metadata is undefined', () => { | |
| let pipe: OpenApiValidationPipe; | |
| let inputFixture: unknown; | |
| let metadataFixture: PipeMetadata; | |
| let result: unknown; | |
| beforeAll(() => { | |
| inputFixture = { name: 'test' }; | |
| metadataFixture = { | |
| methodName: 'create', | |
| parameterIndex: 0, | |
| targetClass: class {} as NewableFunction, | |
| }; | |
| pipe = new OpenApiValidationPipe({} as OpenApi3Dot2Object); | |
| vitest | |
| .mocked(getControllerMethodParameterMetadataList) | |
| .mockReturnValueOnce([]); | |
| describe('when called, and parameter metadata is undefined', () => { | |
| let pipe: OpenApiValidationPipe; | |
| let inputFixture: unknown; | |
| let metadataFixture: PipeMetadata; | |
| let result: unknown; | |
| beforeAll(() => { | |
| inputFixture = { name: 'test' }; | |
| metadataFixture = { | |
| methodName: 'create', | |
| parameterIndex: 0, | |
| targetClass: class {} as NewableFunction, | |
| }; | |
| pipe = new OpenApiValidationPipe({} as OpenApi3Dot2Object); | |
| vitest | |
| .mocked(getControllerMethodParameterMetadataList) | |
| .mockReturnValueOnce(undefined); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@packages/framework/http/libraries/openapi-validation/src/v3Dot2/pipes/OpenApiValidationPipe.spec.ts`
around lines 30 - 49, The test claims "parameter metadata is undefined" but the
mock returns an empty array; change the mocked return of
getControllerMethodParameterMetadataList from [] to undefined
(vitest.mocked(getControllerMethodParameterMetadataList).mockReturnValueOnce(undefined))
so the OpenApiValidationPipe branch for undefined metadata is exercised; update
any related expectations/assertions in this spec (OpenApiValidationPipe.spec.ts)
to reflect the undefined-path behavior if needed.
| if (rawContentType !== undefined) { | ||
| const baseMediaType: string = rawContentType.split(';')[0] ?? ''; | ||
| const baseContentType: string = baseMediaType.trim().toLowerCase(); | ||
|
|
||
| if (!declaredContentTypes.includes(baseContentType)) { | ||
| throw new InversifyValidationError( | ||
| InversifyValidationErrorKind.validationFailed, | ||
| `Unsupported content type: ${baseContentType}. Supported: ${declaredContentTypes.join(', ')}`, | ||
| ); | ||
| } | ||
|
|
||
| return baseContentType; |
There was a problem hiding this comment.
Content-type matching may fail due to case sensitivity.
The baseContentType is lowercased (line 208), but declaredContentTypes preserves the original casing from the OpenAPI spec. Per RFC 7231, media types are case-insensitive, so 'application/json' and 'Application/JSON' should match.
Proposed fix for case-insensitive comparison
const rawContentType: string | undefined =
this.#requestContentTypeProvider?.();
if (rawContentType !== undefined) {
const baseMediaType: string = rawContentType.split(';')[0] ?? '';
const baseContentType: string = baseMediaType.trim().toLowerCase();
- if (!declaredContentTypes.includes(baseContentType)) {
+ const matchedContentType: string | undefined = declaredContentTypes.find(
+ (ct: string): boolean => ct.toLowerCase() === baseContentType,
+ );
+
+ if (matchedContentType === undefined) {
throw new InversifyValidationError(
InversifyValidationErrorKind.validationFailed,
`Unsupported content type: ${baseContentType}. Supported: ${declaredContentTypes.join(', ')}`,
);
}
- return baseContentType;
+ return matchedContentType;
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@packages/framework/http/libraries/openapi-validation/src/v3Dot2/pipes/OpenApiValidationPipe.ts`
around lines 206 - 217, OpenApiValidationPipe currently lowercases the
request-derived baseContentType but compares it against declaredContentTypes
which retain original casing, causing mismatches for differently cased media
types; update the check in the validation block (the code that computes
baseMediaType/baseContentType and uses declaredContentTypes.includes(...)) to
perform a case-insensitive comparison by normalizing declaredContentTypes (e.g.,
map to lowercase/trim) or by comparing baseContentType to each declared entry
lowercased, and keep the thrown InversifyValidationError behavior but use the
normalized values when matching.
Benchmarks results:> root@ benchmarks:http:cjs:low /home/runner/work/monorepo/monorepo
> pnpm run --filter "@inversifyjs/http-benchmarks" benchmarks:run:cjs:low
> @inversifyjs/http-benchmarks@ benchmarks:run:cjs:low /home/runner/work/monorepo/monorepo/packages/framework/http/tools/http-benchmarks
> BUILD_TARGET=cjs VUS=128 ./bin/run-cjs.cjs
[CJS] Running benchmarks...
Express Basic Get Request
┌─────────┬───────────────────────────┬──────────────────┬──────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name │ Latency avg (ms) │ Latency med (ms) │ Throughput avg (req/s) │ Samples │
├─────────┼───────────────────────────┼──────────────────┼──────────────────┼────────────────────────┼─────────┤
│ 0 │ 'currentInversifyExpress' │ '6.999' │ '6.506' │ '9010.848' │ 90217 │
│ 1 │ 'express' │ '6.529' │ '6.230' │ '9686.443' │ 96992 │
│ 2 │ 'NestJSExpress' │ '7.188' │ '7.020' │ '8809.173' │ 88213 │
└─────────┴───────────────────────────┴──────────────────┴──────────────────┴────────────────────────┴─────────┘
currentInversifyExpress vs express Speedup: 0.930x
currentInversifyExpress vs NestJSExpress Speedup: 1.023x
Express v4 Basic Get Request
┌─────────┬────────────────────────────┬──────────────────┬──────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name │ Latency avg (ms) │ Latency med (ms) │ Throughput avg (req/s) │ Samples │
├─────────┼────────────────────────────┼──────────────────┼──────────────────┼────────────────────────┼─────────┤
│ 0 │ 'currentInversifyExpress4' │ '7.137' │ '6.825' │ '8869.882' │ 88822 │
│ 1 │ 'express4' │ '6.834' │ '6.474' │ '9258.860' │ 92699 │
└─────────┴────────────────────────────┴──────────────────┴──────────────────┴────────────────────────┴─────────┘
currentInversifyExpress4 vs express4 Speedup: 0.958x
Fastify Basic Get Request
┌─────────┬───────────────────────────┬──────────────────┬──────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name │ Latency avg (ms) │ Latency med (ms) │ Throughput avg (req/s) │ Samples │
├─────────┼───────────────────────────┼──────────────────┼──────────────────┼────────────────────────┼─────────┤
│ 0 │ 'currentInversifyFastify' │ '4.136' │ '3.611' │ '15111.427' │ 151249 │
│ 1 │ 'fastify' │ '3.774' │ '3.315' │ '16494.594' │ 165114 │
│ 2 │ 'NestJSFastify' │ '4.295' │ '3.787' │ '14469.561' │ 144820 │
└─────────┴───────────────────────────┴──────────────────┴──────────────────┴────────────────────────┴─────────┘
currentInversifyFastify vs fastify Speedup: 0.916x
currentInversifyFastify vs NestJSFastify Speedup: 1.044x
Hono Basic Get Request
┌─────────┬────────────────────────┬──────────────────┬──────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name │ Latency avg (ms) │ Latency med (ms) │ Throughput avg (req/s) │ Samples │
├─────────┼────────────────────────┼──────────────────┼──────────────────┼────────────────────────┼─────────┤
│ 0 │ 'currentInversifyHono' │ '4.568' │ '4.117' │ '13701.084' │ 137149 │
│ 1 │ 'hono' │ '3.995' │ '3.558' │ '15610.128' │ 156244 │
└─────────┴────────────────────────┴──────────────────┴──────────────────┴────────────────────────┴─────────┘
currentInversifyHono vs hono Speedup: 0.878x
uWebSockets Basic Get Request
┌─────────┬───────────────────────────────┬──────────────────┬──────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name │ Latency avg (ms) │ Latency med (ms) │ Throughput avg (req/s) │ Samples │
├─────────┼───────────────────────────────┼──────────────────┼──────────────────┼────────────────────────┼─────────┤
│ 0 │ 'currentInversifyUwebsockets' │ '2.385' │ '1.685' │ '25352.125' │ 253890 │
│ 1 │ 'uwebsockets' │ '2.304' │ '1.691' │ '26556.561' │ 265667 │
└─────────┴───────────────────────────────┴──────────────────┴──────────────────┴────────────────────────┴─────────┘
currentInversifyUwebsockets vs uwebsockets Speedup: 0.955x |
…agger-validator-package
…agger-validator-package
…agger-validator-package
…agger-validator-package
…agger-validator-package
…agger-validator-package
A custom decorator is used instead to get the required data to validate request parts. Validate decorator has been removed. Validation pipes properly traverse openapi schemas instead of relying on metadata getControllerOpenApiMetadata was never required
Benchmarks results:> root@ benchmarks:http:cjs:low /home/runner/work/monorepo/monorepo
> pnpm run --filter "@inversifyjs/http-benchmarks" benchmarks:run:cjs:low
> @inversifyjs/http-benchmarks@ benchmarks:run:cjs:low /home/runner/work/monorepo/monorepo/packages/framework/http/tools/http-benchmarks
> BUILD_TARGET=cjs VUS=128 ./bin/run-cjs.cjs
[CJS] Running benchmarks...
Express Basic Get Request
┌─────────┬───────────────────────────┬──────────────────┬──────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name │ Latency avg (ms) │ Latency med (ms) │ Throughput avg (req/s) │ Samples │
├─────────┼───────────────────────────┼──────────────────┼──────────────────┼────────────────────────┼─────────┤
│ 0 │ 'currentInversifyExpress' │ '5.520' │ '4.809' │ '11426.820' │ 114400 │
│ 1 │ 'express' │ '5.114' │ '4.809' │ '12345.724' │ 123606 │
│ 2 │ 'NestJSExpress' │ '5.502' │ '5.141' │ '11485.383' │ 114994 │
└─────────┴───────────────────────────┴──────────────────┴──────────────────┴────────────────────────┴─────────┘
currentInversifyExpress vs express Speedup: 0.926x
currentInversifyExpress vs NestJSExpress Speedup: 0.995x
Express v4 Basic Get Request
┌─────────┬────────────────────────────┬──────────────────┬──────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name │ Latency avg (ms) │ Latency med (ms) │ Throughput avg (req/s) │ Samples │
├─────────┼────────────────────────────┼──────────────────┼──────────────────┼────────────────────────┼─────────┤
│ 0 │ 'currentInversifyExpress4' │ '5.453' │ '5.099' │ '11592.843' │ 116065 │
│ 1 │ 'express4' │ '5.188' │ '4.867' │ '12172.195' │ 121848 │
└─────────┴────────────────────────────┴──────────────────┴──────────────────┴────────────────────────┴─────────┘
currentInversifyExpress4 vs express4 Speedup: 0.952x
Fastify Basic Get Request
┌─────────┬───────────────────────────┬──────────────────┬──────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name │ Latency avg (ms) │ Latency med (ms) │ Throughput avg (req/s) │ Samples │
├─────────┼───────────────────────────┼──────────────────┼──────────────────┼────────────────────────┼─────────┤
│ 0 │ 'currentInversifyFastify' │ '2.959' │ '2.405' │ '20972.991' │ 209801 │
│ 1 │ 'fastify' │ '2.685' │ '2.165' │ '23025.793' │ 230374 │
│ 2 │ 'NestJSFastify' │ '3.057' │ '2.574' │ '20294.131' │ 202991 │
└─────────┴───────────────────────────┴──────────────────┴──────────────────┴────────────────────────┴─────────┘
currentInversifyFastify vs fastify Speedup: 0.911x
currentInversifyFastify vs NestJSFastify Speedup: 1.033x
Hono Basic Get Request
┌─────────┬────────────────────────┬──────────────────┬──────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name │ Latency avg (ms) │ Latency med (ms) │ Throughput avg (req/s) │ Samples │
├─────────┼────────────────────────┼──────────────────┼──────────────────┼────────────────────────┼─────────┤
│ 0 │ 'currentInversifyHono' │ '3.291' │ '2.772' │ '18971.007' │ 189849 │
│ 1 │ 'hono' │ '2.935' │ '2.391' │ '21161.450' │ 211733 │
└─────────┴────────────────────────┴──────────────────┴──────────────────┴────────────────────────┴─────────┘
currentInversifyHono vs hono Speedup: 0.896x
uWebSockets Basic Get Request
┌─────────┬───────────────────────────────┬──────────────────┬──────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name │ Latency avg (ms) │ Latency med (ms) │ Throughput avg (req/s) │ Samples │
├─────────┼───────────────────────────────┼──────────────────┼──────────────────┼────────────────────────┼─────────┤
│ 0 │ 'currentInversifyUwebsockets' │ '2.041' │ '1.445' │ '29740.733' │ 297460 │
│ 1 │ 'uwebsockets' │ '1.980' │ '1.437' │ '30735.811' │ 307413 │
└─────────┴───────────────────────────────┴──────────────────┴──────────────────┴────────────────────────┴─────────┘
currentInversifyUwebsockets vs uwebsockets Speedup: 0.968x |
There was a problem hiding this comment.
Actionable comments posted: 8
♻️ Duplicate comments (1)
packages/framework/http/libraries/openapi-validation/src/validation/pipes/v3Dot2/OpenApiValidationPipe.int.spec.ts (1)
57-90:⚠️ Potential issue | 🟠 MajorHandle server startup errors by rejecting the outer Promise.
Line 63 throws inside the
listencallback, and there is noerrorlistener beforelisten(). This can cause nondeterministic test hangs/crashes instead of a clean Promise rejection.Proposed fix
- return new Promise<Server>( - (resolve: (value: Server | PromiseLike<Server>) => void) => { - httpServer.listen(0, '127.0.0.1', () => { + return new Promise<Server>( + ( + resolve: (value: Server | PromiseLike<Server>) => void, + reject: (reason?: unknown) => void, + ) => { + const onError = (error: Error): void => { + reject(error); + }; + + httpServer.once('error', onError); + httpServer.listen(0, '127.0.0.1', () => { + httpServer.off('error', onError); const address: AddressInfo | string | null = httpServer.address(); if (address === null || typeof address === 'string') { - throw new Error('Failed to get server address'); + reject(new Error('Failed to get server address')); + return; } @@ - resolve(server); + resolve(server); }); }, );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/framework/http/libraries/openapi-validation/src/validation/pipes/v3Dot2/OpenApiValidationPipe.int.spec.ts` around lines 57 - 90, The test currently throws inside the httpServer.listen callback and lacks an error listener, which can cause hangs; update the Promise returned by createServer so it rejects on startup errors by attaching an httpServer.on('error', handler) before calling httpServer.listen and calling reject(error) from that handler, and replace the throw when address is null/typeof string with reject(new Error(...)); ensure you remove the 'error' listener after resolving or rejecting and keep the existing shutdown implementation and resolve(server) behavior intact so resources are cleaned up.
🧹 Nitpick comments (4)
packages/framework/http/libraries/openapi-validation/src/validation/calculations/buildCompositeValidationHandler.spec.ts (1)
19-154: Align describe labels with the repo test naming convention.The current scopes use
having ...+ nestedwhen called. Please rename scopeddescribelabels to thewhen called, and [condition]pattern for consistency across*.spec.tstests.As per coding guidelines, test names in
**/*.{spec,int.spec,spec-d}.tsshould follow the'when called, and [condition]'pattern.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/framework/http/libraries/openapi-validation/src/validation/calculations/buildCompositeValidationHandler.spec.ts` around lines 19 - 154, Replace the outer describe labels that start with "having ..." with the repo's naming convention "when called, and [condition]" for each scenario around buildCompositeValidationHandler: change "having non object input params" → "when called, and given non-object input params"; "having null input params" → "when called, and given null input params"; "having empty handler map and object input params" → "when called, and given an empty handler map and object input params"; and "having non handler map and object input params with right discriminator" → "when called, and given a non-empty handler map and an object input with the matching discriminator". Keep the inner test bodies and the nested "when called" blocks consistent (remove or merge duplicate nested "when called" labels if needed) and leave calls to buildCompositeValidationHandler, ajvFixture, discriminatorHandlerPair, and handlerMock unchanged.packages/framework/http/libraries/openapi-validation/src/validation/calculations/getMimeType.spec.ts (1)
6-39: Use the standardwhen called, and [condition]scope labels in this spec.The table cases are solid, but the
describe.eachtitle currently useshaving %s. Please rename the scoped context labels to the repo’s standard condition format.As per coding guidelines, test names in
**/*.{spec,int.spec,spec-d}.tsshould follow the'when called, and [condition]'pattern.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/framework/http/libraries/openapi-validation/src/validation/calculations/getMimeType.spec.ts` around lines 6 - 39, Rename the outer table-driven describe.each title from 'having %s' to the standard pattern 'when called, and %s' and remove the nested describe('when called', ...) wrapper so the beforeAll and it blocks live directly under the outer describe; keep the getMimeType calls and expectations unchanged so each case reads "when called, and [condition]".packages/framework/http/libraries/openapi-validation/src/validation/calculations/v3Dot1/getRequestBodyObject.ts (1)
39-40: Minor typo: "derreferencedRequestBodyObject" → "dereferencedRequestBodyObject"The variable name has a double 'r' typo. This is a minor readability issue.
🔤 Suggested fix
- let derreferencedRequestBodyObject: JsonValueObject | undefined = + let dereferencedRequestBodyObject: JsonValueObject | undefined = requestBodyObject as unknown as JsonValueObject;Also update references at lines 66, 68-70, and 73.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/framework/http/libraries/openapi-validation/src/validation/calculations/v3Dot1/getRequestBodyObject.ts` around lines 39 - 40, Rename the misspelled variable derreferencedRequestBodyObject to dereferencedRequestBodyObject throughout getRequestBodyObject (update its declaration and every usage/reference), ensuring any subsequent assignments or checks that reference derreferencedRequestBodyObject are updated to the new name so the function continues to compile and behave the same.packages/framework/http/libraries/openapi-validation/src/validation/calculations/v3Dot2/getRequestBodyObject.ts (1)
39-40: Minor typo: "derreferencedRequestBodyObject" → "dereferencedRequestBodyObject"Same typo as in the v3.1 implementation. Consider fixing in both files for consistency.
🔤 Suggested fix
- let derreferencedRequestBodyObject: JsonValueObject | undefined = + let dereferencedRequestBodyObject: JsonValueObject | undefined = requestBodyObject as unknown as JsonValueObject;Also update references at lines 66, 68-70, and 73.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/framework/http/libraries/openapi-validation/src/validation/calculations/v3Dot2/getRequestBodyObject.ts` around lines 39 - 40, Rename the misspelled variable derreferencedRequestBodyObject to dereferencedRequestBodyObject in getRequestBodyObject (and mirror the same rename in the v3.1 implementation), updating all references including the declaration and usages at the noted positions (lines referenced in the review: the initial declaration and subsequent references around where it's used—e.g., the occurrences previously at lines 66, 68-70, and 73) so the identifier is consistent across the file and between versions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@packages/framework/http/libraries/openapi-validation/src/validation/calculations/getPath.spec.ts`:
- Around line 6-9: Add test tuples to the existing describe.each<[string,
string, string]> table (the array used in the 'having %s' test) that cover a
non-leading-slash path and an absolute URL; for example include cases like
['non-leading path', 'users', '/users'] and ['absolute url',
'http://example.com/users?name=John', '/users'] so the getPath extraction logic
(exercised by the 'having %s' test) is validated for paths without a leading
slash and for full absolute URLs with query strings.
In
`@packages/framework/http/libraries/openapi-validation/src/validation/calculations/getPath.ts`:
- Around line 1-11: getPath currently slices absolute URLs and can return
host-prefixed values (e.g., "//host/users"); update getPath(url: string) to
normalize absolute URLs before extracting the path: detect absolute forms (start
with "http://" or "https://") and obtain the pathname (e.g., via the URL
constructor or by stripping scheme+host) then remove any query string; ensure
you still handle relative URLs and queries the same way so the function always
returns only the path portion (e.g., "/users").
In
`@packages/framework/http/libraries/openapi-validation/src/validation/calculations/v3Dot1/getOperationObject.ts`:
- Around line 23-31: Replace the broad Record<string, ...> cast and direct
indexing of pathItemObject with a safe key check: first validate that method is
one of the OpenAPI operation keys (e.g.,
'get','put','post','delete','options','head','patch','trace') and that (method
in pathItemObject) is true, then perform a typed index into pathItemObject
(e.g., (pathItemObject as OpenApi3Dot1PathItemObject)[method as keyof
OpenApi3Dot1PathItemObject]) to assign operationObject; if the key is missing
keep throwing the existing InversifyValidationError using
InversifyValidationErrorKind.validationFailed. Ensure you use the existing
variables pathItemObject, method, and operationObject in the guard to avoid the
unsafe Record<string, ...> cast.
In
`@packages/framework/http/libraries/openapi-validation/src/validation/calculations/v3Dot1/inferContentType.ts`:
- Around line 11-21: The current inferContentType logic can throw a runtime
TypeError when requestBodyObject.content is missing or not an object and also
gives an inaccurate message when there are zero content types; update the guard
in inferContentType to first validate that requestBodyObject.content is a plain
object and that Object.keys(requestBodyObject.content) yields at least one
entry, throw an InversifyValidationError (using
InversifyValidationErrorKind.validationFailed) with a clear message for the
zero-content case, and only return contentTypes[0] when contentTypes.length ===
1; reference requestBodyObject.content, contentTypes, inferContentType,
InversifyValidationError and InversifyValidationErrorKind when making the
change.
In
`@packages/framework/http/libraries/openapi-validation/src/validation/calculations/v3Dot2/getOperationObject.spec.ts`:
- Around line 19-67: Add a unit test that covers uppercase HTTP methods:
duplicate the existing "having a pathItemObject with the method" scenario but
set methodFixture to 'GET' (uppercase) and assert
getOperationObject(openApiObjectFixture, methodFixture, pathFixture) still calls
getPathItemObject(openApiObjectFixture, pathFixture) and returns the same
operationObjectFixture; this ensures getOperationObject correctly normalizes or
handles uppercase methods when looking up pathItemObject entries.
In
`@packages/framework/http/libraries/openapi-validation/src/validation/calculations/v3Dot2/getOperationObject.ts`:
- Around line 15-31: The lookup in getOperationObject uses the raw method and a
wide Record<string, OpenApi3Dot2OperationObject> cast which hides non-operation
keys and will miss uppercase methods; normalize the incoming method to lowercase
(e.g., const methodKey = method.toLowerCase()) and use a typed key lookup
against OpenApi3Dot2PathItemObject (cast to keyof OpenApi3Dot2PathItemObject)
instead of Record<string,...> so you only access known operation properties;
keep the existing InversifyValidationError logic when the operation is
undefined.
In
`@packages/framework/http/libraries/openapi-validation/src/validation/calculations/v3Dot2/inferContentType.ts`:
- Around line 11-21: The inferContentType logic should guard for a
missing/nullable requestBodyObject.content and handle zero vs multiple entries
distinctly: first normalize contentTypes = requestBodyObject.content ?
Object.keys(requestBodyObject.content) : []; then if contentTypes.length === 1
return that value; if length === 0 throw an InversifyValidationError
(InversifyValidationErrorKind.validationFailed) with a message stating no
content type provided for method ${method}; otherwise (length > 1) throw the
existing multi-content error including contentTypes.join(', ') to preserve
current behavior.
In
`@packages/framework/http/libraries/openapi-validation/src/validation/pipes/v3Dot2/OpenApiValidationPipe.int.spec.ts`:
- Around line 279-290: The test block "when a valid POST /users request is made
with single content type and no Content-Type header" is sending a Content-Type
header in the beforeAll fetch call, so change the fetch invocation inside that
describe/beforeAll to omit the 'Content-Type' header (remove headers or pass an
empty headers object) so the fallback/content-type inference branch is actually
exercised; ensure you update the fetch call in this spec (the beforeAll that
sets response) and keep the body as JSON stringified as-is.
---
Duplicate comments:
In
`@packages/framework/http/libraries/openapi-validation/src/validation/pipes/v3Dot2/OpenApiValidationPipe.int.spec.ts`:
- Around line 57-90: The test currently throws inside the httpServer.listen
callback and lacks an error listener, which can cause hangs; update the Promise
returned by createServer so it rejects on startup errors by attaching an
httpServer.on('error', handler) before calling httpServer.listen and calling
reject(error) from that handler, and replace the throw when address is
null/typeof string with reject(new Error(...)); ensure you remove the 'error'
listener after resolving or rejecting and keep the existing shutdown
implementation and resolve(server) behavior intact so resources are cleaned up.
---
Nitpick comments:
In
`@packages/framework/http/libraries/openapi-validation/src/validation/calculations/buildCompositeValidationHandler.spec.ts`:
- Around line 19-154: Replace the outer describe labels that start with "having
..." with the repo's naming convention "when called, and [condition]" for each
scenario around buildCompositeValidationHandler: change "having non object input
params" → "when called, and given non-object input params"; "having null input
params" → "when called, and given null input params"; "having empty handler map
and object input params" → "when called, and given an empty handler map and
object input params"; and "having non handler map and object input params with
right discriminator" → "when called, and given a non-empty handler map and an
object input with the matching discriminator". Keep the inner test bodies and
the nested "when called" blocks consistent (remove or merge duplicate nested
"when called" labels if needed) and leave calls to
buildCompositeValidationHandler, ajvFixture, discriminatorHandlerPair, and
handlerMock unchanged.
In
`@packages/framework/http/libraries/openapi-validation/src/validation/calculations/getMimeType.spec.ts`:
- Around line 6-39: Rename the outer table-driven describe.each title from
'having %s' to the standard pattern 'when called, and %s' and remove the nested
describe('when called', ...) wrapper so the beforeAll and it blocks live
directly under the outer describe; keep the getMimeType calls and expectations
unchanged so each case reads "when called, and [condition]".
In
`@packages/framework/http/libraries/openapi-validation/src/validation/calculations/v3Dot1/getRequestBodyObject.ts`:
- Around line 39-40: Rename the misspelled variable
derreferencedRequestBodyObject to dereferencedRequestBodyObject throughout
getRequestBodyObject (update its declaration and every usage/reference),
ensuring any subsequent assignments or checks that reference
derreferencedRequestBodyObject are updated to the new name so the function
continues to compile and behave the same.
In
`@packages/framework/http/libraries/openapi-validation/src/validation/calculations/v3Dot2/getRequestBodyObject.ts`:
- Around line 39-40: Rename the misspelled variable
derreferencedRequestBodyObject to dereferencedRequestBodyObject in
getRequestBodyObject (and mirror the same rename in the v3.1 implementation),
updating all references including the declaration and usages at the noted
positions (lines referenced in the review: the initial declaration and
subsequent references around where it's used—e.g., the occurrences previously at
lines 66, 68-70, and 73) so the identifier is consistent across the file and
between versions.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 5c819af5-8694-4c82-9410-ca5d20dffad8
📒 Files selected for processing (53)
.changeset/add-openapi-body-validation-new-package.mdpackages/docs/tools/inversify-validation-code-examples/package.jsonpackages/docs/tools/inversify-validation-code-examples/src/examples/v1/openApiValidation/apiIntroduction.int.spec.tspackages/docs/tools/inversify-validation-code-examples/src/examples/v1/openApiValidation/apiIntroduction.tspackages/docs/tools/inversify-validation-code-examples/src/examples/v1/openApiValidation/apiOpenApiValidationPipe.tspackages/docs/tools/inversify-validation-code-examples/src/examples/v1/openApiValidation/apiValidateDecorator.tspackages/framework/http/libraries/open-api/src/index.tspackages/framework/http/libraries/open-api/src/v3Dot2.tspackages/framework/http/libraries/openapi-validation/package.jsonpackages/framework/http/libraries/openapi-validation/src/index.tspackages/framework/http/libraries/openapi-validation/src/metadata/actions/setValidateMetadata.spec.tspackages/framework/http/libraries/openapi-validation/src/metadata/actions/setValidateMetadata.tspackages/framework/http/libraries/openapi-validation/src/metadata/decorators/ValidatedBody.tspackages/framework/http/libraries/openapi-validation/src/metadata/models/openApiValidationMetadataReflectKey.tspackages/framework/http/libraries/openapi-validation/src/v3Dot1.tspackages/framework/http/libraries/openapi-validation/src/v3Dot2.tspackages/framework/http/libraries/openapi-validation/src/validation/calculations/buildCompositeValidationHandler.spec.tspackages/framework/http/libraries/openapi-validation/src/validation/calculations/buildCompositeValidationHandler.tspackages/framework/http/libraries/openapi-validation/src/validation/calculations/getMimeType.spec.tspackages/framework/http/libraries/openapi-validation/src/validation/calculations/getMimeType.tspackages/framework/http/libraries/openapi-validation/src/validation/calculations/getPath.spec.tspackages/framework/http/libraries/openapi-validation/src/validation/calculations/getPath.tspackages/framework/http/libraries/openapi-validation/src/validation/calculations/v3Dot1/getOperationObject.spec.tspackages/framework/http/libraries/openapi-validation/src/validation/calculations/v3Dot1/getOperationObject.tspackages/framework/http/libraries/openapi-validation/src/validation/calculations/v3Dot1/getPathItemObject.spec.tspackages/framework/http/libraries/openapi-validation/src/validation/calculations/v3Dot1/getPathItemObject.tspackages/framework/http/libraries/openapi-validation/src/validation/calculations/v3Dot1/getRequestBodyObject.spec.tspackages/framework/http/libraries/openapi-validation/src/validation/calculations/v3Dot1/getRequestBodyObject.tspackages/framework/http/libraries/openapi-validation/src/validation/calculations/v3Dot1/handleBodyValidation.spec.tspackages/framework/http/libraries/openapi-validation/src/validation/calculations/v3Dot1/handleBodyValidation.tspackages/framework/http/libraries/openapi-validation/src/validation/calculations/v3Dot1/inferContentType.spec.tspackages/framework/http/libraries/openapi-validation/src/validation/calculations/v3Dot1/inferContentType.tspackages/framework/http/libraries/openapi-validation/src/validation/calculations/v3Dot2/getOperationObject.spec.tspackages/framework/http/libraries/openapi-validation/src/validation/calculations/v3Dot2/getOperationObject.tspackages/framework/http/libraries/openapi-validation/src/validation/calculations/v3Dot2/getPathItemObject.spec.tspackages/framework/http/libraries/openapi-validation/src/validation/calculations/v3Dot2/getPathItemObject.tspackages/framework/http/libraries/openapi-validation/src/validation/calculations/v3Dot2/getRequestBodyObject.spec.tspackages/framework/http/libraries/openapi-validation/src/validation/calculations/v3Dot2/getRequestBodyObject.tspackages/framework/http/libraries/openapi-validation/src/validation/calculations/v3Dot2/handleBodyValidation.spec.tspackages/framework/http/libraries/openapi-validation/src/validation/calculations/v3Dot2/handleBodyValidation.tspackages/framework/http/libraries/openapi-validation/src/validation/calculations/v3Dot2/inferContentType.spec.tspackages/framework/http/libraries/openapi-validation/src/validation/calculations/v3Dot2/inferContentType.tspackages/framework/http/libraries/openapi-validation/src/validation/models/BodyValidationInputParam.tspackages/framework/http/libraries/openapi-validation/src/validation/models/DiscriminatorValidationHandlerPair.tspackages/framework/http/libraries/openapi-validation/src/validation/models/ValidatedDecoratorResult.tspackages/framework/http/libraries/openapi-validation/src/validation/models/ValidationHandler.tspackages/framework/http/libraries/openapi-validation/src/validation/models/v3Dot1/schemaId.tspackages/framework/http/libraries/openapi-validation/src/validation/models/v3Dot2/schemaId.tspackages/framework/http/libraries/openapi-validation/src/validation/models/validatedInputParamTypes.tspackages/framework/http/libraries/openapi-validation/src/validation/pipes/v3Dot1/OpenApiValidationPipe.int.spec.tspackages/framework/http/libraries/openapi-validation/src/validation/pipes/v3Dot1/OpenApiValidationPipe.tspackages/framework/http/libraries/openapi-validation/src/validation/pipes/v3Dot2/OpenApiValidationPipe.int.spec.tspackages/framework/http/libraries/openapi-validation/src/validation/pipes/v3Dot2/OpenApiValidationPipe.ts
✅ Files skipped from review due to trivial changes (9)
- packages/docs/tools/inversify-validation-code-examples/package.json
- packages/framework/http/libraries/openapi-validation/src/v3Dot1.ts
- packages/framework/http/libraries/openapi-validation/src/validation/models/ValidatedDecoratorResult.ts
- .changeset/add-openapi-body-validation-new-package.md
- packages/framework/http/libraries/openapi-validation/src/validation/models/v3Dot1/schemaId.ts
- packages/framework/http/libraries/open-api/src/index.ts
- packages/framework/http/libraries/openapi-validation/src/validation/models/BodyValidationInputParam.ts
- packages/framework/http/libraries/openapi-validation/src/validation/models/DiscriminatorValidationHandlerPair.ts
- packages/framework/http/libraries/openapi-validation/package.json
🚧 Files skipped from review as they are similar to previous changes (4)
- packages/framework/http/libraries/openapi-validation/src/index.ts
- packages/docs/tools/inversify-validation-code-examples/src/examples/v1/openApiValidation/apiValidateDecorator.ts
- packages/docs/tools/inversify-validation-code-examples/src/examples/v1/openApiValidation/apiIntroduction.int.spec.ts
- packages/docs/tools/inversify-validation-code-examples/src/examples/v1/openApiValidation/apiOpenApiValidationPipe.ts
| describe.each<[string, string, string]>([ | ||
| ['url with no query', '/users', '/users'], | ||
| ['url with query', '/users?name=John', '/users'], | ||
| ])('having %s', (_: string, url: string, expected: string) => { |
There was a problem hiding this comment.
Add cases for non-leading and absolute URLs.
Current cases don't cover the non-leading-slash path branch, so incorrect extraction can slip through.
🧪 Suggested additions
describe.each<[string, string, string]>([
['url with no query', '/users', '/users'],
['url with query', '/users?name=John', '/users'],
+ ['url without leading slash', 'users?name=John', '/users'],
+ ['absolute url with query', 'https://example.com/users?name=John', '/users'],
])('having %s', (_: string, url: string, expected: string) => {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| describe.each<[string, string, string]>([ | |
| ['url with no query', '/users', '/users'], | |
| ['url with query', '/users?name=John', '/users'], | |
| ])('having %s', (_: string, url: string, expected: string) => { | |
| describe.each<[string, string, string]>([ | |
| ['url with no query', '/users', '/users'], | |
| ['url with query', '/users?name=John', '/users'], | |
| ['url without leading slash', 'users?name=John', '/users'], | |
| ['absolute url with query', 'https://example.com/users?name=John', '/users'], | |
| ])('having %s', (_: string, url: string, expected: string) => { |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@packages/framework/http/libraries/openapi-validation/src/validation/calculations/getPath.spec.ts`
around lines 6 - 9, Add test tuples to the existing describe.each<[string,
string, string]> table (the array used in the 'having %s' test) that cover a
non-leading-slash path and an absolute URL; for example include cases like
['non-leading path', 'users', '/users'] and ['absolute url',
'http://example.com/users?name=John', '/users'] so the getPath extraction logic
(exercised by the 'having %s' test) is validated for paths without a leading
slash and for full absolute URLs with query strings.
| export function getPath(url: string): string { | ||
| const queryIndex: number = url.indexOf('?'); | ||
|
|
||
| if (url.startsWith('/')) { | ||
| return queryIndex === -1 ? url : url.substring(0, queryIndex); | ||
| } | ||
|
|
||
| return queryIndex === -1 | ||
| ? url.substring(url.indexOf('/')) | ||
| : url.substring(url.indexOf('/'), queryIndex); | ||
| } |
There was a problem hiding this comment.
Path extraction is wrong for absolute URLs.
If url is absolute, current slicing can produce host-prefixed values (e.g., //host/users) that won't match OpenAPI paths keys.
🔧 Proposed fix
export function getPath(url: string): string {
- const queryIndex: number = url.indexOf('?');
-
- if (url.startsWith('/')) {
- return queryIndex === -1 ? url : url.substring(0, queryIndex);
- }
-
- return queryIndex === -1
- ? url.substring(url.indexOf('/'))
- : url.substring(url.indexOf('/'), queryIndex);
+ return new URL(url, 'http://localhost').pathname;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export function getPath(url: string): string { | |
| const queryIndex: number = url.indexOf('?'); | |
| if (url.startsWith('/')) { | |
| return queryIndex === -1 ? url : url.substring(0, queryIndex); | |
| } | |
| return queryIndex === -1 | |
| ? url.substring(url.indexOf('/')) | |
| : url.substring(url.indexOf('/'), queryIndex); | |
| } | |
| export function getPath(url: string): string { | |
| return new URL(url, 'http://localhost').pathname; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@packages/framework/http/libraries/openapi-validation/src/validation/calculations/getPath.ts`
around lines 1 - 11, getPath currently slices absolute URLs and can return
host-prefixed values (e.g., "//host/users"); update getPath(url: string) to
normalize absolute URLs before extracting the path: detect absolute forms (start
with "http://" or "https://") and obtain the pathname (e.g., via the URL
constructor or by stripping scheme+host) then remove any query string; ensure
you still handle relative URLs and queries the same way so the function always
returns only the path portion (e.g., "/users").
| const operationObject: OpenApi3Dot1OperationObject | undefined = ( | ||
| pathItemObject as Record<string, OpenApi3Dot1OperationObject> | ||
| )[method]; | ||
|
|
||
| if (operationObject === undefined) { | ||
| throw new InversifyValidationError( | ||
| InversifyValidationErrorKind.validationFailed, | ||
| `No OpenAPI operation found for method ${method} for path ${path}`, | ||
| ); |
There was a problem hiding this comment.
Avoid broad Record<string, ...> casting for operation lookup.
The current cast allows arbitrary/prototype keys to be treated as operations, which can return non-operation values and shift failures to later code paths with misleading errors. Validate method keys first, then index with constrained keys.
💡 Suggested fix
export function getOperationObject(
openApiObject: OpenApi3Dot1Object,
method: string,
path: string,
): OpenApi3Dot1OperationObject {
@@
- const operationObject: OpenApi3Dot1OperationObject | undefined = (
- pathItemObject as Record<string, OpenApi3Dot1OperationObject>
- )[method];
+ const allowedMethods: ReadonlySet<string> = new Set([
+ 'delete',
+ 'get',
+ 'head',
+ 'options',
+ 'patch',
+ 'post',
+ 'put',
+ 'trace',
+ ]);
+
+ if (!allowedMethods.has(method)) {
+ throw new InversifyValidationError(
+ InversifyValidationErrorKind.validationFailed,
+ `No OpenAPI operation found for method ${method} for path ${path}`,
+ );
+ }
+
+ const operationObject: OpenApi3Dot1OperationObject | undefined =
+ pathItemObject[method as keyof OpenApi3Dot1PathItemObject] as
+ | OpenApi3Dot1OperationObject
+ | undefined;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const operationObject: OpenApi3Dot1OperationObject | undefined = ( | |
| pathItemObject as Record<string, OpenApi3Dot1OperationObject> | |
| )[method]; | |
| if (operationObject === undefined) { | |
| throw new InversifyValidationError( | |
| InversifyValidationErrorKind.validationFailed, | |
| `No OpenAPI operation found for method ${method} for path ${path}`, | |
| ); | |
| export function getOperationObject( | |
| openApiObject: OpenApi3Dot1Object, | |
| method: string, | |
| path: string, | |
| ): OpenApi3Dot1OperationObject { | |
| const allowedMethods: ReadonlySet<string> = new Set([ | |
| 'delete', | |
| 'get', | |
| 'head', | |
| 'options', | |
| 'patch', | |
| 'post', | |
| 'put', | |
| ]); | |
| if (!allowedMethods.has(method)) { | |
| throw new InversifyValidationError( | |
| InversifyValidationErrorKind.validationFailed, | |
| `No OpenAPI operation found for method ${method} for path ${path}`, | |
| ); | |
| } | |
| const operationObject: OpenApi3Dot1OperationObject | undefined = | |
| pathItemObject[method as keyof OpenApi3Dot1PathItemObject] as | |
| | OpenApi3Dot1OperationObject | |
| | undefined; | |
| if (operationObject === undefined) { | |
| throw new InversifyValidationError( | |
| InversifyValidationErrorKind.validationFailed, | |
| `No OpenAPI operation found for method ${method} for path ${path}`, | |
| ); | |
| } | |
| return operationObject; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@packages/framework/http/libraries/openapi-validation/src/validation/calculations/v3Dot1/getOperationObject.ts`
around lines 23 - 31, Replace the broad Record<string, ...> cast and direct
indexing of pathItemObject with a safe key check: first validate that method is
one of the OpenAPI operation keys (e.g.,
'get','put','post','delete','options','head','patch','trace') and that (method
in pathItemObject) is true, then perform a typed index into pathItemObject
(e.g., (pathItemObject as OpenApi3Dot1PathItemObject)[method as keyof
OpenApi3Dot1PathItemObject]) to assign operationObject; if the key is missing
keep throwing the existing InversifyValidationError using
InversifyValidationErrorKind.validationFailed. Ensure you use the existing
variables pathItemObject, method, and operationObject in the guard to avoid the
unsafe Record<string, ...> cast.
| const contentTypes: string[] = Object.keys(requestBodyObject.content); | ||
|
|
||
| if (contentTypes.length === 1) { | ||
| return contentTypes[0] as string; | ||
| } | ||
|
|
||
| throw new InversifyValidationError( | ||
| InversifyValidationErrorKind.validationFailed, | ||
| `Cannot determine content type for request body validation for method ${method}: no content type provided and multiple content types defined in OpenAPI spec (${contentTypes.join( | ||
| ', ', | ||
| )})`, |
There was a problem hiding this comment.
Mirror the same guard/error-text fix here to avoid runtime TypeError.
As written, malformed requestBodyObject.content can bypass domain error handling, and the message is inaccurate for the zero-content case.
💡 Suggested fix
export function inferContentType(
requestBodyObject: OpenApi3Dot1RequestBodyObject,
method: string,
): string {
- const contentTypes: string[] = Object.keys(requestBodyObject.content);
+ const contentTypes: string[] = Object.keys(requestBodyObject.content ?? {});
if (contentTypes.length === 1) {
- return contentTypes[0] as string;
+ return contentTypes[0];
}
+
+ const contentTypesErrorReason: string =
+ contentTypes.length === 0
+ ? 'no content types defined in OpenAPI spec'
+ : `multiple content types defined in OpenAPI spec (${contentTypes.join(', ')})`;
throw new InversifyValidationError(
InversifyValidationErrorKind.validationFailed,
- `Cannot determine content type for request body validation for method ${method}: no content type provided and multiple content types defined in OpenAPI spec (${contentTypes.join(
- ', ',
- )})`,
+ `Cannot determine content type for request body validation for method ${method}: no content type provided and ${contentTypesErrorReason}`,
);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const contentTypes: string[] = Object.keys(requestBodyObject.content); | |
| if (contentTypes.length === 1) { | |
| return contentTypes[0] as string; | |
| } | |
| throw new InversifyValidationError( | |
| InversifyValidationErrorKind.validationFailed, | |
| `Cannot determine content type for request body validation for method ${method}: no content type provided and multiple content types defined in OpenAPI spec (${contentTypes.join( | |
| ', ', | |
| )})`, | |
| const contentTypes: string[] = Object.keys(requestBodyObject.content ?? {}); | |
| if (contentTypes.length === 1) { | |
| return contentTypes[0]; | |
| } | |
| const contentTypesErrorReason: string = | |
| contentTypes.length === 0 | |
| ? 'no content types defined in OpenAPI spec' | |
| : `multiple content types defined in OpenAPI spec (${contentTypes.join(', ')})`; | |
| throw new InversifyValidationError( | |
| InversifyValidationErrorKind.validationFailed, | |
| `Cannot determine content type for request body validation for method ${method}: no content type provided and ${contentTypesErrorReason}`, | |
| ); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@packages/framework/http/libraries/openapi-validation/src/validation/calculations/v3Dot1/inferContentType.ts`
around lines 11 - 21, The current inferContentType logic can throw a runtime
TypeError when requestBodyObject.content is missing or not an object and also
gives an inaccurate message when there are zero content types; update the guard
in inferContentType to first validate that requestBodyObject.content is a plain
object and that Object.keys(requestBodyObject.content) yields at least one
entry, throw an InversifyValidationError (using
InversifyValidationErrorKind.validationFailed) with a clear message for the
zero-content case, and only return contentTypes[0] when contentTypes.length ===
1; reference requestBodyObject.content, contentTypes, inferContentType,
InversifyValidationError and InversifyValidationErrorKind when making the
change.
| describe('having a pathItemObject with the method', () => { | ||
| let openApiObjectFixture: OpenApi3Dot2Object; | ||
| let methodFixture: string; | ||
| let pathFixture: string; | ||
| let operationObjectFixture: OpenApi3Dot2OperationObject; | ||
| let pathItemObjectFixture: OpenApi3Dot2PathItemObject; | ||
|
|
||
| beforeAll(() => { | ||
| methodFixture = 'get'; | ||
| pathFixture = '/users'; | ||
| operationObjectFixture = { | ||
| responses: {}, | ||
| }; | ||
| pathItemObjectFixture = { | ||
| [methodFixture]: operationObjectFixture, | ||
| }; | ||
| openApiObjectFixture = Symbol() as unknown as OpenApi3Dot2Object; | ||
| }); | ||
|
|
||
| describe('when called', () => { | ||
| let result: unknown; | ||
|
|
||
| beforeAll(() => { | ||
| vitest | ||
| .mocked(getPathItemObject) | ||
| .mockReturnValueOnce(pathItemObjectFixture); | ||
|
|
||
| result = getOperationObject( | ||
| openApiObjectFixture, | ||
| methodFixture, | ||
| pathFixture, | ||
| ); | ||
| }); | ||
|
|
||
| afterAll(() => { | ||
| vitest.clearAllMocks(); | ||
| }); | ||
|
|
||
| it('should call getPathItemObject()', () => { | ||
| expect(getPathItemObject).toHaveBeenCalledExactlyOnceWith( | ||
| openApiObjectFixture, | ||
| pathFixture, | ||
| ); | ||
| }); | ||
|
|
||
| it('should return expected result', () => { | ||
| expect(result).toBe(operationObjectFixture); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Add a case for uppercase HTTP methods.
Given runtime methods are often uppercase, add a GET input case to lock expected lookup behavior and prevent regressions.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@packages/framework/http/libraries/openapi-validation/src/validation/calculations/v3Dot2/getOperationObject.spec.ts`
around lines 19 - 67, Add a unit test that covers uppercase HTTP methods:
duplicate the existing "having a pathItemObject with the method" scenario but
set methodFixture to 'GET' (uppercase) and assert
getOperationObject(openApiObjectFixture, methodFixture, pathFixture) still calls
getPathItemObject(openApiObjectFixture, pathFixture) and returns the same
operationObjectFixture; this ensures getOperationObject correctly normalizes or
handles uppercase methods when looking up pathItemObject entries.
| method: string, | ||
| path: string, | ||
| ): OpenApi3Dot2OperationObject { | ||
| const pathItemObject: OpenApi3Dot2PathItemObject = getPathItemObject( | ||
| openApiObject, | ||
| path, | ||
| ); | ||
|
|
||
| const operationObject: OpenApi3Dot2OperationObject | undefined = ( | ||
| pathItemObject as Record<string, OpenApi3Dot2OperationObject> | ||
| )[method]; | ||
|
|
||
| if (operationObject === undefined) { | ||
| throw new InversifyValidationError( | ||
| InversifyValidationErrorKind.validationFailed, | ||
| `No OpenAPI operation found for method ${method} for path ${path}`, | ||
| ); |
There was a problem hiding this comment.
Normalize method before lookup and avoid wide string-key cast.
Using raw method can miss valid operations for uppercase inputs (e.g., GET), and the Record<string, ...> cast hides non-operation keys.
🔧 Proposed fix
export function getOperationObject(
openApiObject: OpenApi3Dot2Object,
method: string,
path: string,
): OpenApi3Dot2OperationObject {
@@
- const operationObject: OpenApi3Dot2OperationObject | undefined = (
- pathItemObject as Record<string, OpenApi3Dot2OperationObject>
- )[method];
+ type PathItemMethodKey =
+ | 'delete'
+ | 'get'
+ | 'head'
+ | 'options'
+ | 'patch'
+ | 'post'
+ | 'put'
+ | 'query'
+ | 'trace';
+
+ const normalizedMethod: PathItemMethodKey = method.toLowerCase() as PathItemMethodKey;
+ const operationObject: OpenApi3Dot2OperationObject | undefined =
+ pathItemObject[normalizedMethod];
@@
- `No OpenAPI operation found for method ${method} for path ${path}`,
+ `No OpenAPI operation found for method ${method} for path ${path}`,
);
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@packages/framework/http/libraries/openapi-validation/src/validation/calculations/v3Dot2/getOperationObject.ts`
around lines 15 - 31, The lookup in getOperationObject uses the raw method and a
wide Record<string, OpenApi3Dot2OperationObject> cast which hides non-operation
keys and will miss uppercase methods; normalize the incoming method to lowercase
(e.g., const methodKey = method.toLowerCase()) and use a typed key lookup
against OpenApi3Dot2PathItemObject (cast to keyof OpenApi3Dot2PathItemObject)
instead of Record<string,...> so you only access known operation properties;
keep the existing InversifyValidationError logic when the operation is
undefined.
| const contentTypes: string[] = Object.keys(requestBodyObject.content); | ||
|
|
||
| if (contentTypes.length === 1) { | ||
| return contentTypes[0] as string; | ||
| } | ||
|
|
||
| throw new InversifyValidationError( | ||
| InversifyValidationErrorKind.validationFailed, | ||
| `Cannot determine content type for request body validation for method ${method}: no content type provided and multiple content types defined in OpenAPI spec (${contentTypes.join( | ||
| ', ', | ||
| )})`, |
There was a problem hiding this comment.
Handle missing/empty content explicitly before key extraction.
This path can currently throw a native TypeError when content is absent/null, and the thrown validation message is misleading when there are zero content types (it always says “multiple”). Please normalize and branch by count before throwing.
💡 Suggested fix
export function inferContentType(
requestBodyObject: OpenApi3Dot2RequestBodyObject,
method: string,
): string {
- const contentTypes: string[] = Object.keys(requestBodyObject.content);
+ const contentTypes: string[] = Object.keys(requestBodyObject.content ?? {});
if (contentTypes.length === 1) {
- return contentTypes[0] as string;
+ return contentTypes[0];
}
+
+ const contentTypesErrorReason: string =
+ contentTypes.length === 0
+ ? 'no content types defined in OpenAPI spec'
+ : `multiple content types defined in OpenAPI spec (${contentTypes.join(', ')})`;
throw new InversifyValidationError(
InversifyValidationErrorKind.validationFailed,
- `Cannot determine content type for request body validation for method ${method}: no content type provided and multiple content types defined in OpenAPI spec (${contentTypes.join(
- ', ',
- )})`,
+ `Cannot determine content type for request body validation for method ${method}: no content type provided and ${contentTypesErrorReason}`,
);
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@packages/framework/http/libraries/openapi-validation/src/validation/calculations/v3Dot2/inferContentType.ts`
around lines 11 - 21, The inferContentType logic should guard for a
missing/nullable requestBodyObject.content and handle zero vs multiple entries
distinctly: first normalize contentTypes = requestBodyObject.content ?
Object.keys(requestBodyObject.content) : []; then if contentTypes.length === 1
return that value; if length === 0 throw an InversifyValidationError
(InversifyValidationErrorKind.validationFailed) with a message stating no
content type provided for method ${method}; otherwise (length > 1) throw the
existing multi-content error including contentTypes.join(', ') to preserve
current behavior.
| describe('when a valid POST /users request is made with single content type and no Content-Type header', () => { | ||
| let response: Response; | ||
|
|
||
| beforeAll(async () => { | ||
| response = await fetch( | ||
| `http://${server.host}:${server.port.toString()}/users`, | ||
| { | ||
| body: JSON.stringify({ name: 'Alice' }), | ||
| headers: { 'Content-Type': 'application/json' }, | ||
| method: 'POST', | ||
| }, | ||
| ); |
There was a problem hiding this comment.
The fallback scenario currently sends a Content-Type header, so the branch is not actually tested.
Line 287 sets Content-Type: application/json while the scenario claims no header. This bypasses the intended content-type inference path.
Proposed fix
describe('when a valid POST /users request is made with single content type and no Content-Type header', () => {
@@
beforeAll(async () => {
response = await fetch(
`http://${server.host}:${server.port.toString()}/users`,
{
- body: JSON.stringify({ name: 'Alice' }),
- headers: { 'Content-Type': 'application/json' },
+ body: new TextEncoder().encode(JSON.stringify({ name: 'Alice' })),
method: 'POST',
},
);
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| describe('when a valid POST /users request is made with single content type and no Content-Type header', () => { | |
| let response: Response; | |
| beforeAll(async () => { | |
| response = await fetch( | |
| `http://${server.host}:${server.port.toString()}/users`, | |
| { | |
| body: JSON.stringify({ name: 'Alice' }), | |
| headers: { 'Content-Type': 'application/json' }, | |
| method: 'POST', | |
| }, | |
| ); | |
| describe('when a valid POST /users request is made with single content type and no Content-Type header', () => { | |
| let response: Response; | |
| beforeAll(async () => { | |
| response = await fetch( | |
| `http://${server.host}:${server.port.toString()}/users`, | |
| { | |
| body: new TextEncoder().encode(JSON.stringify({ name: 'Alice' })), | |
| method: 'POST', | |
| }, | |
| ); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@packages/framework/http/libraries/openapi-validation/src/validation/pipes/v3Dot2/OpenApiValidationPipe.int.spec.ts`
around lines 279 - 290, The test block "when a valid POST /users request is made
with single content type and no Content-Type header" is sending a Content-Type
header in the beforeAll fetch call, so change the fetch invocation inside that
describe/beforeAll to omit the 'Content-Type' header (remove headers or pass an
empty headers object) so the fallback/content-type inference branch is actually
exercised; ensure you update the fetch call in this spec (the beforeAll that
sets response) and keep the body as JSON stringified as-is.
There was a problem hiding this comment.
🧹 Nitpick comments (2)
openspec/changes/add-openapi-body-validation/proposal.md (1)
13-13: Add an explicit migration subsection for the@Validate() +@Body()replacement.Line 13 states the old approach is replaced, but the proposal should include a short “before/after” migration note and compatibility expectation (supported/deprecated/removed) to reduce ambiguity for adopters.
Based on learnings: Maintain API compatibility across versions in inversifyjs/common - a foundation package whose changes affect the entire ecosystem.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@openspec/changes/add-openapi-body-validation/proposal.md` at line 13, The proposal currently states that the original `@Validate()` + `@Body()` approach is replaced but lacks a migration note; add a concise "Migration" subsection to the proposal that shows a before/after example converting `@Validate()` + `@Body()` usage to the new pipe-based approach, state the compatibility expectation (e.g., supported, deprecated, or removed) for `@Validate()`, `@Body()`, and `PipeMetadata`, and mention any necessary steps (e.g., updating to the new pipe that requires request context) so adopters know how to migrate; reference the symbols `@Validate()`, `@Body()`, and `PipeMetadata` in that subsection.packages/framework/http/libraries/openapi-validation/src/metadata/decorators/ValidatedBody.spec.ts (1)
110-355: Extract repeated handler setup into reusable fixture builders.The per-scenario setup is heavily duplicated (request/response/body/method/url/options/expected object). Please move this into fixture classes with static helpers to reduce drift and simplify future behavior changes.
♻️ Refactor sketch
+class ValidatedBodyHandlerFixture { + public static buildOptions( + overrides: Partial<CustomParameterDecoratorHandlerOptions<unknown, unknown>>, + ): CustomParameterDecoratorHandlerOptions<unknown, unknown> { + return { + getBody: vitest.fn(), + getHeaders: vitest.fn(), + getMethod: vitest.fn(), + getUrl: vitest.fn(), + ...overrides, + } as unknown as CustomParameterDecoratorHandlerOptions<unknown, unknown>; + } + + public static buildExpected(params: { + body: unknown; + contentType: string | undefined; + method: string; + url: string; + }): BodyValidationInputParam<unknown> { + return { + body: params.body, + contentType: params.contentType, + method: params.method, + type: validatedInputParamBodyType, + url: params.url, + }; + } +}As per coding guidelines
**/*.{spec,int.spec}.ts: Create reusable test fixtures as classes with static methods following fixture guidelines from docs/testing/fixtures.md.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/framework/http/libraries/openapi-validation/src/metadata/decorators/ValidatedBody.spec.ts` around lines 110 - 355, The tests repeat identical setup for requestFixture/responseFixture/bodyFixture/methodFixture/urlFixture/options and expected BodyValidationInputParam across many describe blocks; refactor by creating a TestFixture class (e.g., BodyHandlerTestFixture) with static builders that produce the options object (matching CustomParameterDecoratorHandlerOptions) and expected result, and helper methods to call handler and return result; update each describe to call the fixtures instead of duplicating setup and keep references to handler, getMimeType, BodyValidationInputParam, and validatedInputParamBodyType so behavior and assertions remain unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@openspec/changes/add-openapi-body-validation/proposal.md`:
- Line 13: The proposal currently states that the original `@Validate()` +
`@Body()` approach is replaced but lacks a migration note; add a concise
"Migration" subsection to the proposal that shows a before/after example
converting `@Validate()` + `@Body()` usage to the new pipe-based approach, state
the compatibility expectation (e.g., supported, deprecated, or removed) for
`@Validate()`, `@Body()`, and `PipeMetadata`, and mention any necessary steps
(e.g., updating to the new pipe that requires request context) so adopters know
how to migrate; reference the symbols `@Validate()`, `@Body()`, and
`PipeMetadata` in that subsection.
In
`@packages/framework/http/libraries/openapi-validation/src/metadata/decorators/ValidatedBody.spec.ts`:
- Around line 110-355: The tests repeat identical setup for
requestFixture/responseFixture/bodyFixture/methodFixture/urlFixture/options and
expected BodyValidationInputParam across many describe blocks; refactor by
creating a TestFixture class (e.g., BodyHandlerTestFixture) with static builders
that produce the options object (matching
CustomParameterDecoratorHandlerOptions) and expected result, and helper methods
to call handler and return result; update each describe to call the fixtures
instead of duplicating setup and keep references to handler, getMimeType,
BodyValidationInputParam, and validatedInputParamBodyType so behavior and
assertions remain unchanged.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 1e0d7a25-cbb3-4f04-bd16-65cb2aeb1f8c
📒 Files selected for processing (8)
openspec/changes/add-openapi-body-validation/design.mdopenspec/changes/add-openapi-body-validation/proposal.mdopenspec/changes/add-openapi-body-validation/specs/openapi-body-validation/spec.mdopenspec/changes/add-openapi-body-validation/tasks.mdpackages/docs/services/inversify-framework-site/blog/2026-04-04-openapi-body-validation/index.mdxpackages/docs/services/inversify-framework-site/validation-docs/openapi/api.mdxpackages/docs/services/inversify-framework-site/validation-docs/openapi/introduction.mdxpackages/framework/http/libraries/openapi-validation/src/metadata/decorators/ValidatedBody.spec.ts
✅ Files skipped from review due to trivial changes (3)
- packages/docs/services/inversify-framework-site/validation-docs/openapi/introduction.mdx
- packages/docs/services/inversify-framework-site/validation-docs/openapi/api.mdx
- openspec/changes/add-openapi-body-validation/design.md
Benchmarks results:> root@ benchmarks:http:cjs:low /home/runner/work/monorepo/monorepo
> pnpm run --filter "@inversifyjs/http-benchmarks" benchmarks:run:cjs:low
> @inversifyjs/http-benchmarks@ benchmarks:run:cjs:low /home/runner/work/monorepo/monorepo/packages/framework/http/tools/http-benchmarks
> BUILD_TARGET=cjs VUS=128 ./bin/run-cjs.cjs
[CJS] Running benchmarks...
Express Basic Get Request
┌─────────┬───────────────────────────┬──────────────────┬──────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name │ Latency avg (ms) │ Latency med (ms) │ Throughput avg (req/s) │ Samples │
├─────────┼───────────────────────────┼──────────────────┼──────────────────┼────────────────────────┼─────────┤
│ 0 │ 'currentInversifyExpress' │ '5.509' │ '4.856' │ '11439.922' │ 114576 │
│ 1 │ 'express' │ '5.100' │ '4.662' │ '12382.908' │ 123904 │
│ 2 │ 'NestJSExpress' │ '5.527' │ '5.126' │ '11428.177' │ 114407 │
└─────────┴───────────────────────────┴──────────────────┴──────────────────┴────────────────────────┴─────────┘
currentInversifyExpress vs express Speedup: 0.924x
currentInversifyExpress vs NestJSExpress Speedup: 1.001x
Express v4 Basic Get Request
┌─────────┬────────────────────────────┬──────────────────┬──────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name │ Latency avg (ms) │ Latency med (ms) │ Throughput avg (req/s) │ Samples │
├─────────┼────────────────────────────┼──────────────────┼──────────────────┼────────────────────────┼─────────┤
│ 0 │ 'currentInversifyExpress4' │ '5.444' │ '5.136' │ '11613.406' │ 116278 │
│ 1 │ 'express4' │ '5.157' │ '4.771' │ '12245.256' │ 122593 │
└─────────┴────────────────────────────┴──────────────────┴──────────────────┴────────────────────────┴─────────┘
currentInversifyExpress4 vs express4 Speedup: 0.948x
Fastify Basic Get Request
┌─────────┬───────────────────────────┬──────────────────┬──────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name │ Latency avg (ms) │ Latency med (ms) │ Throughput avg (req/s) │ Samples │
├─────────┼───────────────────────────┼──────────────────┼──────────────────┼────────────────────────┼─────────┤
│ 0 │ 'currentInversifyFastify' │ '2.979' │ '2.414' │ '20821.892' │ 208408 │
│ 1 │ 'fastify' │ '2.690' │ '2.124' │ '22895.550' │ 229044 │
│ 2 │ 'NestJSFastify' │ '3.111' │ '2.604' │ '20020.216' │ 200339 │
└─────────┴───────────────────────────┴──────────────────┴──────────────────┴────────────────────────┴─────────┘
currentInversifyFastify vs fastify Speedup: 0.909x
currentInversifyFastify vs NestJSFastify Speedup: 1.040x
Hono Basic Get Request
┌─────────┬────────────────────────┬──────────────────┬──────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name │ Latency avg (ms) │ Latency med (ms) │ Throughput avg (req/s) │ Samples │
├─────────┼────────────────────────┼──────────────────┼──────────────────┼────────────────────────┼─────────┤
│ 0 │ 'currentInversifyHono' │ '3.336' │ '2.799' │ '18679.679' │ 186914 │
│ 1 │ 'hono' │ '2.951' │ '2.394' │ '20969.801' │ 209819 │
└─────────┴────────────────────────┴──────────────────┴──────────────────┴────────────────────────┴─────────┘
currentInversifyHono vs hono Speedup: 0.891x
uWebSockets Basic Get Request
┌─────────┬───────────────────────────────┬──────────────────┬──────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name │ Latency avg (ms) │ Latency med (ms) │ Throughput avg (req/s) │ Samples │
├─────────┼───────────────────────────────┼──────────────────┼──────────────────┼────────────────────────┼─────────┤
│ 0 │ 'currentInversifyUwebsockets' │ '2.038' │ '1.408' │ '29690.016' │ 296947 │
│ 1 │ 'uwebsockets' │ '1.983' │ '1.428' │ '30604.556' │ 306104 │
└─────────┴───────────────────────────────┴──────────────────┴──────────────────┴────────────────────────┴─────────┘
currentInversifyUwebsockets vs uwebsockets Speedup: 0.970x |
The actual test case can't hardly be covered
Benchmarks results:> root@ benchmarks:http:cjs:low /home/runner/work/monorepo/monorepo
> pnpm run --filter "@inversifyjs/http-benchmarks" benchmarks:run:cjs:low
> @inversifyjs/http-benchmarks@ benchmarks:run:cjs:low /home/runner/work/monorepo/monorepo/packages/framework/http/tools/http-benchmarks
> BUILD_TARGET=cjs VUS=128 ./bin/run-cjs.cjs
[CJS] Running benchmarks...
Express Basic Get Request
┌─────────┬───────────────────────────┬──────────────────┬──────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name │ Latency avg (ms) │ Latency med (ms) │ Throughput avg (req/s) │ Samples │
├─────────┼───────────────────────────┼──────────────────┼──────────────────┼────────────────────────┼─────────┤
│ 0 │ 'currentInversifyExpress' │ '5.419' │ '5.017' │ '11632.384' │ 116506 │
│ 1 │ 'express' │ '5.098' │ '4.630' │ '12368.322' │ 123884 │
│ 2 │ 'NestJSExpress' │ '5.623' │ '5.162' │ '11231.053' │ 112441 │
└─────────┴───────────────────────────┴──────────────────┴──────────────────┴────────────────────────┴─────────┘
currentInversifyExpress vs express Speedup: 0.940x
currentInversifyExpress vs NestJSExpress Speedup: 1.036x
Express v4 Basic Get Request
┌─────────┬────────────────────────────┬──────────────────┬──────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name │ Latency avg (ms) │ Latency med (ms) │ Throughput avg (req/s) │ Samples │
├─────────┼────────────────────────────┼──────────────────┼──────────────────┼────────────────────────┼─────────┤
│ 0 │ 'currentInversifyExpress4' │ '5.374' │ '5.059' │ '11759.725' │ 117676 │
│ 1 │ 'express4' │ '5.325' │ '5.060' │ '11861.047' │ 118758 │
└─────────┴────────────────────────────┴──────────────────┴──────────────────┴────────────────────────┴─────────┘
currentInversifyExpress4 vs express4 Speedup: 0.991x
Fastify Basic Get Request
┌─────────┬───────────────────────────┬──────────────────┬──────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name │ Latency avg (ms) │ Latency med (ms) │ Throughput avg (req/s) │ Samples │
├─────────┼───────────────────────────┼──────────────────┼──────────────────┼────────────────────────┼─────────┤
│ 0 │ 'currentInversifyFastify' │ '2.912' │ '2.425' │ '21229.797' │ 212432 │
│ 1 │ 'fastify' │ '2.702' │ '2.178' │ '22913.986' │ 229303 │
│ 2 │ 'NestJSFastify' │ '3.147' │ '2.632' │ '19764.773' │ 197769 │
└─────────┴───────────────────────────┴──────────────────┴──────────────────┴────────────────────────┴─────────┘
currentInversifyFastify vs fastify Speedup: 0.926x
currentInversifyFastify vs NestJSFastify Speedup: 1.074x
Hono Basic Get Request
┌─────────┬────────────────────────┬──────────────────┬──────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name │ Latency avg (ms) │ Latency med (ms) │ Throughput avg (req/s) │ Samples │
├─────────┼────────────────────────┼──────────────────┼──────────────────┼────────────────────────┼─────────┤
│ 0 │ 'currentInversifyHono' │ '3.306' │ '2.856' │ '18815.120' │ 188278 │
│ 1 │ 'hono' │ '2.955' │ '2.467' │ '20977.526' │ 209908 │
└─────────┴────────────────────────┴──────────────────┴──────────────────┴────────────────────────┴─────────┘
currentInversifyHono vs hono Speedup: 0.897x
uWebSockets Basic Get Request
┌─────────┬───────────────────────────────┬──────────────────┬──────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name │ Latency avg (ms) │ Latency med (ms) │ Throughput avg (req/s) │ Samples │
├─────────┼───────────────────────────────┼──────────────────┼──────────────────┼────────────────────────┼─────────┤
│ 0 │ 'currentInversifyUwebsockets' │ '2.045' │ '1.432' │ '29577.038' │ 295819 │
│ 1 │ 'uwebsockets' │ '2.008' │ '1.442' │ '30239.769' │ 302446 │
└─────────┴───────────────────────────────┴──────────────────┴──────────────────┴────────────────────────┴─────────┘
currentInversifyUwebsockets vs uwebsockets Speedup: 0.978x |
Context
See #1701.
Summary by CodeRabbit
New Features
Documentation
Tests
Chores