diff --git a/.npmignore b/.npmignore index 666b19bd..70775d1c 100644 --- a/.npmignore +++ b/.npmignore @@ -2,5 +2,7 @@ __tests__/ .github/ .husky/ coverage/ +.babel* .eslint* .prettier* +webpack.* diff --git a/.prettierignore b/.prettierignore index 9f84a832..c9211890 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,3 +1,4 @@ +__tests__/cli/__fixtures__/ coverage/ dist/ packages/ diff --git a/__tests__/__snapshots__/operation.test.js.snap b/__tests__/__snapshots__/operation.test.js.snap index 751b0062..9c355883 100644 --- a/__tests__/__snapshots__/operation.test.js.snap +++ b/__tests__/__snapshots__/operation.test.js.snap @@ -44,6 +44,8 @@ Array [ "properties": Object { "code": Object { "format": "int32", + "maximum": 2147483647, + "minimum": -2147483648, "type": "integer", }, "message": Object { @@ -59,6 +61,8 @@ Array [ "properties": Object { "id": Object { "format": "int64", + "maximum": 9223372036854776000, + "minimum": -9223372036854776000, "type": "integer", }, "name": Object { @@ -75,14 +79,20 @@ Array [ }, "id": Object { "format": "int64", + "maximum": 9223372036854776000, + "minimum": -9223372036854776000, "type": "integer", }, "petId": Object { "format": "int64", + "maximum": 9223372036854776000, + "minimum": -9223372036854776000, "type": "integer", }, "quantity": Object { "format": "int32", + "maximum": 2147483647, + "minimum": -2147483648, "type": "integer", }, "shipDate": Object { @@ -108,6 +118,8 @@ Array [ }, "id": Object { "format": "int64", + "maximum": 9223372036854776000, + "minimum": -9223372036854776000, "readOnly": true, "type": "integer", }, @@ -149,6 +161,8 @@ Array [ "properties": Object { "id": Object { "format": "int64", + "maximum": 9223372036854776000, + "minimum": -9223372036854776000, "type": "integer", }, "name": Object { @@ -167,6 +181,8 @@ Array [ }, "id": Object { "format": "int64", + "maximum": 9223372036854776000, + "minimum": -9223372036854776000, "type": "integer", }, "lastName": Object { @@ -181,6 +197,8 @@ Array [ "userStatus": Object { "description": "User Status", "format": "int32", + "maximum": 2147483647, + "minimum": -2147483648, "type": "integer", }, "username": Object { diff --git a/__tests__/lib/openapi-to-json-schema.test.js b/__tests__/lib/openapi-to-json-schema.test.js index 5eaaddd7..75d6bcc0 100644 --- a/__tests__/lib/openapi-to-json-schema.test.js +++ b/__tests__/lib/openapi-to-json-schema.test.js @@ -407,9 +407,11 @@ describe('`enum` support', () => { describe('`format` support', () => { it('should support format', () => { - expect(toJSONSchema({ type: 'integer', format: 'int32' })).toStrictEqual({ + expect(toJSONSchema({ type: 'integer', format: 'int8' })).toStrictEqual({ type: 'integer', - format: 'int32', + format: 'int8', + minimum: -128, + maximum: 127, }); // Should support nested objects as well. @@ -421,7 +423,57 @@ describe('`format` support', () => { }, }; - expect(toJSONSchema(schema)).toStrictEqual(schema); + expect(toJSONSchema(schema)).toStrictEqual({ + type: 'array', + items: { + type: 'integer', + format: 'int8', + minimum: -128, + maximum: 127, + }, + }); + }); + + describe('minimum/maximum constraints', () => { + describe.each([ + ['integer', 'int8', -128, 127], + ['integer', 'int16', -32768, 32767], + ['integer', 'int32', -2147483648, 2147483647], + ['integer', 'int64', 0 - 2 ** 63, 2 ** 63 - 1], // -9223372036854775808 to 9223372036854775807 + ['integer', 'uint8', 0, 255], + ['integer', 'uint16', 0, 65535], + ['integer', 'uint32', 0, 4294967295], + ['integer', 'uint64', 0, 2 ** 64 - 1], // 0 to 1844674407370955161 + ['number', 'float', 0 - 2 ** 128, 2 ** 128 - 1], // -3.402823669209385e+38 to 3.402823669209385e+38 + ['number', 'double', 0 - Number.MAX_VALUE, Number.MAX_VALUE], + ])('`%s`', (type, format, min, max) => { + it('should add a `minimum` and `maximum` if not present', () => { + expect(toJSONSchema({ type, format })).toStrictEqual({ + type, + format, + minimum: min, + maximum: max, + }); + }); + + it('should alter constraints if present and beyond the allowable points', () => { + expect(toJSONSchema({ type, format, minimum: min ** 19, maximum: max * 2 })).toStrictEqual({ + type, + format, + minimum: min, + maximum: max, + }); + }); + + it('should not touch their constraints if they are within their limits', () => { + expect(toJSONSchema({ type, format, minimum: 0, maximum: 100 })).toStrictEqual({ + type, + format, + minimum: 0, + maximum: 100, + }); + }); + }); }); }); @@ -456,7 +508,7 @@ describe('`additionalProperties` support', () => { ['false', false], ['an empty object', true], ['an object containing a string', { type: 'string' }], - ])('should support when set to `%s`', (tc, additionalProperties) => { + ])('should support additionalProperties when set to `%s`', (tc, additionalProperties) => { const schema = { type: 'array', items: { @@ -474,7 +526,7 @@ describe('`additionalProperties` support', () => { }); }); - it('should support when set to an object containing an array', () => { + it('should support additionalProperties when set to an object that contains an array', () => { const schema = { type: 'array', items: { @@ -486,7 +538,7 @@ describe('`additionalProperties` support', () => { properties: { id: { type: 'integer', - format: 'int64', + format: 'int8', }, }, }, @@ -503,7 +555,9 @@ describe('`additionalProperties` support', () => { properties: { id: { type: 'integer', - format: 'int64', + format: 'int8', + minimum: -128, + maximum: 127, }, }, }, @@ -808,7 +862,7 @@ describe('`example` / `examples` support', () => { }, price: { type: 'integer', - format: 'int32', + format: 'int8', }, }, example: { @@ -846,7 +900,9 @@ describe('`example` / `examples` support', () => { }, price: { type: 'integer', - format: 'int32', + format: 'int8', + minimum: -128, + maximum: 127, examples: [1], }, }, diff --git a/__tests__/operation/__snapshots__/get-parameters-as-json-schema.test.js.snap b/__tests__/operation/__snapshots__/get-parameters-as-json-schema.test.js.snap index 786d54b4..e353716e 100644 --- a/__tests__/operation/__snapshots__/get-parameters-as-json-schema.test.js.snap +++ b/__tests__/operation/__snapshots__/get-parameters-as-json-schema.test.js.snap @@ -100,6 +100,8 @@ Array [ "petId": Object { "description": "Pet id to delete", "format": "int64", + "maximum": 9223372036854776000, + "minimum": -9223372036854776000, "type": "integer", }, }, @@ -220,6 +222,8 @@ Array [ "properties": Object { "id": Object { "format": "int64", + "maximum": 9223372036854776000, + "minimum": -9223372036854776000, "type": "integer", }, "name": Object { @@ -231,6 +235,8 @@ Array [ }, "id": Object { "format": "int64", + "maximum": 9223372036854776000, + "minimum": -9223372036854776000, "readOnly": true, "type": "integer", }, @@ -260,6 +266,8 @@ Array [ "properties": Object { "id": Object { "format": "int64", + "maximum": 9223372036854776000, + "minimum": -9223372036854776000, "type": "integer", }, "name": Object { @@ -293,6 +301,8 @@ Array [ "petId": Object { "description": "ID of pet that needs to be updated", "format": "int64", + "maximum": 9223372036854776000, + "minimum": -9223372036854776000, "type": "integer", }, }, diff --git a/__tests__/operation/get-parameters-as-json-schema.test.js b/__tests__/operation/get-parameters-as-json-schema.test.js index fa9f1737..228c402c 100644 --- a/__tests__/operation/get-parameters-as-json-schema.test.js +++ b/__tests__/operation/get-parameters-as-json-schema.test.js @@ -470,6 +470,8 @@ describe('descriptions', () => { pathId: { type: 'integer', format: 'uint32', + maximum: 4294967295, + minimum: 0, description: 'Description for the pathId', }, }, diff --git a/__tests__/operation/get-response-as-json-schema.test.js b/__tests__/operation/get-response-as-json-schema.test.js index 70136ecc..e9370c92 100644 --- a/__tests__/operation/get-response-as-json-schema.test.js +++ b/__tests__/operation/get-response-as-json-schema.test.js @@ -27,7 +27,7 @@ test('it should return a response as JSON Schema', async () => { schema: { type: 'object', properties: { - code: { type: 'integer', format: 'int32' }, + code: { type: 'integer', format: 'int32', maximum: 2147483647, minimum: -2147483648 }, type: { type: 'string' }, message: { type: 'string' }, }, diff --git a/src/lib/openapi-to-json-schema.js b/src/lib/openapi-to-json-schema.js index 3ef246af..8115b692 100644 --- a/src/lib/openapi-to-json-schema.js +++ b/src/lib/openapi-to-json-schema.js @@ -20,6 +20,37 @@ const UNSUPPORTED_SCHEMA_PROPS = [ 'deprecated', ]; +/** + * List partially sourced from `openapi-schema-to-json-schema`. + * + * @link https://github.com/openapi-contrib/openapi-schema-to-json-schema/blob/master/lib/converters/schema.js#L140-L154 + */ +const FORMAT_OPTIONS = { + INT8_MIN: 0 - 2 ** 7, // -128 + INT8_MAX: 2 ** 7 - 1, // 127 + INT16_MIN: 0 - 2 ** 15, // -32768 + INT16_MAX: 2 ** 15 - 1, // 32767 + INT32_MIN: 0 - 2 ** 31, // -2147483648 + INT32_MAX: 2 ** 31 - 1, // 2147483647 + INT64_MIN: 0 - 2 ** 63, // -9223372036854775808 + INT64_MAX: 2 ** 63 - 1, // 9223372036854775807 + + UINT8_MIN: 0, + UINT8_MAX: 2 ** 8 - 1, // 255 + UINT16_MIN: 0, + UINT16_MAX: 2 ** 16 - 1, // 65535 + UINT32_MIN: 0, + UINT32_MAX: 2 ** 32 - 1, // 4294967295 + UINT64_MIN: 0, + UINT64_MAX: 2 ** 64 - 1, // 18446744073709551615 + + FLOAT_MIN: 0 - 2 ** 128, // -3.402823669209385e+38 + FLOAT_MAX: 2 ** 128 - 1, // 3.402823669209385e+38 + + DOUBLE_MIN: 0 - Number.MAX_VALUE, + DOUBLE_MAX: Number.MAX_VALUE, +}; + /** * Take a string and encode it to be used as a JSON pointer. * @@ -388,6 +419,24 @@ function toJSONSchema(data, opts = {}) { } } + // Ensure that number schemas formats have properly constrained min/max attributes according to whatever type of + // `format` and `type` they adhere to. + if ('format' in schema) { + const formatUpper = schema.format.toUpperCase(); + + if (`${formatUpper}_MIN` in FORMAT_OPTIONS) { + if ((!schema.minimum && schema.minimum !== 0) || schema.minimum < FORMAT_OPTIONS[`${formatUpper}_MIN`]) { + schema.minimum = FORMAT_OPTIONS[`${formatUpper}_MIN`]; + } + } + + if (`${formatUpper}_MAX` in FORMAT_OPTIONS) { + if ((!schema.maximum && schema.maximum !== 0) || schema.maximum > FORMAT_OPTIONS[`${formatUpper}_MAX`]) { + schema.maximum = FORMAT_OPTIONS[`${formatUpper}_MAX`]; + } + } + } + // Users can pass in parameter defaults via JWT User Data: https://docs.readme.com/docs/passing-data-to-jwt // We're checking to see if the defaults being passed in exist on endpoints via jsonpointer if (globalDefaults && Object.keys(globalDefaults).length > 0 && currentLocation) {