Repository: RobinTail/express-zod-api (ts)

Issue:
Parameter descriptions are only outputted on referenced schema, making them invisible in docs
We recently upgraded to express-zod-api v10. This, in general, massively cleaned up our OpenAPI schema and made it work much better with code generation tools (thanks for that! 🙏). Still, it seems like it also introduced a new bug or potentially a new gotcha we weren't aware of.

Here's how to reproduce the problem:

1. Create an endpoint or middleware that has an input with a description:

```ts
export const testEndpoint = endpointFactory.build({
  input: z.object({
    cursor: z
      .string()
      .optional()
      .describe(
        'An optional cursor string used for pagination.' +
          ' This can be retrieved from the `next` property of the previous page response.',
      ),
  }),
  output: z.object({}),
  handler: async () => {
    return {}
  },
})
```

2. Generate the OpenAPI schema and look at the output:

```yml
parameters:
  - name: cursor
    in: query
    required: false
    description: GET /hris/employees parameter
    schema:
      $ref: "#/components/schemas/GetHrisEmployeesParameterCursor"
```

```yml
GetHrisEmployeesParameterCursor:
  type: string
  description: An optional cursor string used for pagination. This can be
    retrieved from the `next` property of the previous page response.
```

The custom description is outputted, but only on the referenced schema, not the actual parameter. On the parameter, the description is not only missing (which might cause documentation tools to pull the description from the schema), but specifically set to something generic (`GET /hris/employees parameter`).


The result is that no helpful information makes it all the way through to the docs:

<img width="256" alt="image" src="https://user-images.githubusercontent.com/29176678/230959250-f2430772-4a81-4a5e-ab61-0f78b15ec2ae.png">

I'm wondering: Is this expected behavior, and there's a workaround, or is this a bug introduced by the new major release? 🤔

Pull request description:
Request param description fix
Fixes #929 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c4d37e46..64bcd9b3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,11 @@
 
 ## Version 10
 
+### v10.1.3
+
+- Fixed issue #929, found and reported by [@shroudedcode](https://github.com/shroudedcode).
+  - Customized description of request parameters have not been depicted correctly when generating the documentation.
+
 ### v10.1.2
 
 - Fixed issue #907, found and reported by [@McMerph](https://github.com/McMerph).
diff --git a/example/example.swagger.yaml b/example/example.swagger.yaml
index 068b5fe8..77484517 100644
--- a/example/example.swagger.yaml
+++ b/example/example.swagger.yaml
@@ -84,7 +84,7 @@ paths:
         - name: id
           in: query
           required: true
-          description: GET /v1/user/retrieve parameter
+          description: a numeric string containing the id of the user
           schema:
             type: string
             pattern: /\d+/
diff --git a/src/open-api-helpers.ts b/src/open-api-helpers.ts
index 7efa5641..a3aea324 100644
--- a/src/open-api-helpers.ts
+++ b/src/open-api-helpers.ts
@@ -642,7 +642,9 @@ export const depictRequestParams = ({
         name,
         in: isPathParam(name) ? "path" : "query",
         required: !shape[name].isOptional(),
-        description: `${method.toUpperCase()} ${path} ${clue}`,
+        description:
+          (oas30.isSchemaObject(depicted) && depicted.description) ||
+          `${method.toUpperCase()} ${path} ${clue}`,
         schema: result,
         ...depictParamExamples(schema, false, name),
       };


Test patch:
diff --git a/tests/unit/__snapshots__/open-api.spec.ts.snap b/tests/unit/__snapshots__/open-api.spec.ts.snap
index 636b5cdd..6db37c8b 100644
--- a/tests/unit/__snapshots__/open-api.spec.ts.snap
+++ b/tests/unit/__snapshots__/open-api.spec.ts.snap
@@ -374,6 +374,92 @@ servers:
 "
 `;
 
+exports[`Open API generator Metadata Issue #929: the location of the custom description should be on the param level 1`] = `
+"openapi: 3.0.0
+info:
+  title: Testing Metadata:description
+  version: 3.4.5
+paths:
+  /hris/employees:
+    get:
+      operationId: GetHrisEmployees
+      responses:
+        "200":
+          description: GET /hris/employees Successful response
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/GetHrisEmployeesSuccessfulResponse"
+        "400":
+          description: GET /hris/employees Error response
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/GetHrisEmployeesErrorResponse"
+              examples:
+                example1:
+                  value:
+                    status: error
+                    error:
+                      message: Sample error message
+      parameters:
+        - name: cursor
+          in: query
+          required: false
+          description: An optional cursor string used for pagination. This can be
+            retrieved from the \`next\` property of the previous page response.
+          schema:
+            $ref: "#/components/schemas/GetHrisEmployeesParameterCursor"
+components:
+  schemas:
+    GetHrisEmployeesParameterCursor:
+      type: string
+      description: An optional cursor string used for pagination. This can be
+        retrieved from the \`next\` property of the previous page response.
+    GetHrisEmployeesSuccessfulResponse:
+      type: object
+      properties:
+        status:
+          type: string
+          enum:
+            - success
+        data:
+          type: object
+          properties: {}
+      required:
+        - status
+        - data
+    GetHrisEmployeesErrorResponse:
+      type: object
+      properties:
+        status:
+          type: string
+          enum:
+            - error
+        error:
+          type: object
+          properties:
+            message:
+              type: string
+          required:
+            - message
+      required:
+        - status
+        - error
+  responses: {}
+  parameters: {}
+  examples: {}
+  requestBodies: {}
+  headers: {}
+  securitySchemes: {}
+  links: {}
+  callbacks: {}
+tags: []
+servers:
+  - url: http://example.com
+"
+`;
+
 exports[`Open API generator Metadata should merge endpoint handler examples with its middleware examples 1`] = `
 "openapi: 3.0.0
 info:
@@ -828,7 +914,7 @@ paths:
         - name: str
           in: query
           required: true
-          description: GET /v1/getSomething parameter
+          description: here is the test
           schema:
             type: string
             description: here is the test
@@ -2139,7 +2225,7 @@ paths:
         - name: id
           in: query
           required: true
-          description: GET /v1/user/retrieve parameter
+          description: a numeric string containing the id of the user
           schema:
             type: string
             pattern: /\\d+/
@@ -2489,7 +2575,7 @@ paths:
         - name: id
           in: query
           required: true
-          description: GET /v1/user/retrieve parameter
+          description: a numeric string containing the id of the user
           schema:
             $ref: "#/components/schemas/GetV1UserRetrieveParameterId"
   "/v1/user/{id}":
diff --git a/tests/unit/open-api.spec.ts b/tests/unit/open-api.spec.ts
index f6186dd1..b044e7dc 100644
--- a/tests/unit/open-api.spec.ts
+++ b/tests/unit/open-api.spec.ts
@@ -872,6 +872,35 @@ describe("Open API generator", () => {
       expect(spec).toMatchSnapshot();
     });
 
+    test("Issue #929: the location of the custom description should be on the param level", () => {
+      const spec = new OpenAPI({
+        composition: "components",
+        config: sampleConfig,
+        routing: {
+          hris: {
+            employees: defaultEndpointsFactory.build({
+              method: "get",
+              input: z.object({
+                cursor: z
+                  .string()
+                  .optional()
+                  .describe(
+                    "An optional cursor string used for pagination." +
+                      " This can be retrieved from the `next` property of the previous page response."
+                  ),
+              }),
+              output: z.object({}),
+              handler: async () => ({}),
+            }),
+          },
+        },
+        version: "3.4.5",
+        title: "Testing Metadata:description",
+        serverUrl: "http://example.com",
+      }).getSpecAsYaml();
+      expect(spec).toMatchSnapshot();
+    });
+
     test("should pass over the example of an individual parameter", () => {
       const spec = new OpenAPI({
         config: sampleConfig,