Skip to content

Commit e85511c

Browse files
authored
fix: place format next to an appropriate type (#202)
1 parent 7139888 commit e85511c

File tree

6 files changed

+72
-31
lines changed

6 files changed

+72
-31
lines changed

src/__fixtures__/formats-schema.json

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,19 @@
1818
"type": "number",
1919
"format": "float"
2020
},
21+
"count": {
22+
"type": ["integer", "null"],
23+
"format": "int32"
24+
},
25+
"size": {
26+
"type": [
27+
"number",
28+
"string"
29+
],
30+
"format": "byte"
31+
},
2132
"notype": {
22-
"format": "date-time"
33+
"format": "date-time"
2334
},
2435
"permissions": {
2536
"type": [

src/components/SchemaRow/SchemaRow.tsx

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,7 @@ import * as React from 'react';
1515
import { COMBINER_NAME_MAP } from '../../consts';
1616
import { useJSVOptionsContext } from '../../contexts';
1717
import { calculateChildrenToShow, isFlattenableNode, isPropertyRequired } from '../../tree';
18-
import {
19-
Caret,
20-
Description,
21-
Format,
22-
getInternalSchemaError,
23-
getValidationsFromSchema,
24-
Types,
25-
Validations,
26-
} from '../shared';
18+
import { Caret, Description, getInternalSchemaError, getValidationsFromSchema, Types, Validations } from '../shared';
2719
import { ChildStack } from '../shared/ChildStack';
2820
import { Properties, useHasProperties } from '../shared/Properties';
2921
import { hoveredNodeAtom, isNodeHoveredAtom } from './state';
@@ -108,12 +100,7 @@ export const SchemaRow: React.FunctionComponent<SchemaRowProps> = React.memo(({
108100
</Box>
109101
)}
110102

111-
{choices.length === 1 && (
112-
<>
113-
<Types schemaNode={typeToShow} />
114-
<Format schemaNode={typeToShow} />
115-
</>
116-
)}
103+
{choices.length === 1 && <Types schemaNode={typeToShow} />}
117104

118105
{onGoToRef && isReferenceNode(schemaNode) && schemaNode.external ? (
119106
<Box

src/components/shared/Format.tsx

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
1-
import { isRegularNode, SchemaNode } from '@stoplight/json-schema-tree';
21
import { Box } from '@stoplight/mosaic';
32
import * as React from 'react';
43

54
type FormatProps = {
6-
schemaNode: SchemaNode;
5+
format: string;
76
};
87

9-
export const Format: React.FunctionComponent<FormatProps> = ({ schemaNode }) => {
10-
if (!isRegularNode(schemaNode) || schemaNode.format === null) {
11-
return null;
12-
}
13-
14-
return <Box as="span" color="muted">{`<${schemaNode.format}>`}</Box>;
8+
export const Format: React.FunctionComponent<FormatProps> = ({ format }) => {
9+
return <Box as="span" color="muted">{`<${format}>`}</Box>;
1510
};

src/components/shared/Types.tsx

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ import {
99
import { Box } from '@stoplight/mosaic';
1010
import * as React from 'react';
1111

12+
import { COMMON_JSON_SCHEMA_AND_OAS_FORMATS } from '../../consts';
13+
import { isPrimitiveArray } from '../../tree';
1214
import { printName } from '../../utils';
15+
import { Format } from './Format';
1316

1417
function shouldRenderName(type: SchemaNodeKind | SchemaCombinerName | '$ref'): boolean {
1518
return type === SchemaNodeKind.Array || type === SchemaNodeKind.Object || type === '$ref';
@@ -29,6 +32,32 @@ function getTypes(schemaNode: RegularNode): Array<SchemaNodeKind | SchemaCombine
2932
);
3033
}
3134

35+
function getFormats(schemaNode: RegularNode): Partial<Record<SchemaNodeKind, string>> {
36+
const formats: Partial<Record<SchemaNodeKind, string>> = {};
37+
38+
if (isPrimitiveArray(schemaNode) && schemaNode.children[0].format !== null) {
39+
formats.array = schemaNode.children[0].format;
40+
}
41+
42+
if (schemaNode.format === null) {
43+
return formats;
44+
}
45+
46+
const types = getTypes(schemaNode);
47+
48+
for (const type of types) {
49+
if (!(type in COMMON_JSON_SCHEMA_AND_OAS_FORMATS)) continue;
50+
51+
if (COMMON_JSON_SCHEMA_AND_OAS_FORMATS[type].includes(schemaNode.format)) {
52+
formats[type] = schemaNode.format;
53+
return formats;
54+
}
55+
}
56+
57+
formats.string = schemaNode.format;
58+
return formats;
59+
}
60+
3261
export const Types: React.FunctionComponent<{ schemaNode: SchemaNode }> = ({ schemaNode }) => {
3362
if (isReferenceNode(schemaNode)) {
3463
return (
@@ -43,14 +72,20 @@ export const Types: React.FunctionComponent<{ schemaNode: SchemaNode }> = ({ sch
4372
}
4473

4574
const types = getTypes(schemaNode);
75+
const formats = getFormats(schemaNode);
4676

47-
if (types.length === 0) return null;
77+
if (types.length === 0) {
78+
return formats.string !== void 0 ? <Format format={formats.string} /> : null;
79+
}
4880

4981
const rendered = types.map((type, i, { length }) => (
5082
<React.Fragment key={type}>
5183
<Box as="span" textOverflow="truncate" color="muted">
5284
{shouldRenderName(type) ? printName(schemaNode) ?? type : type}
5385
</Box>
86+
87+
{type in formats ? <Format format={formats[type]} /> : null}
88+
5489
{i < length - 1 && (
5590
<Box as="span" key={`${i}-sep`} color="muted">
5691
{' or '}

src/components/shared/__tests__/Format.spec.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { JSONSchema4 } from 'json-schema';
55
import * as React from 'react';
66

77
import { SchemaRow } from '../../SchemaRow';
8-
import { Format } from '../Format';
8+
import { Types } from '../Types';
99
import { buildTree, findNodeWithPath } from './utils';
1010

1111
describe('Format component', () => {
@@ -14,23 +14,28 @@ describe('Format component', () => {
1414

1515
it('should render next to a single type', () => {
1616
const wrapper = mount(<SchemaRow schemaNode={findNodeWithPath(tree, ['properties', 'id'])!} nestingLevel={0} />);
17-
expect(wrapper.find(Format)).toHaveText('<float>');
17+
expect(wrapper.find(Types)).toHaveText('number<float>');
1818
wrapper.unmount();
1919
});
2020

21-
it('should render next to an array of types', () => {
21+
it.each`
22+
property | text
23+
${'count'} | ${'integer<int32> or null'}
24+
${'date-of-birth'} | ${'number or string<date-time> or array'}
25+
${'size'} | ${'number<byte> or string'}
26+
`('given $property property, should render next to the appropriate type', ({ property, text }) => {
2227
const wrapper = mount(
23-
<SchemaRow schemaNode={findNodeWithPath(tree, ['properties', 'date-of-birth'])!} nestingLevel={0} />,
28+
<SchemaRow schemaNode={findNodeWithPath(tree, ['properties', property])!} nestingLevel={0} />,
2429
);
25-
expect(wrapper.find(Format)).toHaveText('<date-time>');
30+
expect(wrapper.find(Types)).toHaveText(text);
2631
wrapper.unmount();
2732
});
2833

2934
it('should render even when the type(s) is/are missing', () => {
3035
const wrapper = mount(
3136
<SchemaRow schemaNode={findNodeWithPath(tree, ['properties', 'notype'])!} nestingLevel={0} />,
3237
);
33-
expect(wrapper.find(Format)).toHaveText('<date-time>');
38+
expect(wrapper.find(Types)).toHaveText('<date-time>');
3439
wrapper.unmount();
3540
});
3641
});

src/consts.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ export const COMBINER_PRETTY_NAMES: Readonly<Dictionary<string, SchemaCombinerNa
88
[SchemaCombinerName.OneOf]: 'or',
99
};
1010

11+
export const COMMON_JSON_SCHEMA_AND_OAS_FORMATS = {
12+
// strings are omitted because they are the default type to apply format to
13+
number: ['byte', 'int32', 'int64', 'float', 'double'],
14+
get integer() {
15+
return this.number;
16+
},
17+
};
18+
1119
export const NESTING_OFFSET: SpaceVals = 3;
1220

1321
export const CARET_ICON_SIZE = 'sm';

0 commit comments

Comments
 (0)