1 | 'use strict';
|
2 | var isPlainObject = require('lodash/isPlainObject');
|
3 | var isFunction = require('lodash/isFunction');
|
4 | var _find = require('lodash/find');
|
5 | var _merge = require('lodash/merge');
|
6 | var _identity = require('lodash/identity');
|
7 | var _transform = require('lodash/transform');
|
8 | var _mapValues = require('lodash/mapValues');
|
9 | var _mapKeys = require('lodash/mapKeys');
|
10 | var _pick = require('lodash/pick');
|
11 | var _pickBy = require('lodash/pickBy');
|
12 | var _keys = require('lodash/keys');
|
13 | var _each = require('lodash/each');
|
14 | var _isNil = require('lodash/isNil');
|
15 | var Inflector = require('./inflector');
|
16 |
|
17 | module.exports = function (collectionName, record, payload, opts) {
|
18 | function isComplexType(obj) {
|
19 | return Array.isArray(obj) || isPlainObject(obj);
|
20 | }
|
21 |
|
22 | function keyForAttribute(attribute) {
|
23 | if (isPlainObject(attribute)) {
|
24 | return _transform(attribute, function (result, value, key) {
|
25 | if (isComplexType(value)) {
|
26 | result[keyForAttribute(key)] = keyForAttribute(value);
|
27 | } else {
|
28 | result[keyForAttribute(key)] = value;
|
29 | }
|
30 | });
|
31 | } else if (Array.isArray(attribute)) {
|
32 | return attribute.map(function (attr) {
|
33 | if (isComplexType(attr)) {
|
34 | return keyForAttribute(attr);
|
35 | } else {
|
36 | return attr;
|
37 | }
|
38 | });
|
39 | } else {
|
40 | if (isFunction(opts.keyForAttribute)) {
|
41 | return opts.keyForAttribute(attribute);
|
42 | } else {
|
43 | return Inflector.caserize(attribute, opts);
|
44 | }
|
45 | }
|
46 | }
|
47 |
|
48 | function getId() {
|
49 | return opts.id || 'id';
|
50 | }
|
51 |
|
52 | function getRef(current, item, opts) {
|
53 | if (isFunction(opts.ref)) {
|
54 | return opts.ref(current, item);
|
55 | } else if (opts.ref === true) {
|
56 | if (Array.isArray(item)) {
|
57 | return item.map(function (val) {
|
58 | return String(val);
|
59 | });
|
60 | } else if (item) {
|
61 | return String(item);
|
62 | }
|
63 | } else if (item && item[opts.ref]){
|
64 | return String(item[opts.ref]);
|
65 | }
|
66 | }
|
67 |
|
68 | function getType(str, attrVal) {
|
69 | var type;
|
70 | attrVal = attrVal || {};
|
71 |
|
72 | if (isFunction(opts.typeForAttribute)) {
|
73 | type = opts.typeForAttribute(str, attrVal);
|
74 | }
|
75 |
|
76 |
|
77 | if ((opts.pluralizeType === undefined || opts.pluralizeType) && type === undefined) {
|
78 | type = Inflector.pluralize(str);
|
79 | }
|
80 |
|
81 | if (type === undefined) {
|
82 | type = str;
|
83 | }
|
84 |
|
85 | return type;
|
86 | }
|
87 |
|
88 | function getLinks(current, links, dest) {
|
89 | return _mapValues(links, function (value) {
|
90 | if (isFunction(value)) {
|
91 | return value(record, current, dest);
|
92 | } else {
|
93 | return value;
|
94 | }
|
95 | });
|
96 | }
|
97 |
|
98 | function getMeta(current, meta) {
|
99 | if (isFunction(meta)) {
|
100 | return meta(record);
|
101 | } else {
|
102 | return _mapValues(meta, function (value) {
|
103 | if (isFunction(value)) {
|
104 | return value(record, current);
|
105 | } else {
|
106 | return value;
|
107 | }
|
108 | });
|
109 | }
|
110 | }
|
111 |
|
112 | function pick(obj, attributes) {
|
113 | return _mapKeys(_pick(obj, attributes), function (value, key) {
|
114 | return keyForAttribute(key);
|
115 | });
|
116 | }
|
117 |
|
118 | function isCompoundDocumentIncluded(included, item) {
|
119 | return _find(payload.included, { id: item.id, type: item.type });
|
120 | }
|
121 |
|
122 | function pushToIncluded(dest, include) {
|
123 | var included = isCompoundDocumentIncluded(dest, include);
|
124 | if (included) {
|
125 |
|
126 | included.relationships = _merge(included.relationships,
|
127 | _pickBy(include.relationships, _identity));
|
128 |
|
129 |
|
130 | included.attributes = _merge(included.attributes,
|
131 | _pickBy(include.attributes, _identity));
|
132 | } else {
|
133 | if (!dest.included) { dest.included = []; }
|
134 | dest.included.push(include);
|
135 | }
|
136 | }
|
137 |
|
138 | this.serialize = function (dest, current, attribute, opts) {
|
139 | var that = this;
|
140 | var data = null;
|
141 |
|
142 | if (opts && opts.ref) {
|
143 | if (!dest.relationships) { dest.relationships = {}; }
|
144 |
|
145 | if (Array.isArray(current[attribute])) {
|
146 | data = current[attribute].map(function (item) {
|
147 | return that.serializeRef(item, current, attribute, opts);
|
148 | });
|
149 | } else {
|
150 | data = that.serializeRef(current[attribute], current, attribute,
|
151 | opts);
|
152 | }
|
153 |
|
154 | dest.relationships[keyForAttribute(attribute)] = {};
|
155 | if (!opts.ignoreRelationshipData) {
|
156 | dest.relationships[keyForAttribute(attribute)].data = data;
|
157 | }
|
158 |
|
159 | if (opts.relationshipLinks) {
|
160 | var links = getLinks(current[attribute], opts.relationshipLinks, dest);
|
161 | if (links.related) {
|
162 | dest.relationships[keyForAttribute(attribute)].links = links;
|
163 | }
|
164 | }
|
165 |
|
166 | if (opts.relationshipMeta) {
|
167 | dest.relationships[keyForAttribute(attribute)].meta =
|
168 | getMeta(current[attribute], opts.relationshipMeta);
|
169 | }
|
170 | } else {
|
171 | if (Array.isArray(current[attribute])) {
|
172 | if (current[attribute].length && isPlainObject(current[attribute][0])) {
|
173 | data = current[attribute].map(function (item) {
|
174 | return that.serializeNested(item, current, attribute, opts);
|
175 | });
|
176 | } else {
|
177 | data = current[attribute];
|
178 | }
|
179 |
|
180 | dest.attributes[keyForAttribute(attribute)] = data;
|
181 | } else if (isPlainObject(current[attribute])) {
|
182 | data = that.serializeNested(current[attribute], current, attribute, opts);
|
183 | dest.attributes[keyForAttribute(attribute)] = data;
|
184 | } else {
|
185 | dest.attributes[keyForAttribute(attribute)] = current[attribute];
|
186 | }
|
187 | }
|
188 | };
|
189 |
|
190 | this.serializeRef = function (dest, current, attribute, opts) {
|
191 | var that = this;
|
192 | var id = getRef(current, dest, opts);
|
193 | var type = getType(attribute, dest);
|
194 |
|
195 | var relationships = [];
|
196 | var includedAttrs = [];
|
197 |
|
198 | if (opts.attributes) {
|
199 | if (dest) {
|
200 | opts.attributes.forEach(function (attr) {
|
201 | if (opts[attr] && !dest[attr] && opts[attr].nullIfMissing) {
|
202 | dest[attr] = null;
|
203 | }
|
204 | });
|
205 | }
|
206 | relationships = opts.attributes.filter(function (attr) {
|
207 | return opts[attr];
|
208 | });
|
209 |
|
210 | includedAttrs = opts.attributes.filter(function (attr) {
|
211 | return !opts[attr];
|
212 | });
|
213 | }
|
214 |
|
215 | var included = { type: type, id: id };
|
216 | if (includedAttrs) { included.attributes = pick(dest, includedAttrs); }
|
217 |
|
218 | relationships.forEach(function (relationship) {
|
219 | if (dest && (isComplexType(dest[relationship]) || dest[relationship] === null)) {
|
220 | that.serialize(included, dest, relationship, opts[relationship]);
|
221 | }
|
222 | });
|
223 |
|
224 | if (includedAttrs.length &&
|
225 | (opts.included === undefined || opts.included)) {
|
226 | if (opts.includedLinks) {
|
227 | included.links = getLinks(dest, opts.includedLinks);
|
228 | }
|
229 |
|
230 | if (typeof id !== 'undefined') { pushToIncluded(payload, included); }
|
231 | }
|
232 |
|
233 | return typeof id !== 'undefined' ? { type: type, id: id } : null;
|
234 | };
|
235 |
|
236 | this.serializeNested = function (dest, current, attribute, opts) {
|
237 | var that = this;
|
238 |
|
239 | var embeds = [];
|
240 | var attributes = [];
|
241 |
|
242 | if (opts && opts.attributes) {
|
243 | embeds = opts.attributes.filter(function (attr) {
|
244 | return opts[attr];
|
245 | });
|
246 |
|
247 | attributes = opts.attributes.filter(function (attr) {
|
248 | return !opts[attr];
|
249 | });
|
250 | } else {
|
251 | attributes = _keys(dest);
|
252 | }
|
253 |
|
254 | var ret = {};
|
255 | if (attributes) { ret.attributes = pick(dest, attributes); }
|
256 |
|
257 | embeds.forEach(function (embed) {
|
258 | if (isComplexType(dest[embed])) {
|
259 | that.serialize(ret, dest, embed, opts[embed]);
|
260 | }
|
261 | });
|
262 |
|
263 | return ret.attributes;
|
264 | };
|
265 |
|
266 | this.perform = function () {
|
267 | var that = this;
|
268 |
|
269 | if( record === null ){
|
270 | return null;
|
271 | }
|
272 |
|
273 |
|
274 | if (opts && opts.transform) {
|
275 | record = opts.transform(record);
|
276 | }
|
277 |
|
278 |
|
279 | var data = { type: getType(collectionName, record) };
|
280 | if (!_isNil(record[getId()])) { data.id = String(record[getId()]); }
|
281 |
|
282 |
|
283 | if (opts.dataLinks) {
|
284 | data.links = getLinks(record, opts.dataLinks);
|
285 | }
|
286 |
|
287 |
|
288 | if (opts.dataMeta) {
|
289 | data.meta = getMeta(record, opts.dataMeta);
|
290 | }
|
291 |
|
292 | _each(opts.attributes, function (attribute) {
|
293 | var splittedAttributes = attribute.split(':');
|
294 |
|
295 | if (opts[attribute] && !record[attribute] && opts[attribute].nullIfMissing) {
|
296 | record[attribute] = null;
|
297 | }
|
298 |
|
299 | if (splittedAttributes[0] in record) {
|
300 | if (!data.attributes) { data.attributes = {}; }
|
301 |
|
302 | var attributeMap = attribute;
|
303 | if (splittedAttributes.length > 1) {
|
304 | attribute = splittedAttributes[0];
|
305 | attributeMap = splittedAttributes[1];
|
306 | }
|
307 |
|
308 | that.serialize(data, record, attribute, opts[attributeMap]);
|
309 | }
|
310 | });
|
311 |
|
312 | return data;
|
313 | };
|
314 | };
|