Skip to content

Commit

Permalink
fix: exclusiveMin/Max shows incorect range (#1799)
Browse files Browse the repository at this point in the history
* fix: exclusiveMin/Max shows incorect range

* cover all number range cases & add unit tests

* add more tests

* fix maximum value

* simplify humanizeNumberRange function

* simplify exclusive checks

* Update src/utils/openapi.ts

Co-authored-by: Roman Hotsiy <gotsijroman@gmail.com>

* update test coverage

* linting

* revert weird prettier changes

* add md files to prettier ignore

Co-authored-by: Roman Hotsiy <gotsijroman@gmail.com>
  • Loading branch information
Oprysk and RomanHotsiy authored Nov 24, 2021
1 parent 4fb5f91 commit b604bd8
Show file tree
Hide file tree
Showing 14 changed files with 163 additions and 93 deletions.
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.md
47 changes: 24 additions & 23 deletions config/docker/index.tpl.html
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>%PAGE_TITLE%</title>
<link rel="icon" href="%PAGE_FAVICON%" />
<style>
body {
margin: 0;
padding: 0;
}

<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>%PAGE_TITLE%</title>
<link rel="icon" href="%PAGE_FAVICON%">
<style>
body {
margin: 0;
padding: 0;
}
redoc {
display: block;
}
</style>
<link
href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700"
rel="stylesheet"
/>
</head>

redoc {
display: block;
}
</style>
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
</head>

<body>
<redoc spec-url="%SPEC_URL%" %REDOC_OPTIONS%></redoc>
<script src="redoc.standalone.js"></script>
</body>

</html>
<body>
<redoc spec-url="%SPEC_URL%" %REDOC_OPTIONS%></redoc>
<script src="redoc.standalone.js"></script>
</body>
</html>
10 changes: 5 additions & 5 deletions docs/deployment/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ The following options are supported:
### OpenAPI definition

You will need an OpenAPI definition. For testing purposes, you can use one of the following sample OpenAPI definitions:

- OpenAPI 3.0
- [Rebilly Users OpenAPI Definition](https://raw.githubusercontent.com/Rebilly/api-definitions/main/openapi/users.yaml)
- [Swagger Petstore Sample OpenAPI Definition](https://petstore3.swagger.io/api/v3/openapi.json)
- [Rebilly Users OpenAPI Definition](https://raw.githubusercontent.com/Rebilly/api-definitions/main/openapi/users.yaml)
- [Swagger Petstore Sample OpenAPI Definition](https://petstore3.swagger.io/api/v3/openapi.json)
- OpenAPI 2.0
- [Thingful OpenAPI Definition](https://raw.githubusercontent.com/thingful/openapi-spec/master/spec/swagger.yaml)
- [Fitbit Plus OpenAPI Definition](https://raw.githubusercontent.com/TwineHealth/TwineDeveloperDocs/master/spec/swagger.yaml)

- [Thingful OpenAPI Definition](https://raw.githubusercontent.com/thingful/openapi-spec/master/spec/swagger.yaml)
- [Fitbit Plus OpenAPI Definition](https://raw.githubusercontent.com/TwineHealth/TwineDeveloperDocs/master/spec/swagger.yaml)

:::info OpenAPI specification
For more information on the OpenAPI specification, refer to the [Learning OpenAPI 3](https://redoc.ly/docs/resources/learning-openapi/)
Expand Down
16 changes: 9 additions & 7 deletions docs/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ replace the `spec-url` attribute with the URL or local file address to your defi
<head>
<title>Redoc</title>
<!-- needed for adaptive design -->
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700"
rel="stylesheet"
/>

<!--
Redoc doesn't change outer page styles
Expand All @@ -28,17 +31,16 @@ replace the `spec-url` attribute with the URL or local file address to your defi
</style>
</head>
<body>
<!--
<!--
Redoc element with link to your OpenAPI definition
-->
<redoc spec-url='http://petstore.swagger.io/v2/swagger.json'></redoc>
<redoc spec-url="http://petstore.swagger.io/v2/swagger.json"></redoc>
<!--
Link to Redoc JavaScript on CDN for rendering standalone element
-->
<script src="https://cdn.jsdelivr.net/npm/redoc@latest/bundles/redoc.standalone.js"> </script>
<script src="https://cdn.jsdelivr.net/npm/redoc@latest/bundles/redoc.standalone.js"></script>
</body>
</html>

```

:::attention Running Redoc locally requires an HTTP server
Expand Down
16 changes: 8 additions & 8 deletions src/common-elements/dropdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,16 @@ export const StyledDropdown = styled(Dropdown)`
width: auto;
background: white;
color: #263238;
font-family: ${(props) => props.theme.typography.headings.fontFamily};
font-family: ${props => props.theme.typography.headings.fontFamily};
font-size: 0.929em;
line-height: 1.5em;
cursor: pointer;
transition: border 0.25s ease, color 0.25s ease, box-shadow 0.25s ease;
&:hover,
&:focus-within {
border: 1px solid ${(props) => props.theme.colors.primary.main};
color: ${(props) => props.theme.colors.primary.main};
box-shadow: 0px 0px 0px 1px ${(props) => props.theme.colors.primary.main};
border: 1px solid ${props => props.theme.colors.primary.main};
color: ${props => props.theme.colors.primary.main};
box-shadow: 0px 0px 0px 1px ${props => props.theme.colors.primary.main};
}
.dropdown-selector {
display: inline-flex;
Expand All @@ -48,7 +48,7 @@ export const StyledDropdown = styled(Dropdown)`
margin-bottom: 5px;
}
.dropdown-selector-value {
font-family: ${(props) => props.theme.typography.headings.fontFamily};
font-family: ${props => props.theme.typography.headings.fontFamily};
position: relative;
font-size: 0.929em;
width: 100%;
Expand All @@ -63,7 +63,7 @@ export const StyledDropdown = styled(Dropdown)`
right: 3px;
top: 50%;
transform: translateY(-50%);
border-color: ${(props) => props.theme.colors.primary.main} transparent transparent;
border-color: ${props => props.theme.colors.primary.main} transparent transparent;
border-style: solid;
border-width: 0.35em 0.35em 0;
width: 0;
Expand Down Expand Up @@ -128,8 +128,8 @@ export const SimpleDropdown = styled(StyledDropdown)`
border: none;
box-shadow: none;
.dropdown-selector-value {
color: ${(props) => props.theme.colors.primary.main};
text-shadow: 0px 0px 0px ${(props) => props.theme.colors.primary.main};
color: ${props => props.theme.colors.primary.main};
text-shadow: 0px 0px 0px ${props => props.theme.colors.primary.main};
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/components/Callbacks/CallbackTitle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ const CallbackTitleWrapper = styled.button`
`;

const CallbackName = styled.span<{ deprecated?: boolean }>`
text-decoration: ${(props) => (props.deprecated ? 'line-through' : 'none')};
text-decoration: ${props => (props.deprecated ? 'line-through' : 'none')};
margin-right: 8px;
`;

const OperationBadgeStyled = styled(OperationBadge)`
margin: 0px 5px 0px 0px;
margin: 0 5px 0 0;
`;
4 changes: 2 additions & 2 deletions src/components/JsonViewer/JsonViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ class Json extends React.PureComponent<JsonProps> {
<button onClick={this.collapseAll}> Collapse all </button>
</SampleControls>
<OptionsContext.Consumer>
{(options) => (
{options => (
<PrismDiv
className={this.props.className}
// tslint:disable-next-line
ref={(node) => (this.node = node!)}
ref={node => (this.node = node!)}
dangerouslySetInnerHTML={{
__html: jsonToHTML(this.props.data, options.jsonSampleExpandLevel),
}}
Expand Down
8 changes: 4 additions & 4 deletions src/components/JsonViewer/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ export const jsonStyles = css`
pointer-events: none;
}
font-family: ${(props) => props.theme.typography.code.fontFamily};
font-size: ${(props) => props.theme.typography.code.fontSize};
font-family: ${props => props.theme.typography.code.fontFamily};
font-size: ${props => props.theme.typography.code.fontSize};
white-space: ${({ theme }) => (theme.typography.code.wrap ? 'pre-wrap' : 'pre')};
contain: content;
Expand Down Expand Up @@ -51,8 +51,8 @@ export const jsonStyles = css`
background-color: transparent;
border: 0;
color: #fff;
font-family: ${(props) => props.theme.typography.code.fontFamily};
font-size: ${(props) => props.theme.typography.code.fontSize};
font-family: ${props => props.theme.typography.code.fontFamily};
font-size: ${props => props.theme.typography.code.fontSize};
padding-right: 6px;
padding-left: 6px;
padding-top: 0;
Expand Down
2 changes: 1 addition & 1 deletion src/components/Operation/Operation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export class Operation extends React.Component<OperationProps> {

return (
<OptionsContext.Consumer>
{(options) => (
{options => (
<OperationRow>
<MiddlePanel>
<H2>
Expand Down
8 changes: 4 additions & 4 deletions src/components/Responses/styled.elements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ export const StyledResponseTitle = styled(ResponseTitle)`
background-color: #f2f2f2;
cursor: pointer;
color: ${(props) => props.theme.colors.responses[props.type].color};
background-color: ${(props) => props.theme.colors.responses[props.type].backgroundColor};
color: ${props => props.theme.colors.responses[props.type].color};
background-color: ${props => props.theme.colors.responses[props.type].backgroundColor};
&:focus {
outline: auto;
outline-color: ${(props) => props.theme.colors.responses[props.type].color};
outline-color: ${props => props.theme.colors.responses[props.type].color};
}
${(props) =>
${props =>
(props.empty &&
`
cursor: default;
Expand Down
10 changes: 5 additions & 5 deletions src/components/SecurityRequirement/SecurityRequirement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { SecurityRequirementModel } from '../../services/models/SecurityRequirem
import { linksCss } from '../Markdown/styled.elements';

const ScopeName = styled.code`
font-size: ${(props) => props.theme.typography.code.fontSize};
font-family: ${(props) => props.theme.typography.code.fontFamily};
font-size: ${props => props.theme.typography.code.fontSize};
font-family: ${props => props.theme.typography.code.fontFamily};
border: 1px solid ${({ theme }) => theme.colors.border.dark};
margin: 0 3px;
padding: 0.2em;
Expand Down Expand Up @@ -67,12 +67,12 @@ export class SecurityRequirement extends React.PureComponent<SecurityRequirement
return (
<SecurityRequirementOrWrap>
{security.schemes.length ? (
security.schemes.map((scheme) => {
security.schemes.map(scheme => {
return (
<SecurityRequirementAndWrap key={scheme.id}>
<Link to={scheme.sectionId}>{scheme.id}</Link>
{scheme.scopes.length > 0 && ' ('}
{scheme.scopes.map((scope) => (
{scheme.scopes.map(scope => (
<ScopeName key={scope}>{scope}</ScopeName>
))}
{scheme.scopes.length > 0 && ') '}
Expand All @@ -92,7 +92,7 @@ const AuthHeaderColumn = styled.div`
`;

const SecuritiesColumn = styled.div`
width: ${(props) => props.theme.schema.defaultDetailsWidth};
width: ${props => props.theme.schema.defaultDetailsWidth};
${media.lessThan('small')`
margin-top: 10px;
`}
Expand Down
6 changes: 3 additions & 3 deletions src/services/RedocNormalizedOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export class RedocNormalizedOptions {
}
if (typeof value === 'string') {
const res = {};
value.split(',').forEach((code) => {
value.split(',').forEach(code => {
res[code.trim()] = true;
});
return res;
Expand Down Expand Up @@ -138,7 +138,7 @@ export class RedocNormalizedOptions {
case 'false':
return false;
default:
return value.split(',').map((ext) => ext.trim());
return value.split(',').map(ext => ext.trim());
}
}

Expand Down Expand Up @@ -266,7 +266,7 @@ export class RedocNormalizedOptions {
this.maxDisplayedEnumValues = argValueToNumber(raw.maxDisplayedEnumValues);
const ignoreNamedSchemas = Array.isArray(raw.ignoreNamedSchemas)
? raw.ignoreNamedSchemas
: raw.ignoreNamedSchemas?.split(',').map((s) => s.trim());
: raw.ignoreNamedSchemas?.split(',').map(s => s.trim());
this.ignoreNamedSchemas = new Set(ignoreNamedSchemas);
this.hideSchemaPattern = argValueToBoolean(raw.hideSchemaPattern);
this.generatedPayloadSamplesMaxDepth =
Expand Down
71 changes: 71 additions & 0 deletions src/utils/__tests__/openapi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
pluralizeType,
serializeParameterValue,
sortByRequired,
humanizeNumberRange,
} from '../';

import { FieldModel, OpenAPIParser, RedocNormalizedOptions } from '../../services';
Expand Down Expand Up @@ -410,6 +411,76 @@ describe('Utils', () => {
});
});

describe('openapi humanizeNumberRange', () => {
it('should return `>=` when only minimum value present or exclusiveMinimum = false', () => {
const expected = '>= 0';
expect(humanizeNumberRange({ minimum: 0 })).toEqual(expected);
expect(humanizeNumberRange({ minimum: 0, exclusiveMinimum: false })).toEqual(expected);
});

it('should return `>` when minimum value present and exclusiveMinimum set to true', () => {
expect(humanizeNumberRange({ minimum: 0, exclusiveMinimum: true })).toEqual('> 0');
});

it('should return `<=` when only maximum value present or exclusiveMinimum = false', () => {
const expected = '<= 10';
expect(humanizeNumberRange({ maximum: 10 })).toEqual(expected);
expect(humanizeNumberRange({ maximum: 10, exclusiveMaximum: false })).toEqual(expected);
});

it('should return `<` when maximum value present and exclusiveMaximum set to true', () => {
expect(humanizeNumberRange({ maximum: 10, exclusiveMaximum: true })).toEqual('< 10');
});

it('should return correct range for minimum and maximum values and with different exclusive set', () => {
expect(humanizeNumberRange({ minimum: 0, maximum: 10 })).toEqual('[ 0 .. 10 ]');
expect(
humanizeNumberRange({
minimum: 0,
exclusiveMinimum: true,
maximum: 10,
exclusiveMaximum: true,
}),
).toEqual('( 0 .. 10 )');
expect(
humanizeNumberRange({
minimum: 0,
maximum: 10,
exclusiveMaximum: true,
}),
).toEqual('[ 0 .. 10 )');
expect(
humanizeNumberRange({
minimum: 0,
exclusiveMinimum: true,
maximum: 10,
}),
).toEqual('( 0 .. 10 ]');
});

it('should return correct range exclusive values only', () => {
expect(humanizeNumberRange({ exclusiveMinimum: 0 })).toEqual('> 0');
expect(humanizeNumberRange({ exclusiveMaximum: 10 })).toEqual('< 10');
expect(humanizeNumberRange({ exclusiveMinimum: 0, exclusiveMaximum: 10 })).toEqual(
'( 0 .. 10 )',
);
});

it('should return correct min value', () => {
expect(humanizeNumberRange({ minimum: 5, exclusiveMinimum: 10 })).toEqual('> 5');
expect(humanizeNumberRange({ minimum: -5, exclusiveMinimum: -10 })).toEqual('> -10');
});

it('should return correct max value', () => {
expect(humanizeNumberRange({ maximum: 10, exclusiveMaximum: 15 })).toEqual('< 15');
expect(humanizeNumberRange({ maximum: -10, exclusiveMaximum: -15 })).toEqual('< -10');
});

it('should return undefined', () => {
expect(humanizeNumberRange({})).toEqual(undefined);
});
});

describe('openapi humanizeConstraints', () => {
const itemConstraintSchema = (
min?: number,
Expand Down
Loading

0 comments on commit b604bd8

Please sign in to comment.