relay-runtime
Version:
A core runtime for building GraphQL-driven applications.
179 lines (165 loc) • 4.61 kB
Flow
/**
* 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
*/
;
import type {PayloadError} from '../network/RelayNetworkTypes';
// $FlowFixMe[recursive-definition]
const SELF: Self = Symbol('$SELF');
export opaque type Self = typeof SELF;
export type TRelayFieldErrorForDisplay = $ReadOnly<{
path?: $ReadOnlyArray<string | number>,
severity?: 'CRITICAL' | 'ERROR' | 'WARNING',
}>;
// We display a subset of the TRelayFieldError to the user. Removing the message by default.
export type TRelayFieldError = $ReadOnly<{
...TRelayFieldErrorForDisplay,
message: string,
}>;
/**
* 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<TRelayFieldError, 'path'>>,
>;
function buildErrorTrie(
errors: ?$ReadOnlyArray<PayloadError>,
): RelayErrorTrie | null {
if (errors == null) {
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<TRelayFieldError> | 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,
});