Repository: cdimascio/express-openapi-validator
Pull request #962 (2024-09-04 22:17:21)

fix: Dereference path parameters
### 🐞 The Bug
The OpenAPI spec loader has a `discoverRoutes` method which explores an OpenAPI document and gathers information about the paths and parameters used.
The list of discovered path parameters is used to install parameter-specific middleware in `src/openapi.validator.ts#installPathParams`
Path parameters declared with `$ref` were not detected in the `discoverRoutes` implementation, leading to the un-coerced values being used.

### The Fix 🔧 
By dereferencing each path parameter when building this list, we should see the same behavior for referenced path parameters and for inline path parameters.

Closes https://github.com/cdimascio/express-openapi-validator/issues/803


Patch:
diff --git a/src/framework/openapi.spec.loader.ts b/src/framework/openapi.spec.loader.ts
index 3ccb148..73dcfe1 100644
--- a/src/framework/openapi.spec.loader.ts
+++ b/src/framework/openapi.spec.loader.ts
@@ -1,3 +1,4 @@
+import { dereferenceParameter } from '../middlewares/parsers/util';
 import { OpenAPIFramework } from './index';
 import {
   OpenAPIFrameworkAPIContext,
@@ -68,13 +69,15 @@ export class OpenApiSpecLoader {
               ) {
                 continue;
               }
-              const pathParams = new Set<string>();
-              const parameters = [...schema.parameters ?? [], ...methods.parameters ?? []]
-              for (const param of parameters) {
-                if (param.in === 'path') {
-                  pathParams.add(param.name);
-                }
-              }
+
+              const pathParams = [
+                ...(schema.parameters ?? []),
+                ...(methods.parameters ?? []),
+              ]
+                .map(param => dereferenceParameter(apiDoc, param))
+                .filter(param => param.in === 'path')
+                .map(param => param.name);
+
               const openApiRoute = `${bp}${path}`;
               const expressRoute = `${openApiRoute}`
                 .split(':')
@@ -86,7 +89,7 @@ export class OpenApiSpecLoader {
                 expressRoute,
                 openApiRoute,
                 method: method.toUpperCase(),
-                pathParams: Array.from(pathParams),
+                pathParams: Array.from(new Set(pathParams)),
               });
             }
           }


Test patch:
diff --git a/test/operation.handler.spec.ts b/test/operation.handler.spec.ts
index b7e60fe..771013a 100644
--- a/test/operation.handler.spec.ts
+++ b/test/operation.handler.spec.ts
@@ -120,4 +120,12 @@ describe('custom operation handler', () => {
       });
   });
 
+  it('should coerce path parameters', async () => {
+    return request(app)
+      .get(`${basePath}/users/123/info`)
+      .expect(200)
+      .then((r) => {
+        expect(r.text).to.be.equal('{"id":123}');
+      });
+  });
 });
diff --git a/test/resources/eov-operations.modulepath.yaml b/test/resources/eov-operations.modulepath.yaml
index 1eb397d..8b0ca08 100644
--- a/test/resources/eov-operations.modulepath.yaml
+++ b/test/resources/eov-operations.modulepath.yaml
@@ -185,8 +185,35 @@ paths:
                 properties:
                   success:
                     type: boolean
+  /users/{userID}/info:
+    get:
+      description: Get info about a User
+      summary: Get info about a User
+      operationId: user.info
+      security: []
+      parameters: 
+        - $ref: '#/components/parameters/userID'
+      responses:
+        '200':
+          description: Returns info about a User.
+          content:
+            'application/json':
+              schema:
+                type: object
+                properties:
+                  id:
+                    $ref: '#/components/schemas/UserID'
 
 components:
+  parameters:
+    userID:
+      name: userID
+      in: path
+      required: true
+      schema: 
+        $ref: '#/components/schemas/UserID'
+        # type: number
+
   schemas:
     Pet:
       required:
@@ -210,6 +237,9 @@ components:
         - dog
         - cat
 
+    UserID:
+      type: number
+
     Error:
       required:
         - code
diff --git a/test/resources/routes/user.js b/test/resources/routes/user.js
new file mode 100644
index 0000000..9edb0b5
--- /dev/null
+++ b/test/resources/routes/user.js
@@ -0,0 +1,3 @@
+module.exports = {
+  info: (req, res) => res.status(200).send({ id: req.params.userID })
+};