2
2
// Licensed to the Ed-Fi Alliance under one or more agreements.
3
3
// The Ed-Fi Alliance licenses this file to you under the Apache License, Version 2.0.
4
4
// See the LICENSE and NOTICES files in the project root for more information.
5
+ import invariant from 'ts-invariant' ;
5
6
import { JSONPath as jsonPath } from 'jsonpath-plus' ;
6
- import { DocumentIdentity } from '../model/DocumentIdentity' ;
7
7
import { DocumentReference } from '../model/DocumentReference' ;
8
8
import { DocumentObjectKey } from '../model/api-schema/DocumentObjectKey' ;
9
9
import { DocumentPaths } from '../model/api-schema/DocumentPaths' ;
10
- import { JsonPath } from '../model/api-schema/JsonPath' ;
11
10
import { ResourceSchema } from '../model/api-schema/ResourceSchema' ;
11
+ import { DocumentIdentity } from '../model/DocumentIdentity' ;
12
+
13
+ /**
14
+ * In extracting DocumentReferences, there is an intermediate step where document values are resolved
15
+ * from a JsonPath. JsonPaths return arrays of values when the path goes into an array.
16
+ * This is the case for collections of document references.
17
+ *
18
+ * This means that each path resolves to one document value in *each* document reference in the collection.
19
+ * For each DocumentObjectKey of a reference, IntermediateDocumentReferences holds the array of resolved document values
20
+ * for a path.
21
+ *
22
+ * For example, given a document with a collection of ClassPeriod references:
23
+ *
24
+ * classPeriods: [
25
+ * {
26
+ * classPeriodReference: {
27
+ * schoolId: '24',
28
+ * classPeriodName: 'z1',
29
+ * },
30
+ * },
31
+ * {
32
+ * classPeriodReference: {
33
+ * schoolId: '25',
34
+ * classPeriodName: 'z2',
35
+ * },
36
+ * },
37
+ * ]
38
+ *
39
+ * With JsonPaths for ClassPeriod references:
40
+ * "* $.classPeriods[*].classPeriodReference.schoolId" for schoolId and
41
+ * "$.classPeriods[*].classPeriodReference.classPeriodName" for classPeriodName,
42
+ * the IntermediateDocumentReferences would be:
43
+ *
44
+ * {
45
+ * schoolId: ['24', '25'],
46
+ * classPeriodName: ['z1', 'z2']
47
+ * }
48
+ *
49
+ * IntermediateDocumentReferences here contains information for two DocumentReferences, but as "slices" in the wrong
50
+ * orientation.
51
+ */
52
+ type IntermediateDocumentReferences = { [ key : DocumentObjectKey ] : any [ ] } ;
12
53
13
54
/**
14
55
* Takes a resource schema and an API document for that resource and
@@ -24,24 +65,55 @@ export function extractDocumentReferences(resourceSchema: ResourceSchema, docume
24
65
// Only applies to non-descriptor references
25
66
if ( documentPaths . isDescriptor ) return ;
26
67
27
- const documentIdentity : DocumentIdentity = [ ] ;
28
- // Build up documentIdentity in order
29
- documentPaths . pathOrder . forEach ( ( documentKey : DocumentObjectKey ) => {
30
- const documentJsonPath : JsonPath = documentPaths . paths [ documentKey ] ;
31
- const documentValue : any = jsonPath ( {
68
+ // Build up intermediateDocumentReferences
69
+ const intermediateDocumentReferences : IntermediateDocumentReferences = { } ;
70
+ Object . entries ( documentPaths . paths ) . forEach ( ( [ documentKey , documentJsonPath ] ) => {
71
+ const documentValuesSlice : any [ ] = jsonPath ( {
32
72
path : documentJsonPath ,
33
73
json : documentBody ,
34
74
flatten : true ,
35
75
} ) ;
36
- documentIdentity . push ( { documentKey, documentValue } ) ;
37
- } ) ;
38
76
39
- result . push ( {
40
- documentIdentity,
41
- isDescriptor : documentPaths . isDescriptor ,
42
- projectName : documentPaths . projectName ,
43
- resourceName : documentPaths . resourceName ,
77
+ invariant (
78
+ Array . isArray ( documentValuesSlice ) ,
79
+ `JsonPath ${ documentJsonPath } should have returned an array but instead was ${ documentValuesSlice } ` ,
80
+ ) ;
81
+
82
+ // Path can be empty if reference is optional
83
+ if ( documentValuesSlice . length === 0 ) return ;
84
+
85
+ intermediateDocumentReferences [ documentKey ] = documentValuesSlice ;
44
86
} ) ;
87
+
88
+ const documentValuesSlices = Object . values ( intermediateDocumentReferences ) ;
89
+
90
+ // Empty if reference is optional and no values
91
+ if ( documentValuesSlices . length === 0 ) return ;
92
+
93
+ // Number of document values from resolved JsonPaths should all be the same, otherwise something is very wrong
94
+ invariant (
95
+ documentValuesSlices . every (
96
+ ( documentValuesSlice ) => documentValuesSlice . length === documentValuesSlices [ 0 ] . length ,
97
+ `Length of document value slices are not equal` ,
98
+ ) ,
99
+ ) ;
100
+
101
+ // Reorient intermediateDocumentReferences into actual references
102
+ for ( let index = 0 ; index < documentValuesSlices [ 0 ] . length ; index += 1 ) {
103
+ const documentIdentity : DocumentIdentity = [ ] ;
104
+
105
+ // Build the document identity in the correct path order
106
+ documentPaths . pathOrder . forEach ( ( documentKey : DocumentObjectKey ) => {
107
+ documentIdentity . push ( { documentKey, documentValue : intermediateDocumentReferences [ documentKey ] [ index ] } ) ;
108
+ } ) ;
109
+
110
+ result . push ( {
111
+ documentIdentity,
112
+ isDescriptor : documentPaths . isDescriptor ,
113
+ projectName : documentPaths . projectName ,
114
+ resourceName : documentPaths . resourceName ,
115
+ } ) ;
116
+ }
45
117
} ) ;
46
118
47
119
return result ;
0 commit comments