-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for copying field errors into the store
Reviewed By: tyao1 Differential Revision: D47607969 fbshipit-source-id: b568e792e632e253371f8478dd20519e23017fd3
- Loading branch information
1 parent
2c5cac4
commit c4618c6
Showing
17 changed files
with
2,300 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
/** | ||
* Copyright (c) Meta Platforms, Inc. and affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
* | ||
* @flow | ||
* @format | ||
* @oncall relay | ||
*/ | ||
|
||
'use strict'; | ||
|
||
import type {PayloadError} from '../network/RelayNetworkTypes'; | ||
|
||
const RelayFeatureFlags = require('../util/RelayFeatureFlags'); | ||
|
||
const SELF: Self = Symbol('$SELF'); | ||
|
||
export opaque type Self = typeof SELF; | ||
|
||
export type RelayFieldError = $ReadOnly<{ | ||
message: string, | ||
path?: $ReadOnlyArray<string | number>, | ||
severity?: 'CRITICAL' | 'ERROR' | 'WARNING', | ||
}>; | ||
|
||
/** | ||
* This is a highly-specialized data structure that is designed | ||
* to store the field errors of a GraphQL response in such a way | ||
* that they can be performantly retrieved during normalization. | ||
* | ||
* In particular, the trie can be constructed in O(N) time, where | ||
* N is the number of errors, so long as the depth of the GraphQL | ||
* response data, and therefore the expected length of any error | ||
* paths, is relatively small and constant. | ||
* | ||
* As we recursively traverse the data in the GraphQL response | ||
* during normalization, we can get the sub trie for any field | ||
* in O(1) time. | ||
*/ | ||
export opaque type RelayErrorTrie = Map< | ||
string | number | Self, | ||
RelayErrorTrie | Array<Omit<RelayFieldError, 'path'>>, | ||
>; | ||
|
||
function buildErrorTrie( | ||
errors: ?$ReadOnlyArray<PayloadError>, | ||
): RelayErrorTrie | null { | ||
if (errors == null) { | ||
return null; | ||
} | ||
if (!RelayFeatureFlags.ENABLE_FIELD_ERROR_HANDLING) { | ||
return null; | ||
} | ||
const trie: $NonMaybeType<RelayErrorTrie> = new Map(); | ||
// eslint-disable-next-line no-unused-vars | ||
ERRORS: for (const {path, locations: _, ...error} of errors) { | ||
if (path == null) { | ||
continue; | ||
} | ||
const {length} = path; | ||
if (length === 0) { | ||
continue; | ||
} | ||
const lastIndex = length - 1; | ||
let currentTrie = trie; | ||
for (let index = 0; index < lastIndex; index++) { | ||
const key = path[index]; | ||
const existingValue = currentTrie.get(key); | ||
if (existingValue instanceof Map) { | ||
currentTrie = existingValue; | ||
continue; | ||
} | ||
const newValue: RelayErrorTrie = new Map(); | ||
if (Array.isArray(existingValue)) { | ||
newValue.set(SELF, existingValue); | ||
} | ||
currentTrie.set(key, newValue); | ||
currentTrie = newValue; | ||
} | ||
let lastKey: string | number | symbol = path[lastIndex]; | ||
let container = currentTrie.get(lastKey); | ||
if (container instanceof Map) { | ||
currentTrie = container; | ||
container = currentTrie.get(lastKey); | ||
lastKey = SELF; | ||
} | ||
if (Array.isArray(container)) { | ||
container.push(error); | ||
} else { | ||
currentTrie.set(lastKey, [error]); | ||
} | ||
} | ||
return trie; | ||
} | ||
|
||
function getErrorsByKey( | ||
trie: RelayErrorTrie, | ||
key: string | number, | ||
): $ReadOnlyArray<RelayFieldError> | null { | ||
const value = trie.get(key); | ||
if (value == null) { | ||
return null; | ||
} | ||
if (Array.isArray(value)) { | ||
return value; | ||
} | ||
const errors: Array< | ||
$ReadOnly<{ | ||
message: string, | ||
path?: Array<string | number>, | ||
severity?: 'CRITICAL' | 'ERROR' | 'WARNING', | ||
}>, | ||
> = []; | ||
recursivelyCopyErrorsIntoArray(value, errors); | ||
return errors; | ||
} | ||
|
||
function recursivelyCopyErrorsIntoArray( | ||
trieOrSet: RelayErrorTrie, | ||
errors: Array< | ||
$ReadOnly<{ | ||
message: string, | ||
path?: Array<string | number>, | ||
severity?: 'CRITICAL' | 'ERROR' | 'WARNING', | ||
}>, | ||
>, | ||
): void { | ||
for (const [childKey, value] of trieOrSet) { | ||
const oldLength = errors.length; | ||
if (Array.isArray(value)) { | ||
errors.push(...value); | ||
} else { | ||
recursivelyCopyErrorsIntoArray(value, errors); | ||
} | ||
if (childKey === SELF) { | ||
continue; | ||
} | ||
const newLength = errors.length; | ||
for (let index = oldLength; index < newLength; index++) { | ||
const error = errors[index]; | ||
if (error.path == null) { | ||
errors[index] = { | ||
...error, | ||
path: [childKey], | ||
}; | ||
} else { | ||
error.path.unshift(childKey); | ||
} | ||
} | ||
} | ||
} | ||
|
||
function getNestedErrorTrieByKey( | ||
trie: RelayErrorTrie, | ||
key: string | number, | ||
): RelayErrorTrie | null { | ||
const value = trie.get(key); | ||
if (value instanceof Map) { | ||
return value; | ||
} | ||
return null; | ||
} | ||
|
||
module.exports = ({ | ||
SELF, | ||
buildErrorTrie, | ||
getNestedErrorTrieByKey, | ||
getErrorsByKey, | ||
}: { | ||
SELF: typeof SELF, | ||
buildErrorTrie: typeof buildErrorTrie, | ||
getNestedErrorTrieByKey: typeof getNestedErrorTrieByKey, | ||
getErrorsByKey: typeof getErrorsByKey, | ||
}); |
Oops, something went wrong.