-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Expand file tree
/
Copy pathRelayErrorTrie.js
More file actions
166 lines (153 loc) · 4.26 KB
/
RelayErrorTrie.js
File metadata and controls
166 lines (153 loc) · 4.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
/**
* 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,
PayloadExtensions,
} 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,
extensions?: PayloadExtensions,
}>;
/**
* 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: NonNullable<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<TRelayFieldError> = [];
recursivelyCopyErrorsIntoArray(value, errors);
return errors;
}
function recursivelyCopyErrorsIntoArray(
trieOrSet: RelayErrorTrie,
errors: Array<TRelayFieldError>,
): 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];
errors[index] = {
...error,
path: error.path == null ? [childKey] : [childKey, ...error.path],
};
}
}
}
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,
getErrorsByKey,
getNestedErrorTrieByKey,
} as {
SELF: typeof SELF,
buildErrorTrie: typeof buildErrorTrie,
getNestedErrorTrieByKey: typeof getNestedErrorTrieByKey,
getErrorsByKey: typeof getErrorsByKey,
};