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
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ test('Throws if a field has error with explicit error handling enabled', () => {
componentDisplayName,
);
}).toThrowError(
"Relay: Received a field error in the server response for field 'me.name' in 'FragmentResourceSemanticNonNullTestFragment1'. Message: There was an error!",
"Relay: Received a field error in the server response for field 'name' in 'FragmentResourceSemanticNonNullTestFragment1'. Message: There was an error!",
);
});

Expand Down
30 changes: 20 additions & 10 deletions packages/relay-runtime/store/RelayReader.js
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,11 @@ class RelayReader {
};
}

_maybeAddFieldErrors(record: Record, storageKey: string): void {
_maybeAddFieldErrors(
record: Record,
storageKey: string,
fieldName: string,
): void {
const errors = RelayModernRecord.getErrors(record, storageKey);

if (errors == null) {
Expand All @@ -247,7 +251,11 @@ class RelayReader {
const error = errors[i];
this._fieldErrors.push({
error,
fieldPath: (error.path ?? []).join('.'),
// Like the other field error kinds, start from this field's name and
// let _prependPreviousErrors add the parent fields on the way out.
// The stored error has no usable `path`: the normalizer strips it when
// attaching the error to the record.
fieldPath: fieldName,
handled: false,
kind: 'relay_field_payload.error',
owner,
Expand Down Expand Up @@ -1334,7 +1342,7 @@ class RelayReader {
Array.isArray(value) &&
value.length === 0)
) {
this._maybeAddFieldErrors(record, storageKey);
this._maybeAddFieldErrors(record, storageKey, fieldName);
} else if (value === undefined) {
this._markDataAsMissing(fieldName);
}
Expand All @@ -1361,7 +1369,7 @@ class RelayReader {
if (linkedID == null) {
data[fieldName] = linkedID;
if (linkedID === null) {
this._maybeAddFieldErrors(record, storageKey);
this._maybeAddFieldErrors(record, storageKey, fieldName);
} else if (linkedID === undefined) {
this._markDataAsMissing(fieldName);
}
Expand Down Expand Up @@ -1416,9 +1424,9 @@ class RelayReader {
* Similarly, when creating field errors, we simply initialize the `fieldPath`
* as the direct field name.
*
* Today we only use this apporach for `missing_expected_data` and
* `missing_required_field` errors, but we intend to broaden it to handle all
* field error paths.
* Today we use this apporach for `missing_expected_data`,
* `missing_required_field` and `relay_field_payload` errors, but we intend
* to broaden it to handle all field error paths.
*/
_prependPreviousErrors(
prevErrors: ?Array<FieldError>,
Expand All @@ -1429,7 +1437,8 @@ class RelayReader {
const event = this._fieldErrors[i];
if (
event.owner === this._fragmentName &&
(event.kind === 'missing_expected_data.throw' ||
(event.kind === 'relay_field_payload.error' ||
event.kind === 'missing_expected_data.throw' ||
event.kind === 'missing_expected_data.log' ||
event.kind === 'missing_required_field.throw' ||
event.kind === 'missing_required_field.log')
Expand Down Expand Up @@ -1465,7 +1474,7 @@ class RelayReader {
if (externalRef === undefined) {
this._markDataAsMissing(fieldName);
} else if (externalRef === null) {
this._maybeAddFieldErrors(record, storageKey);
this._maybeAddFieldErrors(record, storageKey, fieldName);
}
return data[fieldName];
}
Expand All @@ -1491,6 +1500,7 @@ class RelayReader {
record: Record,
data: SelectorData,
): ?unknown {
const fieldName = field.alias ?? field.name;
const storageKey = getStorageKey(field, this._variables);
if (this._fieldGranularNotificationDataID != null) {
this._seenRecords.add(
Expand All @@ -1507,7 +1517,7 @@ class RelayReader {
Array.isArray(linkedIDs) &&
linkedIDs.length === 0)
) {
this._maybeAddFieldErrors(record, storageKey);
this._maybeAddFieldErrors(record, storageKey, fieldName);
}
return this._readLinkedIds(field, linkedIDs, record, data);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -638,7 +638,7 @@ describe('RelayReader @catch', () => {
expect(fieldErrors).toEqual([
{
error: {message: 'There was an error!', path: ['me', 'lastName']},
fieldPath: 'me.lastName',
fieldPath: 'me.myAlias.lastName',
handled: true,
kind: 'relay_field_payload.error',
owner:
Expand Down Expand Up @@ -689,7 +689,7 @@ describe('RelayReader @catch', () => {
expect(fieldErrors).toEqual([
{
error: {message: 'There was an error!', path: ['me', 'lastName']},
fieldPath: 'me.lastName',
fieldPath: 'me.myAlias.lastName',
handled: true,
kind: 'relay_field_payload.error',
owner:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,17 @@

const {graphql} = require('../../query/GraphQLTag');
const RelayFeatureFlags = require('../../util/RelayFeatureFlags');
const defaultGetDataID = require('../defaultGetDataID');
const {
createOperationDescriptor,
} = require('../RelayModernOperationDescriptor');
const RelayModernRecord = require('../RelayModernRecord');
const {createNormalizationSelector} = require('../RelayModernSelector');
const RelayModernStore = require('../RelayModernStore');
const {read} = require('../RelayReader');
const RelayRecordSource = require('../RelayRecordSource');
const {normalize} = require('../RelayResponseNormalizer');
const {ROOT_ID, ROOT_TYPE} = require('../RelayStoreUtils');

describe('RelayReader error fields', () => {
it('adds the errors to fieldErrors', () => {
Expand Down Expand Up @@ -68,6 +73,38 @@ describe('RelayReader error fields', () => {
]);
});

it('reports the field path of a server error stored by the normalizer', () => {
// The normalizer drops `path` when it attaches an error to a record, so
// the reader has to work out the path itself.
const FooQuery = require('./__generated__/RelayReaderRelayErrorHandlingTest1Query.graphql');
const source = new RelayRecordSource();
source.set(ROOT_ID, RelayModernRecord.create(ROOT_ID, ROOT_TYPE));
normalize(
source,
createNormalizationSelector(FooQuery.operation, ROOT_ID, {}),
{me: {__typename: 'User', id: '1', lastName: null}},
{
getDataID: defaultGetDataID,
treatMissingFieldsAsNull: false,
deferDeduplicatedFields: false,
log: null,
},
[{message: 'There was an error!', path: ['me', 'lastName']}],
);
const operation = createOperationDescriptor(FooQuery, {});
const {fieldErrors} = read(source, operation.fragment, null);
expect(fieldErrors).toEqual([
{
owner: 'RelayReaderRelayErrorHandlingTest1Query',
fieldPath: 'me.lastName',
error: {message: 'There was an error!'},
kind: 'relay_field_payload.error',
shouldThrow: false,
handled: false,
},
]);
});

it('adds the errors to fieldErrors including missingData - without @catch', () => {
const source = RelayRecordSource.create({
'client:root': {
Expand Down Expand Up @@ -166,7 +203,7 @@ describe('RelayReader error fields', () => {
expect(fieldErrors).toEqual([
{
owner: 'RelayReaderRelayErrorHandlingTestMissingPluralQuery',
fieldPath: 'me.lastName',
fieldPath: 'nodes.0.lastName',
kind: 'relay_field_payload.error',
error: {
message: 'There was an error!',
Expand Down Expand Up @@ -294,7 +331,7 @@ describe('RelayReader error fields', () => {
expect(fieldErrors).toEqual([
{
error: {message: 'There was an error!', path: ['me', 'lastName']},
fieldPath: 'me.lastName',
fieldPath: 'lastName',
// handled is true because we handled the error by making
// last_name_throw_on_field_error null
handled: true,
Expand Down Expand Up @@ -342,7 +379,7 @@ describe('RelayReader error fields', () => {
expect(fieldErrors).toEqual([
{
error: {message: 'There was an error!', path: ['me', 'lastName']},
fieldPath: 'me.lastName',
fieldPath: 'lastName',
// handled is false because we want this to throw
// last_name_throw_on_field_error null
handled: false,
Expand Down Expand Up @@ -918,7 +955,7 @@ describe('RelayReader error fields', () => {

expect(fieldErrors).toEqual([
{
fieldPath: '',
fieldPath: 'node.friends.edges',
handled: false,
error: {message: 'There was an error!'},
kind: 'relay_field_payload.error',
Expand Down Expand Up @@ -968,7 +1005,7 @@ describe('RelayReader error fields', () => {

expect(fieldErrors).toEqual([
{
fieldPath: '',
fieldPath: 'node.emailAddresses',
handled: false,
error: {message: 'There was an error!'},
kind: 'relay_field_payload.error',
Expand Down Expand Up @@ -1033,7 +1070,7 @@ describe('RelayReader error fields', () => {
expect(data.node.friends.edges).toEqual([]);
expect(fieldErrors).toEqual([
{
fieldPath: '',
fieldPath: 'node.friends.edges',
handled: false,
error: {message: 'There was an error!'},
kind: 'relay_field_payload.error',
Expand Down Expand Up @@ -1082,7 +1119,7 @@ describe('RelayReader error fields', () => {
expect(data.node.emailAddresses).toEqual([]);
expect(fieldErrors).toEqual([
{
fieldPath: '',
fieldPath: 'node.emailAddresses',
handled: false,
error: {message: 'There was an error!'},
kind: 'relay_field_payload.error',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ test('Field error with @throwOnFieldError', async () => {
expect(results).toEqual([
{
error: new Error(
"Relay: Received a field error in the server response for field '' in 'observeFragmentTestThrowOnFieldErrorFragment'. Message: error",
"Relay: Received a field error in the server response for field 'name' in 'observeFragmentTestThrowOnFieldErrorFragment'. Message: error",
),
state: 'error',
},
Expand Down Expand Up @@ -510,7 +510,7 @@ test('Field error with @relay(plural: true) @throwOnFieldError', async () => {
expect(results).toEqual([
{
error: new Error(
"Relay: Received a field error in the server response for field '' in 'observeFragmentTestPluralThrowOnFieldErrorFragment'. Message: error",
"Relay: Received a field error in the server response for field 'name' in 'observeFragmentTestPluralThrowOnFieldErrorFragment'. Message: error",
),
state: 'error',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ test('Field error with @throwOnFieldError', async () => {
expect(results).toEqual([
{
error: new Error(
"Relay: Received a field error in the server response for field '' in 'observeQueryTestThrowOnFieldErrorQuery'. Message: error",
"Relay: Received a field error in the server response for field 'me.name' in 'observeQueryTestThrowOnFieldErrorQuery'. Message: error",
),
state: 'error',
},
Expand Down