Skip to content
This repository was archived by the owner on Nov 8, 2024. It is now read-only.

Commit 7764adf

Browse files
committed
feat(core): dereference elements in valueOf
1 parent 48c8f1f commit 7764adf

File tree

7 files changed

+1163
-33
lines changed

7 files changed

+1163
-33
lines changed

packages/api-elements/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# API Elements (JavaScript) CHANGELOG
22

3+
## 0.3.2 (2020-09-15)
4+
5+
### Enhancements
6+
7+
- Dereference elements in valueOf
8+
39
## 0.3.1 (2020-09-07)
410

511
### Bug Fixes

packages/api-elements/lib/define-value-of.js

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const {
2121
isRef,
2222
isObjectWithUndefinedValues,
2323
trivialValue,
24+
getStructureMembers,
2425
} = require('./utils');
2526

2627
/**
@@ -35,7 +36,7 @@ function mapValue(e, f, elements) {
3536
return undefined;
3637
}
3738

38-
if (e.content && !isEmptyArray(e) && !isObjectWithUndefinedValues(e)) {
39+
if (e.content && !isEmptyArray(e, elements) && !isObjectWithUndefinedValues(e, elements)) {
3940
const result = f(e, elements, 'content');
4041

4142
if (result !== undefined) {
@@ -59,8 +60,8 @@ function mapValue(e, f, elements) {
5960
}
6061
}
6162

62-
// reconsider content for array element (prefer sample/default first)
63-
if (isNonEmptyArray(e)) {
63+
// reconsider content for array and object element (prefer sample/default first)
64+
if (isNonEmptyArray(e, elements) && isObject(e, elements)) {
6465
const result = f(e, elements, 'content');
6566

6667
if (result !== undefined) {
@@ -88,16 +89,17 @@ function mapValue(e, f, elements) {
8889
}
8990

9091
const result = elements[e.element];
91-
if (result) {
92+
if (result !== undefined) {
9293
const inheritedElements = R.filter(el => !el.id.equals(e.element), elements);
9394
return mapValue(result, f, inheritedElements);
9495
}
9596
}
9697

97-
if (isEnum(e)) {
98-
const enums = e.enumerations;
99-
if (enums && enums.content && enums.content[0]) {
100-
const result = f(enums.content[0], elements, 'generated');
98+
if (isEnum(e, elements)) {
99+
const content = getStructureMembers(e, elements);
100+
101+
if (content && content[0]) {
102+
const result = f(content[0], elements, 'generated');
101103
if (result !== undefined) {
102104
return result;
103105
}
@@ -112,7 +114,7 @@ function mapValue(e, f, elements) {
112114
}
113115
}
114116

115-
if (isArray(e) && e.isEmpty) {
117+
if ((isArray(e, elements) && e.isEmpty) || isObject(e, elements)) {
116118
return f(e, elements, 'generated');
117119
}
118120

@@ -130,24 +132,26 @@ function reduceValue(e, elements) {
130132
return mapValue(e, e => e.content, elements);
131133
}
132134

133-
if (isPrimitive(e)) {
135+
if (isPrimitive(e, elements)) {
134136
return e.content;
135137
}
136138

137139
if (e instanceof NullElement) {
138140
return null;
139141
}
140142

141-
if (isEnum(e)) {
143+
if (isEnum(e, elements)) {
142144
return mapValue(e.content, reduceValue, elements);
143145
}
144146

145-
if (isObject(e)) {
147+
if (isObject(e, elements)) {
146148
let result = {};
147149

148150
const isFixedElement = isFixed(e);
149151

150-
e.content.some((item) => {
152+
const content = getStructureMembers(e, elements);
153+
154+
content.some((item) => {
151155
const isSkippable = isOptional(item) || (!isFixedElement && !isRequired(item));
152156

153157
const key = mapValue(item.key, reduceValue, elements);
@@ -177,8 +181,9 @@ function reduceValue(e, elements) {
177181
return result;
178182
}
179183

180-
if (isArray(e)) {
181-
const result = e.map(item => mapValue(item, reduceValue, elements));
184+
if (isArray(e, elements)) {
185+
const content = getStructureMembers(e, elements);
186+
const result = content.map(item => mapValue(item, reduceValue, elements));
182187

183188
if (!isFixed(e)) {
184189
return result.filter(item => item !== undefined);

packages/api-elements/lib/utils.js

Lines changed: 150 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
/* eslint-disable no-underscore-dangle */
2-
32
const R = require('ramda');
3+
44
const {
5-
ArrayElement,
6-
ObjectElement,
75
StringElement,
86
BooleanElement,
97
NumberElement,
108
NullElement,
119
} = require('minim');
1210

13-
const EnumElement = require('./elements/Enum');
11+
/**
12+
* Get element attribute
13+
* @param {element} e - element - element
14+
* @param {string} attribute
15+
* @return {boolean}
16+
*/
17+
const getAttribute = (e, attribute) => e._attributes && e.attributes.get(attribute);
1418

1519
/**
1620
* Check if element has a typeAttribute
@@ -19,7 +23,7 @@ const EnumElement = require('./elements/Enum');
1923
* @return {boolean}
2024
*/
2125
function hasTypeAttribute(e, attribute) {
22-
const typeAttributes = e._attributes && e.attributes.get('typeAttributes');
26+
const typeAttributes = getAttribute(e, 'typeAttributes');
2327
if (typeAttributes) {
2428
return typeAttributes.includes(attribute);
2529
}
@@ -55,33 +59,71 @@ const isNullable = e => hasTypeAttribute(e, 'nullable');
5559
*/
5660
const isOptional = e => hasTypeAttribute(e, 'optional');
5761

62+
const baseTypes = new Set(['boolean', 'string', 'number', 'array', 'object', 'enum']);
63+
/**
64+
* Check if element is one of the base types
65+
* @param {element} e - element
66+
* @return {boolean}
67+
*/
68+
const isBaseType = e => baseTypes.has(e && e.element);
69+
70+
/**
71+
* Get the element type - prefer a base type, if found
72+
* @param {element} e - element
73+
* @param {object=} elements - object map of elements to look for inherited type
74+
* @return {string}
75+
*/
76+
const getType = (e, elements) => {
77+
if (e === undefined) {
78+
return undefined;
79+
}
80+
if (isBaseType(e)) {
81+
return e.element;
82+
}
83+
84+
const inheritedType = e.element && elements && elements[e.element];
85+
if (inheritedType !== undefined) {
86+
return getType(inheritedType, elements);
87+
}
88+
89+
return e && e.element;
90+
};
91+
92+
const primitives = new Set(['string', 'number', 'boolean', 'null']);
5893
/**
5994
* Check if the element is of a primitive type
6095
* @param {element} e - element
96+
* @param {object=} elements - object map of elements to look for inherited type
6197
* @return {boolean}
6298
*/
63-
const isPrimitive = e => (e instanceof StringElement) || (e instanceof NumberElement) || (e instanceof BooleanElement);
99+
const isPrimitive = (e, elements) => {
100+
const type = getType(e, elements);
101+
return primitives.has(String(type));
102+
};
64103

65104
/**
66105
* Check if the element type is Enum
67106
* @param {element} e - element
107+
* @param {object=} elements - object map of elements to look for inherited type
68108
* @return {boolean}
69109
*/
70-
const isEnum = e => e instanceof EnumElement;
110+
const isEnum = (e, elements) => getType(e, elements) === 'enum';
71111

72112
/**
73113
* Check if the element type is Array
74114
* @param {element} e - element
115+
* @param {object=} elements - object map of elements to look for inherited type
75116
* @return {boolean}
76117
*/
77-
const isArray = e => e instanceof ArrayElement;
118+
const isArray = (e, elements) => getType(e, elements) === 'array';
78119

79120
/**
80121
* Check if the element type is Object
81122
* @param {element} e - element
123+
* @param {object=} elements - object map of elements to look for inherited type
82124
* @return {boolean}
83125
*/
84-
const isObject = e => e instanceof ObjectElement;
126+
const isObject = (e, elements) => getType(e, elements) === 'object';
85127

86128
/**
87129
* Get the element default
@@ -149,23 +191,27 @@ const hasNoValue = R.complement(hasValue);
149191
/**
150192
* Check if the element is of a primitive type and has no value (content/sample/default)
151193
* @param {element} e - element
194+
* @param {object=} elements - object map of elements to look for inherited type
152195
* @return {boolean}
153196
*/
154-
const isNoValuePrimitive = R.both(isPrimitive, hasNoValue);
197+
const isNoValuePrimitive = (e, elements) => isPrimitive(e, elements) && hasNoValue(e);
155198

156199
/**
157200
* Check if the element type is array and is not empty
158201
* @param {element} e - element
202+
* @param {object=} elements - object map of elements to look for inherited type
159203
* @return {boolean}
160204
*/
161-
const isNonEmptyArray = e => isArray(e) && e.content && !e.isEmpty;
205+
const isNonEmptyArray = (e, elements) => isArray(e, elements) && e.content !== undefined && !e.isEmpty;
162206

163207
/**
164208
* Check if the element type is array and has only primitive elements with no value
165209
* @param {element} e - element
210+
* @param {object=} elements - object map of elements to look for inherited type
166211
* @return {boolean}
167212
*/
168-
const isEmptyArray = e => isArray(e) && e.content.every(isNoValuePrimitive);
213+
const isEmptyArray = (e, elements) => isArray(e, elements)
214+
&& (e.content === undefined || e.content.every(member => isNoValuePrimitive(member, elements)));
169215

170216
/**
171217
* Check if the element type is 'ref'
@@ -177,10 +223,12 @@ const isRef = e => e && e.element === 'ref';
177223
/**
178224
* Check if the element type is object and has all property values undefined
179225
* @param {element} e - element
226+
* @param {object=} elements - object map of elements to look for inherited type
180227
* @return {boolean}
181228
*/
182-
const isObjectWithUndefinedValues = e => isObject(e)
183-
&& e.content.every(prop => prop.value === undefined || prop.value.content === undefined);
229+
const isObjectWithUndefinedValues = (e, elements) => isObject(e, elements) && e.content.every(
230+
prop => prop.value === undefined || prop.value.content === undefined
231+
);
184232

185233
/**
186234
* Get a trivial value, to fill the unset, according to the element type
@@ -207,6 +255,93 @@ function trivialValue(e) {
207255
return undefined;
208256
}
209257

258+
/**
259+
* Get a key for the element member
260+
* This is used to identify each member on the Map, allowing overrides in Objects
261+
* @param {element} e - element
262+
* @param {object=} elements - object map of elements to look for inherited type
263+
* @return {(string|number|object|null)} - Map key
264+
*/
265+
function getMemberKey(e, elements) {
266+
if (isPrimitive(e, elements)) {
267+
// return unique identifier
268+
return Math.random();
269+
}
270+
271+
const key = e && e.content && e.content.key && e.content.key.toValue();
272+
const content = e && e.content;
273+
const type = e.element;
274+
275+
return key || content || type;
276+
}
277+
278+
/**
279+
* Get an Array with the element members
280+
* @param {element} e - element
281+
* @param {object=} elements - object map of elements to look for inherited type
282+
* @return {element[]} - element members
283+
*/
284+
function getMembers(e, elements) {
285+
if (e === undefined) {
286+
return [];
287+
}
288+
289+
if (isEnum(e, elements)) {
290+
const enumerations = getAttribute(e, 'enumerations');
291+
if (enumerations && enumerations.content !== undefined) {
292+
return enumerations.content;
293+
}
294+
}
295+
296+
if (Array.isArray(e.content)) {
297+
return e.content;
298+
}
299+
300+
return [e];
301+
}
302+
303+
/**
304+
* Get a Map with all the element members, including references
305+
* @param {element} e - element
306+
* @param {object=} elements - object map of elements to look for inherited type
307+
* @return {Map<element>} - element members
308+
*/
309+
function getAllMembersMap(e, elements) {
310+
if (e === undefined) {
311+
return new Map();
312+
}
313+
314+
const typeElement = elements && elements[e.element];
315+
const typeMembersMap = getAllMembersMap(typeElement, elements);
316+
const ownMembers = getMembers(e, elements);
317+
const ownMembersMap = new Map();
318+
319+
ownMembers.forEach((member) => {
320+
if (isRef(member)) {
321+
const refElement = elements && elements[member.content];
322+
const refMembersMap = getAllMembersMap(refElement, elements);
323+
refMembersMap.forEach((refMember) => {
324+
ownMembersMap.set(getMemberKey(refMember, elements), refMember);
325+
});
326+
} else {
327+
ownMembersMap.set(getMemberKey(member, elements), member);
328+
}
329+
});
330+
return new Map([...typeMembersMap, ...ownMembersMap]);
331+
}
332+
333+
/**
334+
* Get an Array with all the element members, including references
335+
* @param {element} e - element
336+
* @param {object=} elements - object map of elements to look for inherited type
337+
* @return {element[]} - element members
338+
*/
339+
function getStructureMembers(e, elements) {
340+
const membersMap = getAllMembersMap(e, elements);
341+
342+
return Array.from(membersMap.values());
343+
}
344+
210345
module.exports = {
211346
isFixed,
212347
isRequired,
@@ -223,4 +358,5 @@ module.exports = {
223358
isRef,
224359
isObjectWithUndefinedValues,
225360
trivialValue,
361+
getStructureMembers,
226362
};

packages/api-elements/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "api-elements",
3-
"version": "0.3.1",
3+
"version": "0.3.2",
44
"description": "API Elements JavaScript",
55
"author": "Apiary.io <[email protected]>",
66
"license": "MIT",

0 commit comments

Comments
 (0)