Skip to content

Commit b604bd8

Browse files
OpryskRomanHotsiy
andauthored
fix: exclusiveMin/Max shows incorect range (Redocly#1799)
* 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 <[email protected]> * update test coverage * linting * revert weird prettier changes * add md files to prettier ignore Co-authored-by: Roman Hotsiy <[email protected]>
1 parent 4fb5f91 commit b604bd8

File tree

14 files changed

+163
-93
lines changed

14 files changed

+163
-93
lines changed

.prettierignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.md

config/docker/index.tpl.html

+24-23
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,28 @@
11
<!DOCTYPE html>
22
<html>
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1" />
6+
<title>%PAGE_TITLE%</title>
7+
<link rel="icon" href="%PAGE_FAVICON%" />
8+
<style>
9+
body {
10+
margin: 0;
11+
padding: 0;
12+
}
313

4-
<head>
5-
<meta charset="UTF-8" />
6-
<meta name="viewport" content="width=device-width, initial-scale=1">
7-
<title>%PAGE_TITLE%</title>
8-
<link rel="icon" href="%PAGE_FAVICON%">
9-
<style>
10-
body {
11-
margin: 0;
12-
padding: 0;
13-
}
14+
redoc {
15+
display: block;
16+
}
17+
</style>
18+
<link
19+
href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700"
20+
rel="stylesheet"
21+
/>
22+
</head>
1423

15-
redoc {
16-
display: block;
17-
}
18-
</style>
19-
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
20-
</head>
21-
22-
<body>
23-
<redoc spec-url="%SPEC_URL%" %REDOC_OPTIONS%></redoc>
24-
<script src="redoc.standalone.js"></script>
25-
</body>
26-
27-
</html>
24+
<body>
25+
<redoc spec-url="%SPEC_URL%" %REDOC_OPTIONS%></redoc>
26+
<script src="redoc.standalone.js"></script>
27+
</body>
28+
</html>

docs/deployment/intro.md

+5-5
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,13 @@ The following options are supported:
2929
### OpenAPI definition
3030

3131
You will need an OpenAPI definition. For testing purposes, you can use one of the following sample OpenAPI definitions:
32+
3233
- OpenAPI 3.0
33-
- [Rebilly Users OpenAPI Definition](https://raw.githubusercontent.com/Rebilly/api-definitions/main/openapi/users.yaml)
34-
- [Swagger Petstore Sample OpenAPI Definition](https://petstore3.swagger.io/api/v3/openapi.json)
34+
- [Rebilly Users OpenAPI Definition](https://raw.githubusercontent.com/Rebilly/api-definitions/main/openapi/users.yaml)
35+
- [Swagger Petstore Sample OpenAPI Definition](https://petstore3.swagger.io/api/v3/openapi.json)
3536
- OpenAPI 2.0
36-
- [Thingful OpenAPI Definition](https://raw.githubusercontent.com/thingful/openapi-spec/master/spec/swagger.yaml)
37-
- [Fitbit Plus OpenAPI Definition](https://raw.githubusercontent.com/TwineHealth/TwineDeveloperDocs/master/spec/swagger.yaml)
38-
37+
- [Thingful OpenAPI Definition](https://raw.githubusercontent.com/thingful/openapi-spec/master/spec/swagger.yaml)
38+
- [Fitbit Plus OpenAPI Definition](https://raw.githubusercontent.com/TwineHealth/TwineDeveloperDocs/master/spec/swagger.yaml)
3939

4040
:::info OpenAPI specification
4141
For more information on the OpenAPI specification, refer to the [Learning OpenAPI 3](https://redoc.ly/docs/resources/learning-openapi/)

docs/quickstart.md

+9-7
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@ replace the `spec-url` attribute with the URL or local file address to your defi
1313
<head>
1414
<title>Redoc</title>
1515
<!-- needed for adaptive design -->
16-
<meta charset="utf-8"/>
17-
<meta name="viewport" content="width=device-width, initial-scale=1">
18-
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
16+
<meta charset="utf-8" />
17+
<meta name="viewport" content="width=device-width, initial-scale=1" />
18+
<link
19+
href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700"
20+
rel="stylesheet"
21+
/>
1922

2023
<!--
2124
Redoc doesn't change outer page styles
@@ -28,17 +31,16 @@ replace the `spec-url` attribute with the URL or local file address to your defi
2831
</style>
2932
</head>
3033
<body>
31-
<!--
34+
<!--
3235
Redoc element with link to your OpenAPI definition
3336
-->
34-
<redoc spec-url='http://petstore.swagger.io/v2/swagger.json'></redoc>
37+
<redoc spec-url="http://petstore.swagger.io/v2/swagger.json"></redoc>
3538
<!--
3639
Link to Redoc JavaScript on CDN for rendering standalone element
3740
-->
38-
<script src="https://cdn.jsdelivr.net/npm/redoc@latest/bundles/redoc.standalone.js"> </script>
41+
<script src="https://cdn.jsdelivr.net/npm/redoc@latest/bundles/redoc.standalone.js"></script>
3942
</body>
4043
</html>
41-
4244
```
4345

4446
:::attention Running Redoc locally requires an HTTP server

src/common-elements/dropdown.ts

+8-8
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,16 @@ export const StyledDropdown = styled(Dropdown)`
2828
width: auto;
2929
background: white;
3030
color: #263238;
31-
font-family: ${(props) => props.theme.typography.headings.fontFamily};
31+
font-family: ${props => props.theme.typography.headings.fontFamily};
3232
font-size: 0.929em;
3333
line-height: 1.5em;
3434
cursor: pointer;
3535
transition: border 0.25s ease, color 0.25s ease, box-shadow 0.25s ease;
3636
&:hover,
3737
&:focus-within {
38-
border: 1px solid ${(props) => props.theme.colors.primary.main};
39-
color: ${(props) => props.theme.colors.primary.main};
40-
box-shadow: 0px 0px 0px 1px ${(props) => props.theme.colors.primary.main};
38+
border: 1px solid ${props => props.theme.colors.primary.main};
39+
color: ${props => props.theme.colors.primary.main};
40+
box-shadow: 0px 0px 0px 1px ${props => props.theme.colors.primary.main};
4141
}
4242
.dropdown-selector {
4343
display: inline-flex;
@@ -48,7 +48,7 @@ export const StyledDropdown = styled(Dropdown)`
4848
margin-bottom: 5px;
4949
}
5050
.dropdown-selector-value {
51-
font-family: ${(props) => props.theme.typography.headings.fontFamily};
51+
font-family: ${props => props.theme.typography.headings.fontFamily};
5252
position: relative;
5353
font-size: 0.929em;
5454
width: 100%;
@@ -63,7 +63,7 @@ export const StyledDropdown = styled(Dropdown)`
6363
right: 3px;
6464
top: 50%;
6565
transform: translateY(-50%);
66-
border-color: ${(props) => props.theme.colors.primary.main} transparent transparent;
66+
border-color: ${props => props.theme.colors.primary.main} transparent transparent;
6767
border-style: solid;
6868
border-width: 0.35em 0.35em 0;
6969
width: 0;
@@ -128,8 +128,8 @@ export const SimpleDropdown = styled(StyledDropdown)`
128128
border: none;
129129
box-shadow: none;
130130
.dropdown-selector-value {
131-
color: ${(props) => props.theme.colors.primary.main};
132-
text-shadow: 0px 0px 0px ${(props) => props.theme.colors.primary.main};
131+
color: ${props => props.theme.colors.primary.main};
132+
text-shadow: 0px 0px 0px ${props => props.theme.colors.primary.main};
133133
}
134134
}
135135
}

src/components/Callbacks/CallbackTitle.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,10 @@ const CallbackTitleWrapper = styled.button`
4848
`;
4949

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

5555
const OperationBadgeStyled = styled(OperationBadge)`
56-
margin: 0px 5px 0px 0px;
56+
margin: 0 5px 0 0;
5757
`;

src/components/JsonViewer/JsonViewer.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,11 @@ class Json extends React.PureComponent<JsonProps> {
3434
<button onClick={this.collapseAll}> Collapse all </button>
3535
</SampleControls>
3636
<OptionsContext.Consumer>
37-
{(options) => (
37+
{options => (
3838
<PrismDiv
3939
className={this.props.className}
4040
// tslint:disable-next-line
41-
ref={(node) => (this.node = node!)}
41+
ref={node => (this.node = node!)}
4242
dangerouslySetInnerHTML={{
4343
__html: jsonToHTML(this.props.data, options.jsonSampleExpandLevel),
4444
}}

src/components/JsonViewer/style.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ export const jsonStyles = css`
66
pointer-events: none;
77
}
88
9-
font-family: ${(props) => props.theme.typography.code.fontFamily};
10-
font-size: ${(props) => props.theme.typography.code.fontSize};
9+
font-family: ${props => props.theme.typography.code.fontFamily};
10+
font-size: ${props => props.theme.typography.code.fontSize};
1111
1212
white-space: ${({ theme }) => (theme.typography.code.wrap ? 'pre-wrap' : 'pre')};
1313
contain: content;
@@ -51,8 +51,8 @@ export const jsonStyles = css`
5151
background-color: transparent;
5252
border: 0;
5353
color: #fff;
54-
font-family: ${(props) => props.theme.typography.code.fontFamily};
55-
font-size: ${(props) => props.theme.typography.code.fontSize};
54+
font-family: ${props => props.theme.typography.code.fontFamily};
55+
font-size: ${props => props.theme.typography.code.fontSize};
5656
padding-right: 6px;
5757
padding-left: 6px;
5858
padding-top: 0;

src/components/Operation/Operation.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export class Operation extends React.Component<OperationProps> {
4242

4343
return (
4444
<OptionsContext.Consumer>
45-
{(options) => (
45+
{options => (
4646
<OperationRow>
4747
<MiddlePanel>
4848
<H2>

src/components/Responses/styled.elements.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@ export const StyledResponseTitle = styled(ResponseTitle)`
1414
background-color: #f2f2f2;
1515
cursor: pointer;
1616
17-
color: ${(props) => props.theme.colors.responses[props.type].color};
18-
background-color: ${(props) => props.theme.colors.responses[props.type].backgroundColor};
17+
color: ${props => props.theme.colors.responses[props.type].color};
18+
background-color: ${props => props.theme.colors.responses[props.type].backgroundColor};
1919
&:focus {
2020
outline: auto;
21-
outline-color: ${(props) => props.theme.colors.responses[props.type].color};
21+
outline-color: ${props => props.theme.colors.responses[props.type].color};
2222
}
23-
${(props) =>
23+
${props =>
2424
(props.empty &&
2525
`
2626
cursor: default;

src/components/SecurityRequirement/SecurityRequirement.tsx

+5-5
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import { SecurityRequirementModel } from '../../services/models/SecurityRequirem
77
import { linksCss } from '../Markdown/styled.elements';
88

99
const ScopeName = styled.code`
10-
font-size: ${(props) => props.theme.typography.code.fontSize};
11-
font-family: ${(props) => props.theme.typography.code.fontFamily};
10+
font-size: ${props => props.theme.typography.code.fontSize};
11+
font-family: ${props => props.theme.typography.code.fontFamily};
1212
border: 1px solid ${({ theme }) => theme.colors.border.dark};
1313
margin: 0 3px;
1414
padding: 0.2em;
@@ -67,12 +67,12 @@ export class SecurityRequirement extends React.PureComponent<SecurityRequirement
6767
return (
6868
<SecurityRequirementOrWrap>
6969
{security.schemes.length ? (
70-
security.schemes.map((scheme) => {
70+
security.schemes.map(scheme => {
7171
return (
7272
<SecurityRequirementAndWrap key={scheme.id}>
7373
<Link to={scheme.sectionId}>{scheme.id}</Link>
7474
{scheme.scopes.length > 0 && ' ('}
75-
{scheme.scopes.map((scope) => (
75+
{scheme.scopes.map(scope => (
7676
<ScopeName key={scope}>{scope}</ScopeName>
7777
))}
7878
{scheme.scopes.length > 0 && ') '}
@@ -92,7 +92,7 @@ const AuthHeaderColumn = styled.div`
9292
`;
9393

9494
const SecuritiesColumn = styled.div`
95-
width: ${(props) => props.theme.schema.defaultDetailsWidth};
95+
width: ${props => props.theme.schema.defaultDetailsWidth};
9696
${media.lessThan('small')`
9797
margin-top: 10px;
9898
`}

src/services/RedocNormalizedOptions.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export class RedocNormalizedOptions {
7272
}
7373
if (typeof value === 'string') {
7474
const res = {};
75-
value.split(',').forEach((code) => {
75+
value.split(',').forEach(code => {
7676
res[code.trim()] = true;
7777
});
7878
return res;
@@ -138,7 +138,7 @@ export class RedocNormalizedOptions {
138138
case 'false':
139139
return false;
140140
default:
141-
return value.split(',').map((ext) => ext.trim());
141+
return value.split(',').map(ext => ext.trim());
142142
}
143143
}
144144

@@ -266,7 +266,7 @@ export class RedocNormalizedOptions {
266266
this.maxDisplayedEnumValues = argValueToNumber(raw.maxDisplayedEnumValues);
267267
const ignoreNamedSchemas = Array.isArray(raw.ignoreNamedSchemas)
268268
? raw.ignoreNamedSchemas
269-
: raw.ignoreNamedSchemas?.split(',').map((s) => s.trim());
269+
: raw.ignoreNamedSchemas?.split(',').map(s => s.trim());
270270
this.ignoreNamedSchemas = new Set(ignoreNamedSchemas);
271271
this.hideSchemaPattern = argValueToBoolean(raw.hideSchemaPattern);
272272
this.generatedPayloadSamplesMaxDepth =

src/utils/__tests__/openapi.test.ts

+71
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
pluralizeType,
1111
serializeParameterValue,
1212
sortByRequired,
13+
humanizeNumberRange,
1314
} from '../';
1415

1516
import { FieldModel, OpenAPIParser, RedocNormalizedOptions } from '../../services';
@@ -410,6 +411,76 @@ describe('Utils', () => {
410411
});
411412
});
412413

414+
describe('openapi humanizeNumberRange', () => {
415+
it('should return `>=` when only minimum value present or exclusiveMinimum = false', () => {
416+
const expected = '>= 0';
417+
expect(humanizeNumberRange({ minimum: 0 })).toEqual(expected);
418+
expect(humanizeNumberRange({ minimum: 0, exclusiveMinimum: false })).toEqual(expected);
419+
});
420+
421+
it('should return `>` when minimum value present and exclusiveMinimum set to true', () => {
422+
expect(humanizeNumberRange({ minimum: 0, exclusiveMinimum: true })).toEqual('> 0');
423+
});
424+
425+
it('should return `<=` when only maximum value present or exclusiveMinimum = false', () => {
426+
const expected = '<= 10';
427+
expect(humanizeNumberRange({ maximum: 10 })).toEqual(expected);
428+
expect(humanizeNumberRange({ maximum: 10, exclusiveMaximum: false })).toEqual(expected);
429+
});
430+
431+
it('should return `<` when maximum value present and exclusiveMaximum set to true', () => {
432+
expect(humanizeNumberRange({ maximum: 10, exclusiveMaximum: true })).toEqual('< 10');
433+
});
434+
435+
it('should return correct range for minimum and maximum values and with different exclusive set', () => {
436+
expect(humanizeNumberRange({ minimum: 0, maximum: 10 })).toEqual('[ 0 .. 10 ]');
437+
expect(
438+
humanizeNumberRange({
439+
minimum: 0,
440+
exclusiveMinimum: true,
441+
maximum: 10,
442+
exclusiveMaximum: true,
443+
}),
444+
).toEqual('( 0 .. 10 )');
445+
expect(
446+
humanizeNumberRange({
447+
minimum: 0,
448+
maximum: 10,
449+
exclusiveMaximum: true,
450+
}),
451+
).toEqual('[ 0 .. 10 )');
452+
expect(
453+
humanizeNumberRange({
454+
minimum: 0,
455+
exclusiveMinimum: true,
456+
maximum: 10,
457+
}),
458+
).toEqual('( 0 .. 10 ]');
459+
});
460+
461+
it('should return correct range exclusive values only', () => {
462+
expect(humanizeNumberRange({ exclusiveMinimum: 0 })).toEqual('> 0');
463+
expect(humanizeNumberRange({ exclusiveMaximum: 10 })).toEqual('< 10');
464+
expect(humanizeNumberRange({ exclusiveMinimum: 0, exclusiveMaximum: 10 })).toEqual(
465+
'( 0 .. 10 )',
466+
);
467+
});
468+
469+
it('should return correct min value', () => {
470+
expect(humanizeNumberRange({ minimum: 5, exclusiveMinimum: 10 })).toEqual('> 5');
471+
expect(humanizeNumberRange({ minimum: -5, exclusiveMinimum: -10 })).toEqual('> -10');
472+
});
473+
474+
it('should return correct max value', () => {
475+
expect(humanizeNumberRange({ maximum: 10, exclusiveMaximum: 15 })).toEqual('< 15');
476+
expect(humanizeNumberRange({ maximum: -10, exclusiveMaximum: -15 })).toEqual('< -10');
477+
});
478+
479+
it('should return undefined', () => {
480+
expect(humanizeNumberRange({})).toEqual(undefined);
481+
});
482+
});
483+
413484
describe('openapi humanizeConstraints', () => {
414485
const itemConstraintSchema = (
415486
min?: number,

0 commit comments

Comments
 (0)