Repository: stoplightio/spectral (ts)

Issue:
`operation-2xx-response` validation does not respect '2XX' response code
**Describe the bug**
`operation-2xx-response` does not respect '2XX' response code. According to OAI3 '2XX' is a valid range response code definition.
Validation result: 
```
OpenAPI 3.x detected

/tmp/build/test.yaml
 13:17  warning  operation-2xx-response  Operation must have at least one `2xx` response.  paths./test.post.responses

✖ 1 problem (0 errors, 1 warning, 0 infos, 0 hints)
```

**To Reproduce**

```
openapi: "3.0.0"
info:
  version: test
  title: test
  description: test

servers:
  - url: /

paths:
  '/test':
    post:
      responses:
        '2XX':
          description: Success!
        default:
          description: default
```

**Expected behavior**
No warnings or errors after validation

**Environment (remove any that are not applicable):**
 - Library version: 5.9.1 (via docker image)

**Additional context**
We use OAI to describe an API that our customers would have to implement in order to integrate with our system. Any 2xx would be accepted as successful response.


Pull request:
fix(rulesets): operation-success-response rule does not respect 2XX and 3XX

Fixes #1727 

**Checklist**

- [x] Tests added / updated
- [x] Docs added / updated

**Does this PR introduce a breaking change?**

- [ ] Yes
- [x] No



Patch:
diff --git a/packages/rulesets/src/oas/functions/oasOpSuccessResponse.ts b/packages/rulesets/src/oas/functions/oasOpSuccessResponse.ts
index d1a22d4e..19ecc4ac 100644
--- a/packages/rulesets/src/oas/functions/oasOpSuccessResponse.ts
+++ b/packages/rulesets/src/oas/functions/oasOpSuccessResponse.ts
@@ -1,22 +1,32 @@
-import type { IFunction } from '@stoplight/spectral-core';
-import { isObject } from './utils/isObject';
+import { createRulesetFunction } from '@stoplight/spectral-core';
+import { oas3 } from '@stoplight/spectral-formats';
 
-export const oasOpSuccessResponse: IFunction = targetVal => {
-  if (!isObject(targetVal)) {
-    return;
-  }
+export const oasOpSuccessResponse = createRulesetFunction<Record<string, unknown>, null>(
+  {
+    input: {
+      type: 'object',
+    },
+    options: null,
+  },
+  (input, opts, context) => {
+    const isOAS3X = context.document.formats?.has(oas3) === true;
+
+    for (const response of Object.keys(input)) {
+      if (isOAS3X && (response === '2XX' || response === '3XX')) {
+        return;
+      }
 
-  for (const response of Object.keys(targetVal)) {
-    if (Number(response) >= 200 && Number(response) < 400) {
-      return;
+      if (Number(response) >= 200 && Number(response) < 400) {
+        return;
+      }
     }
-  }
 
-  return [
-    {
-      message: 'Operation must define at least a single 2xx or 3xx response',
-    },
-  ];
-};
+    return [
+      {
+        message: 'Operation must define at least a single 2xx or 3xx response',
+      },
+    ];
+  },
+);
 
 export default oasOpSuccessResponse;


Test patch:
diff --git a/packages/rulesets/src/oas/__tests__/operation-success-response.test.ts b/packages/rulesets/src/oas/__tests__/operation-success-response.test.ts
index a684023e..488e4350 100644
--- a/packages/rulesets/src/oas/__tests__/operation-success-response.test.ts
+++ b/packages/rulesets/src/oas/__tests__/operation-success-response.test.ts
@@ -131,4 +131,81 @@ testRule('operation-success-response', [
     },
     errors: [],
   },
+
+  {
+    name: 'oas2 document has 2XX and 3XX responses set',
+    document: {
+      swagger: '2.0',
+      paths: {
+        '/path': {
+          get: {
+            responses: {
+              '3XX': {},
+            },
+          },
+          post: {
+            responses: {
+              '2XX': {},
+            },
+          },
+        },
+      },
+    },
+    errors: [
+      {
+        message: 'Operation must have at least one "2xx" or "3xx" response.',
+        path: ['paths', '/path', 'get', 'responses'],
+        severity: DiagnosticSeverity.Warning,
+      },
+      {
+        message: 'Operation must have at least one "2xx" or "3xx" response.',
+        path: ['paths', '/path', 'post', 'responses'],
+        severity: DiagnosticSeverity.Warning,
+      },
+    ],
+  },
+
+  {
+    name: 'oas 3.0.x document has 2XX and 3XX responses set',
+    document: {
+      openapi: '3.0.3',
+      paths: {
+        '/path': {
+          get: {
+            responses: {
+              '3XX': {},
+            },
+          },
+          post: {
+            responses: {
+              '2XX': {},
+            },
+          },
+        },
+      },
+    },
+    errors: [],
+  },
+
+  {
+    name: 'oas 3.1.x document has 2XX and 3XX responses set',
+    document: {
+      openapi: '3.1.0',
+      paths: {
+        '/path': {
+          get: {
+            responses: {
+              '3XX': {},
+            },
+          },
+          post: {
+            responses: {
+              '2XX': {},
+            },
+          },
+        },
+      },
+    },
+    errors: [],
+  },
 ]);