Skip to content

Commit 35f9503

Browse files
committed
feat(error-check): add check for error message
1 parent c33572c commit 35f9503

File tree

5 files changed

+268
-5
lines changed

5 files changed

+268
-5
lines changed

src/components/SchemaRow/SchemaRow.tsx

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons/faExclamationTriangle.js';
1+
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons/faExclamationTriangle';
22
import {
33
isMirroredNode,
44
isReferenceNode,
@@ -15,7 +15,15 @@ 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 { Caret, Description, Format, getValidationsFromSchema, Types, Validations } from '../shared';
18+
import {
19+
Caret,
20+
Description,
21+
Format,
22+
getInternalSchemaError,
23+
getValidationsFromSchema,
24+
Types,
25+
Validations,
26+
} from '../shared';
1927
import { ChildStack } from '../shared/ChildStack';
2028
import { Properties, useHasProperties } from '../shared/Properties';
2129
import { hoveredNodeAtom, isNodeHoveredAtom } from './state';
@@ -70,6 +78,8 @@ export const SchemaRow: React.FunctionComponent<SchemaRowProps> = React.memo(({
7078
const validations = isRegularNode(schemaNode) ? schemaNode.validations : {};
7179
const hasProperties = useHasProperties({ required, deprecated, validations });
7280

81+
const internalSchemaError = getInternalSchemaError(schemaNode);
82+
7383
return (
7484
<>
7585
<Flex
@@ -158,9 +168,14 @@ export const SchemaRow: React.FunctionComponent<SchemaRowProps> = React.memo(({
158168
hideExamples={hideExamples}
159169
/>
160170

161-
{isBrokenRef && (
171+
{(isBrokenRef || internalSchemaError.hasError) && (
162172
// TODO (JJ): Add mosaic tooltip showing ref error
163-
<Icon title={refNode!.error!} color="danger" icon={faExclamationTriangle} size="sm" />
173+
<Icon
174+
title={refNode?.error! || internalSchemaError.error}
175+
color="danger"
176+
icon={faExclamationTriangle}
177+
size="sm"
178+
/>
164179
)}
165180
</VStack>
166181

src/components/SchemaRow/TopLevelSchemaRow.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { faCaretDown } from '@fortawesome/free-solid-svg-icons/faCaretDown.js';
2+
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons/faExclamationTriangle';
23
import { isRegularNode, RegularNode } from '@stoplight/json-schema-tree';
34
import { Box, Flex, HStack, Icon, Menu, Pressable } from '@stoplight/mosaic';
45
import { useUpdateAtom } from 'jotai/utils';
@@ -9,6 +10,7 @@ import { useIsOnScreen } from '../../hooks/useIsOnScreen';
910
import { calculateChildrenToShow, isComplexArray } from '../../tree';
1011
import { showPathCrumbsAtom } from '../PathCrumbs/state';
1112
import { ChildStack } from '../shared/ChildStack';
13+
import { getInternalSchemaError } from '../shared/Validations';
1214
import { SchemaRow, SchemaRowProps } from './SchemaRow';
1315
import { useChoices } from './useChoices';
1416

@@ -17,12 +19,18 @@ export const TopLevelSchemaRow = ({ schemaNode }: Pick<SchemaRowProps, 'schemaNo
1719
const childNodes = React.useMemo(() => calculateChildrenToShow(selectedChoice.type), [selectedChoice.type]);
1820
const nestingLevel = 0;
1921

22+
const internalSchemaError = getInternalSchemaError(schemaNode);
23+
2024
// regular objects are flattened at the top level
2125
if (isRegularNode(schemaNode) && isPureObjectNode(schemaNode)) {
2226
return (
2327
<>
2428
<ScrollCheck />
2529
<ChildStack schemaNode={schemaNode} childNodes={childNodes} currentNestingLevel={nestingLevel} />
30+
{internalSchemaError.hasError && (
31+
// TODO (JJ): Add mosaic tooltip showing ref error
32+
<Icon title={internalSchemaError.error} color="danger" icon={faExclamationTriangle} size="sm" />
33+
)}
2634
</>
2735
);
2836
}

src/components/__tests__/SchemaRow.spec.tsx

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,87 @@ describe('SchemaRow component', () => {
4949
});
5050
});
5151

52+
describe('resolving permission error', () => {
53+
let tree: RootNode;
54+
let schema: JSONSchema4;
55+
56+
it('given an object schema is marked as internal, a permission denied error messsage should be shown', () => {
57+
schema = {
58+
type: 'object',
59+
'x-sl-internally-excluded': true,
60+
'x-sl-error-message': 'You do not have permission to view this reference',
61+
};
62+
tree = buildTree(schema);
63+
const wrapper = mount(<SchemaRow schemaNode={tree.children[0]!} nestingLevel={0} />);
64+
expect(wrapper.find(Icon).at(0)).toHaveProp('title', `You do not have permission to view this reference`);
65+
wrapper.unmount();
66+
});
67+
68+
it('given a number schema is marked as internal, a permission denied error messsage should be shown', () => {
69+
schema = {
70+
type: 'number',
71+
'x-sl-internally-excluded': true,
72+
'x-sl-error-message': 'You do not have permission to view this reference',
73+
};
74+
tree = buildTree(schema);
75+
const wrapper = mount(<SchemaRow schemaNode={tree.children[0]!} nestingLevel={0} />);
76+
expect(wrapper.find(Icon).at(0)).toHaveProp('title', `You do not have permission to view this reference`);
77+
wrapper.unmount();
78+
});
79+
80+
it('given an integer schema is marked as internal, a permission denied error messsage should be shown', () => {
81+
schema = {
82+
type: 'integer',
83+
'x-sl-internally-excluded': true,
84+
'x-sl-error-message': 'You do not have permission to view this reference',
85+
};
86+
tree = buildTree(schema);
87+
const wrapper = mount(<SchemaRow schemaNode={tree.children[0]!} nestingLevel={0} />);
88+
expect(wrapper.find(Icon).at(0)).toHaveProp('title', `You do not have permission to view this reference`);
89+
wrapper.unmount();
90+
});
91+
92+
it('given a string schema is marked as internal, a permission denied error messsage should be shown', () => {
93+
schema = {
94+
type: 'string',
95+
'x-sl-internally-excluded': true,
96+
'x-sl-error-message': 'You do not have permission to view this reference',
97+
};
98+
tree = buildTree(schema);
99+
const wrapper = mount(<SchemaRow schemaNode={tree.children[0]!} nestingLevel={0} />);
100+
expect(wrapper.find(Icon).at(0)).toHaveProp('title', `You do not have permission to view this reference`);
101+
wrapper.unmount();
102+
});
103+
104+
it('given a boolean schema is marked as internal, a permission denied error messsage should be shown', () => {
105+
schema = {
106+
type: 'boolean',
107+
'x-sl-internally-excluded': true,
108+
'x-sl-error-message': 'You do not have permission to view this reference',
109+
};
110+
tree = buildTree(schema);
111+
const wrapper = mount(<SchemaRow schemaNode={tree.children[0]!} nestingLevel={0} />);
112+
expect(wrapper.find(Icon).at(0)).toHaveProp('title', `You do not have permission to view this reference`);
113+
wrapper.unmount();
114+
});
115+
116+
it('given an array schema is marked as internal, a permission denied error messsage should be shown', () => {
117+
schema = {
118+
title: 'test',
119+
type: 'array',
120+
items: {
121+
type: 'array',
122+
'x-sl-internally-excluded': true,
123+
'x-sl-error-message': 'You do not have permission to view this reference',
124+
},
125+
};
126+
tree = buildTree(schema);
127+
const wrapper = mount(<SchemaRow schemaNode={tree.children[0]!} nestingLevel={0} />);
128+
expect(wrapper.find(Icon).at(0)).toHaveProp('title', `You do not have permission to view this reference`);
129+
wrapper.unmount();
130+
});
131+
});
132+
52133
describe('required property', () => {
53134
let schema: JSONSchema4;
54135

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import 'jest-enzyme';
2+
3+
import { RootNode } from '@stoplight/json-schema-tree';
4+
import { Icon } from '@stoplight/mosaic';
5+
import { mount } from 'enzyme';
6+
import { JSONSchema4 } from 'json-schema';
7+
import * as React from 'react';
8+
9+
import { TopLevelSchemaRow } from '../SchemaRow/TopLevelSchemaRow';
10+
import { buildTree } from '../shared/__tests__/utils';
11+
12+
describe('resolving permission error', () => {
13+
let tree: RootNode;
14+
let schema: JSONSchema4;
15+
16+
it('given an object schema has a mixture of properties with and without x-sl-error-message, a permission denied error messsage should be shown on properties with x-sl-error-message', () => {
17+
schema = {
18+
title: 'User',
19+
type: 'object',
20+
description: '',
21+
properties: {
22+
id: {
23+
type: 'integer',
24+
description: 'Unique identifier for the given user.',
25+
},
26+
firstName: {
27+
type: 'string',
28+
},
29+
mailingAddress: {
30+
type: 'object',
31+
'x-sl-error-message': 'You do not have permission to view this reference',
32+
'x-sl-internally-excluded': true,
33+
},
34+
billingAddresses: {
35+
type: 'array',
36+
'x-sl-error-message': 'You do not have permission to view this reference',
37+
'x-sl-internally-excluded': true,
38+
},
39+
},
40+
};
41+
42+
tree = buildTree(schema);
43+
const wrapper = mount(<TopLevelSchemaRow schemaNode={tree.children[0]!} />);
44+
expect(wrapper.find(Icon).at(0)).toHaveProp('title', `You do not have permission to view this reference`);
45+
expect(wrapper.find(Icon).at(1)).toHaveProp('title', `You do not have permission to view this reference`);
46+
expect(wrapper.find(Icon).at(2)).not.toHaveProp('title', `You do not have permission to view this reference`);
47+
expect(wrapper.find(Icon).at(3)).not.toHaveProp('title', `You do not have permission to view this reference`);
48+
wrapper.unmount();
49+
});
50+
51+
it('given an object schema with all properties containing x-sl-error-message, a permission denied error messsage should be shown for each', () => {
52+
schema = {
53+
title: 'User',
54+
type: 'object',
55+
description: '',
56+
properties: {
57+
mailingAddress: {
58+
type: 'object',
59+
'x-sl-error-message': 'You do not have permission to view this reference',
60+
'x-sl-internally-excluded': true,
61+
},
62+
testAddress: {
63+
type: 'string',
64+
'x-sl-error-message': 'You do not have permission to view this reference',
65+
'x-sl-internally-excluded': true,
66+
},
67+
billingAddresses: {
68+
type: 'array',
69+
'x-sl-error-message': 'You do not have permission to view this reference',
70+
'x-sl-internally-excluded': true,
71+
},
72+
},
73+
};
74+
75+
tree = buildTree(schema);
76+
const wrapper = mount(<TopLevelSchemaRow schemaNode={tree.children[0]!} />);
77+
expect(wrapper.find(Icon).at(0)).toHaveProp('title', `You do not have permission to view this reference`);
78+
expect(wrapper.find(Icon).at(1)).toHaveProp('title', `You do not have permission to view this reference`);
79+
expect(wrapper.find(Icon).at(2)).toHaveProp('title', `You do not have permission to view this reference`);
80+
wrapper.unmount();
81+
});
82+
83+
it('given an object schema where the toplevel contains x-sl-error-message, a permission denied error messsage should be shown', () => {
84+
schema = {
85+
type: 'object',
86+
'x-sl-error-message': 'You do not have permission to view this reference',
87+
'x-sl-internally-excluded': true,
88+
};
89+
tree = buildTree(schema);
90+
const wrapper = mount(<TopLevelSchemaRow schemaNode={tree.children[0]!} />);
91+
expect(wrapper.find(Icon).at(0)).toHaveProp('title', `You do not have permission to view this reference`);
92+
wrapper.unmount();
93+
});
94+
95+
it('given an object schema has properties without ax-sl-error-message, a permission denied error messsage should not be shown', () => {
96+
schema = {
97+
title: 'User',
98+
type: 'object',
99+
description: '',
100+
properties: {
101+
id: {
102+
type: 'integer',
103+
description: 'Unique identifier for the given user.',
104+
},
105+
firstName: {
106+
type: 'string',
107+
},
108+
otherName: {
109+
type: 'array',
110+
items: {
111+
title: 'Address',
112+
type: 'object',
113+
description: '',
114+
properties: {
115+
id: {
116+
type: 'integer',
117+
description: 'Unique identifier for the given user.',
118+
},
119+
location: {
120+
type: 'string',
121+
},
122+
},
123+
},
124+
},
125+
},
126+
};
127+
128+
tree = buildTree(schema);
129+
const wrapper = mount(<TopLevelSchemaRow schemaNode={tree.children[0]!} />);
130+
expect(wrapper.find(Icon).at(1)).not.toHaveProp('title', `You do not have permission to view this reference`);
131+
expect(wrapper.find(Icon).at(2)).not.toHaveProp('title', `You do not have permission to view this reference`);
132+
expect(wrapper.find(Icon).at(3)).not.toHaveProp('title', `You do not have permission to view this reference`);
133+
wrapper.unmount();
134+
});
135+
});

src/components/shared/Validations.tsx

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isRegularNode, RegularNode } from '@stoplight/json-schema-tree';
1+
import { isRegularNode, RegularNode, SchemaNode } from '@stoplight/json-schema-tree';
22
import { Flex, HStack, Text } from '@stoplight/mosaic';
33
import { Dictionary } from '@stoplight/types';
44
import capitalize from 'lodash/capitalize.js';
@@ -211,3 +211,27 @@ function getFilteredValidations(schemaNode: RegularNode) {
211211

212212
return schemaNode.validations;
213213
}
214+
215+
export function getInternalSchemaError(schemaNode: SchemaNode, defaultErrorMessage?: string) {
216+
let errorMessage: string | undefined;
217+
console.log(schemaNode);
218+
const fragment: unknown = schemaNode.fragment;
219+
if (typeof fragment === 'object' && fragment !== null) {
220+
const fragmentErrorMessage = fragment['x-sl-error-message'];
221+
if (typeof fragmentErrorMessage === 'string') {
222+
errorMessage = fragmentErrorMessage ?? defaultErrorMessage;
223+
} else {
224+
const items: unknown = fragment['items'];
225+
if (typeof items === 'object' && items !== null) {
226+
const itemsErrorMessage = items['x-sl-error-message'];
227+
if (typeof itemsErrorMessage === 'string') {
228+
errorMessage = itemsErrorMessage ?? defaultErrorMessage;
229+
}
230+
}
231+
}
232+
}
233+
return {
234+
hasError: !!errorMessage,
235+
error: errorMessage,
236+
};
237+
}

0 commit comments

Comments
 (0)