jxp 2.14.6 → 2.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/libs/datamunging.js +97 -80
- package/libs/docs.js +66 -10
- package/libs/jxp.js +166 -68
- package/libs/schema_description.js +66 -19
- package/models/test_model.js +13 -12
- package/package.json +14 -14
- package/templates/model.pug +24 -5
- package/test/test.js +531 -440
package/libs/datamunging.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
var _ = require("underscore");
|
|
2
2
|
|
|
3
|
-
var input1 = {
|
|
3
|
+
var input1 = {
|
|
4
4
|
due_date: '2016-02-01T00:00:00Z',
|
|
5
5
|
from_document: '',
|
|
6
6
|
allow_online_payment: 'false',
|
|
@@ -41,45 +41,51 @@ var input1 = {
|
|
|
41
41
|
id: '93490358',
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
var test1 = {
|
|
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
|
-
|
|
44
|
+
var test1 = {
|
|
45
|
+
due_date: '2016-02-01T00:00:00Z',
|
|
46
|
+
from_document: '',
|
|
47
|
+
allow_online_payment: 'false',
|
|
48
|
+
paid: 'true',
|
|
49
|
+
locked: 'false',
|
|
50
|
+
customer_id: '2446859',
|
|
51
|
+
modified: '2016-02-02T15:09:06.983',
|
|
52
|
+
created: '2016-02-01T21:09:37.873',
|
|
53
|
+
date: '2016-02-01T00:00:00Z',
|
|
54
|
+
inclusive: 'false',
|
|
55
|
+
discount_percentage: '0',
|
|
56
|
+
tax_reference: '',
|
|
57
|
+
discount: '0',
|
|
58
|
+
amount_due: '0',
|
|
59
|
+
printed: 'false',
|
|
60
|
+
editable: 'true',
|
|
61
|
+
has_attachments: 'false',
|
|
62
|
+
has_notes: 'false',
|
|
63
|
+
has_anticipated_date: 'false',
|
|
64
|
+
lines:
|
|
65
|
+
[{
|
|
66
|
+
SelectionId: '6824935',
|
|
67
|
+
TaxTypeId: '965302',
|
|
68
|
+
ID: '74465675',
|
|
69
|
+
LineType: '0',
|
|
70
|
+
Quantity: '1',
|
|
71
|
+
Unit: '',
|
|
72
|
+
TaxPercentage: '0.14',
|
|
73
|
+
DiscountPercentage: '0',
|
|
74
|
+
Discount: '0'
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
ID: '74465676',
|
|
78
|
+
LineType: '0',
|
|
79
|
+
Quantity: '4',
|
|
80
|
+
Unit: '',
|
|
81
|
+
TaxPercentage: '0.14',
|
|
82
|
+
DiscountPercentage: '0',
|
|
83
|
+
Discount: '0',
|
|
84
|
+
Comments: ''
|
|
85
|
+
}],
|
|
86
|
+
location: '547d506b184d110c0601314c',
|
|
87
|
+
id: '93490358'
|
|
88
|
+
}
|
|
83
89
|
|
|
84
90
|
input2 = {
|
|
85
91
|
'test[0][Yo]': "0Yo",
|
|
@@ -161,39 +167,45 @@ var allMatch = /^[a-zA-Z\d_\-]+|\[[a-zA-Z\d_\-][a-zA-Z_\-\d]*\]|\[\d+\]/g;
|
|
|
161
167
|
|
|
162
168
|
// Based conceptually on the _.extend() function in underscore.js ( see http://documentcloud.github.com/underscore/#extend for more details )
|
|
163
169
|
|
|
164
|
-
function deepExtend(obj) {
|
|
165
|
-
|
|
166
|
-
|
|
170
|
+
function deepExtend(obj, depth = 0) {
|
|
171
|
+
const MAX_DEPTH = 20; // Prevent infinite recursion
|
|
172
|
+
if (depth > MAX_DEPTH) {
|
|
173
|
+
console.warn('Maximum depth exceeded in deepExtend');
|
|
174
|
+
return obj;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
var parentRE = /#{\s*?_\s*?}/,
|
|
178
|
+
slice = Array.prototype.slice;
|
|
167
179
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
180
|
+
_.each(slice.call(arguments, 1), function (source) {
|
|
181
|
+
for (var prop in source) {
|
|
182
|
+
if (_.isUndefined(obj[prop]) || _.isFunction(obj[prop]) || _.isNull(source[prop]) || _.isDate(source[prop])) {
|
|
183
|
+
obj[prop] = source[prop];
|
|
184
|
+
}
|
|
185
|
+
else if (_.isString(source[prop]) && parentRE.test(source[prop])) {
|
|
186
|
+
if (_.isString(obj[prop])) {
|
|
187
|
+
obj[prop] = source[prop].replace(parentRE, obj[prop]);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
else if (_.isArray(obj[prop]) || _.isArray(source[prop])) {
|
|
191
|
+
if (!_.isArray(obj[prop]) || !_.isArray(source[prop])) {
|
|
192
|
+
throw new Error('Trying to combine an array with a non-array (' + prop + ')');
|
|
193
|
+
} else {
|
|
194
|
+
obj[prop] = _.reject(_.deepExtend(_.clone(obj[prop]), source[prop], depth + 1), function (item) { return _.isNull(item); });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
else if (_.isObject(obj[prop]) || _.isObject(source[prop])) {
|
|
198
|
+
if (!_.isObject(obj[prop]) || !_.isObject(source[prop])) {
|
|
199
|
+
throw new Error('Trying to combine an object with a non-object (' + prop + ')');
|
|
200
|
+
} else {
|
|
201
|
+
obj[prop] = _.deepExtend(_.clone(obj[prop]), source[prop], depth + 1);
|
|
202
|
+
}
|
|
203
|
+
} else {
|
|
204
|
+
obj[prop] = source[prop];
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
return obj;
|
|
197
209
|
};
|
|
198
210
|
|
|
199
211
|
_.mixin({ 'deepExtend': deepExtend });
|
|
@@ -259,7 +271,13 @@ function isArray(a) {
|
|
|
259
271
|
return Array.isArray(a);
|
|
260
272
|
}
|
|
261
273
|
|
|
262
|
-
var assignPropVal = function(parts, result, val) {
|
|
274
|
+
var assignPropVal = function (parts, result, val, depth = 0) {
|
|
275
|
+
const MAX_DEPTH = 20; // Prevent infinite recursion
|
|
276
|
+
if (depth > MAX_DEPTH) {
|
|
277
|
+
console.warn('Maximum depth exceeded in assignPropVal');
|
|
278
|
+
return result;
|
|
279
|
+
}
|
|
280
|
+
|
|
263
281
|
var part = parts.shift().replace(/\[|\]/g, "");
|
|
264
282
|
if (parts.length) {
|
|
265
283
|
if (isNumeric(parts[0].replace(/\[|\]/g, ""))) {
|
|
@@ -267,14 +285,14 @@ var assignPropVal = function(parts, result, val) {
|
|
|
267
285
|
} else {
|
|
268
286
|
result[part] = {};
|
|
269
287
|
}
|
|
270
|
-
assignPropVal(parts, result[part], val);
|
|
288
|
+
assignPropVal(parts, result[part], val, depth + 1);
|
|
271
289
|
} else {
|
|
272
290
|
result[part] = val;
|
|
273
291
|
}
|
|
274
292
|
return result;
|
|
275
293
|
}
|
|
276
294
|
|
|
277
|
-
var deserialize = function(input) {
|
|
295
|
+
var deserialize = function (input) {
|
|
278
296
|
// console.log("INPUT", input);
|
|
279
297
|
var result = {};
|
|
280
298
|
var newobj = {};
|
|
@@ -283,20 +301,19 @@ var deserialize = function(input) {
|
|
|
283
301
|
// console.log(prop, parts);
|
|
284
302
|
var val = input[prop];
|
|
285
303
|
if (parts) {
|
|
286
|
-
var tmp = assignPropVal(parts, result, val);
|
|
287
|
-
newobj = _.deepExtend(newobj, tmp);
|
|
304
|
+
var tmp = assignPropVal(parts, result, val, 0);
|
|
305
|
+
newobj = _.deepExtend(newobj, tmp, 0);
|
|
288
306
|
} else {
|
|
289
307
|
// console.log("NOT ARRAY", parts);
|
|
290
308
|
newobj[prop] = input[prop];
|
|
291
309
|
}
|
|
292
|
-
|
|
293
310
|
}
|
|
294
311
|
// input = newobj;
|
|
295
312
|
// console.log("Munge!", newobj);
|
|
296
313
|
return newobj;
|
|
297
314
|
}
|
|
298
315
|
|
|
299
|
-
var runTest = function(name, input, test) {
|
|
316
|
+
var runTest = function (name, input, test) {
|
|
300
317
|
var result = deserialize(input);
|
|
301
318
|
if (_.isEqual(result, test)) {
|
|
302
319
|
console.log("PASSED " + name);
|
|
@@ -306,7 +323,7 @@ var runTest = function(name, input, test) {
|
|
|
306
323
|
}
|
|
307
324
|
}
|
|
308
325
|
|
|
309
|
-
var test = function() {
|
|
326
|
+
var test = function () {
|
|
310
327
|
runTest("Test 1", input1, test1);
|
|
311
328
|
runTest("Test 2", input2, test2);
|
|
312
329
|
runTest("Test 3", input3, test3);
|
package/libs/docs.js
CHANGED
|
@@ -8,6 +8,50 @@ const readFile = util.promisify(fs.readFile);
|
|
|
8
8
|
const schema_description = require("./schema_description");
|
|
9
9
|
const errors = require("restify-errors");
|
|
10
10
|
|
|
11
|
+
// Helper function to safely serialize schema fields
|
|
12
|
+
const serializeField = (field) => {
|
|
13
|
+
const safeField = {
|
|
14
|
+
path: field.path,
|
|
15
|
+
instance: field.instance,
|
|
16
|
+
options: { ...field.options },
|
|
17
|
+
validators: field.validators?.map(v => ({
|
|
18
|
+
type: v.type?.name,
|
|
19
|
+
message: v.message
|
|
20
|
+
})),
|
|
21
|
+
isRequired: field.isRequired
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Handle default value
|
|
25
|
+
if (typeof field.defaultValue === 'function') {
|
|
26
|
+
safeField.defaultValue = '[Function]';
|
|
27
|
+
} else if (field.defaultValue === undefined && field.options.default !== undefined) {
|
|
28
|
+
if (typeof field.options.default === 'function') {
|
|
29
|
+
safeField.defaultValue = '[Function]';
|
|
30
|
+
} else {
|
|
31
|
+
safeField.defaultValue = field.options.default;
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
34
|
+
safeField.defaultValue = field.defaultValue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Handle special cases for different field types
|
|
38
|
+
if (field.instance === 'Array' && field.caster) {
|
|
39
|
+
safeField.arrayType = field.caster.instance;
|
|
40
|
+
// Handle array defaults specially
|
|
41
|
+
if (Array.isArray(safeField.defaultValue)) {
|
|
42
|
+
safeField.defaultValue = JSON.stringify(safeField.defaultValue);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (field.instance === 'Embedded' || field.instance === 'DocumentArray') {
|
|
46
|
+
safeField.schema = Object.keys(field.schema.paths).map(p => ({
|
|
47
|
+
path: p,
|
|
48
|
+
type: field.schema.paths[p].instance
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return safeField;
|
|
53
|
+
};
|
|
54
|
+
|
|
11
55
|
class Docs {
|
|
12
56
|
constructor(opts) {
|
|
13
57
|
this.config = Object.assign({}, opts.config);
|
|
@@ -16,7 +60,7 @@ class Docs {
|
|
|
16
60
|
this.package = require(path.join(process.cwd(), "package.json"));
|
|
17
61
|
}
|
|
18
62
|
|
|
19
|
-
renderTemplate(res, template_file, data={}) {
|
|
63
|
+
renderTemplate(res, template_file, data = {}) {
|
|
20
64
|
try {
|
|
21
65
|
const template = pug.compileFile(path.join(__dirname, `../templates/${template_file}.pug`));
|
|
22
66
|
data.title = data.title || `${this.package.name} API Documentation`;
|
|
@@ -28,10 +72,10 @@ class Docs {
|
|
|
28
72
|
res.writeHead(200, {
|
|
29
73
|
'Content-Length': Buffer.byteLength(body),
|
|
30
74
|
'Content-Type': 'text/html'
|
|
31
|
-
|
|
75
|
+
});
|
|
32
76
|
res.write(body);
|
|
33
77
|
res.end();
|
|
34
|
-
} catch(err) {
|
|
78
|
+
} catch (err) {
|
|
35
79
|
console.error(err);
|
|
36
80
|
return new errors.InternalServerError(err.toString());
|
|
37
81
|
}
|
|
@@ -64,7 +108,7 @@ class Docs {
|
|
|
64
108
|
try {
|
|
65
109
|
res.send(this.models);
|
|
66
110
|
next();
|
|
67
|
-
} catch(err) {
|
|
111
|
+
} catch (err) {
|
|
68
112
|
return new errors.InternalServerError(err.toString());
|
|
69
113
|
}
|
|
70
114
|
}
|
|
@@ -73,7 +117,7 @@ class Docs {
|
|
|
73
117
|
try {
|
|
74
118
|
this.renderTemplate(res, "index", {});
|
|
75
119
|
next();
|
|
76
|
-
} catch(err) {
|
|
120
|
+
} catch (err) {
|
|
77
121
|
return new errors.InternalServerError(err.toString());
|
|
78
122
|
}
|
|
79
123
|
}
|
|
@@ -83,7 +127,7 @@ class Docs {
|
|
|
83
127
|
const body = await readFile(path.join(__dirname, `../docs`, req.params.md_doc));
|
|
84
128
|
const md_contents = md.render(body.toString()).body;
|
|
85
129
|
this.renderTemplate(res, "md", { md_contents });
|
|
86
|
-
} catch(err) {
|
|
130
|
+
} catch (err) {
|
|
87
131
|
return new errors.InternalServerError(err.toString());
|
|
88
132
|
}
|
|
89
133
|
}
|
|
@@ -91,13 +135,25 @@ class Docs {
|
|
|
91
135
|
model(req, res, next) {
|
|
92
136
|
try {
|
|
93
137
|
const model = this.models[req.params.modelname];
|
|
94
|
-
|
|
95
|
-
const fields = Object.keys(model.schema.paths);
|
|
138
|
+
const fields = Object.keys(model.schema.paths);
|
|
96
139
|
fields.sort();
|
|
97
140
|
const perms = model.schema.opts.perms;
|
|
98
|
-
|
|
141
|
+
|
|
142
|
+
// Prepare safe field data for template
|
|
143
|
+
const safeFields = {};
|
|
144
|
+
fields.forEach(fieldName => {
|
|
145
|
+
safeFields[fieldName] = serializeField(model.schema.paths[fieldName]);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
this.renderTemplate(res, "model", {
|
|
149
|
+
model,
|
|
150
|
+
fields,
|
|
151
|
+
perms,
|
|
152
|
+
safeFields
|
|
153
|
+
});
|
|
99
154
|
next();
|
|
100
|
-
} catch(err) {
|
|
155
|
+
} catch (err) {
|
|
156
|
+
console.error(err);
|
|
101
157
|
return new errors.InternalServerError(err.toString());
|
|
102
158
|
}
|
|
103
159
|
}
|