Repository: stoplightio/spectral
Pull request #1921 (2021-10-29 21:16:29)

feat(rulesets): add validation rule for oas2 discriminator
This PR adds an oas rule to check compliance with discriminator definition in OpenAPI v2.

**Checklist**

- [x] Tests added / updated
- [x] Docs added / updated

**Does this PR introduce a breaking change?**

- [ ] Yes
- [x] No

> If indicated yes above, please describe the breaking change(s).
>
> **Remove this quote before creating the PR.**

**Screenshots**

If applicable, add screenshots or gifs to help demonstrate the changes. If not applicable, remove this screenshots section before creating the PR.

**Additional context**

Add any other context about the pull request here. Remove this section if there is no additional context.



Patch:
diff --git a/docs/reference/openapi-rules.md b/docs/reference/openapi-rules.md
index 2803f41d..2e4e17f3 100644
--- a/docs/reference/openapi-rules.md
+++ b/docs/reference/openapi-rules.md
@@ -451,6 +451,12 @@ OpenAPI host `schemes` must be present and non-empty array.
 
 **Recommended:** Yes
 
+### oas2-discriminator
+
+The discriminator property MUST be defined at this schema and it MUST be in the required property list.
+
+**Recommended:** Yes
+
 ### oas2-host-not-example
 
 Server URL should not point at example.com.
diff --git a/packages/rulesets/src/oas/functions/index.ts b/packages/rulesets/src/oas/functions/index.ts
index c29cf646..2fe7c0a2 100644
--- a/packages/rulesets/src/oas/functions/index.ts
+++ b/packages/rulesets/src/oas/functions/index.ts
@@ -11,3 +11,4 @@ export { default as oasPathParam } from './oasPathParam';
 export { default as oasTagDefined } from './oasTagDefined';
 export { default as oasUnusedComponent } from './oasUnusedComponent';
 export { default as oasOpIdUnique } from './oasOpIdUnique';
+export { default as oasDiscriminator } from './oasDiscriminator';
diff --git a/packages/rulesets/src/oas/functions/oasDiscriminator.ts b/packages/rulesets/src/oas/functions/oasDiscriminator.ts
new file mode 100644
index 00000000..46c2f9d8
--- /dev/null
+++ b/packages/rulesets/src/oas/functions/oasDiscriminator.ts
@@ -0,0 +1,37 @@
+import type { IFunction, IFunctionResult } from '@stoplight/spectral-core';
+import { isObject } from './utils/isObject';
+
+export const oasDiscriminator: IFunction = (schema, _opts, { path }) => {
+  /**
+   * This function verifies:
+   *
+   * 1. The discriminator property name is defined at this schema.
+   * 2. The discriminator property is in the required property list.
+   */
+
+  if (!isObject(schema)) return;
+
+  if (typeof schema.discriminator !== 'string') return;
+
+  const discriminatorName = schema.discriminator;
+
+  const results: IFunctionResult[] = [];
+
+  if (!isObject(schema.properties) || !Object.keys(schema.properties).some(k => k === discriminatorName)) {
+    results.push({
+      message: `The discriminator property must be defined in this schema.`,
+      path: [...path, 'properties'],
+    });
+  }
+
+  if (!Array.isArray(schema.required) || !schema.required.some(n => n === discriminatorName)) {
+    results.push({
+      message: `The discriminator property must be in the required property list.`,
+      path: [...path, 'required'],
+    });
+  }
+
+  return results;
+};
+
+export default oasDiscriminator;
diff --git a/packages/rulesets/src/oas/index.ts b/packages/rulesets/src/oas/index.ts
index 6d67540f..59350209 100644
--- a/packages/rulesets/src/oas/index.ts
+++ b/packages/rulesets/src/oas/index.ts
@@ -23,6 +23,7 @@ import {
   oasDocumentSchema,
   oasOpSecurityDefined,
   oasSchema,
+  oasDiscriminator,
 } from './functions';
 
 export { ruleset as default };
@@ -400,6 +401,18 @@ const ruleset = {
         },
       },
     },
+    'oas2-discriminator': {
+      description: 'discriminator property must be defined and required',
+      recommended: true,
+      formats: [oas2],
+      severity: 0,
+      message: '{{error}}',
+      given: '$.definitions[?(@.discriminator)]',
+      type: 'validation',
+      then: {
+        function: oasDiscriminator,
+      },
+    },
     'oas2-host-not-example': {
       description: 'Host URL must not point at example.com.',
       recommended: false,


Test patch:
diff --git a/packages/rulesets/src/oas/__tests__/oas2-discriminator.test.ts b/packages/rulesets/src/oas/__tests__/oas2-discriminator.test.ts
new file mode 100644
index 00000000..a948754e
--- /dev/null
+++ b/packages/rulesets/src/oas/__tests__/oas2-discriminator.test.ts
@@ -0,0 +1,94 @@
+import { DiagnosticSeverity } from '@stoplight/types';
+import testRule from '../../__tests__/__helpers__/tester';
+
+testRule('oas2-discriminator', [
+  {
+    name: 'valid discriminator',
+    document: {
+      swagger: '2.0',
+      definitions: {
+        Pet: {
+          type: 'object',
+          description: 'Schema with valid discriminator',
+          discriminator: 'petType',
+          properties: {
+            name: {
+              type: 'string',
+            },
+            petType: {
+              type: 'string',
+            },
+          },
+          required: ['name', 'petType'],
+        },
+      },
+      Person: {
+        type: 'object',
+        properties: {
+          id: {
+            type: 'integer',
+          },
+          name: {
+            type: 'string',
+          },
+        },
+      },
+    },
+    errors: [],
+  },
+
+  {
+    name: 'discriminator property not defined',
+    document: {
+      swagger: '2.0',
+      definitions: {
+        Pet: {
+          type: 'object',
+          discriminator: 'petType',
+          properties: {
+            name: {
+              type: 'string',
+            },
+          },
+          required: ['name', 'petType'],
+        },
+      },
+    },
+    errors: [
+      {
+        message: 'The discriminator property must be defined in this schema.',
+        path: ['definitions', 'Pet', 'properties'],
+        severity: DiagnosticSeverity.Error,
+      },
+    ],
+  },
+
+  {
+    name: 'discriminator property not required',
+    document: {
+      swagger: '2.0',
+      definitions: {
+        Pet: {
+          type: 'object',
+          discriminator: 'petType',
+          properties: {
+            name: {
+              type: 'string',
+            },
+            petType: {
+              type: 'string',
+            },
+          },
+          required: ['name'],
+        },
+      },
+    },
+    errors: [
+      {
+        message: 'The discriminator property must be in the required property list.',
+        path: ['definitions', 'Pet', 'required'],
+        severity: DiagnosticSeverity.Error,
+      },
+    ],
+  },
+]);