Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions packages/relay-runtime/store/RelayReader.js
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,10 @@ class RelayReader {
value = this._asResult(_value);
break;
case 'NULL':
if (this._fieldErrors != null && this._fieldErrors.length > 0) {
if (
this._fieldErrors != null &&
this._fieldErrors.some(e => !e.handled)
) {
value = null;
}
break;
Expand Down Expand Up @@ -479,12 +482,16 @@ class RelayReader {
* responsibility to ensure that errors are marked as handled.
*/
_asResult<T>(value: T): Result<T, unknown> {
if (this._fieldErrors == null || this._fieldErrors.length === 0) {
// Errors already handled by an inner @catch must not influence this
// @catch's outcome — they have been consumed by their own boundary and
// are only flowing through `_fieldErrors` for logging.
const unhandled = this._fieldErrors?.filter(e => !e.handled);
if (unhandled == null || unhandled.length === 0) {
return {ok: true, value};
}

// TODO: Should we be hiding log level events here?
const errors = this._fieldErrors
const errors = unhandled
.map(error => {
switch (error.kind) {
case 'relay_field_payload.error':
Expand Down
110 changes: 110 additions & 0 deletions packages/relay-runtime/store/__tests__/RelayReader-CatchFields-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -904,4 +904,114 @@ describe('RelayReader @catch', () => {
},
]);
});

it('nested @catch(to: RESULT): server error on inner field should be handled by the inner @catch, not bubble to the outer @catch', () => {
const source = RelayRecordSource.create({
'client:root': {
__id: 'client:root',
__typename: '__Root',
me: {__ref: '1'},
},
'1': {
__id: '1',
id: '1',
__typename: 'User',
lastName: null,
__errors: {
lastName: [
{
message: 'There was an error!',
path: ['me', 'lastName'],
},
],
},
},
});

const FooQuery = graphql`
query RelayReaderCatchFieldsTestNestedResultQuery {
me @catch(to: RESULT) {
lastName @catch(to: RESULT)
}
}
`;
const operation = createOperationDescriptor(FooQuery, {id: '1'});
const {data, fieldErrors} = read(source, operation.fragment, null);
expect(data).toEqual({
me: {
ok: true,
value: {
lastName: {
ok: false,
errors: [
{
path: ['me', 'lastName'],
},
],
},
},
},
});

expect(fieldErrors).toEqual([
{
error: {message: 'There was an error!', path: ['me', 'lastName']},
fieldPath: 'me.lastName',
handled: true,
kind: 'relay_field_payload.error',
owner: 'RelayReaderCatchFieldsTestNestedResultQuery',
shouldThrow: false,
},
]);
});

it('nested @catch(to: NULL): server error on inner field should be nulled by the inner @catch, leaving the outer field intact', () => {
const source = RelayRecordSource.create({
'client:root': {
__id: 'client:root',
__typename: '__Root',
me: {__ref: '1'},
},
'1': {
__id: '1',
id: '1',
__typename: 'User',
lastName: null,
__errors: {
lastName: [
{
message: 'There was an error!',
path: ['me', 'lastName'],
},
],
},
},
});

const FooQuery = graphql`
query RelayReaderCatchFieldsTestNestedNullQuery {
me @catch(to: NULL) {
lastName @catch(to: NULL)
}
}
`;
const operation = createOperationDescriptor(FooQuery, {id: '1'});
const {data, fieldErrors} = read(source, operation.fragment, null);
expect(data).toEqual({
me: {
lastName: null,
},
});

expect(fieldErrors).toEqual([
{
error: {message: 'There was an error!', path: ['me', 'lastName']},
fieldPath: 'me.lastName',
handled: true,
kind: 'relay_field_payload.error',
owner: 'RelayReaderCatchFieldsTestNestedNullQuery',
shouldThrow: false,
},
]);
});
});

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading