Repository: stoplightio/spectral (ts)

Issue:
Nullable enum warning for null value in example section
I receive warning when trying to use null value in example section for property with enum.

**To Reproduce**

1. Given this OpenAPI/AsyncAPI document:
[nullable_enum.yaml](https://github.com/stoplightio/spectral/files/5274847/nullable_enum.txt)

2. Run this CLI command:
> spectral lint -v nullable_enum.yaml

3. See error
> Found 82 rules (66 enabled)
> Linting nullable_enum.yaml
> OpenAPI 3.x detected
> 
> nullable_enum.yaml
>  39:23  warning  typed-enum  Enum value `null` does not respect the specified type `string`.
> ✖ 1 problem (0 errors, 1 warning, 0 infos, 0 hints)
> 

**Expected behavior**
No warnings produced

**Environment:**
 - Library version: 5.5.0
 - OS: Debian 9 (image node:14.11-slim from Docker Hub)

**Additional context**
FROM node:14.11-slim
RUN yarn global add @stoplight/spectral
ADD nullable_enum.yaml nullable_enum.yaml
RUN spectral lint -v nullable_enum.yaml



Pull request:
fix(typed-enum): respect nullable in case of OAS3 document
Fixes #1353.


**Checklist**

- [x] Tests added / updated
- [x] Docs added / updated

**Does this PR introduce a breaking change?**

- [ ] Yes
- [x] No



Patch:
diff --git a/src/rulesets/oas/functions/typedEnum.ts b/src/rulesets/oas/functions/typedEnum.ts
index 9230f793..34578f28 100644
--- a/src/rulesets/oas/functions/typedEnum.ts
+++ b/src/rulesets/oas/functions/typedEnum.ts
@@ -17,7 +17,20 @@ export const typedEnum: IFunction = function (this: IFunctionContext, targetVal,
     return;
   }
 
-  const innerSchema = { type: initialSchema.type, enum: initialSchema.enum };
+  const isOAS3 = otherValues.documentInventory.document.formats?.includes('oas3') === true;
+
+  let innerSchema;
+  if (isOAS3 && targetVal.nullable === true) {
+    const type = Array.isArray(initialSchema.type) ? [...initialSchema.type] : [initialSchema.type];
+    if (!type.includes('null')) {
+      type.push('null');
+    }
+
+    innerSchema = { type, enum: initialSchema.enum };
+  } else {
+    innerSchema = { type: initialSchema.type, enum: initialSchema.enum };
+  }
+
   const schemaObject = { schema: innerSchema };
 
   const incorrectValues: Array<{ index: number; val: unknown }> = [];


Test patch:
diff --git a/src/rulesets/oas/__tests__/typed-enum.ts b/src/rulesets/oas/__tests__/typed-enum.ts
index 4b48bf78..3a2a8633 100644
--- a/src/rulesets/oas/__tests__/typed-enum.ts
+++ b/src/rulesets/oas/__tests__/typed-enum.ts
@@ -1,18 +1,25 @@
 import { DiagnosticSeverity } from '@stoplight/types';
 import { functions } from '../../../functions';
-import { RuleType, Spectral } from '../../../index';
+import { KNOWN_FORMATS, RuleType, Spectral } from '../../../index';
 import { setFunctionContext } from '../../evaluators';
 import { typedEnum } from '../functions/typedEnum';
 import { rules } from '../index.json';
 
 describe('typed-enum', () => {
   const s = new Spectral();
-  s.setFunctions({ typedEnum: setFunctionContext({ functions }, typedEnum) });
-  s.setRules({
-    'typed-enum': Object.assign(rules['typed-enum'], {
-      recommended: true,
-      type: RuleType[rules['typed-enum'].type],
-    }),
+
+  beforeEach(() => {
+    s.setFunctions({ typedEnum: setFunctionContext({ functions }, typedEnum) });
+    s.setRules({
+      'typed-enum': Object.assign(rules['typed-enum'], {
+        recommended: true,
+        type: RuleType[rules['typed-enum'].type],
+      }),
+    });
+
+    for (const [name, fn] of KNOWN_FORMATS) {
+      s.registerFormat(name, fn);
+    }
   });
 
   describe('oas2', () => {
@@ -70,6 +77,31 @@ describe('typed-enum', () => {
         },
       ]);
     });
+
+    test('does not support nullable', async () => {
+      const doc = {
+        swagger: '2.0',
+        definitions: {
+          Test: {
+            type: 'string',
+            nullable: true,
+            enum: ['OK', 'FAILED', null],
+          },
+        },
+      };
+
+      const results = await s.run(doc);
+
+      expect(results).toEqual([
+        {
+          code: 'typed-enum',
+          message: 'Enum value `null` does not respect the specified type `string`.',
+          path: ['definitions', 'Test', 'enum', '2'],
+          range: expect.any(Object),
+          severity: DiagnosticSeverity.Warning,
+        },
+      ]);
+    });
   });
 
   describe('oas3', () => {
@@ -131,5 +163,24 @@ describe('typed-enum', () => {
         },
       ]);
     });
+
+    test('supports nullable', async () => {
+      const doc = {
+        openapi: '3.0.0',
+        components: {
+          schemas: {
+            Test: {
+              type: 'string',
+              nullable: true,
+              enum: ['OK', 'FAILED', null],
+            },
+          },
+        },
+      };
+
+      const results = await s.run(doc);
+
+      expect(results).toEqual([]);
+    });
   });
 });
diff --git a/src/rulesets/oas/functions/__tests__/typedEnum.test.ts b/src/rulesets/oas/functions/__tests__/typedEnum.test.ts
index 58437449..8bf84479 100644
--- a/src/rulesets/oas/functions/__tests__/typedEnum.test.ts
+++ b/src/rulesets/oas/functions/__tests__/typedEnum.test.ts
@@ -1,13 +1,18 @@
 import { functions } from '../../../../functions';
 import { typedEnum } from '../typedEnum';
+import { DocumentInventory } from '../../../../documentInventory';
+import { Document } from '../../../../document';
+import * as Parsers from '../../../../parsers';
 
 function runTypedEnum(targetVal: any) {
+  const doc = new Document(JSON.stringify(targetVal), Parsers.Json);
+
   return typedEnum.call(
     { functions },
     targetVal,
     null,
     { given: ['$'] },
-    { given: null, original: null, documentInventory: {} as any, rule: {} as any },
+    { given: null, original: null, documentInventory: new DocumentInventory(doc, {} as any), rule: {} as any },
   );
 }