diff --git a/docs/schema.md b/docs/schema.md new file mode 100644 index 0000000..7086196 --- /dev/null +++ b/docs/schema.md @@ -0,0 +1,54 @@ +# Schema support + +The `Schema` object in [Open API v3 spec][oaschema] describes several properties +that are shared from JSON Schema, deviations from JSON Schema, or in addition +to JSON Schema. The document descibes this project's support for these +properties. + +## Properties + +The following properties are supported, and implemented according to the +[JSON Schema Validation spec][jsschema]: + +- [x] multipleOf +- [x] maximum +- [x] exclusiveMaximum +- [x] minimum +- [x] exclusiveMinimum +- [x] maxLength +- [x] minLength +- [x] pattern +- [x] maxItems +- [x] minItems +- [x] uniqueItems +- [x] maxProperties +- [x] minProperties +- [x] format +- [x] required +- [x] enum + +## Adjusted JSON Schema Properties + +The OpenAPI specification also supports several additional properties from JSON +Schema, with some adjustments. This project attempts to honor these adjustments, +with any exceptions outlined below: + +- [x] type - Value may be an array, multiple types are supported. +- [x] allOf +- [ ] oneOf +- [ ] anyOf +- [ ] not +- [x] items +- [x] properties +- [ ] additionalProperties +- [x] description +- [x] format +- [x] default + +## Fixed Fields + +At this time, the project supports no [fixed fields][ff]. + +[ff]: https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.md#fixed-fields-20 +[jsschema]: http://json-schema.org/latest/json-schema-validation.html#rfc.section.6 +[oaschema]: https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.md#schemaObject diff --git a/src/components/ArrayProperty/ArrayProperty.js b/src/components/ArrayProperty/ArrayProperty.js new file mode 100644 index 0000000..b77ac6b --- /dev/null +++ b/src/components/ArrayProperty/ArrayProperty.js @@ -0,0 +1,53 @@ +import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; + +export default class ArrayProperty extends PureComponent { + render() { + const { constraints } = this.props; + + if (!constraints) { + return null; + } + + const { minItems, maxItems, uniqueItems } = constraints; + const validations = []; + + if (uniqueItems) { + validations.push('items must be unique'); + } + + if (maxItems !== undefined && minItems !== undefined) { + // Be succint if the minItems is the same maxItems + // ie. value can only be of `x` length. + if (maxItems === minItems) { + validations.push(`${minItems} items`); + } else { + validations.push(`${minItems}-${maxItems} items`); + } + } else if (minItems !== undefined) { + validations.push(`at least ${minItems} items`); + } else if (maxItems !== undefined) { + validations.push(`at most ${maxItems} items`); + } + + if (!validations.length) { + return null; + } + + return ( + + {validations.map(constraint => + {constraint} + )} + + ); + } +} + +ArrayProperty.propTypes = { + constraints: PropTypes.shape({ + maxItems: PropTypes.number, + minItems: PropTypes.number, + uniqueItems: PropTypes.bool + }) +}; diff --git a/src/components/BodySchema/BodySchema.js b/src/components/BodySchema/BodySchema.js index 81f0c04..04153d4 100644 --- a/src/components/BodySchema/BodySchema.js +++ b/src/components/BodySchema/BodySchema.js @@ -38,19 +38,20 @@ export default class BodySchema extends Component { if (properties.length === iterator) { isLast = true; } - if (property.type === 'array' && expandedProp.indexOf(property.name) !== -1 && property.properties !== undefined) { + + if (property.type.includes('array') && expandedProp.includes(property.name) && property.properties !== undefined) { return createFragment({ property: this.renderPropertyRow(property, isLast, true), subset: this.renderSubsetProperties(property, true) }); - } else if (property.type === 'array' && property.properties !== undefined) { + } else if (property.type.includes('array') && property.properties !== undefined) { return this.renderPropertyRow(property, isLast, false); - } else if (property.type === 'object' && expandedProp.indexOf(property.name) !== -1 && property.properties !== undefined) { + } else if (property.type.includes('object') && expandedProp.includes(property.name) && property.properties !== undefined) { return createFragment({ property: this.renderPropertyRow(property, isLast, true), subset: this.renderSubsetProperties(property) }); - } else if (property.type === 'object' && property.properties !== undefined) { + } else if (property.type.includes('object') && property.properties !== undefined) { return this.renderPropertyRow(property, isLast, false); } else { return this.renderPropertyRow(property, isLast); @@ -71,6 +72,7 @@ export default class BodySchema extends Component { description={property.description} enumValues={property.enum} defaultValue={property.defaultValue} + constraints={property.constraints} onClick={this.onClick.bind(this, property.name)} isRequired={property.required} isOpen={isOpen} @@ -110,7 +112,7 @@ export default class BodySchema extends Component { onClick(propertyName) { const { expandedProp } = this.state; - if (expandedProp.indexOf(propertyName) !== -1) { + if (expandedProp.includes(propertyName)) { const newExpanded = expandedProp.filter((prop) => prop !== propertyName); this.setState({ expandedProp: newExpanded }); } else { diff --git a/src/components/Method/Method.js b/src/components/Method/Method.js index 2a03330..372015c 100644 --- a/src/components/Method/Method.js +++ b/src/components/Method/Method.js @@ -75,14 +75,7 @@ export default class Method extends Component { return (

Responses

- {responses.map((response) => { - return ( - - ); - })} + {responses.map((r) => )}
); } diff --git a/src/components/NumericProperty/NumericProperty.js b/src/components/NumericProperty/NumericProperty.js new file mode 100644 index 0000000..2c36b07 --- /dev/null +++ b/src/components/NumericProperty/NumericProperty.js @@ -0,0 +1,58 @@ +import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; + +export default class NumericProperty extends PureComponent { + render() { + const { constraints } = this.props; + + if (!constraints) { + return null; + } + + const { exclusiveMinimum, exclusiveMaximum, maximum, minimum, multipleOf } = constraints; + const validations = []; + + if (multipleOf) { + validations.push(`multiple of ${multipleOf}`); + } + + // We're following JSON-Schema Draft 6, which states that exclusive* are + // integers, not boolean values. Also using `undefined` to prevent edge + // cases where the value is 0 or 1. + if (maximum !== undefined && minimum !== undefined) { + validations.push(`${minimum}…${maximum}`); + } else if (exclusiveMaximum !== undefined && exclusiveMinimum !== undefined) { + validations.push(`${exclusiveMinimum}…${exclusiveMaximum}`); + } else if (minimum !== undefined) { + validations.push(`≥ ${minimum}`); + } else if (maximum !== undefined) { + validations.push(`≤ ${maximum}`); + } else if (exclusiveMinimum !== undefined) { + validations.push(`> ${exclusiveMinimum}`); + } else if (exclusiveMaximum !== undefined) { + validations.push(`< ${exclusiveMaximum}`); + } + + if (!validations.length) { + return null; + } + + return ( + + {validations.map(constraint => + {constraint} + )} + + ); + } +} + +NumericProperty.propTypes = { + constraints: PropTypes.shape({ + exclusiveMinimum: PropTypes.number, + exclusiveMaximum: PropTypes.number, + maximum: PropTypes.number, + minimum: PropTypes.number, + multipleOf: PropTypes.number + }) +}; diff --git a/src/components/ObjectProperty/ObjectProperty.js b/src/components/ObjectProperty/ObjectProperty.js new file mode 100644 index 0000000..0420696 --- /dev/null +++ b/src/components/ObjectProperty/ObjectProperty.js @@ -0,0 +1,48 @@ +import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; + +export default class ObjectProperty extends PureComponent { + render() { + const { constraints } = this.props; + + if (!constraints) { + return null; + } + + const { minProperties, maxProperties } = constraints; + const validations = []; + + if (maxProperties !== undefined && minProperties !== undefined) { + // Be succint if the minProperties is the same maxProperties + // ie. value can only be of `x` length. + if (maxProperties === minProperties) { + validations.push(`${minProperties} properties`); + } else { + validations.push(`${minProperties}-${maxProperties} properties`); + } + } else if (minProperties !== undefined) { + validations.push(`at least ${minProperties} properties`); + } else if (maxProperties !== undefined) { + validations.push(`at most ${maxProperties} properties`); + } + + if (!validations.length) { + return null; + } + + return ( + + {validations.map(constraint => + {constraint} + )} + + ); + } +} + +ObjectProperty.propTypes = { + constraints: PropTypes.shape({ + maxProperties: PropTypes.number, + minProperties: PropTypes.number + }) +}; diff --git a/src/components/Property/Property.js b/src/components/Property/Property.js index b512abc..dfbcfa4 100644 --- a/src/components/Property/Property.js +++ b/src/components/Property/Property.js @@ -2,14 +2,20 @@ import React, { Component } from 'react'; import classNames from 'classnames'; import PropTypes from 'prop-types'; +import ArrayProperty from '../ArrayProperty/ArrayProperty'; import Description from '../Description/Description'; import Indicator from '../Indicator/Indicator'; +import NumericProperty from '../NumericProperty/NumericProperty'; +import ObjectProperty from '../ObjectProperty/ObjectProperty'; +import StringProperty from '../StringProperty/StringProperty'; import './Property.scss'; export default class Property extends Component { render() { - const { name, type, description, isRequired, enumValues, defaultValue, onClick, isOpen, isLast } = this.props; + const { + name, type, description, constraints, isRequired, enumValues, defaultValue, onClick, isOpen, isLast + } = this.props; let subtype; if (type.includes('array')) { @@ -20,6 +26,7 @@ export default class Property extends Component { if (isOpen !== undefined) { isClickable = true; } + let status; if (isOpen) { status = 'open'; @@ -41,10 +48,18 @@ export default class Property extends Component { } - {type.join(', ')}{subtype && of {subtype}} + + {!subtype ? type.join(', ') : {subtype}[]} + {!subtype && constraints && constraints.format && + <{constraints.format}>} + {isRequired && Required} + {['number', 'integer'].some(t => type.includes(t)) && } + {type.includes('string') && } + {type.includes('array') && } + {type.includes('object') && } {enumValues && this.renderEnumValues(enumValues)} - {defaultValue && this.renderDefaultValue(defaultValue)} + {defaultValue !== undefined && this.renderDefaultValue(defaultValue)} {description && } @@ -97,10 +112,19 @@ Property.propTypes = { description: PropTypes.string, constraints: PropTypes.shape({ format: PropTypes.string, - minLength: PropTypes.number, - exclusiveMinimum: PropTypes.bool, + exclusiveMinimum: PropTypes.number, + exclusiveMaximum: PropTypes.number, + maximum: PropTypes.number, + maxItems: PropTypes.number, maxLength: PropTypes.number, - exclusiveMaximum: PropTypes.bool + maxProperties: PropTypes.number, + minimum: PropTypes.number, + minItems: PropTypes.number, + minLength: PropTypes.number, + minProperties: PropTypes.number, + multipleOf: PropTypes.number, + pattern: PropTypes.string, + uniqueItems: PropTypes.bool }), enumValues: PropTypes.array, defaultValue: PropTypes.any, diff --git a/src/components/StringProperty/StringProperty.js b/src/components/StringProperty/StringProperty.js new file mode 100644 index 0000000..053410a --- /dev/null +++ b/src/components/StringProperty/StringProperty.js @@ -0,0 +1,53 @@ +import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; + +export default class StringProperty extends PureComponent { + render() { + const { constraints } = this.props; + + if (!constraints) { + return null; + } + + const { pattern, minLength, maxLength } = constraints; + const validations = []; + + if (pattern) { + validations.push(`/${pattern}/`); + } + + if (maxLength !== undefined && minLength !== undefined) { + // Be succint if the minLength is the same maxLength + // ie. value can only be of `x` length. + if (maxLength === minLength) { + validations.push(`${minLength} chars`); + } else { + validations.push(`${minLength}-${maxLength} chars`); + } + } else if (minLength !== undefined) { + validations.push(`at least ${minLength} chars`); + } else if (maxLength !== undefined) { + validations.push(`at most ${maxLength} chars`); + } + + if (!validations.length) { + return null; + } + + return ( + + {validations.map(constraint => + {constraint} + )} + + ); + } +} + +StringProperty.propTypes = { + constraints: PropTypes.shape({ + pattern: PropTypes.string, + maxLength: PropTypes.number, + minLength: PropTypes.number + }) +}; diff --git a/src/parser/open-api/constraintsParser.js b/src/parser/open-api/constraintsParser.js index 66847a1..39cec5d 100644 --- a/src/parser/open-api/constraintsParser.js +++ b/src/parser/open-api/constraintsParser.js @@ -1,5 +1,5 @@ // https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.md#schema-object -const VALIDATION_KEYWORDS = [ +export const VALIDATION_KEYWORDS = [ 'format', 'exclusiveMaximum', 'exclusiveMinimum', @@ -28,8 +28,19 @@ export function hasConstraints(property) { ); } +/** + * Given a property, extract all the constraints from it and return a new + * object with those constraints. + * + * @param {Object} property + * @return {Object} + */ export function getConstraints(property) { - return { + return Object.keys(property).reduce((constraints, key) => { + if (VALIDATION_KEYWORDS.includes(key)) { + constraints[key] = property[key]; + } - }; + return constraints; + }, {}); } diff --git a/test/components/ArrayProperty.test.js b/test/components/ArrayProperty.test.js new file mode 100644 index 0000000..bcc789f --- /dev/null +++ b/test/components/ArrayProperty.test.js @@ -0,0 +1,67 @@ +import React from 'react'; +import ArrayProperty from './../../src/components/ArrayProperty/ArrayProperty'; +import renderer from 'react-test-renderer'; + +describe('', () => { + it('renders nothing if there are no applicable constraints', () => { + let tree = renderer.create( + + ).toJSON(); + + expect(tree).toBeNull(); + + tree = renderer.create( + + ).toJSON(); + + expect(tree).toBeNull(); + }); + + it('renders uniqueItems', () => { + const tree = renderer.create( + + ); + + expect(tree).toMatchSnapshot(); + }); + + it('renders minItems', () => { + const tree = renderer.create( + + ); + + expect(tree).toMatchSnapshot(); + }); + + it('renders maxItems', () => { + const tree = renderer.create( + + ); + + expect(tree).toMatchSnapshot(); + }); + + it('renders minItems and maxItems', () => { + const tree = renderer.create( + + ); + + expect(tree).toMatchSnapshot(); + }); + + it('renders minItems and maxItems when they are the same value', () => { + const tree = renderer.create( + + ); + + expect(tree).toMatchSnapshot(); + }); + + it('renders uniqueItems, minItems and maxItems', () => { + const tree = renderer.create( + + ); + + expect(tree).toMatchSnapshot(); + }); +}); diff --git a/test/components/NumericProperty.test.js b/test/components/NumericProperty.test.js new file mode 100644 index 0000000..23fa0d2 --- /dev/null +++ b/test/components/NumericProperty.test.js @@ -0,0 +1,83 @@ +import React from 'react'; +import NumericProperty from './../../src/components/NumericProperty/NumericProperty'; +import renderer from 'react-test-renderer'; + +describe('', () => { + it('renders nothing if there are no applicable constraints', () => { + let tree = renderer.create( + + ).toJSON(); + + expect(tree).toBeNull(); + + tree = renderer.create( + + ).toJSON(); + + expect(tree).toBeNull(); + }); + + it('renders multipleOf', () => { + const tree = renderer.create( + + ); + + expect(tree).toMatchSnapshot(); + }); + + it('renders maximum', () => { + const tree = renderer.create( + + ); + + expect(tree).toMatchSnapshot(); + }); + + it('renders minimum', () => { + const tree = renderer.create( + + ); + + expect(tree).toMatchSnapshot(); + }); + + it('renders minimum and maximum', () => { + const tree = renderer.create( + + ); + + expect(tree).toMatchSnapshot(); + }); + + it('renders exclusiveMaximum', () => { + const tree = renderer.create( + + ); + + expect(tree).toMatchSnapshot(); + }); + + it('renders exclusiveMinimum', () => { + const tree = renderer.create( + + ); + + expect(tree).toMatchSnapshot(); + }); + + it('renders exclusiveMinimum and exclusiveMaximum', () => { + const tree = renderer.create( + + ); + + expect(tree).toMatchSnapshot(); + }); + + it('renders a combination of constraints', () => { + const tree = renderer.create( + + ); + + expect(tree).toMatchSnapshot(); + }); +}); diff --git a/test/components/ObjectProperty.test.js b/test/components/ObjectProperty.test.js new file mode 100644 index 0000000..58456c9 --- /dev/null +++ b/test/components/ObjectProperty.test.js @@ -0,0 +1,45 @@ +import React from 'react'; +import ObjectProperty from './../../src/components/ObjectProperty/ObjectProperty'; +import renderer from 'react-test-renderer'; + +describe('', () => { + it('renders nothing if there are no constraints', () => { + const tree = renderer.create( + + ).toJSON(); + + expect(tree).toBeNull(); + }); + + it('renders minProperties', () => { + const tree = renderer.create( + + ); + + expect(tree).toMatchSnapshot(); + }); + + it('renders maxProperties', () => { + const tree = renderer.create( + + ); + + expect(tree).toMatchSnapshot(); + }); + + it('renders minProperties and maxProperties', () => { + const tree = renderer.create( + + ); + + expect(tree).toMatchSnapshot(); + }); + + it('renders minProperties and maxProperties when they are the same value', () => { + const tree = renderer.create( + + ); + + expect(tree).toMatchSnapshot(); + }); +}); diff --git a/test/components/Property.test.js b/test/components/Property.test.js index 711591e..9f16742 100644 --- a/test/components/Property.test.js +++ b/test/components/Property.test.js @@ -49,6 +49,19 @@ describe('', () => { expect(tree).toMatchSnapshot(); }); + it('can render a property with numerical constraints', () => { + const shallow = new ReactShallowRenderer(); + const tree = shallow.render( + + ); + + expect(tree).toMatchSnapshot(); + }); + it('can render a property with multiple types', () => { const tree = renderer.create( ', () => { expect(tree).toMatchSnapshot(); }); + + it('can render a property with a format', () => { + const tree = renderer.create( + + ); + + expect(tree).toMatchSnapshot(); + }); }); diff --git a/test/components/StringProperty.test.js b/test/components/StringProperty.test.js new file mode 100644 index 0000000..f3da774 --- /dev/null +++ b/test/components/StringProperty.test.js @@ -0,0 +1,67 @@ +import React from 'react'; +import StringProperty from './../../src/components/StringProperty/StringProperty'; +import renderer from 'react-test-renderer'; + +describe('', () => { + it('renders nothing if there are no applicable constraints', () => { + let tree = renderer.create( + + ).toJSON(); + + expect(tree).toBeNull(); + + tree = renderer.create( + + ).toJSON(); + + expect(tree).toBeNull(); + }); + + it('renders pattern', () => { + const tree = renderer.create( + + ); + + expect(tree).toMatchSnapshot(); + }); + + it('renders minLength', () => { + const tree = renderer.create( + + ); + + expect(tree).toMatchSnapshot(); + }); + + it('renders maxLength', () => { + const tree = renderer.create( + + ); + + expect(tree).toMatchSnapshot(); + }); + + it('renders minLength and maxLength', () => { + const tree = renderer.create( + + ); + + expect(tree).toMatchSnapshot(); + }); + + it('renders minLength and maxLength when they are the same value', () => { + const tree = renderer.create( + + ); + + expect(tree).toMatchSnapshot(); + }); + + it('renders pattern, minLength and maxLength', () => { + const tree = renderer.create( + + ); + + expect(tree).toMatchSnapshot(); + }); +}); diff --git a/test/components/__snapshots__/ArrayProperty.test.js.snap b/test/components/__snapshots__/ArrayProperty.test.js.snap new file mode 100644 index 0000000..bd287c7 --- /dev/null +++ b/test/components/__snapshots__/ArrayProperty.test.js.snap @@ -0,0 +1,66 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` renders maxItems 1`] = ` + + + at most 10 items + + +`; + +exports[` renders minItems 1`] = ` + + + at least 2 items + + +`; + +exports[` renders minItems and maxItems 1`] = ` + + + 4-10 items + + +`; + +exports[` renders minItems and maxItems when they are the same value 1`] = ` + + + 4 items + + +`; + +exports[` renders uniqueItems 1`] = ` + + + items must be unique + + +`; + +exports[` renders uniqueItems, minItems and maxItems 1`] = ` + + + items must be unique + + + 4-10 items + + +`; diff --git a/test/components/__snapshots__/NumericProperty.test.js.snap b/test/components/__snapshots__/NumericProperty.test.js.snap new file mode 100644 index 0000000..3ef8085 --- /dev/null +++ b/test/components/__snapshots__/NumericProperty.test.js.snap @@ -0,0 +1,86 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` renders a combination of constraints 1`] = ` + + + multiple of 2 + + + 2…20 + + +`; + +exports[` renders exclusiveMaximum 1`] = ` + + + < 2 + + +`; + +exports[` renders exclusiveMinimum 1`] = ` + + + > 2 + + +`; + +exports[` renders exclusiveMinimum and exclusiveMaximum 1`] = ` + + + 2…5 + + +`; + +exports[` renders maximum 1`] = ` + + + ≤ 2 + + +`; + +exports[` renders minimum 1`] = ` + + + ≥ 2 + + +`; + +exports[` renders minimum and maximum 1`] = ` + + + 2…5 + + +`; + +exports[` renders multipleOf 1`] = ` + + + multiple of 2 + + +`; diff --git a/test/components/__snapshots__/ObjectProperty.test.js.snap b/test/components/__snapshots__/ObjectProperty.test.js.snap new file mode 100644 index 0000000..a05805f --- /dev/null +++ b/test/components/__snapshots__/ObjectProperty.test.js.snap @@ -0,0 +1,41 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` renders maxProperties 1`] = ` + + + at most 10 properties + + +`; + +exports[` renders minProperties 1`] = ` + + + at least 2 properties + + +`; + +exports[` renders minProperties and maxProperties 1`] = ` + + + 4-10 properties + + +`; + +exports[` renders minProperties and maxProperties when they are the same value 1`] = ` + + + 4 properties + + +`; diff --git a/test/components/__snapshots__/Property.test.js.snap b/test/components/__snapshots__/Property.test.js.snap index 468c6aa..09ed8ba 100644 --- a/test/components/__snapshots__/Property.test.js.snap +++ b/test/components/__snapshots__/Property.test.js.snap @@ -15,7 +15,9 @@ exports[` can render a basic property 1`] = ` - + string can render a basic property 1`] = ` `; -exports[` can render a property with a subtype 1`] = ` +exports[` can render a property with a format 1`] = ` can render a property with a subtype 1`] = ` className="property-name" > - data + value - - array + + string + + < + email + > + + + +`; + +exports[` can render a property with a subtype 1`] = ` + + - of - object + data + + + + + + object + [] + @@ -68,7 +104,9 @@ exports[` can render a property with description 1`] = ` - + string can render a property with description 1`] = ` > Required + @@ -98,7 +139,9 @@ exports[` can render a property with enum 1`] = ` - + string can render a property with multiple types 1`] = ` - + string, number `; + +exports[` can render a property with numerical constraints 1`] = ` + + + + type + + + + + number + + + Required + + + + +`; diff --git a/test/components/__snapshots__/StringProperty.test.js.snap b/test/components/__snapshots__/StringProperty.test.js.snap new file mode 100644 index 0000000..ae94b28 --- /dev/null +++ b/test/components/__snapshots__/StringProperty.test.js.snap @@ -0,0 +1,66 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` renders maxLength 1`] = ` + + + at most 10 chars + + +`; + +exports[` renders minLength 1`] = ` + + + at least 2 chars + + +`; + +exports[` renders minLength and maxLength 1`] = ` + + + 4-10 chars + + +`; + +exports[` renders minLength and maxLength when they are the same value 1`] = ` + + + 4 chars + + +`; + +exports[` renders pattern 1`] = ` + + + /^[a-zA-Z0-9_-]+$/ + + +`; + +exports[` renders pattern, minLength and maxLength 1`] = ` + + + /^[a-z]/ + + + 4-10 chars + + +`; diff --git a/test/parser/constraintsParser.test.js b/test/parser/constraintsParser.test.js new file mode 100644 index 0000000..2b7af4e --- /dev/null +++ b/test/parser/constraintsParser.test.js @@ -0,0 +1,56 @@ +import { VALIDATION_KEYWORDS, hasConstraints, getConstraints } from '../../src/parser/open-api/constraintsParser'; + +describe('VALIDATION_KEYWORDS', () => { + test('keywords match what is supported by Open API', () => { + expect(VALIDATION_KEYWORDS).toEqual([ + 'format', + 'exclusiveMaximum', + 'exclusiveMinimum', + 'maximum', + 'maxItems', + 'maxLength', + 'maxProperties', + 'minimum', + 'minItems', + 'minLength', + 'minProperties', + 'multipleOf', + 'pattern', + 'uniqueItems' + ]); + }); +}); + +describe('#hasConstraints', () => { + test('returns true when the property contains constraints', () => { + const property = { + type: 'string', + pattern: '^[a-zA-Z0-9_-]+$' + }; + + expect(hasConstraints(property)).toBeTruthy(); + }); + + test('returns false when the property has no constraints', () => { + const property = { + type: 'string' + }; + + expect(hasConstraints(property)).toBeFalsy(); + }); +}); + +describe('#getConstraints', () => { + test('can create a constraints object with appropriate constraints', () => { + const property = { + type: 'string', + pattern: '^[a-zA-Z0-9_-]+$' + }; + + const constraints = { + pattern: '^[a-zA-Z0-9_-]+$' + }; + + expect(getConstraints(property)).toEqual(constraints); + }); +}); diff --git a/test/parser/open-api/data/schemaParser/complexOutputSchema.json b/test/parser/open-api/data/schemaParser/complexOutputSchema.json index e626027..9eaaeb0 100644 --- a/test/parser/open-api/data/schemaParser/complexOutputSchema.json +++ b/test/parser/open-api/data/schemaParser/complexOutputSchema.json @@ -18,7 +18,10 @@ "required": true, "type": [ "string" - ] + ], + "constraints": { + "pattern": "^[a-zA-Z0-9_-]+$" + } }, { "name": "value", diff --git a/test/parser/open-api/v3/data/outputs/accounts.json b/test/parser/open-api/v3/data/outputs/accounts.json index a4c7234..8aa6ae3 100644 --- a/test/parser/open-api/v3/data/outputs/accounts.json +++ b/test/parser/open-api/v3/data/outputs/accounts.json @@ -119,7 +119,10 @@ "string" ], "required": true, - "description": "Email of the user" + "description": "Email of the user", + "constraints": { + "format": "email" + } } ] } @@ -435,7 +438,10 @@ "string" ], "required": true, - "description": "Email of the user" + "description": "Email of the user", + "constraints": { + "format": "email" + } }, { "name": "bearerToken", diff --git a/test/parser/open-api/v3/data/outputs/carrier.json b/test/parser/open-api/v3/data/outputs/carrier.json index 6fc2282..caaed4f 100644 --- a/test/parser/open-api/v3/data/outputs/carrier.json +++ b/test/parser/open-api/v3/data/outputs/carrier.json @@ -36,6 +36,9 @@ "array" ], "required": true, + "constraints": { + "minItems": 1 + }, "properties": [ { "name": "id", @@ -80,7 +83,10 @@ "type": [ "string" ], - "required": true + "required": true, + "constraints": { + "format": "uri" + } }, { "name": "services", @@ -88,6 +94,9 @@ "array" ], "required": true, + "constraints": { + "uniqueItems": true + }, "properties": [ { "name": "id",