1
- import stampit from 'stampit' ;
2
1
import { propEq } from 'ramda' ;
3
2
import { ApiDOMError } from '@swagger-api/apidom-error' ;
4
3
import {
@@ -20,7 +19,9 @@ import MaximumResolveDepthError from '../../../errors/MaximumResolveDepthError';
20
19
import * as url from '../../../util/url' ;
21
20
import parse from '../../../parse' ;
22
21
import Reference from '../../../Reference' ;
22
+ import ReferenceSet from '../../../ReferenceSet' ;
23
23
import { evaluate } from './selectors/element-id' ;
24
+ import type { ReferenceOptions } from '../../../options' ;
24
25
25
26
// @ts -ignore
26
27
const visitAsync = visit [ Symbol . for ( 'nodejs.util.promisify.custom' ) ] ;
@@ -38,159 +39,163 @@ const visitAsync = visit[Symbol.for('nodejs.util.promisify.custom')];
38
39
* WARNING: this implementation only supports referencing elements in the local document. Points 2-4 are not supported.
39
40
*/
40
41
41
- const ApiDOMDereferenceVisitor = stampit ( {
42
- props : {
43
- reference : null ,
44
- options : null ,
45
- } ,
46
- init ( { reference, options } ) {
42
+ export interface ApiDOMDereferenceVisitorOptions {
43
+ readonly reference : Reference ;
44
+ readonly options : ReferenceOptions ;
45
+ }
46
+
47
+ class ApiDOMDereferenceVisitor {
48
+ protected readonly reference : Reference ;
49
+
50
+ protected readonly options : ReferenceOptions ;
51
+
52
+ constructor ( { reference, options } : ApiDOMDereferenceVisitorOptions ) {
47
53
this . reference = reference ;
48
54
this . options = options ;
49
- } ,
50
- methods : {
51
- toBaseURI ( uri : string ) : string {
52
- return url . resolve ( this . reference . uri , url . sanitize ( url . stripHash ( uri ) ) ) ;
53
- } ,
54
-
55
- async toReference ( uri : string ) : Promise < Reference > {
56
- // detect maximum depth of resolution
57
- if ( this . reference . depth >= this . options . resolve . maxDepth ) {
58
- throw new MaximumResolveDepthError (
59
- `Maximum resolution depth of ${ this . options . resolve . maxDepth } has been exceeded by file "${ this . reference . uri } "` ,
60
- ) ;
61
- }
62
-
63
- const baseURI = this . toBaseURI ( uri ) ;
64
- const { refSet } = this . reference ;
65
-
66
- // we've already processed this Reference in past
67
- if ( refSet . has ( baseURI ) ) {
68
- return refSet . find ( propEq ( baseURI , 'uri' ) ) ;
69
- }
70
-
71
- const parseResult = await parse ( url . unsanitize ( baseURI ) , {
72
- ...this . options ,
73
- parse : { ...this . options . parse , mediaType : 'text/plain' } ,
74
- } ) ;
55
+ }
56
+
57
+ protected toBaseURI ( uri : string ) : string {
58
+ return url . resolve ( this . reference . uri , url . sanitize ( url . stripHash ( uri ) ) ) ;
59
+ }
75
60
76
- // register new mutable reference with a refSet
77
- const mutableReference = new Reference ( {
78
- uri : baseURI ,
79
- value : cloneDeep ( parseResult ) ,
61
+ protected async toReference ( uri : string ) : Promise < Reference > {
62
+ // detect maximum depth of resolution
63
+ if ( this . reference . depth >= this . options . resolve . maxDepth ) {
64
+ throw new MaximumResolveDepthError (
65
+ `Maximum resolution depth of ${ this . options . resolve . maxDepth } has been exceeded by file "${ this . reference . uri } "` ,
66
+ ) ;
67
+ }
68
+
69
+ const baseURI = this . toBaseURI ( uri ) ;
70
+ const { refSet } = this . reference as { refSet : ReferenceSet } ;
71
+
72
+ // we've already processed this Reference in past
73
+ if ( refSet . has ( baseURI ) ) {
74
+ return refSet . find ( propEq ( baseURI , 'uri' ) ) ! ;
75
+ }
76
+
77
+ const parseResult = await parse ( url . unsanitize ( baseURI ) , {
78
+ ...this . options ,
79
+ parse : { ...this . options . parse , mediaType : 'text/plain' } ,
80
+ } ) ;
81
+
82
+ // register new mutable reference with a refSet
83
+ const mutableReference = new Reference ( {
84
+ uri : baseURI ,
85
+ value : cloneDeep ( parseResult ) ,
86
+ depth : this . reference . depth + 1 ,
87
+ } ) ;
88
+ refSet . add ( mutableReference ) ;
89
+
90
+ if ( this . options . dereference . immutable ) {
91
+ // register new immutable reference with a refSet
92
+ const immutableReference = new Reference ( {
93
+ uri : `immutable://${ baseURI } ` ,
94
+ value : parseResult ,
80
95
depth : this . reference . depth + 1 ,
81
96
} ) ;
82
- refSet . add ( mutableReference ) ;
83
-
84
- if ( this . options . dereference . immutable ) {
85
- // register new immutable reference with a refSet
86
- const immutableReference = new Reference ( {
87
- uri : `immutable://${ baseURI } ` ,
88
- value : parseResult ,
89
- depth : this . reference . depth + 1 ,
90
- } ) ;
91
- refSet . add ( immutableReference ) ;
92
- }
93
-
94
- return mutableReference ;
95
- } ,
96
-
97
- async RefElement (
98
- refElement : RefElement ,
99
- key : string | number ,
100
- parent : Element | undefined ,
101
- path : ( string | number ) [ ] ,
102
- ancestors : [ Element | Element [ ] ] ,
97
+ refSet . add ( immutableReference ) ;
98
+ }
99
+
100
+ return mutableReference ;
101
+ }
102
+
103
+ public async RefElement (
104
+ refElement : RefElement ,
105
+ key : string | number ,
106
+ parent : Element | undefined ,
107
+ path : ( string | number ) [ ] ,
108
+ ancestors : [ Element | Element [ ] ] ,
109
+ ) {
110
+ const refURI = toValue ( refElement ) ;
111
+ const refNormalizedURI = refURI . includes ( '#' ) ? refURI : `#${ refURI } ` ;
112
+ const retrievalURI = this . toBaseURI ( refNormalizedURI ) ;
113
+ const isInternalReference = url . stripHash ( this . reference . uri ) === retrievalURI ;
114
+ const isExternalReference = ! isInternalReference ;
115
+
116
+ // ignore resolving internal RefElements
117
+ if ( ! this . options . resolve . internal && isInternalReference ) {
118
+ // skip traversing this ref element
119
+ return false ;
120
+ }
121
+ // ignore resolving external RefElements
122
+ if ( ! this . options . resolve . external && isExternalReference ) {
123
+ // skip traversing this ref element
124
+ return false ;
125
+ }
126
+
127
+ const reference = await this . toReference ( refNormalizedURI ) ;
128
+ const refBaseURI = url . resolve ( retrievalURI , refNormalizedURI ) ;
129
+ const elementID = uriToElementID ( refBaseURI ) ;
130
+ let referencedElement : unknown | Element | undefined = evaluate (
131
+ elementID ,
132
+ reference . value . result as Element ,
133
+ ) ;
134
+
135
+ if ( ! isElement ( referencedElement ) ) {
136
+ throw new ApiDOMError ( `Referenced element with id="${ elementID } " was not found` ) ;
137
+ }
138
+
139
+ if ( refElement === referencedElement ) {
140
+ throw new ApiDOMError ( 'RefElement cannot reference itself' ) ;
141
+ }
142
+
143
+ if ( isRefElement ( referencedElement ) ) {
144
+ throw new ApiDOMError ( 'RefElement cannot reference another RefElement' ) ;
145
+ }
146
+
147
+ if ( isExternalReference ) {
148
+ // dive deep into the fragment
149
+ const visitor = new ApiDOMDereferenceVisitor ( { reference, options : this . options } ) ;
150
+ referencedElement = await visitAsync ( referencedElement , visitor ) ;
151
+ }
152
+
153
+ /**
154
+ * When path is used, it references the given property of the referenced element
155
+ */
156
+ const referencedElementPath : string = toValue ( refElement . path ) ;
157
+ if ( referencedElementPath !== 'element' && isElement ( referencedElement ) ) {
158
+ referencedElement = refract ( referencedElement [ referencedElementPath ] ) ;
159
+ }
160
+
161
+ /**
162
+ * Transclusion of a Ref Element SHALL be defined in the if/else block below.
163
+ */
164
+ if (
165
+ isObjectElement ( referencedElement ) &&
166
+ isObjectElement ( ancestors [ ancestors . length - 1 ] ) &&
167
+ Array . isArray ( parent ) &&
168
+ typeof key === 'number'
103
169
) {
104
- const refURI = toValue ( refElement ) ;
105
- const refNormalizedURI = refURI . includes ( '#' ) ? refURI : `#${ refURI } ` ;
106
- const retrievalURI = this . toBaseURI ( refNormalizedURI ) ;
107
- const isInternalReference = url . stripHash ( this . reference . uri ) === retrievalURI ;
108
- const isExternalReference = ! isInternalReference ;
109
-
110
- // ignore resolving internal RefElements
111
- if ( ! this . options . resolve . internal && isInternalReference ) {
112
- // skip traversing this ref element
113
- return false ;
114
- }
115
- // ignore resolving external RefElements
116
- if ( ! this . options . resolve . external && isExternalReference ) {
117
- // skip traversing this ref element
118
- return false ;
119
- }
120
-
121
- const reference = await this . toReference ( refNormalizedURI ) ;
122
- const refBaseURI = url . resolve ( retrievalURI , refNormalizedURI ) ;
123
- const elementID = uriToElementID ( refBaseURI ) ;
124
- let referencedElement : unknown | Element | undefined = evaluate (
125
- elementID ,
126
- reference . value . result ,
127
- ) ;
128
-
129
- if ( ! isElement ( referencedElement ) ) {
130
- throw new ApiDOMError ( `Referenced element with id="${ elementID } " was not found` ) ;
131
- }
132
-
133
- if ( refElement === referencedElement ) {
134
- throw new ApiDOMError ( 'RefElement cannot reference itself' ) ;
135
- }
136
-
137
- if ( isRefElement ( referencedElement ) ) {
138
- throw new ApiDOMError ( 'RefElement cannot reference another RefElement' ) ;
139
- }
140
-
141
- if ( isExternalReference ) {
142
- // dive deep into the fragment
143
- const visitor = ApiDOMDereferenceVisitor ( { reference, options : this . options } ) ;
144
- referencedElement = await visitAsync ( referencedElement , visitor ) ;
145
- }
146
-
147
170
/**
148
- * When path is used, it references the given property of the referenced element
171
+ * If the Ref Element is held by an Object Element and references an Object Element,
172
+ * its content entries SHALL be inserted in place of the Ref Element.
149
173
*/
150
- const referencedElementPath : string = toValue ( refElement . path ) ;
151
- if ( referencedElementPath !== 'element' && isElement ( referencedElement ) ) {
152
- referencedElement = refract ( referencedElement [ referencedElementPath ] ) ;
153
- }
154
-
174
+ parent . splice ( key , 1 , ...referencedElement . content ) ;
175
+ } else if (
176
+ isArrayElement ( referencedElement ) &&
177
+ Array . isArray ( parent ) &&
178
+ typeof key === 'number'
179
+ ) {
155
180
/**
156
- * Transclusion of a Ref Element SHALL be defined in the if/else block below.
181
+ * If the Ref Element is held by an Array Element and references an Array Element,
182
+ * its content entries SHALL be inserted in place of the Ref Element.
157
183
*/
158
- if (
159
- isObjectElement ( referencedElement ) &&
160
- isObjectElement ( ancestors [ ancestors . length - 1 ] ) &&
161
- Array . isArray ( parent ) &&
162
- typeof key === 'number'
163
- ) {
164
- /**
165
- * If the Ref Element is held by an Object Element and references an Object Element,
166
- * its content entries SHALL be inserted in place of the Ref Element.
167
- */
168
- parent . splice ( key , 1 , ...referencedElement . content ) ;
169
- } else if (
170
- isArrayElement ( referencedElement ) &&
171
- Array . isArray ( parent ) &&
172
- typeof key === 'number'
173
- ) {
174
- /**
175
- * If the Ref Element is held by an Array Element and references an Array Element,
176
- * its content entries SHALL be inserted in place of the Ref Element.
177
- */
178
- parent . splice ( key , 1 , ...referencedElement . content ) ;
179
- } else if ( isMemberElement ( parent ) ) {
180
- /**
181
- * The Ref Element is substituted by the Element it references.
182
- */
183
- parent . value = referencedElement ; // eslint-disable-line no-param-reassign
184
- } else if ( Array . isArray ( parent ) ) {
185
- /**
186
- * The Ref Element is substituted by the Element it references.
187
- */
188
- parent [ key ] = referencedElement ; // eslint-disable-line no-param-reassign
189
- }
190
-
191
- return ! parent ? referencedElement : false ;
192
- } ,
193
- } ,
194
- } ) ;
184
+ parent . splice ( key , 1 , ...referencedElement . content ) ;
185
+ } else if ( isMemberElement ( parent ) ) {
186
+ /**
187
+ * The Ref Element is substituted by the Element it references.
188
+ */
189
+ parent . value = referencedElement ; // eslint-disable-line no-param-reassign
190
+ } else if ( Array . isArray ( parent ) ) {
191
+ /**
192
+ * The Ref Element is substituted by the Element it references.
193
+ */
194
+ parent [ key ] = referencedElement ; // eslint-disable-line no-param-reassign
195
+ }
196
+
197
+ return ! parent ? referencedElement : false ;
198
+ }
199
+ }
195
200
196
201
export default ApiDOMDereferenceVisitor ;
0 commit comments