# All files / server-old/src/config parse.js

92.75% Statements 64/69
90.72% Branches 88/97
100% Functions 3/3
92.42% Lines 61/66

Press n or j to go to the next uncovered block, b, p or k for the previous block.

    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

    27x
    27x

    27x

    1212x

    1212x
    28x

    1184x
    10x
    1x

    9x

    1183x

    7833x

    7833x

    7171x

    7171x

    7171x
    6921x
    250x
    1x

    7170x
    6955x
    215x

    7170x
    6955x

    7170x

    28680x
    7170x
    2896x

    7170x
    106x

    106x

    7170x
    356x
    248x

    356x
    356x
    123x

    7170x
    234x

    7170x
    2x
    1x

    7169x
    232x
    2x

    7167x

    1878x

    2553x

    1878x
    2x

    7165x
    4x
    4x
    3x

    7162x
    2891x

    1174x
    7814x
    7814x
    2x

    1174x

     // parse.js
    // Reads an schema and retrieves the proper options from it

    // Errors specifics to this submodule
    const OptionsError = require('./errors');
    const path = require('path');

    // The main function.
    // - arg: user options from the argument in the main server() function
    // - env: the environment variables as read from env.js
    // - parent: if it's a submodule, the global configuration
    const parse = module.exports = async (schema, arg = {}, env= {}, parent = {}) => {

      // Fully parsed options will be stored here
      const options = {};

      // For plugins, accept "false" as an option to nuke a full plugin
      if (arg === false && parent) {
        return false;
      }

      // Accepts a single option instead of an object and it will be mapped to its
      // root value. Example: server(2000) === server({ port: 2000 })
      if (typeof arg !== 'object') {
        if (!schema.__root) {
          throw new OptionsError('notobject');
        }
        arg = { [schema.__root]: arg };
      }

      // Loop each of the defined options
      for (let name in schema) {

        // RETRIEVAL
        // Make the definition local so it's easier to handle
        const def = schema[name];
        let value;

        // Skip the control variables such as '__root'
        if (/^__/.test(name)) continue;

        // Make sure we are dealing with a valid schema definition for this option
        Iif (typeof def !== 'object') {
          throw new Error('Invalid option definition: ' + JSON.stringify(def));
        }

        // The user defined a function to find the actual value manually
        Iif (def.find) {
          value = await def.find({ arg, env, def, parent, schema });
        } else {

          // Use the user-passed option unles explictly told not to
          if (def.arg !== false) {
            def.arg = def.arg === true ? name : def.arg || name;
          } else if (arg[name] && process.env.NODE_ENV === 'test') {
            throw new OptionsError('noarg', { name });
          }

          // Use the environment variable unless explicitly told not to
          if (def.env !== false) {
            def.env = (def.env === true ? name : def.env || name).toUpperCase();
          } else Iif (env[name.toUpperCase()] && process.env.NODE_ENV === 'test') {
            if (!/^win/.test(process.platform) || name !== 'public') {
              throw new OptionsError('noenv', { name });
            }
          }

          // Make sure to use the name if we are inheriting with true
          if (def.env !== false) {
            def.inherit = (def.inherit === true ? name : def.inherit || name);
          }

          // List of possibilities, from HIGHER preference to LOWER preference
          // Removes the empty one and gets the first one as it has HIGHER preference
          const possible = [
            env[def.env],
            arg[def.arg],
            parent[def.inherit],
            def.default
          ].filter(value => typeof value !== 'undefined');
          if (possible.length) {
            value = possible[0];
          }
        }

        // Extend the base object or user object with new values if these are not set
        if (def.extend && (typeof value === 'undefined' || typeof value === 'object')) {
          Iif (typeof value === 'undefined') {
            value = {};
          }
          value = Object.assign({}, def.default, value);
        }

        // Normalize the "public" folder or file
        if ((def.file || def.folder) && typeof value === 'string') {
          if (!path.isAbsolute(value)) {
            value = path.join(process.cwd(), value);
          }
          value = path.normalize(value);
          if (def.folder && value[value.length - 1] !== path.sep) {
            value = value + path.sep;
          }
        }

        // A final hook for the schema to call up on the value
        if (def.clean) {
          value = await def.clean(value, { arg, env, parent, def, schema });
        }

        // VALIDATION
        // Validate that it is set
        if (def.required) {
          if (typeof value === 'undefined') {
            throw new OptionsError('required', { name });
          }
          // TODO: check that the file and folder exist
        }

        if (def.enum) {
          if (!def.enum.includes(value)) {
            throw new OptionsError('enum', { name, value, possible: def.enum });
          }
        }

        // Validate the type (only if there's a value)
        if (def.type && value) {

          // Parse valid types into a simple array of strings: ['string', 'number']
          def.type = (def.type instanceof Array ? def.type : [def.type])
            // pulls up the name for primitives such as String, Number, etc
            .map(one => (one.name ? one.name : one).toLowerCase());

          // Make sure it is one of the valid types
          if (!def.type.includes(typeof value)) {
            throw new OptionsError('type', {
              name, expected: def.type, value
            });
          }
        }

        if (def.validate) {
          let ret = def.validate(value, def, options);
          if (ret instanceof Error) throw ret;
          if (!ret) throw new OptionsError('validate', { name, value });
        }

        if (typeof value !== 'undefined') {
          options[name] = value;
        }
      }

      // If the property 'options' exists handle it recursively
      for (let name in schema) {
        const def = schema[name];
        if (def.options) {
          options[name] = await parse(def.options, arg[name], env, options);
        }
      }

      return options;
    };