Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: replace isPrimitiveType with more sophisticated isPrimitiveSchema #519

Merged
merged 3 commits into from
Nov 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions packages/ruleset/src/functions/inline-schema-rules.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const {
isJsonMimeType,
isArraySchema,
isEmptyObjectSchema,
isPrimitiveType,
isPrimitiveSchema,
isRefSiblingSchema,
validateSubschemas
} = require('../utils');
Expand Down Expand Up @@ -35,7 +35,7 @@ function inlineResponseSchema(schema, options, { path }) {
if (
!schema.$ref &&
isJsonMimeType(mimeType) &&
!isPrimitiveType(schema) &&
!isPrimitiveSchema(schema) &&
!arrayItemsAreRefOrPrimitive(schema) &&
!isRefSiblingSchema(schema)
) {
Expand Down Expand Up @@ -72,7 +72,7 @@ function inlineRequestSchema(schema, options, { path }) {
if (
!schema.$ref &&
isJsonMimeType(mimeType) &&
!isPrimitiveType(schema) &&
!isPrimitiveSchema(schema) &&
!arrayItemsAreRefOrPrimitive(schema) &&
!isRefSiblingSchema(schema)
) {
Expand Down Expand Up @@ -111,9 +111,11 @@ function inlineRequestSchema(schema, options, { path }) {
* @returns an array containing the violations found or [] if no violations
*/
function inlinePropertySchema(schema, options, { path }) {
// Check each sub-schema that is reachable from "schema" (properties,
// additionalProperties, allOf/anyOf/oneOf, array items, etc.) .
return validateSubschemas(schema, path, checkForInlineNestedObjectSchema);
// If "schema" is not a primitive, then check each sub-schema that is reachable from
// "schema" (properties, additionalProperties, allOf/anyOf/oneOf, array items, etc.).
return isPrimitiveSchema(schema)
? []
: validateSubschemas(schema, path, checkForInlineNestedObjectSchema);
}

/**
Expand All @@ -129,7 +131,7 @@ function checkForInlineNestedObjectSchema(schema, path) {
// then bail out now to avoid a warning.
if (
schema.$ref ||
isPrimitiveType(schema) ||
isPrimitiveSchema(schema) ||
isRefSiblingSchema(schema) ||
isEmptyObjectSchema(schema) ||
isArraySchema(schema)
Expand Down Expand Up @@ -182,5 +184,5 @@ function checkForInlineNestedObjectSchema(schema, path) {
function arrayItemsAreRefOrPrimitive(schema) {
const isArray = isArraySchema(schema);
const items = isArray && getCompositeSchemaAttribute(schema, 'items');
return items && (items.$ref || isPrimitiveType(items));
return items && (items.$ref || isPrimitiveSchema(items));
}
16 changes: 16 additions & 0 deletions packages/ruleset/src/utils/get-schema-type.js
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,21 @@ const isStringSchema = schema => {
return checkCompositeSchemaForConstraint(schema, s => s.type === 'string');
};

/**
* Returns `true` for a primitive schema (anything encoded as a JSON string, number, or boolean).
*
* @param {object} schema - Simple or composite OpenAPI 3.0 schema object.
* @returns {boolean}
*/
const isPrimitiveSchema = schema => {
// This implementation should remain stable when additional specific types are added
return (
!isObjectSchema(schema) &&
!isArraySchema(schema) &&
getSchemaType(schema) !== SchemaType.UNKNOWN
);
};

module.exports = {
SchemaType,
getSchemaType,
Expand All @@ -311,5 +326,6 @@ module.exports = {
isIntegerSchema,
isNumberSchema,
isObjectSchema,
isPrimitiveSchema,
isStringSchema
};
1 change: 0 additions & 1 deletion packages/ruleset/src/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ module.exports = {
isDeprecated: require('./is-deprecated'),
isEmptyObjectSchema: require('./is-empty-object-schema'),
isObject: require('./is-object'),
isPrimitiveType: require('./is-primitive-type'),
isRefSiblingSchema: require('./is-ref-sibling-schema'),
mergeAllOfSchemaProperties: require('./merge-allof-schema-properties'),
...require('./mimetype-utils'),
Expand Down
13 changes: 0 additions & 13 deletions packages/ruleset/src/utils/is-primitive-type.js

This file was deleted.

18 changes: 18 additions & 0 deletions packages/ruleset/test/inline-property-schema.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,24 @@ describe('Spectral rule: inline-property-schema', () => {
const results = await testRule(ruleId, rule, testDocument);
expect(results).toHaveLength(0);
});

it('Composed primitive schema', async () => {
const testDocument = makeCopy(rootDocument);

testDocument.components.schemas.Car.properties['inline_prop'] = {
oneOf: [
{
type: 'string'
},
{
type: 'string'
}
]
};

const results = await testRule(ruleId, rule, testDocument);
expect(results).toHaveLength(0);
});
});

describe('Should yield errors', () => {
Expand Down
20 changes: 20 additions & 0 deletions packages/ruleset/test/inline-request-schema.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,26 @@ describe('Spectral rule: inline-request-schema', () => {
const results = await testRule(ruleId, rule, testDocument);
expect(results).toHaveLength(0);
});

it('Composed primitive schema', async () => {
const testDocument = makeCopy(rootDocument);

testDocument.paths['/v1/drinks'].post.requestBody.content[
'application/json'
].schema = {
oneOf: [
{
type: 'string'
},
{
type: 'string'
}
]
};

const results = await testRule(ruleId, rule, testDocument);
expect(results).toHaveLength(0);
});
});

describe('Should yield errors', () => {
Expand Down
20 changes: 20 additions & 0 deletions packages/ruleset/test/inline-response-schema.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,26 @@ describe('Spectral rule: inline-response-schema', () => {
const results = await testRule(ruleId, rule, testDocument);
expect(results).toHaveLength(0);
});

it('Composed primitive schema', async () => {
const testDocument = makeCopy(rootDocument);

testDocument.paths['/v1/movies'].post.responses['201'].content[
'application/json'
].schema = {
oneOf: [
{
type: 'string'
},
{
type: 'string'
}
]
};

const results = await testRule(ruleId, rule, testDocument);
expect(results).toHaveLength(0);
});
});

describe('Should yield errors', () => {
Expand Down
30 changes: 30 additions & 0 deletions packages/ruleset/test/is-object-schema.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,34 @@ describe('Utility function: isObjectSchema()', () => {
})
).toBe(true);
});

it('should recurse through `oneOf` and `allOf` (implicit object type)', async () => {
expect(
isObjectSchema({
oneOf: [
{
allOf: [{ properties: {} }, {}]
},
{
properties: {}
}
]
})
).toBe(true);
});

it('should recurse through `allOf` (implicit object type)', async () => {
expect(
isObjectSchema({
allOf: [
{
allOf: [{ properties: {} }, {}]
},
{
properties: {}
}
]
})
).toBe(true);
});
});
142 changes: 142 additions & 0 deletions packages/ruleset/test/is-primitive-schema.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
const { isPrimitiveSchema } = require('../src/utils');

describe('Utility function: isPrimitiveSchema()', () => {
it('should return `false` for `undefined`', async () => {
expect(isPrimitiveSchema(undefined)).toBe(false);
});

it('should return `false` for `null`', async () => {
expect(isPrimitiveSchema(null)).toBe(false);
});

it('should return `false` for an array', async () => {
expect(isPrimitiveSchema([])).toBe(false);
});

it('should return `false` for an empty object', async () => {
expect(isPrimitiveSchema({})).toBe(false);
});

it('should return `true` for a boolean schema', async () => {
expect(isPrimitiveSchema({ type: 'boolean' })).toBe(true);
});

it('should return `true` for a byte schema', async () => {
expect(isPrimitiveSchema({ type: 'string', format: 'byte' })).toBe(true);
});

it('should return `true` for a double schema', async () => {
expect(isPrimitiveSchema({ type: 'number', format: 'double' })).toBe(true);
});

it('should return `true` for an enumeration', async () => {
expect(isPrimitiveSchema({ type: 'string', enum: ['foo', 'bar'] })).toBe(
true
);
});

it('should return `true` for a float schema', async () => {
expect(isPrimitiveSchema({ type: 'number', format: 'float' })).toBe(true);
});

it('should return `true` for a int32 schema', async () => {
expect(isPrimitiveSchema({ type: 'integer', format: 'int32' })).toBe(true);
});

it('should return `true` for a int64 schema', async () => {
expect(isPrimitiveSchema({ type: 'integer', format: 'int64' })).toBe(true);
});

it('should return `true` for a integer schema', async () => {
expect(isPrimitiveSchema({ type: 'integer' })).toBe(true);
});

it('should return `true` for a number schema', async () => {
expect(isPrimitiveSchema({ type: 'number' })).toBe(true);
});

it('should return `true` for a string schema', async () => {
expect(isPrimitiveSchema({ type: 'string' })).toBe(true);
});

it('should return true for a composed schema that resolves to "int32"', async () => {
expect(
isPrimitiveSchema({
oneOf: [
{
allOf: [
{
anyOf: [
{ type: 'integer', format: 'int32' },
{ type: 'integer', format: 'int32' }
]
},
{}
]
},
{ type: 'integer', format: 'int32' }
]
})
).toBe(true);
});

it('should return true for a composed schema that resolves to "double"', async () => {
expect(
isPrimitiveSchema({
oneOf: [
{
allOf: [
{
anyOf: [
{ type: 'number', format: 'double' },
{ type: 'number', format: 'double' }
]
},
{}
]
},
{ type: 'number', format: 'double' }
]
})
).toBe(true);
});

it('should return true for a composed schema that resolves to "number"', async () => {
expect(
isPrimitiveSchema({
oneOf: [
{
allOf: [{ anyOf: [{ type: 'number' }, { type: 'number' }] }, {}]
},
{ type: 'number' }
]
})
).toBe(true);
});

it('should return true for a composed schema that resolves to "boolean"', async () => {
expect(
isPrimitiveSchema({
oneOf: [
{
allOf: [{ anyOf: [{ type: 'boolean' }, { type: 'boolean' }] }, {}]
},
{ type: 'boolean' }
]
})
).toBe(true);
});

it('should return true for a composed schema that resolves to "string"', async () => {
expect(
isPrimitiveSchema({
oneOf: [
{
allOf: [{ anyOf: [{ type: 'string' }, { type: 'string' }] }, {}]
},
{ type: 'string' }
]
})
).toBe(true);
});
});