Repository: stoplightio/spectral
Pull request #1284 (2020-07-16 10:57:36)

fix(oas): improve examples validation
Fixes https://github.com/stoplightio/spectral/issues/901

**Checklist**

- [x] Tests added / updated
- [x] Docs added / updated

**Does this PR introduce a breaking change?**

- [ ] Yes
- [x] No



philsturgeon: These rules have always been hell, due to the _incredibly confusing_ nature of all the different types of examples that can show up anywhere in OAS2, OAS3, and JSON Schema.

It honestly might need custom functions.

Using https://github.com/stoplightio/studio/issues/381 as a reference to all the bajillion different combinations of rule, can we think about how we'd implement what combination of examples to handle:

Schema Objects (for OAS2, OAS3, JSON Schema) when there could be x-example or example or examples or x-examples depending on whatever type of thing is there, and the value is always a bare value, or an array of bare values.

Then there's Media Types, Parameters, etc which are sometimes "Schema like" but not, or might actually have a "Schema" inside them...

No one rule could do everything, and we keep getting bug reports when we miss a certain specific combination, so whatever we do needs to work for everything. 

P.S No need to validate externalValue.
P0lip: @philsturgeon I added oas2-valid-example rule.
philsturgeon: Excellent! I'm going to try this out on https://github.com/philsturgeon/examples-example and see how far we get. I'd suggest we use these as a basis for test harness.

Once we've got the ones which are meant to fail to actually fail, we can try them with $ref variants too.
philsturgeon: Amazing work here, running this branch on from https://github.com/philsturgeon/examples-examples (the oas3.yaml) I am seeing these errors:

```
 145:30  error  oas3-valid-content-schema-example    `example` property type should be boolean
 149:30  error  oas3-valid-content-schema-example    `example` property should match format `date-time`
 153:30  error  oas3-valid-content-schema-example    `example` property should match format `url`
 176:20  error  oas3-valid-oas-parameter-example     `example` property should be equal to one of the allowed values: foo, bar
 192:22  error  oas3-valid-parameter-schema-example  `example` property should be equal to one of the allowed values: foo, bar
 204:22  error  oas3-valid-oas-parameter-example     `value` property type should be string
 206:21  error  oas3-valid-oas-parameter-example     `value` property type should be string
 269:25  error  oas3-valid-oas-content-example       `value` property should have required property `id`
 289:25  error  oas3-valid-oas-content-example       `value` property should have required property `user`
```

The first 5 are in v5.5 but the last 4 are only in this branch. The errors are not wildly useful as they are, but they are catching onto the fact that there are problems in the examples. 

>  204:22  error  oas3-valid-oas-parameter-example     `value` property type should be string
>  206:21  error  oas3-valid-oas-parameter-example     `value` property type should be string

```
        - name: multiple-examples
          description: Valid to its schema
          in: query
          schema: 
            type: string
            enum: [foo, bar]
          examples: 
            the-good:
              value: foo
            the-bad:
              value: 123                       # line 204: it's noticed this isn't a string!
            the-ugly:
              value: [an, array]            # line 206: it's noticed this isnt a string either
```

It's also noticed some response examples using the `examples` keywords:

```yaml
        "500":
          description: Fail, because the example is nonsense
          content:
            application/json:
              schema:
                properties:
                  id:
                    type: integer
                  name:
                    type: string
                  completed:
                    type: boolean
                  completed_at:
                    type: string
                    format: date-time
                required:
                  - id
                  - name
                  - completed
              examples:
                response:
                  value:
                    completed: Not a boolean
                    completed_at: Not a date
```

So that's cool.

It's still missing this:

```
      "500":
        description: Fail, because the example is nonsense
        content:
          application/json:
            schema:
              properties:
                id:
                  type: integer
                name:
                  type: string
                completed:
                  type: boolean
                completed_at:
                  type: string
                  format: date-time
              required:
                - id
                - name
                - completed
              # notice how im in the schema object, not next to it
              example:
                id: 1
                completed: Not a boolean
                completed_at: Not a date
```

I'll have a look at OAS2 too.
philsturgeon: For oas2.yaml in that repo I'm seeing these two errors:

```
 176:30    error  oas2-valid-response-example  `application/json` property should have required property `name`
 193:30    error  oas2-valid-response-example  `application/json` property should have required property `user`
```

That's two more than the latest release! 

Similarly to oas3 feedback, if a response example is an object that sits inside a schema, it doesnt seem to get picked up. This example should fail, but doesn't:

```
        '500':
          description: 'Fail, because the example is nonsense'
          schema:
            properties: 
              id:
                type: integer
              name:
                type: string
              completed: 
                type: boolean
              completed_at:
                type: string
                format: date-time
            required:
              - id
              - name
              - completed
            # notice how im in the schema object, not next to it
            example:
              id: 1
              completed: Not a boolean
              completed_at: 'Not a date'
```

OAS2 "property examples" should also fail, but currently do not. These do work in OAS3.

```
        '501':
          description: 'Fail, because the property example is nonsense'
          schema:
            properties: 
              some-bool:
                type: boolean
                example: not a bool
              some-date:
                type: string
                format: date-time
                example: not a date
              some-url:
                type: string
                format: url
                example: not a URL
```


P0lip: @philsturgeon I think this should be ready for another round.
P0lip: @philsturgeon ah, seems like the same rule targets the same portion of the spec.
I'll tweak them.
P0lip: @philsturgeon how do you feel about merging some of the rules?
Since `given` might be an array now, we do not need to have plenty of rules such as `oas2-valid-response-schema-example` or `oas2-valid-parameter-example`. What do you think?
As this is a potential breaking change, we might postpone it until 6.0
philsturgeon: Yeah I thought we could probably get away with some rule merges so long as we can get the right function options into place. I let seems like different targets will need different function options, but if you can get the tests to pass with the changed rules then... go ahead! 

Maybe we can do the minimal change now and merge the rules as a follow up in 6.0, like you said. 
P0lip: > Maybe we can do the minimal change now and merge the rules as a follow up in 6.0, like you said.

I'd need to drop a rule or two and merge them into some existing one.
I'll create a follow-up PR, so that 5.5 can be released without any breaking changes included.

Patch:
diff --git a/src/functions/index.ts b/src/functions/index.ts
index 8544be8e..ac4d32e4 100644
--- a/src/functions/index.ts
+++ b/src/functions/index.ts
@@ -5,7 +5,7 @@ import { falsy } from './falsy';
 import { length } from './length';
 import { pattern } from './pattern';
 import { schema } from './schema';
-import { schemaPath } from './schema-path';
+import { schemaPath } from './schemaPath';
 import { truthy } from './truthy';
 import { undefined } from './undefined';
 import { unreferencedReusableObject } from './unreferencedReusableObject';
diff --git a/src/rulesets/oas/functions/oasDocumentSchema.ts b/src/rulesets/oas/functions/oasDocumentSchema.ts
index dbb540c9..00ac409c 100644
--- a/src/rulesets/oas/functions/oasDocumentSchema.ts
+++ b/src/rulesets/oas/functions/oasDocumentSchema.ts
@@ -2,7 +2,7 @@ import * as AJV from 'ajv';
 import { ISchemaOptions } from '../../../functions/schema';
 import { IFunction, IFunctionContext, IFunctionResult } from '../../../types';
 
-function shouldIgnoreError(error: AJV.ErrorObject) {
+function shouldIgnoreError(error: AJV.ErrorObject): boolean {
   return (
     // oneOf is a fairly error as we have 2 options to choose from for most of the time.
     error.keyword === 'oneOf' ||
@@ -44,7 +44,7 @@ export function prepareResults(errors: AJV.ErrorObject[]) {
   }
 }
 
-function applyManualReplacements(errors: IFunctionResult[]) {
+function applyManualReplacements(errors: IFunctionResult[]): void {
   for (const error of errors) {
     if (error.path === void 0) continue;
 
diff --git a/src/rulesets/oas/functions/oasExample.ts b/src/rulesets/oas/functions/oasExample.ts
new file mode 100644
index 00000000..4b19b542
--- /dev/null
+++ b/src/rulesets/oas/functions/oasExample.ts
@@ -0,0 +1,139 @@
+import type { IFunction, IFunctionContext, IFunctionResult } from '../../../types';
+import type { Dictionary } from '@stoplight/types';
+import type { ISchemaOptions } from '../../../functions/schema';
+
+function isObject(value: unknown): value is Dictionary<any> {
+  return value !== null && typeof value === 'object';
+}
+
+interface IOasExampleOptions {
+  oasVersion: 2 | 3;
+  schemaField: string;
+  type: 'media' | 'schema';
+}
+
+const ORDER_CHECK = {
+  media: {
+    2: [
+      {
+        field: 'examples',
+        multiple: true,
+        keyed: false,
+      },
+    ],
+    3: [
+      {
+        field: 'example',
+        multiple: false,
+        keyed: false,
+      },
+      {
+        field: 'examples',
+        multiple: true,
+        keyed: true,
+      },
+    ],
+  },
+  schema: {
+    2: [
+      {
+        field: 'example',
+        multiple: false,
+        keyed: false,
+      },
+      {
+        field: 'x-example',
+        multiple: false,
+        keyed: false,
+      },
+    ],
+    3: [
+      {
+        field: 'example',
+        multiple: false,
+        keyed: false,
+      },
+    ],
+  },
+};
+
+export const oasExample: IFunction<IOasExampleOptions> = function (
+  this: IFunctionContext,
+  targetVal,
+  opts,
+  paths,
+  otherValues,
+) {
+  if (!isObject(targetVal)) {
+    return;
+  }
+
+  const schemaOpts: ISchemaOptions = {
+    schema: opts.schemaField === '$' ? targetVal : targetVal[opts.schemaField],
+    oasVersion: opts.oasVersion,
+  };
+
+  const order = ORDER_CHECK[opts.type][opts.oasVersion];
+
+  for (const { field, keyed, multiple } of order) {
+    if (!(field in targetVal)) {
+      continue;
+    }
+
+    const value = targetVal[field];
+
+    if (multiple) {
+      if (!isObject(value)) {
+        continue;
+      }
+
+      const results: IFunctionResult[] = [];
+
+      for (const exampleKey of Object.keys(value)) {
+        const exampleValue = value[exampleKey];
+        if (keyed && !isObject(exampleValue)) {
+          // should be covered by oas3-examples-value-or-externalValue
+          continue;
+        }
+
+        const targetPath = [...paths.given, field, exampleKey];
+
+        if (keyed) {
+          targetPath.push('value');
+        }
+
+        const result = this.functions.schema.call(
+          this,
+          keyed ? exampleValue.value : exampleValue,
+          schemaOpts,
+          {
+            given: paths.given,
+            target: targetPath,
+          },
+          otherValues,
+        );
+
+        if (Array.isArray(result)) {
+          results.push(...result);
+        }
+      }
+
+      return results;
+    } else {
+      return this.functions.schema.call(
+        this,
+        value,
+        schemaOpts,
+        {
+          given: paths.given,
+          target: [...paths.given, field],
+        },
+        otherValues,
+      );
+    }
+  }
+
+  return;
+};
+
+export default oasExample;
diff --git a/src/rulesets/oas/index.json b/src/rulesets/oas/index.json
index abab8cc9..556ab6bd 100644
--- a/src/rulesets/oas/index.json
+++ b/src/rulesets/oas/index.json
@@ -3,6 +3,7 @@
   "formats": ["oas2", "oas3"],
   "functions": [
     "oasDocumentSchema",
+    "oasExample",
     "oasOpSuccessResponse",
     "oasOpFormDataConsumeCheck",
     "oasOpIdUnique",
@@ -406,13 +407,13 @@
       "formats": ["oas2"],
       "severity": 0,
       "type": "validation",
-      "given": "$..parameters..[?(@.in == 'body')]..[?(@property !== 'properties' && @.example && ( @.type || @.format || @.$ref ))]",
+      "given": "$..parameters..[?(@property !== 'properties' && (@.example || @['x-example']) && (@.type || @.format || @.$ref || @.properties || @.items))]",
       "then": {
-        "function": "schemaPath",
+        "function": "oasExample",
         "functionOptions": {
-          "field": "example",
-          "schemaPath": "$",
-          "oasVersion": 2
+          "schemaField": "$",
+          "oasVersion": 2,
+          "type": "schema"
         }
       }
     },
@@ -423,13 +424,47 @@
       "formats": ["oas2"],
       "severity": 0,
       "type": "validation",
-      "given": "$..definitions..[?(@property !== 'properties' && @.example && (@.type || @.format || @.$ref))]",
+      "given": "$..definitions..[?(@property !== 'properties' && (@.example || @['x-example']) && (@.type || @.format || @.$ref || @.properties || @.items))]",
       "then": {
-        "function": "schemaPath",
+        "function": "oasExample",
         "functionOptions": {
-          "field": "example",
-          "schemaPath": "$",
-          "oasVersion": 2
+          "schemaField": "$",
+          "oasVersion": 2,
+          "type": "schema"
+        }
+      }
+    },
+    "oas2-valid-response-example": {
+      "description": "Examples must be valid against their defined schema.",
+      "message": "{{error}}",
+      "recommended": true,
+      "formats": ["oas2"],
+      "severity": 0,
+      "type": "validation",
+      "given": "$..responses..[?(@.schema && @.examples)]",
+      "then": {
+        "function": "oasExample",
+        "functionOptions": {
+          "schemaField": "schema",
+          "oasVersion": 2,
+          "type": "media"
+        }
+      }
+    },
+    "oas2-valid-response-schema-example": {
+      "description": "Examples must be valid against their defined schema.",
+      "message": "{{error}}",
+      "recommended": true,
+      "formats": ["oas2"],
+      "severity": 0,
+      "type": "validation",
+      "given": "$..responses..[?(@property !== 'properties' && (@.example || @['x-example']) && (@.type || @.format || @.$ref || @.properties || @.items))]",
+      "then": {
+        "function": "oasExample",
+        "functionOptions": {
+          "schemaField": "$",
+          "oasVersion": 2,
+          "type": "schema"
         }
       }
     },
@@ -584,14 +619,15 @@
       "message": "{{error}}",
       "recommended": true,
       "severity": 0,
+      "formats": ["oas3"],
       "type": "validation",
-      "given": "$..parameters..[?(@.example && @.schema)]",
+      "given": "$..parameters..[?(@.schema && (@.example || @.examples))]",
       "then": {
-        "function": "schemaPath",
+        "function": "oasExample",
         "functionOptions": {
-          "field": "example",
-          "schemaPath": "$.schema",
-          "oasVersion": 3
+          "schemaField": "schema",
+          "oasVersion": 3,
+          "type": "media"
         }
       }
     },
@@ -602,13 +638,13 @@
       "severity": 0,
       "formats": ["oas3"],
       "type": "validation",
-      "given": "$..headers..[?(@.example && @.schema)]",
+      "given": "$..headers..[?(@.schema && (@.example || @.examples))]",
       "then": {
-        "function": "schemaPath",
+        "function": "oasExample",
         "functionOptions": {
-          "field": "example",
-          "schemaPath": "$.schema",
-          "oasVersion": 3
+          "schemaField": "schema",
+          "oasVersion": 3,
+          "type": "media"
         }
       }
     },
@@ -619,13 +655,13 @@
       "severity": 0,
       "formats": ["oas3"],
       "type": "validation",
-      "given": "$..content..[?(@.example && @.schema)]",
+      "given": "$..content..[?(@.schema && (@.example || @.examples))]",
       "then": {
-        "function": "schemaPath",
+        "function": "oasExample",
         "functionOptions": {
-          "field": "example",
-          "schemaPath": "$.schema",
-          "oasVersion": 3
+          "schemaField": "schema",
+          "oasVersion": 3,
+          "type": "media"
         }
       }
     },
@@ -636,13 +672,13 @@
       "severity": 0,
       "formats": ["oas3"],
       "type": "validation",
-      "given": "$..parameters..[?(@property !== 'properties' && @.example && (@.type || @.format || @.$ref))]",
+      "given": "$..parameters..[?(@property !== 'properties' && @.example && (@.type || @.format || @.$ref || @.properties || @.items))]",
       "then": {
-        "function": "schemaPath",
+        "function": "oasExample",
         "functionOptions": {
-          "field": "example",
-          "schemaPath": "$",
-          "oasVersion": 3
+          "schemaField": "$",
+          "oasVersion": 3,
+          "type": "schema"
         }
       }
     },
@@ -653,13 +689,13 @@
       "severity": 0,
       "formats": ["oas3"],
       "type": "validation",
-      "given": "$..headers..[?(@property !== 'properties' && @.example && (@.type || @.format || @.$ref))]",
+      "given": "$..headers..[?(@property !== 'properties' && @.example && (@.type || @.format || @.$ref || @.properties || @.items))]",
       "then": {
-        "function": "schemaPath",
+        "function": "oasExample",
         "functionOptions": {
-          "field": "example",
-          "schemaPath": "$",
-          "oasVersion": 3
+          "schemaField": "$",
+          "oasVersion": 3,
+          "type": "schema"
         }
       }
     },
@@ -670,30 +706,16 @@
       "formats": ["oas3"],
       "recommended": true,
       "type": "validation",
-      "given": "$.components.schemas..[?(@property !== 'properties' && @.example && (@.type || @.format || @.$ref))]",
-      "then": {
-        "function": "schemaPath",
-        "functionOptions": {
-          "field": "example",
-          "schemaPath": "$",
-          "oasVersion": 3
-        }
-      }
-    },
-    "oas3-valid-content-schema-example": {
-      "description": "Examples must be valid against their defined schema.",
-      "message": "{{error}}",
-      "severity": 0,
-      "formats": ["oas3"],
-      "recommended": true,
-      "type": "validation",
-      "given": "$..content..[?(@property !== 'properties' && @.example && (@.type || @.format || @.$ref))]",
+      "given": [
+        "$.components.schemas..[?(@property !== 'properties' && @.example && (@.type || @.format || @.$ref || @.properties || @.items))]",
+        "$..content..[?(@property !== 'properties' && @.example && (@.type || @.format || @.$ref || @.properties || @.items))]"
+      ],
       "then": {
-        "function": "schemaPath",
+        "function": "oasExample",
         "functionOptions": {
-          "field": "example",
-          "schemaPath": "$",
-          "oasVersion": 3
+          "schemaField": "$",
+          "oasVersion": 3,
+          "type": "schema"
         }
       }
     },


Test patch:
diff --git a/src/functions/__tests__/schema-path.test.ts b/src/functions/__tests__/schemaPath.test.ts
similarity index 98%
rename from src/functions/__tests__/schema-path.test.ts
rename to src/functions/__tests__/schemaPath.test.ts
index 5d30516c..f4eccf12 100644
--- a/src/functions/__tests__/schema-path.test.ts
+++ b/src/functions/__tests__/schemaPath.test.ts
@@ -1,4 +1,4 @@
-import { schemaPath } from '../schema-path';
+import { schemaPath } from '../schemaPath';
 
 function runSchemaPath(target: any, field: string, schemaPathStr: string) {
   return schemaPath(target, { field, schemaPath: schemaPathStr }, { given: [], target: [] }, {
diff --git a/src/functions/schema-path.ts b/src/functions/schemaPath.ts
similarity index 100%
rename from src/functions/schema-path.ts
rename to src/functions/schemaPath.ts
diff --git a/src/rulesets/oas/__tests__/oas2-valid-definition-example.ts b/src/rulesets/oas/__tests__/oas2-valid-definition-example.ts
index 551c3644..3a85ca44 100644
--- a/src/rulesets/oas/__tests__/oas2-valid-definition-example.ts
+++ b/src/rulesets/oas/__tests__/oas2-valid-definition-example.ts
@@ -1,6 +1,9 @@
 import { DiagnosticSeverity } from '@stoplight/types';
 import { RuleType, Spectral } from '../../../spectral';
 import * as ruleset from '../index.json';
+import oasExample from '../functions/oasExample';
+import { setFunctionContext } from '../../evaluators';
+import { functions } from '../../../functions';
 
 describe('oas2-valid-definition-example', () => {
   let s: Spectral;
@@ -8,6 +11,7 @@ describe('oas2-valid-definition-example', () => {
   beforeEach(() => {
     s = new Spectral();
     s.registerFormat('oas2', () => true);
+    s.setFunctions({ oasExample: setFunctionContext({ functions }, oasExample) });
     s.setRules({
       'oas2-valid-definition-example': Object.assign(ruleset.rules['oas2-valid-definition-example'], {
         recommended: true,
@@ -16,13 +20,13 @@ describe('oas2-valid-definition-example', () => {
     });
   });
 
-  test('will pass when simple example is valid', async () => {
+  test.each(['example', 'x-example'])('will pass when simple %s is valid', async field => {
     const results = await s.run({
       definitions: [
         {
           xoxo: {
             type: 'string',
-            example: 'doggie',
+            [field]: 'doggie',
           },
         },
       ],
@@ -30,13 +34,13 @@ describe('oas2-valid-definition-example', () => {
     expect(results).toHaveLength(0);
   });
 
-  test('will fail when simple example is invalid', async () => {
+  test.each(['example', 'x-example'])('will fail when simple %s is invalid', async field => {
     const results = await s.run({
       definitions: [
         {
           xoxo: {
             type: 'string',
-            example: 123,
+            [field]: 123,
           },
         },
       ],
@@ -44,7 +48,7 @@ describe('oas2-valid-definition-example', () => {
     expect(results).toEqual([
       expect.objectContaining({
         code: 'oas2-valid-definition-example',
-        message: `\`example\` property type should be string`,
+        message: `\`${field}\` property type should be string`,
         severity: DiagnosticSeverity.Error,
       }),
     ]);
diff --git a/src/rulesets/oas/__tests__/oas2-valid-parameter-example.ts b/src/rulesets/oas/__tests__/oas2-valid-parameter-example.ts
index c2580224..44362828 100644
--- a/src/rulesets/oas/__tests__/oas2-valid-parameter-example.ts
+++ b/src/rulesets/oas/__tests__/oas2-valid-parameter-example.ts
@@ -1,6 +1,9 @@
 import { DiagnosticSeverity } from '@stoplight/types';
 import { RuleType, Spectral } from '../../../spectral';
 import * as ruleset from '../index.json';
+import { setFunctionContext } from '../../evaluators';
+import { functions } from '../../../functions';
+import oasExample from '../functions/oasExample';
 
 describe('oas2-valid-parameter-example', () => {
   let s: Spectral;
@@ -8,6 +11,7 @@ describe('oas2-valid-parameter-example', () => {
   beforeEach(() => {
     s = new Spectral();
     s.registerFormat('oas2', () => true);
+    s.setFunctions({ oasExample: setFunctionContext({ functions }, oasExample) });
     s.setRules({
       'oas2-valid-parameter-example': Object.assign(ruleset.rules['oas2-valid-parameter-example'], {
         recommended: true,
@@ -16,14 +20,14 @@ describe('oas2-valid-parameter-example', () => {
     });
   });
 
-  test('will pass when simple example is valid', async () => {
+  test.each(['example', 'x-example'])('will pass when %s example is valid', async field => {
     const results = await s.run({
       parameters: [
         {
           in: 'body',
           schema: {
             type: 'string',
-            example: 'doggie',
+            [field]: 'doggie',
           },
         },
       ],
@@ -31,29 +35,14 @@ describe('oas2-valid-parameter-example', () => {
     expect(results).toHaveLength(0);
   });
 
-  test('will ignore when not in body', async () => {
-    const results = await s.run({
-      parameters: [
-        {
-          in: 'query',
-          schema: {
-            type: 'number',
-            example: 'doggie',
-          },
-        },
-      ],
-    });
-    expect(results).toHaveLength(0);
-  });
-
-  test('will fail when simple example is invalid', async () => {
+  test.each(['example', 'x-example'])('will fail when %s example is invalid', async field => {
     const results = await s.run({
       parameters: [
         {
           in: 'body',
           schema: {
             type: 'string',
-            example: 123,
+            [field]: 123,
           },
         },
       ],
@@ -61,7 +50,7 @@ describe('oas2-valid-parameter-example', () => {
     expect(results).toEqual([
       expect.objectContaining({
         code: 'oas2-valid-parameter-example',
-        message: '`example` property type should be string',
+        message: `\`${field}\` property type should be string`,
         severity: DiagnosticSeverity.Error,
       }),
     ]);
diff --git a/src/rulesets/oas/__tests__/oas2-valid-response-example.ts b/src/rulesets/oas/__tests__/oas2-valid-response-example.ts
new file mode 100644
index 00000000..d3d6d6d2
--- /dev/null
+++ b/src/rulesets/oas/__tests__/oas2-valid-response-example.ts
@@ -0,0 +1,63 @@
+import { DiagnosticSeverity } from '@stoplight/types';
+import { RuleType, Spectral } from '../../../spectral';
+import * as ruleset from '../index.json';
+import { setFunctionContext } from '../../evaluators';
+import { functions } from '../../../functions';
+import oasExample from '../functions/oasExample';
+
+describe('oas2-valid-response-example', () => {
+  let s: Spectral;
+
+  beforeEach(() => {
+    s = new Spectral();
+    s.registerFormat('oas2', () => true);
+    s.setFunctions({ oasExample: setFunctionContext({ functions }, oasExample) });
+    s.setRules({
+      'oas2-valid-response-example': Object.assign(ruleset.rules['oas2-valid-response-example'], {
+        recommended: true,
+        type: RuleType[ruleset.rules['oas2-valid-parameter-example'].type],
+      }),
+    });
+  });
+
+  test('will pass when examples are valid', async () => {
+    const results = await s.run({
+      responses: {
+        200: {
+          schema: {
+            type: 'string',
+          },
+          examples: {
+            'application/json': 'test',
+            'application/yaml': '',
+          },
+        },
+      },
+    });
+    expect(results).toHaveLength(0);
+  });
+
+  test('will fail when example is invalid', async () => {
+    const results = await s.run({
+      responses: {
+        200: {
+          schema: {
+            type: 'string',
+          },
+          examples: {
+            'application/json': 'test',
+            'application/yaml': 2,
+          },
+        },
+      },
+    });
+    expect(results).toEqual([
+      expect.objectContaining({
+        code: 'oas2-valid-response-example',
+        message: '`application/yaml` property type should be string',
+        severity: DiagnosticSeverity.Error,
+        path: ['responses', '200', 'examples', 'application/yaml'],
+      }),
+    ]);
+  });
+});
diff --git a/src/rulesets/oas/__tests__/oas3-valid-content-schema-example.ts b/src/rulesets/oas/__tests__/oas3-valid-content-schema-example.ts
deleted file mode 100644
index 28596f4f..00000000
--- a/src/rulesets/oas/__tests__/oas3-valid-content-schema-example.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import testRule from './templates/_schema-example';
-
-const ruleName = 'oas3-valid-content-schema-example';
-
-describe(ruleName, () => testRule(ruleName, 'content'));
diff --git a/src/rulesets/oas/__tests__/oas3-valid-oas-parameter-example.ts b/src/rulesets/oas/__tests__/oas3-valid-oas-parameter-example.ts
index 2cace07c..7f9685de 100644
--- a/src/rulesets/oas/__tests__/oas3-valid-oas-parameter-example.ts
+++ b/src/rulesets/oas/__tests__/oas3-valid-oas-parameter-example.ts
@@ -1,6 +1,9 @@
 import { DiagnosticSeverity } from '@stoplight/types';
 import { RuleType, Spectral } from '../../../spectral';
 import * as ruleset from '../index.json';
+import { setFunctionContext } from '../../evaluators';
+import { functions } from '../../../functions';
+import oasExample from '../functions/oasExample';
 
 describe('oas3-valid-oas-parameter-example', () => {
   let s: Spectral;
@@ -9,6 +12,7 @@ describe('oas3-valid-oas-parameter-example', () => {
     s = new Spectral();
 
     s.registerFormat('oas3', () => true);
+    s.setFunctions({ oasExample: setFunctionContext({ functions }, oasExample) });
     s.setRules({
       'oas3-valid-oas-parameter-example': Object.assign(ruleset.rules['oas3-valid-oas-parameter-example'], {
         recommended: true,
diff --git a/src/rulesets/oas/__tests__/oas3-valid-parameter-schema-example.ts b/src/rulesets/oas/__tests__/oas3-valid-parameter-schema-example.ts
index 2d58dce9..0b1cf061 100644
--- a/src/rulesets/oas/__tests__/oas3-valid-parameter-schema-example.ts
+++ b/src/rulesets/oas/__tests__/oas3-valid-parameter-schema-example.ts
@@ -1,6 +1,9 @@
 import { DiagnosticSeverity } from '@stoplight/types';
 import { RuleType, Spectral } from '../../../spectral';
 import * as ruleset from '../index.json';
+import { setFunctionContext } from '../../evaluators';
+import { functions } from '../../../functions';
+import oasExample from '../functions/oasExample';
 
 describe('oas3-valid-parameter-schema-example', () => {
   let s: Spectral;
@@ -8,6 +11,7 @@ describe('oas3-valid-parameter-schema-example', () => {
   beforeEach(() => {
     s = new Spectral();
     s.registerFormat('oas3', () => true);
+    s.setFunctions({ oasExample: setFunctionContext({ functions }, oasExample) });
     s.setRules({
       'oas3-valid-parameter-schema-example': Object.assign(ruleset.rules['oas3-valid-parameter-schema-example'], {
         recommended: true,
diff --git a/src/rulesets/oas/__tests__/oas3-valid-schema-example.ts b/src/rulesets/oas/__tests__/oas3-valid-schema-example.ts
index 3487e42b..0362c3d4 100644
--- a/src/rulesets/oas/__tests__/oas3-valid-schema-example.ts
+++ b/src/rulesets/oas/__tests__/oas3-valid-schema-example.ts
@@ -1,6 +1,9 @@
 import { DiagnosticSeverity } from '@stoplight/types';
 import { RuleType, Spectral } from '../../../spectral';
 import * as ruleset from '../index.json';
+import { setFunctionContext } from '../../evaluators';
+import { functions } from '../../../functions';
+import oasExample from '../functions/oasExample';
 
 describe('oas3-valid-schema-example', () => {
   let s: Spectral;
@@ -8,6 +11,7 @@ describe('oas3-valid-schema-example', () => {
   beforeEach(() => {
     s = new Spectral();
     s.registerFormat('oas3', () => true);
+    s.setFunctions({ oasExample: setFunctionContext({ functions }, oasExample) });
     s.setRules({
       'oas3-valid-schema-example': Object.assign(ruleset.rules['oas3-valid-schema-example'], {
         recommended: true,
diff --git a/src/rulesets/oas/__tests__/operation-success-response.ts b/src/rulesets/oas/__tests__/operation-success-response.ts
index 2e923ed9..244a194a 100644
--- a/src/rulesets/oas/__tests__/operation-success-response.ts
+++ b/src/rulesets/oas/__tests__/operation-success-response.ts
@@ -65,8 +65,9 @@ describe('operation-success-response', () => {
     expect(results).toHaveLength(0);
   });
 
-  ['put', 'post', 'delete', 'options', 'head', 'patch', 'trace'].forEach(method => {
-    test(`warns if HTTP verb ${method} is missing a success response`, async () => {
+  test.each(['put', 'post', 'delete', 'options', 'head', 'patch', 'trace'])(
+    'warns if HTTP verb %s is missing a success response',
+    async method => {
       const obj = {
         swagger: '2.0',
         paths: {
@@ -86,8 +87,8 @@ describe('operation-success-response', () => {
           path: ['paths', '/path', method, 'responses'],
         }),
       ]);
-    });
-  });
+    },
+  );
 
   test('warns about missing success response', async () => {
     const results = await spectral.run({
diff --git a/src/rulesets/oas/__tests__/templates/_oas-example.ts b/src/rulesets/oas/__tests__/templates/_oas-example.ts
index cc90a3ea..e05dbce5 100644
--- a/src/rulesets/oas/__tests__/templates/_oas-example.ts
+++ b/src/rulesets/oas/__tests__/templates/_oas-example.ts
@@ -1,6 +1,9 @@
 import { DiagnosticSeverity } from '@stoplight/types';
 import { RuleType, Spectral } from '../../../../spectral';
 import { rules } from '../../index.json';
+import { setFunctionContext } from '../../../evaluators';
+import { functions } from '../../../../functions';
+import oasExample from '../../functions/oasExample';
 
 export default (ruleName: string, path: string) => {
   let s: Spectral;
@@ -8,6 +11,7 @@ export default (ruleName: string, path: string) => {
   beforeEach(() => {
     s = new Spectral();
     s.registerFormat('oas3', () => true);
+    s.setFunctions({ oasExample: setFunctionContext({ functions }, oasExample) });
     s.setRules({
       [ruleName]: Object.assign(rules[ruleName], {
         recommended: true,
@@ -245,4 +249,30 @@ export default (ruleName: string, path: string) => {
 
     expect(results).toHaveLength(0);
   });
+
+  test('will fail when simple example is invalid (examples)', async () => {
+    const results = await s.run({
+      openapi: '3.0.0',
+      [path]: {
+        xoxo: {
+          schema: {
+            type: 'string',
+          },
+          examples: {
+            test1: {
+              value: 123,
+            },
+          },
+        },
+      },
+    });
+
+    expect(results).toEqual([
+      expect.objectContaining({
+        severity: DiagnosticSeverity.Error,
+        code: ruleName,
+        message: '`value` property type should be string',
+      }),
+    ]);
+  });
 };
diff --git a/src/rulesets/oas/__tests__/templates/_schema-example.ts b/src/rulesets/oas/__tests__/templates/_schema-example.ts
index a37597b2..0e7da1ca 100644
--- a/src/rulesets/oas/__tests__/templates/_schema-example.ts
+++ b/src/rulesets/oas/__tests__/templates/_schema-example.ts
@@ -1,6 +1,9 @@
 import { DiagnosticSeverity } from '@stoplight/types';
 import { RuleType, Spectral } from '../../../../spectral';
 import { rules } from '../../index.json';
+import { setFunctionContext } from '../../../evaluators';
+import { functions } from '../../../../functions';
+import oasExample from '../../functions/oasExample';
 
 export default (ruleName: string, path: string) => {
   let s: Spectral;
@@ -8,6 +11,7 @@ export default (ruleName: string, path: string) => {
   beforeEach(() => {
     s = new Spectral();
     s.registerFormat('oas3', () => true);
+    s.setFunctions({ oasExample: setFunctionContext({ functions }, oasExample) });
     s.setRules({
       [ruleName]: Object.assign(rules[ruleName], {
         recommended: true,
diff --git a/test-harness/scenarios/enabled-rules-amount.oas3.scenario b/test-harness/scenarios/enabled-rules-amount.oas3.scenario
index 06df64ec..9cbb763e 100644
--- a/test-harness/scenarios/enabled-rules-amount.oas3.scenario
+++ b/test-harness/scenarios/enabled-rules-amount.oas3.scenario
@@ -24,7 +24,7 @@ components:
 ====command====
 {bin} lint {document} --ruleset ./rulesets/parameter-description.oas3.yaml -v
 ====stdout====
-Found 53 rules (1 enabled)
+Found 54 rules (1 enabled)
 Linting {document}
 OpenAPI 3.x detected
 
diff --git a/test-harness/scenarios/examples.oas2.scenario b/test-harness/scenarios/examples.oas2.scenario
new file mode 100644
index 00000000..d57fd478
--- /dev/null
+++ b/test-harness/scenarios/examples.oas2.scenario
@@ -0,0 +1,275 @@
+====test====
+All kinds of examples are validated - OAS2
+====document====
+# https://github.com/philsturgeon/examples-examples/blob/master/oas2.yaml
+swagger: '2.0'
+info:
+  title: Examples Examples
+  version: '1.0'
+  description: |-
+    Examples of loads of examples, some right, some wrong.
+  contact:
+    name: Phil Sturgeon
+    email: phil@stoplight.io
+    url: https://stoplight.io
+  license:
+    name: MIT
+host: example.org
+schemes:
+- http
+consumes:
+- application/json
+produces:
+- application/json
+tags:
+  - name: media-examples
+    description: >
+      The sort of examples which show up in the [Responses
+      Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#responseObject),
+      which are `examples` with a mime type as a key.
+  - name: schema-examples
+    description: >
+      The sort of examples which show up for properties inside a [Schema
+      Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#schemaObject).
+paths:
+  "/schema-example":
+    get:
+      operationId: schema-examples-inline
+      description: when schema examples are inline (no $ref involved)
+      tags:
+        - schema-examples
+      responses:
+        '200':
+          description: 'Success, because the example matches the schema'
+          schema:
+            properties:
+              id:
+                type: integer
+              name:
+                type: string
+              completed:
+                type: boolean
+              completed_at:
+                type: string
+                format: date-time
+            required:
+              - id
+              - name
+              - completed
+            # notice how im in the schemq object, not next to it
+            example:
+              id: 1
+              name: get food
+              completed: false
+              completed_at: '1955-04-23T13:22:52.685Z'
+        '500':
+          description: 'Fail, because the example is nonsense'
+          schema:
+            properties:
+              id:
+                type: integer
+              name:
+                type: string
+              completed:
+                type: boolean
+              completed_at:
+                type: string
+                format: date-time
+            required:
+              - id
+              - name
+              - completed
+            # notice how im in the schema object, not next to it
+            example:
+              id: 1
+              name: fail
+              completed: Not a boolean
+              completed_at: 'Not a date'
+        '501':
+          description: 'Fail, because the property example is nonsense'
+          schema:
+            properties:
+              some-bool:
+                type: boolean
+                example: not a bool
+              some-date:
+                type: string
+                format: date-time
+                example: not a date
+              some-url:
+                type: string
+                format: url
+                example: not a URL
+
+  /param-examples:
+    get:
+      operationId: parameter-example
+      description: when parameter examples are inline (no $ref involved)
+      tags:
+        - parameter-examples
+      parameters:
+        - name: single-example-good
+          description: Valid to its schema
+          in: query
+          type: string
+          enum: [foo, bar]
+          x-example: foo
+
+        - name: single-example-bad
+          description: Not valid to its schema
+          in: query
+          type: string
+          enum: [foo, bar]
+          x-example: not "foo" or "bar" so this is bad
+
+        # OAS2 does not have the schema keyword here, so no need to support that.
+        # OAS2 does not have multiple examples support for keywords, so no need to support that.
+
+      responses:
+        '200':
+          description: 'ok'
+
+  "/response-examples":
+    get:
+      operationId: response-examples-inline
+      description: when examples are inline (no $ref involved)
+      tags:
+        - media-examples
+      responses:
+        '200':
+          description: 'Success, because the example matches the schema'
+          schema:
+            properties:
+              id:
+                type: integer
+              name:
+                type: string
+              completed:
+                type: boolean
+              completed_at:
+                type: string
+                format: date-time
+            required:
+              - id
+              - name
+              - completed
+          examples:
+            application/json:
+              id: 1
+              name: get food
+              completed: false
+              completed_at: '1955-04-23T13:22:52.685Z'
+        '500':
+          description: 'Fail, because the example is nonsense'
+          schema:
+            properties:
+              id:
+                type: integer
+              name:
+                type: string
+              completed:
+                type: boolean
+              completed_at:
+                type: string
+                format: date-time
+            required:
+              - id
+              - name
+              - completed
+          examples:
+            application/json:
+              id: 1
+              completed: Not a boolean
+              completed_at: 'Not a date'
+
+  "/response-examples-via-$ref":
+    get:
+      operationId: response-examples-via-$ref
+      description: When the example is compared against a schema that ia a $ref to another file
+      tags:
+        - media-examples
+      responses:
+        '200':
+          description: 'Success, because the example matches the definition in a $ref'
+          schema:
+            "$ref": "#/definitions/todo-full"
+          examples:
+            application/json:
+              id: 1
+              name: get food
+              completed: false
+              completed_at: '1955-04-23T13:22:52.685Z'
+              created_at: '1994-11-05T03:26:51.471Z'
+              updated_at: '1989-07-29T11:30:06.701Z'
+definitions:
+  todo-full:
+    title: Todo Full
+    allOf:
+      - "$ref": "#/definitions/todo-partial"
+      - type: object
+        properties:
+          id:
+            type: integer
+            minimum: 0
+            maximum: 1000000
+          completed_at:
+            type: string
+            format: date-time
+          created_at:
+            type: string
+            format: date-time
+          updated_at:
+            type: string
+            format: date-time
+          user:
+            "$ref": "#/definitions/user"
+        required:
+          - id
+          - user
+    x-tags:
+      - Todos
+  todo-partial:
+    title: Todo Partial
+    type: object
+    properties:
+      name:
+        type: string
+      completed:
+        type: boolean
+    required:
+      - name
+      - completed
+    x-tags:
+      - Todos
+  user:
+    title: User
+    type: object
+    properties:
+      name:
+        type: string
+        description: The user's full name.
+      age:
+        type: number
+        minimum: 0
+        maximum: 150
+    required:
+      - name
+      - age
+    x-tags:
+      - Todos
+====command====
+{bin} lint {document}
+====stdout====
+OpenAPI 2.0 (Swagger) detected
+
+{document}
+  83:26    error  oas2-valid-response-schema-example  `completed` property type should be boolean
+  91:26    error  oas2-valid-response-schema-example  `example` property type should be boolean
+  95:26    error  oas2-valid-response-schema-example  `example` property should match format `date-time`
+  99:26    error  oas2-valid-response-schema-example  `example` property should match format `url`
+ 106:11  warning  operation-tag-defined               Operation tags should be defined in global tags.
+ 120:22    error  oas2-valid-parameter-example        `x-example` property should be equal to one of the allowed values: foo, bar
+ 177:30    error  oas2-valid-response-example         `application/json` property should have required property `name`
+ 194:30    error  oas2-valid-response-example         `application/json` property should have required property `user`
+
+✖ 8 problems (7 errors, 1 warning, 0 infos, 0 hints)
diff --git a/test-harness/scenarios/examples.oas3.scenario b/test-harness/scenarios/examples.oas3.scenario
new file mode 100644
index 00000000..b50dcf1a
--- /dev/null
+++ b/test-harness/scenarios/examples.oas3.scenario
@@ -0,0 +1,366 @@
+====test====
+All kinds of examples are validated - OAS3
+====document====
+# https://github.com/philsturgeon/examples-examples/blob/master/oas3.yaml
+openapi: 3.0.0
+info:
+  title: Examples Examples
+  version: "1.0"
+  description: Examples of loads of examples, some right, some wrong.
+  contact:
+    name: Phil Sturgeon
+    email: phil@stoplight.io
+    url: https://stoplight.io
+  license:
+    name: MIT
+tags:
+
+  - name: media-examples
+    description: >
+      The sort of examples which show up in the [Media Type
+      Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#mediaTypeObject),
+      which are `example` or `examples`. There is more than just an `s` difference between these keywords, they're different shapes too.
+      `example` is singular example which just contains the actual example value.
+      ```yaml
+        post:
+          summary: Adds a new user
+          requestBody:
+            content:
+              application/json:    # Media type
+                schema:            # Request body contents
+                  $ref: '#/components/schemas/User'
+                example:
+                  id: 10
+                  name: Jessica Smith
+          responses:
+            '200':
+              description: OK
+      ```
+      `examples` is an array of objects, which have an arbitrary string which acts as a nickname for that example,
+      and that property is another object which contains several optional properties including a
+      `value` property, which then contains the actual example.
+      ```yaml
+        post:
+          summary: Adds a new user
+          requestBody:
+            content:
+              application/json:     # Media type
+                schema:             # Request body contents
+                  $ref: '#/components/schemas/User'
+                examples:
+                  Jessica:   # Example 1
+                    value:   # <-- this is different
+                      id: 10
+                      name: Jessica Smith
+                  Ron:       # Example 2
+                    value:   # <-- this is different
+                      id: 11
+                      name: Ron Stewart
+          responses:
+            '200':
+              description: OK
+      ```
+      Confused? Good. Me too.
+  - name: parameter-examples
+    description: >
+      Parameters (things in the Parameter Object) can have examples or an
+      example, or a THIRD THING which is an example INSIDE a schema. BLOODY
+      HELL.
+  - name: schema-examples
+    description:
+      The sort of examples which show up for properties inside a Schema Object.
+      This is a singular example keyword, and takes a literal value.
+
+servers:
+  - url: http://example.org
+paths:
+  /schema-example:
+    get:
+      operationId: schema-examples
+      description: when schema examples are inline (no $ref involved)
+      tags:
+        - schema-examples
+      responses:
+        "200":
+          description: Success, because the example matches the schema
+          content:
+            application/json:
+              schema:
+                properties:
+                  id:
+                    type: integer
+                  name:
+                    type: string
+                  completed:
+                    type: boolean
+                  completed_at:
+                    type: string
+                    format: date-time
+                required:
+                  - id
+                  - name
+                  - completed
+                # notice how im in the schemq object, not next to it
+                example:
+                  id: 1
+                  name: get food
+                  completed: false
+                  completed_at: 1955-04-23T13:22:52.685Z
+        "500":
+          description: Fail, because the example is nonsense
+          content:
+            application/json:
+              schema:
+                properties:
+                  id:
+                    type: integer
+                  name:
+                    type: string
+                  completed:
+                    type: boolean
+                  completed_at:
+                    type: string
+                    format: date-time
+                required:
+                  - id
+                  - name
+                  - completed
+                # notice how im in the schema object, not next to it
+                example:
+                  id: 1
+                  completed: Not a boolean
+                  completed_at: Not a date
+        "501":
+          description: Fail, because the property example is nonsense
+          content:
+            application/json:
+              schema:
+                properties:
+                  some-bool:
+                    type: boolean
+                    example: not a bool
+                  some-date:
+                    type: string
+                    format: date-time
+                    example: not a date
+                  some-url:
+                    type: string
+                    format: url
+                    example: not a URL
+
+  /param-examples:
+    get:
+      operationId: parameter-example
+      description: when parameter examples are inline (no $ref involved)
+      tags:
+        - parameter-examples
+      parameters:
+        - name: single-example-good
+          description: Valid to its schema
+          in: query
+          schema:
+            type: string
+            enum: [foo, bar]
+          example: foo
+
+        - name: single-example-bad
+          description: Not valid to its schema
+          in: query
+          schema:
+            type: string
+            enum: [foo, bar]
+          example: not "foo" or "bar" so this is bad
+
+        - name: single-schema-example-good
+          description: Valid to its schema
+          in: query
+          schema:
+            type: string
+            enum: [foo, bar]
+            example: foo
+
+        - name: single-schema-example-bad
+          description: Not valid to its schema
+          in: query
+          schema:
+            type: string
+            enum: [foo, bar]
+            example: not "foo" or "bar" so this is bad
+
+        - name: multiple-examples
+          description: Valid to its schema
+          in: query
+          schema:
+            type: string
+            enum: [foo, bar]
+          examples:
+            the-good:
+              value: foo
+            the-bad:
+              value: 123
+            the-ugly:
+              value: [an, array]
+
+        # Seeing as OAS3 supports a single parameter example, multiple parameter
+        # examples, and schema examples, there is no need for x-example or x-examples at all.
+
+      responses:
+        '200':
+          description: 'ok'
+
+  /response-examples:
+    get:
+      operationId: response-examples-inline
+      description: when examples are inline (no $ref involved)
+      tags:
+        - media-examples
+      responses:
+        "200":
+          description: Success, because the example matches the schema
+          content:
+            application/json:
+              schema:
+                properties:
+                  id:
+                    type: integer
+                  name:
+                    type: string
+                  completed:
+                    type: boolean
+                  completed_at:
+                    type: string
+                    format: date-time
+                required:
+                  - id
+                  - name
+                  - completed
+              examples:
+                response:
+                  value:
+                    id: 1
+                    name: get food
+                    completed: false
+                    completed_at: 1955-04-23T13:22:52.685Z
+        "500":
+          description: Fail, because the example is nonsense
+          content:
+            application/json:
+              schema:
+                properties:
+                  id:
+                    type: integer
+                  name:
+                    type: string
+                  completed:
+                    type: boolean
+                  completed_at:
+                    type: string
+                    format: date-time
+                required:
+                  - id
+                  - name
+                  - completed
+              examples:
+                response:
+                  value:
+                    completed: Not a boolean
+                    completed_at: Not a date
+
+  /response-examples-via-$ref:
+    get:
+      operationId: response-examples-via-$ref
+      description: When the example is compared against a schema that ia a $ref to
+        another file
+      tags:
+        - media-examples
+      responses:
+        "200":
+          description: Success, because the example matches the definition in a $ref
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/todo-full"
+              examples:
+                response:
+                  value:
+                    id: 1
+                    name: get food
+                    completed: false
+                    completed_at: 1955-04-23T13:22:52.685Z
+                    created_at: 1994-11-05T03:26:51.471Z
+                    updated_at: 1989-07-29T11:30:06.701Z
+components:
+  schemas:
+    todo-full:
+      title: Todo Full
+      allOf:
+        - "$ref": "#/components/schemas/todo-partial"
+        - type: object
+          properties:
+            id:
+              type: integer
+              minimum: 0
+              maximum: 1000000
+            completed_at:
+              type: string
+              format: date-time
+            created_at:
+              type: string
+              format: date-time
+            updated_at:
+              type: string
+              format: date-time
+            user:
+              "$ref": "#/components/schemas/user"
+          required:
+            - id
+            - user
+      x-tags:
+        - Todos
+    todo-partial:
+      title: Todo Partial
+      type: object
+      properties:
+        name:
+          type: string
+        completed:
+          type: boolean
+      required:
+        - name
+        - completed
+      x-tags:
+        - Todos
+    user:
+      title: User
+      type: object
+      properties:
+        name:
+          type: string
+          description: The user's full name.
+        age:
+          type: number
+          minimum: 0
+          maximum: 150
+      required:
+        - name
+        - age
+      x-tags:
+        - Todos
+====command====
+{bin} lint {document}
+====stdout====
+OpenAPI 3.x detected
+
+{document}
+ 126:25  error  oas3-valid-schema-example            `example` property should have required property `name`
+ 138:30  error  oas3-valid-schema-example            `example` property type should be boolean
+ 142:30  error  oas3-valid-schema-example            `example` property should match format `date-time`
+ 146:30  error  oas3-valid-schema-example            `example` property should match format `url`
+ 169:20  error  oas3-valid-oas-parameter-example     `example` property should be equal to one of the allowed values: foo, bar
+ 185:22  error  oas3-valid-parameter-schema-example  `example` property should be equal to one of the allowed values: foo, bar
+ 197:22  error  oas3-valid-oas-parameter-example     `value` property type should be string
+ 199:21  error  oas3-valid-oas-parameter-example     `value` property type should be string
+ 262:25  error  oas3-valid-oas-content-example       `value` property should have required property `id`
+ 282:25  error  oas3-valid-oas-content-example       `value` property should have required property `user`
+
+✖ 10 problems (10 errors, 0 warnings, 0 infos, 0 hints)
diff --git a/test-harness/scenarios/valid-response-examples.oas2.scenario b/test-harness/scenarios/valid-response-examples.oas2.scenario
new file mode 100644
index 00000000..f15793ad
--- /dev/null
+++ b/test-harness/scenarios/valid-response-examples.oas2.scenario
@@ -0,0 +1,44 @@
+====test====
+Response Examples - are validated to comply with schema
+====document====
+swagger: "2.0"
+paths:
+  /pets:
+    get:
+      responses:
+        '200':
+          description: abc
+          schema:
+            type: boolean
+          examples:
+            application/json: 2
+      post:
+        responses:
+          '200':
+            description: abc
+            schema:
+              type: object
+              properties:
+                user:
+                  type: string
+              required:
+                - user
+            examples:
+              application/json:
+                user: Joe
+              application/yaml:
+                _user: Kate
+====asset:ruleset====
+extends: [[spectral:oas, off]]
+rules:
+  oas2-valid-response-example: error
+====command====
+{bin} lint {document} --ruleset {asset:ruleset}
+====stdout====
+OpenAPI 2.0 (Swagger) detected
+
+{document}
+ 11:31  error  oas2-valid-response-example  `application/json` property type should be boolean
+ 26:32  error  oas2-valid-response-example  `application/yaml` property should have required property `user`
+
+✖ 2 problems (2 errors, 0 warnings, 0 infos, 0 hints)