-
Notifications
You must be signed in to change notification settings - Fork 114
/
denormalize_node.dart
115 lines (101 loc) · 3.58 KB
/
denormalize_node.dart
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
import 'package:gql/ast.dart';
import 'package:normalize/src/utils/field_key.dart';
import 'package:normalize/src/utils/expand_fragments.dart';
import 'package:normalize/src/utils/exceptions.dart';
import 'package:normalize/src/config/normalization_config.dart';
import 'package:normalize/src/utils/is_dangling_reference.dart';
import 'package:normalize/src/policies/field_policy.dart';
import 'package:normalize/src/utils/well_known_directives.dart';
/// Returns a denormalized object for a given [SelectionSetNode].
///
/// This is called recursively as the AST is traversed.
Object? denormalizeNode({
required SelectionSetNode? selectionSet,
required Object? dataForNode,
required NormalizationConfig config,
}) {
if (dataForNode == null) return null;
if (dataForNode is List) {
return dataForNode
.where((data) => !isDanglingReference(data, config))
.map(
(data) => denormalizeNode(
selectionSet: selectionSet,
dataForNode: data,
config: config,
),
)
.toList();
}
// If this is a leaf node, return the data
if (selectionSet == null) return dataForNode;
if (dataForNode is Map) {
final denormalizedData = dataForNode.containsKey(config.referenceKey)
? config.read(dataForNode[config.referenceKey]) ?? {}
: Map<String, dynamic>.from(dataForNode);
final typename = denormalizedData['__typename'];
final typePolicy = config.typePolicies[typename];
final subNodes = expandFragments(
typename: typename,
selectionSet: selectionSet,
fragmentMap: config.fragmentMap,
possibleTypes: config.possibleTypes,
variables: config.variables,
);
final result = subNodes.fold<Map<String, dynamic>>(
{},
(result, fieldNode) {
final fieldPolicy =
(typePolicy?.fields ?? const {})[fieldNode.name.value];
final policyCanRead = fieldPolicy?.read != null;
final fieldName = FieldKey(
fieldNode,
config.variables,
fieldPolicy,
).toString();
final resultKey = fieldNode.alias?.value ?? fieldNode.name.value;
/// If the policy can't read,
/// and the key is missing from the data,
/// we have partial data
bool isSkippedValue = false;
if (!policyCanRead &&
!denormalizedData.containsKey(fieldName) &&
!(isSkippedValue = isSkipped(fieldNode, config.variables))) {
if (config.allowPartialData) {
return result;
}
throw PartialDataException(path: [resultKey]);
}
if (isSkippedValue) {
return result;
}
try {
if (policyCanRead) {
// we can denormalize missing fields with policies
// because they may be purely virtualized
return result
..[resultKey] = fieldPolicy!.read!(
denormalizedData[fieldName],
FieldFunctionOptions(
field: fieldNode,
config: config,
),
);
}
return result
..[resultKey] = denormalizeNode(
selectionSet: fieldNode.selectionSet,
dataForNode: denormalizedData[fieldName],
config: config,
);
} on PartialDataException catch (e) {
throw PartialDataException(path: [fieldName, ...e.path]);
}
},
);
return result.isEmpty ? null : result;
}
throw Exception(
'There are sub-selections on this node, but the data is not null, an Array, or a Map',
);
}