UNPKG

6.24 kBJavaScriptView Raw
1/**
2 * Copyright 2013-2022 the PM2 project authors. All rights reserved.
3 * Use of this source code is governed by a license that
4 * can be found in the LICENSE file.
5 */
6var util = require('util');
7
8/**
9 * Validator of configured file / commander options.
10 */
11var Config = module.exports = {
12 _errMsgs: {
13 'require': '"%s" is required',
14 'type' : 'Expect "%s" to be a typeof %s, but now is %s',
15 'regex' : 'Verify "%s" with regex failed, %s',
16 'max' : 'The maximum of "%s" is %s, but now is %s',
17 'min' : 'The minimum of "%s" is %s, but now is %s'
18 },
19 /**
20 * Schema definition.
21 * @returns {exports|*}
22 */
23 get schema(){
24 // Cache.
25 if (this._schema) {
26 return this._schema;
27 }
28 // Render aliases.
29 this._schema = require('../API/schema');
30 for (var k in this._schema) {
31 if (k.indexOf('\\') > 0) {
32 continue;
33 }
34 var aliases = [
35 k.split('_').map(function(n, i){
36 if (i != 0 && n && n.length > 1) {
37 return n[0].toUpperCase() + n.slice(1);
38 }
39 return n;
40 }).join('')
41 ];
42
43 if (this._schema[k].alias && Array.isArray(this._schema[k].alias)) {
44 // If multiple aliases, merge
45 this._schema[k].alias.forEach(function(alias) {
46 aliases.splice(0, 0, alias);
47 });
48 }
49 else if (this._schema[k].alias)
50 aliases.splice(0, 0, this._schema[k].alias);
51
52 this._schema[k].alias = aliases;
53 }
54 return this._schema;
55 }
56};
57
58/**
59 * Filter / Alias options
60 */
61Config.filterOptions = function(cmd) {
62 var conf = {};
63 var schema = this.schema;
64
65 for (var key in schema) {
66 var aliases = schema[key].alias;
67 aliases && aliases.forEach(function(alias){
68 if (typeof(cmd[alias]) !== 'undefined') {
69 conf[key] || (conf[key] = cmd[alias]);
70 }
71 });
72 }
73
74 return conf;
75};
76
77/**
78 * Verify JSON configurations.
79 * @param {Object} json
80 * @returns {{errors: Array, config: {}}}
81 */
82Config.validateJSON = function(json){
83 // clone config
84 var conf = Object.assign({}, json),
85 res = {};
86 this._errors = [];
87
88 var regexKeys = {}, defines = this.schema;
89
90 for (var sk in defines) {
91 // Pick up RegExp keys.
92 if (sk.indexOf('\\') >= 0) {
93 regexKeys[sk] = false;
94 continue;
95 }
96
97 var aliases = defines[sk].alias;
98
99 aliases && aliases.forEach(function(alias){
100 conf[sk] || (conf[sk] = json[alias]);
101 })
102
103 var val = conf[sk];
104 delete conf[sk];
105
106 // Validate key-value pairs.
107 if (val === undefined ||
108 val === null ||
109 ((val = this._valid(sk, val)) === null)) {
110
111 // If value is not defined
112 // Set default value (via schema.json)
113 if (typeof(defines[sk].default) !== 'undefined')
114 res[sk] = defines[sk].default;
115 continue;
116 }
117 //console.log(sk, val, val === null, val === undefined);
118 res[sk] = val;
119 }
120
121 // Validate RegExp values.
122 var hasRegexKey = false;
123 for (var k in regexKeys) {
124 hasRegexKey = true;
125 regexKeys[k] = new RegExp(k);
126 }
127 if (hasRegexKey) {
128 for (var k in conf) {
129 for (var rk in regexKeys) {
130 if (regexKeys[rk].test(k))
131 if (this._valid(k, conf[k], defines[rk])) {
132 res[k] = conf[k];
133 delete conf[k];
134 }
135 }
136 }
137 }
138
139 return {errors: this._errors, config: res};
140};
141
142/**
143 * Validate key-value pairs by specific schema
144 * @param {String} key
145 * @param {Mixed} value
146 * @param {Object} sch
147 * @returns {*}
148 * @private
149 */
150Config._valid = function(key, value, sch){
151 var sch = sch || this.schema[key],
152 scht = typeof sch.type == 'string' ? [sch.type] : sch.type;
153
154 // Required value.
155 var undef = typeof value == 'undefined';
156 if(this._error(sch.require && undef, 'require', key)){
157 return null;
158 }
159
160 // If undefined, make a break.
161 if (undef) {
162 return null;
163 }
164
165 // Wrap schema types.
166 scht = scht.map(function(t){
167 return '[object ' + t[0].toUpperCase() + t.slice(1) + ']'
168 });
169
170 // Typeof value.
171 var type = Object.prototype.toString.call(value), nt = '[object Number]';
172
173 // Auto parse Number
174 if (type != '[object Boolean]' && scht.indexOf(nt) >= 0 && !isNaN(value)) {
175 value = parseFloat(value);
176 type = nt;
177 }
178
179 // Verify types.
180 if (this._error(!~scht.indexOf(type), 'type', key, scht.join(' / '), type)) {
181 return null;
182 }
183
184 // Verify RegExp if exists.
185 if (this._error(type == '[object String]' && sch.regex && !(new RegExp(sch.regex)).test(value),
186 'regex', key, sch.desc || ('should match ' + sch.regex))) {
187 return null;
188 }
189
190 // Verify maximum / minimum of Number value.
191 if (type == '[object Number]') {
192 if (this._error(typeof sch.max != 'undefined' && value > sch.max, 'max', key, sch.max, value)) {
193 return null;
194 }
195 if (this._error(typeof sch.min != 'undefined' && value < sch.min, 'min', key, sch.min, value)) {
196 return null;
197 }
198 }
199
200 // If first type is Array, but current is String, try to split them.
201 if(scht.length > 1 && type != scht[0] && type == '[object String]'){
202 if(scht[0] == '[object Array]') {
203 // unfortunately, js does not support lookahead RegExp (/(?<!\\)\s+/) now (until next ver).
204 value = value.split(/([\w\-]+\="[^"]*")|([\w\-]+\='[^']*')|"([^"]*)"|'([^']*)'|\s/)
205 .filter(function(v){
206 return v && v.trim();
207 });
208 }
209 }
210
211 // Custom types: sbyte && stime.
212 if(sch.ext_type && type == '[object String]' && value.length >= 2) {
213 var seed = {
214 'sbyte': {
215 'G': 1024 * 1024 * 1024,
216 'M': 1024 * 1024,
217 'K': 1024
218 },
219 'stime': {
220 'h': 60 * 60 * 1000,
221 'm': 60 * 1000,
222 's': 1000
223 }
224 }[sch.ext_type];
225
226 if(seed){
227 value = parseFloat(value.slice(0, -1)) * (seed[value.slice(-1)]);
228 }
229 }
230 return value;
231};
232
233/**
234 * Wrap errors.
235 * @param {Boolean} possible A value indicates whether it is an error or not.
236 * @param {String} type
237 * @returns {*}
238 * @private
239 */
240Config._error = function(possible, type){
241 if (possible) {
242 var args = Array.prototype.slice.call(arguments);
243 args.splice(0, 2, this._errMsgs[type]);
244 this._errors && this._errors.push(util.format.apply(null, args));
245 }
246 return possible;
247}