summaryrefslogtreecommitdiffstats
path: root/toolkit/jetpack/sdk/deprecated/api-utils.js
blob: 856fc50cb602ab421cca82bcae58373a43096735 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
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
197
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "deprecated"
};

const { merge } = require("../util/object");
const { union } = require("../util/array");
const { isNil, isRegExp } = require("../lang/type");

// The possible return values of getTypeOf.
const VALID_TYPES = [
  "array",
  "boolean",
  "function",
  "null",
  "number",
  "object",
  "string",
  "undefined",
  "regexp"
];

const { isArray } = Array;

/**
 * Returns a validated options dictionary given some requirements.  If any of
 * the requirements are not met, an exception is thrown.
 *
 * @param  options
 *         An object, the options dictionary to validate.  It's not modified.
 *         If it's null or otherwise falsey, an empty object is assumed.
 * @param  requirements
 *         An object whose keys are the expected keys in options.  Any key in
 *         options that is not present in requirements is ignored.  Each value
 *         in requirements is itself an object describing the requirements of
 *         its key.  There are four optional keys in this object:
 *           map: A function that's passed the value of the key in options.
 *                map's return value is taken as the key's value in the final
 *                validated options, is, and ok.  If map throws an exception
 *                it's caught and discarded, and the key's value is its value in
 *                options.
 *           is:  An array containing any number of the typeof type names.  If
 *                the key's value is none of these types, it fails validation.
 *                Arrays, null and regexps are identified by the special type names
 *                "array", "null", "regexp"; "object" will not match either.  No type
 *                coercion is done.
 *           ok:  A function that's passed the key's value.  If it returns
 *                false, the value fails validation.
 *           msg: If the key's value fails validation, an exception is thrown.
 *                This string will be used as its message.  If undefined, a
 *                generic message is used, unless is is defined, in which case
 *                the message will state that the value needs to be one of the
 *                given types.
 * @return An object whose keys are those keys in requirements that are also in
 *         options and whose values are the corresponding return values of map
 *         or the corresponding values in options.  Note that any keys not
 *         shared by both requirements and options are not in the returned
 *         object.
 */
exports.validateOptions = function validateOptions(options, requirements) {
  options = options || {};
  let validatedOptions = {};

  for (let key in requirements) {
    let isOptional = false;
    let mapThrew = false;
    let req = requirements[key];
    let [optsVal, keyInOpts] = (key in options) ?
                               [options[key], true] :
                               [undefined, false];
    if (req.map) {
      try {
        optsVal = req.map(optsVal);
      }
      catch (err) {
        if (err instanceof RequirementError)
          throw err;

        mapThrew = true;
      }
    }
    if (req.is) {
      let types = req.is;

      if (!isArray(types) && isArray(types.is))
        types = types.is;

      if (isArray(types)) {
        isOptional = ['undefined', 'null'].every(v => ~types.indexOf(v));

        // Sanity check the caller's type names.
        types.forEach(function (typ) {
          if (VALID_TYPES.indexOf(typ) < 0) {
            let msg = 'Internal error: invalid requirement type "' + typ + '".';
            throw new Error(msg);
          }
        });
        if (types.indexOf(getTypeOf(optsVal)) < 0)
          throw new RequirementError(key, req);
      }
    }

    if (req.ok && ((!isOptional || !isNil(optsVal)) && !req.ok(optsVal)))
      throw new RequirementError(key, req);

    if (keyInOpts || (req.map && !mapThrew && optsVal !== undefined))
      validatedOptions[key] = optsVal;
  }

  return validatedOptions;
};

exports.addIterator = function addIterator(obj, keysValsGenerator) {
  obj.__iterator__ = function(keysOnly, keysVals) {
    let keysValsIterator = keysValsGenerator.call(this);

    // "for (.. in ..)" gets only keys, "for each (.. in ..)" gets values,
    // and "for (.. in Iterator(..))" gets [key, value] pairs.
    let index = keysOnly ? 0 : 1;
    while (true)
      yield keysVals ? keysValsIterator.next() : keysValsIterator.next()[index];
  };
};

// Similar to typeof, except arrays, null and regexps are identified by "array" and
// "null" and "regexp", not "object".
var getTypeOf = exports.getTypeOf = function getTypeOf(val) {
  let typ = typeof(val);
  if (typ === "object") {
    if (!val)
      return "null";
    if (isArray(val))
      return "array";
    if (isRegExp(val))
      return "regexp";
  }
  return typ;
}

function RequirementError(key, requirement) {
  Error.call(this);

  this.name = "RequirementError";

  let msg = requirement.msg;
  if (!msg) {
    msg = 'The option "' + key + '" ';
    msg += requirement.is ?
           "must be one of the following types: " + requirement.is.join(", ") :
           "is invalid.";
  }

  this.message = msg;
}
RequirementError.prototype = Object.create(Error.prototype);

var string = { is: ['string', 'undefined', 'null'] };
exports.string = string;

var number = { is: ['number', 'undefined', 'null'] };
exports.number = number;

var boolean = { is: ['boolean', 'undefined', 'null'] };
exports.boolean = boolean;

var object = { is: ['object', 'undefined', 'null'] };
exports.object = object;

var array = { is: ['array', 'undefined', 'null'] };
exports.array = array;

var isTruthyType = type => !(type === 'undefined' || type === 'null');
var findTypes = v => { while (!isArray(v) && v.is) v = v.is; return v };

function required(req) {
  let types = (findTypes(req) || VALID_TYPES).filter(isTruthyType);

  return merge({}, req, {is: types});
}
exports.required = required;

function optional(req) {
  req = merge({is: []}, req);
  req.is = findTypes(req).filter(isTruthyType).concat('undefined', 'null');

  return req;
}
exports.optional = optional;

function either(...types) {
  return union.apply(null, types.map(findTypes));
}
exports.either = either;