Config.js 6.1 KB

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