Repository: Redocly/redocly-cli (ts)

Issue:
Path parameter isn't defined
**Describe the bug**
If a path has a named placeholder but doesn't have a path parameter, then we should show the problem.

> Each template expression in the path MUST correspond to a path parameter that is included in the Path Item itself and/or in each of the Path Item's Operations.

**To Reproduce**
Steps to reproduce the behavior:
1. Given this `.redocly.yaml` file (just the `recommended` rules)
2. Contact Adam or see a sample below.
3. Run this command with these arguments... `openapi lint`
4. See no error

**Expected behavior**
I expect to see this error: https://redoc.ly/docs/cli/built-in-rules/#path-parameters-defined

**Logs**

```
validating /home/adam/Downloads/2020-09-23-13-03-openapi-od_3p8p9x.yaml...
/home/adam/Downloads/2020-09-23-13-03-openapi-od_3p8p9x.yaml: validated in in 86ms

Woohoo! Your OpenAPI definition is valid. 🎉
```

**OpenAPI definition**

This is an excerpt.

The one with the path template was defined first:
```
paths:
  /something/{id}:
   parameters:
     - name: foo
       in: header
       required: true
       schema:
         type: string
  /something:
# ...
```

**`openapi-cli` Version(s)**
1.0.0-beta.14

**`Node.js` Version(s)**
v12.6.0

**Additional context**

Contact Adam if you need a full OpenAPI definition.

Pull request description:
fix: path parameter not defined
## What/Why/How?
If parameters ware not defined in the other operation but exist in first lint did not catch error.
For example 
```yaml

paths:
  /case/{a}:
    get:
      parameters: 
        - name: a
          in: path
          schema: 
            type: number 
      summary: Case GET 
      description: Should not fail cause parameters exist
      responses:
        '200':
          description: Successful operation

    post: 
      summary: Case POST
      description: Should fail cause no parameters
      responses:
        '200':
          description: Successful operation
```
<img width="411" alt="Screenshot 2022-09-26 at 16 04 04" src="https://user-images.githubusercontent.com/52038205/192283750-06901ec2-8221-47eb-8d07-0dee3bc43f53.png">


## Reference
Closes #211 

## Testing

## Screenshots (optional)
Now it throw appropriate error
<img width="678" alt="Screenshot 2022-09-26 at 16 04 46" src="https://user-images.githubusercontent.com/52038205/192284014-f5ed06c5-a33d-4149-8e1d-5bb1d84b6fe4.png">

## Check yourself

- [ ] Code is linted
- [ ] Tested with redoc/reference-docs/workflows
- [ ] All new/updated code is covered with tests

## Security

- [ ] Security impact of change has been considered
- [ ] Code follows company security practices and guidelines


Patch:
diff --git a/packages/core/src/rules/common/path-params-defined.ts b/packages/core/src/rules/common/path-params-defined.ts
index 62dab13e..08bffac5 100644
--- a/packages/core/src/rules/common/path-params-defined.ts
+++ b/packages/core/src/rules/common/path-params-defined.ts
@@ -9,6 +9,7 @@ export const PathParamsDefined: Oas3Rule | Oas2Rule = () => {
   let pathTemplateParams: Set<string>;
   let definedPathParams: Set<string>;
   let currentPath: string;
+  let definedOperationParams: Set<string>;
 
   return {
     PathItem: {
@@ -31,9 +32,15 @@ export const PathParamsDefined: Oas3Rule | Oas2Rule = () => {
         }
       },
       Operation: {
+        enter() {
+          definedOperationParams = new Set();
+        },
         leave(_op: object, { report, location }: UserContext) {
           for (const templateParam of Array.from(pathTemplateParams.keys())) {
-            if (!definedPathParams.has(templateParam)) {
+            if (
+              !definedOperationParams.has(templateParam) &&
+              !definedPathParams.has(templateParam)
+            ) {
               report({
                 message: `The operation does not define the path parameter \`{${templateParam}}\` expected by path \`${currentPath}\`.`,
                 location: location.child(['parameters']).key(), // report on operation
@@ -43,7 +50,7 @@ export const PathParamsDefined: Oas3Rule | Oas2Rule = () => {
         },
         Parameter(parameter: Oas2Parameter | Oas3Parameter, { report, location }: UserContext) {
           if (parameter.in === 'path' && parameter.name) {
-            definedPathParams.add(parameter.name);
+            definedOperationParams.add(parameter.name);
             if (!pathTemplateParams.has(parameter.name)) {
               report({
                 message: `Path parameter \`${parameter.name}\` is not used in the path \`${currentPath}\`.`,


Test patch:
diff --git a/packages/core/src/rules/common/__tests__/path-params-defined.test.ts b/packages/core/src/rules/common/__tests__/path-params-defined.test.ts
index 67ebec35..46620d2e 100644
--- a/packages/core/src/rules/common/__tests__/path-params-defined.test.ts
+++ b/packages/core/src/rules/common/__tests__/path-params-defined.test.ts
@@ -130,4 +130,73 @@ describe('Oas3 path-params-defined', () => {
       ]
     `);
   });
+
+  it('should fail cause POST has no parameters', async () => {
+    const document = parseYamlToDocument(
+      outdent`
+          openapi: 3.0.0
+          paths:
+            /pets/{a}:
+              get:
+                parameters:
+                 - name: a
+                   in: path
+              post:
+                description: without parameters
+      `,
+      'foobar.yaml'
+    );
+
+    const results = await lintDocument({
+      externalRefResolver: new BaseResolver(),
+      document,
+      config: await makeConfig({ 'path-params-defined': 'error' }),
+    });
+
+    expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
+      Array [
+        Object {
+          "location": Array [
+            Object {
+              "pointer": "#/paths/~1pets~1{a}/post/parameters",
+              "reportOnKey": true,
+              "source": "foobar.yaml",
+            },
+          ],
+          "message": "The operation does not define the path parameter \`{a}\` expected by path \`/pets/{a}\`.",
+          "ruleId": "path-params-defined",
+          "severity": "error",
+          "suggest": Array [],
+        },
+      ]
+    `);
+  });
+
+  it('should apply parameters for POST operation from path parameters', async () => {
+    const document = parseYamlToDocument(
+      outdent`
+          openapi: 3.0.0
+          paths:
+            /pets/{a}:
+              parameters:
+                - name: a
+                  in: path
+              get:
+                parameters:
+                 - name: a
+                   in: path
+              post:
+                description: without parameters
+      `,
+      'foobar.yaml'
+    );
+
+    const results = await lintDocument({
+      externalRefResolver: new BaseResolver(),
+      document,
+      config: await makeConfig({ 'path-params-defined': 'error' }),
+    });
+
+    expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
+  });
 });