'use strict';

// Adapted from the following sources
// - https://github.com/jsdom/webidl-conversions
//   Copyright Domenic Denicola. Licensed under BSD-2-Clause License.
//   Original license at https://github.com/jsdom/webidl-conversions/blob/master/LICENSE.md.
// - https://github.com/denoland/deno
//   Copyright Deno authors. Licensed under MIT License.
//   Original license at https://github.com/denoland/deno/blob/main/LICENSE.md.
// Changes include using primordials and stripping the code down to only what
// WebCryptoAPI needs.

const {
  ArrayBufferIsView,
  ArrayPrototypeIncludes,
  ArrayPrototypePush,
  ArrayPrototypeSort,
  MathPow,
  MathTrunc,
  Number,
  NumberIsFinite,
  NumberParseInt,
  ObjectPrototypeHasOwnProperty,
  ObjectPrototypeIsPrototypeOf,
  SafeArrayIterator,
  String,
  StringPrototypeStartsWith,
  StringPrototypeToLowerCase,
  TypedArrayPrototypeGetBuffer,
  TypedArrayPrototypeGetSymbolToStringTag,
} = primordials;

const {
  makeException,
  createEnumConverter,
  createSequenceConverter,
} = require('internal/webidl');

const {
  lazyDOMException,
  kEmptyObject,
  setOwnProperty,
} = require('internal/util');
const { CryptoKey } = require('internal/crypto/webcrypto');
const {
  getDataViewOrTypedArrayBuffer,
  validateMaxBufferLength,
  kNamedCurveAliases,
} = require('internal/crypto/util');
const { isArrayBuffer, isSharedArrayBuffer } = require('internal/util/types');

// https://tc39.es/ecma262/#sec-tonumber
function toNumber(value, opts = kEmptyObject) {
  switch (typeof value) {
    case 'number':
      return value;
    case 'bigint':
      throw makeException(
        'is a BigInt and cannot be converted to a number.',
        opts);
    case 'symbol':
      throw makeException(
        'is a Symbol and cannot be converted to a number.',
        opts);
    default:
      return Number(value);
  }
}

function type(V) {
  if (V === null)
    return 'Null';

  switch (typeof V) {
    case 'undefined':
      return 'Undefined';
    case 'boolean':
      return 'Boolean';
    case 'number':
      return 'Number';
    case 'string':
      return 'String';
    case 'symbol':
      return 'Symbol';
    case 'bigint':
      return 'BigInt';
    case 'object': // Fall through
    case 'function': // Fall through
    default:
      // Per ES spec, typeof returns an implementation-defined value that is not
      // any of the existing ones for uncallable non-standard exotic objects.
      // Yet Type() which the Web IDL spec depends on returns Object for such
      // cases. So treat the default case as an object.
      return 'Object';
  }
}

const integerPart = MathTrunc;

function validateByteLength(buf, name, target) {
  if (buf.byteLength !== target) {
    throw lazyDOMException(
      `${name} must contain exactly ${target} bytes`,
      'OperationError');
  }
}

function AESLengthValidator(V, dict) {
  if (V !== 128 && V !== 192 && V !== 256)
    throw lazyDOMException(
      'AES key length must be 128, 192, or 256 bits',
      'OperationError');
}

function namedCurveValidator(V, dict) {
  if (!ObjectPrototypeHasOwnProperty(kNamedCurveAliases, V))
    throw lazyDOMException(
      'Unrecognized namedCurve',
      'NotSupportedError');
}

// This was updated to only consider bitlength up to 32 used by WebCryptoAPI
function createIntegerConversion(bitLength) {
  const lowerBound = 0;
  const upperBound = MathPow(2, bitLength) - 1;

  const twoToTheBitLength = MathPow(2, bitLength);

  return (V, opts = kEmptyObject) => {
    let x = toNumber(V, opts);

    if (opts.enforceRange) {
      if (!NumberIsFinite(x)) {
        throw makeException(
          'is not a finite number.',
          opts);
      }

      x = integerPart(x);

      if (x < lowerBound || x > upperBound) {
        throw makeException(
          `is outside the expected range of ${lowerBound} to ${upperBound}.`,
          { __proto__: null, ...opts, code: 'ERR_OUT_OF_RANGE' },
        );
      }

      return x;
    }

    if (!NumberIsFinite(x) || x === 0) {
      return 0;
    }

    x = integerPart(x);

    if (x >= lowerBound && x <= upperBound) {
      return x;
    }

    x = x % twoToTheBitLength;

    return x;
  };
}

const converters = {};

converters.boolean = (val) => !!val;
converters.octet = createIntegerConversion(8);
converters['unsigned short'] = createIntegerConversion(16);
converters['unsigned long'] = createIntegerConversion(32);

converters.DOMString = function(V, opts = kEmptyObject) {
  if (typeof V === 'string') {
    return V;
  } else if (typeof V === 'symbol') {
    throw makeException(
      'is a Symbol and cannot be converted to a string.',
      opts);
  }

  return String(V);
};

converters.object = (V, opts) => {
  if (type(V) !== 'Object') {
    throw makeException(
      'is not an object.',
      opts);
  }

  return V;
};

const isNonSharedArrayBuffer = isArrayBuffer;

function ensureSHA(V, label) {
  if (
    typeof V === 'string' ?
      !StringPrototypeStartsWith(StringPrototypeToLowerCase(V), 'sha') :
      V.name?.toLowerCase?.().startsWith('sha') === false
  )
    throw lazyDOMException(
      `Only SHA hashes are supported in ${label}`, 'NotSupportedError');
}

converters.Uint8Array = (V, opts = kEmptyObject) => {
  if (!ArrayBufferIsView(V) ||
    TypedArrayPrototypeGetSymbolToStringTag(V) !== 'Uint8Array') {
    throw makeException(
      'is not an Uint8Array object.',
      opts);
  }
  if (isSharedArrayBuffer(TypedArrayPrototypeGetBuffer(V))) {
    throw makeException(
      'is a view on a SharedArrayBuffer, which is not allowed.',
      opts);
  }

  return V;
};

converters.BufferSource = (V, opts = kEmptyObject) => {
  if (ArrayBufferIsView(V)) {
    if (isSharedArrayBuffer(getDataViewOrTypedArrayBuffer(V))) {
      throw makeException(
        'is a view on a SharedArrayBuffer, which is not allowed.',
        opts);
    }

    return V;
  }

  if (!isNonSharedArrayBuffer(V)) {
    throw makeException(
      'is not instance of ArrayBuffer, Buffer, TypedArray, or DataView.',
      opts);
  }

  return V;
};

converters['sequence<DOMString>'] = createSequenceConverter(
  converters.DOMString);

function requiredArguments(length, required, opts = kEmptyObject) {
  if (length < required) {
    throw makeException(
      `${required} argument${
        required === 1 ? '' : 's'
      } required, but only ${length} present.`,
      { __proto__: null, ...opts, context: '', code: 'ERR_MISSING_ARGS' });
  }
}

function createDictionaryConverter(name, dictionaries) {
  let hasRequiredKey = false;
  const allMembers = [];
  for (let i = 0; i < dictionaries.length; i++) {
    const member = dictionaries[i];
    if (member.required) {
      hasRequiredKey = true;
    }
    ArrayPrototypePush(allMembers, member);
  }
  ArrayPrototypeSort(allMembers, (a, b) => {
    if (a.key === b.key) {
      return 0;
    }
    return a.key < b.key ? -1 : 1;
  });

  return function(V, opts = kEmptyObject) {
    const typeV = type(V);
    switch (typeV) {
      case 'Undefined':
      case 'Null':
      case 'Object':
        break;
      default:
        throw makeException(
          'can not be converted to a dictionary',
          opts);
    }
    const esDict = V;
    const idlDict = {};

    // Fast path null and undefined.
    if (V == null && !hasRequiredKey) {
      return idlDict;
    }

    for (const member of new SafeArrayIterator(allMembers)) {
      const key = member.key;

      let esMemberValue;
      if (typeV === 'Undefined' || typeV === 'Null') {
        esMemberValue = undefined;
      } else {
        esMemberValue = esDict[key];
      }

      if (esMemberValue !== undefined) {
        const context = `'${key}' of '${name}'${
          opts.context ? ` (${opts.context})` : ''
        }`;
        const idlMemberValue = member.converter(esMemberValue, {
          __proto__: null,
          ...opts,
          context,
        });
        member.validator?.(idlMemberValue, esDict);
        setOwnProperty(idlDict, key, idlMemberValue);
      } else if (member.required) {
        throw makeException(
          `can not be converted to '${name}' because '${key}' is required in '${name}'.`,
          { __proto__: null, ...opts, code: 'ERR_MISSING_OPTION' });
      }
    }

    return idlDict;
  };
}

function createInterfaceConverter(name, prototype) {
  return (V, opts) => {
    if (!ObjectPrototypeIsPrototypeOf(prototype, V)) {
      throw makeException(
        `is not of type ${name}.`,
        opts);
    }
    return V;
  };
}

converters.AlgorithmIdentifier = (V, opts) => {
  // Union for (object or DOMString)
  if (type(V) === 'Object') {
    return converters.object(V, opts);
  }
  return converters.DOMString(V, opts);
};

converters.KeyFormat = createEnumConverter('KeyFormat', [
  'raw',
  'raw-public',
  'raw-seed',
  'raw-secret',
  'raw-private',
  'pkcs8',
  'spki',
  'jwk',
]);

converters.KeyUsage = createEnumConverter('KeyUsage', [
  'encrypt',
  'decrypt',
  'sign',
  'verify',
  'deriveKey',
  'deriveBits',
  'wrapKey',
  'unwrapKey',
  'encapsulateBits',
  'decapsulateBits',
  'encapsulateKey',
  'decapsulateKey',
]);

converters['sequence<KeyUsage>'] = createSequenceConverter(converters.KeyUsage);

converters.HashAlgorithmIdentifier = converters.AlgorithmIdentifier;

const dictAlgorithm = [
  {
    key: 'name',
    converter: converters.DOMString,
    required: true,
  },
];

converters.Algorithm = createDictionaryConverter(
  'Algorithm', dictAlgorithm);

converters.BigInteger = converters.Uint8Array;

const dictRsaKeyGenParams = [
  ...new SafeArrayIterator(dictAlgorithm),
  {
    key: 'modulusLength',
    converter: (V, opts) =>
      converters['unsigned long'](V, { ...opts, enforceRange: true }),
    required: true,
  },
  {
    key: 'publicExponent',
    converter: converters.BigInteger,
    required: true,
  },
];

converters.RsaKeyGenParams = createDictionaryConverter(
  'RsaKeyGenParams', dictRsaKeyGenParams);

converters.RsaHashedKeyGenParams = createDictionaryConverter(
  'RsaHashedKeyGenParams', [
    ...new SafeArrayIterator(dictRsaKeyGenParams),
    {
      key: 'hash',
      converter: converters.HashAlgorithmIdentifier,
      validator: (V, dict) => ensureSHA(V, 'RsaHashedKeyGenParams'),
      required: true,
    },
  ]);

converters.RsaHashedImportParams = createDictionaryConverter(
  'RsaHashedImportParams', [
    ...new SafeArrayIterator(dictAlgorithm),
    {
      key: 'hash',
      converter: converters.HashAlgorithmIdentifier,
      validator: (V, dict) => ensureSHA(V, 'RsaHashedImportParams'),
      required: true,
    },
  ]);

converters.NamedCurve = converters.DOMString;

converters.EcKeyImportParams = createDictionaryConverter(
  'EcKeyImportParams', [
    ...new SafeArrayIterator(dictAlgorithm),
    {
      key: 'namedCurve',
      converter: converters.NamedCurve,
      validator: namedCurveValidator,
      required: true,
    },
  ]);

converters.EcKeyGenParams = createDictionaryConverter(
  'EcKeyGenParams', [
    ...new SafeArrayIterator(dictAlgorithm),
    {
      key: 'namedCurve',
      converter: converters.NamedCurve,
      validator: namedCurveValidator,
      required: true,
    },
  ]);

converters.AesKeyGenParams = createDictionaryConverter(
  'AesKeyGenParams', [
    ...new SafeArrayIterator(dictAlgorithm),
    {
      key: 'length',
      converter: (V, opts) =>
        converters['unsigned short'](V, { ...opts, enforceRange: true }),
      validator: AESLengthValidator,
      required: true,
    },
  ]);

function validateZeroLength(parameterName) {
  return (V, dict) => {
    if (V.byteLength) {
      throw lazyDOMException(
        `Non zero-length ${parameterName} is not supported.`, 'NotSupportedError');
    }
  };
}

converters.RsaPssParams = createDictionaryConverter(
  'RsaPssParams', [
    ...new SafeArrayIterator(dictAlgorithm),
    {
      key: 'saltLength',
      converter: (V, opts) =>
        converters['unsigned long'](V, { ...opts, enforceRange: true }),
      required: true,
    },
  ]);

converters.RsaOaepParams = createDictionaryConverter(
  'RsaOaepParams', [
    ...new SafeArrayIterator(dictAlgorithm),
    {
      key: 'label',
      converter: converters.BufferSource,
    },
  ]);

converters.EcdsaParams = createDictionaryConverter(
  'EcdsaParams', [
    ...new SafeArrayIterator(dictAlgorithm),
    {
      key: 'hash',
      converter: converters.HashAlgorithmIdentifier,
      validator: (V, dict) => ensureSHA(V, 'EcdsaParams'),
      required: true,
    },
  ]);

for (const { 0: name, 1: zeroError } of [['HmacKeyGenParams', 'OperationError'], ['HmacImportParams', 'DataError']]) {
  converters[name] = createDictionaryConverter(
    name, [
      ...new SafeArrayIterator(dictAlgorithm),
      {
        key: 'hash',
        converter: converters.HashAlgorithmIdentifier,
        validator: (V, dict) => ensureSHA(V, name),
        required: true,
      },
      {
        key: 'length',
        converter: (V, opts) =>
          converters['unsigned long'](V, { ...opts, enforceRange: true }),
        validator: validateMacKeyLength(`${name}.length`, zeroError),
      },
    ]);
}

const simpleDomStringKey = (key) => ({ key, converter: converters.DOMString });

converters.RsaOtherPrimesInfo = createDictionaryConverter(
  'RsaOtherPrimesInfo', [
    simpleDomStringKey('r'),
    simpleDomStringKey('d'),
    simpleDomStringKey('t'),
  ]);
converters['sequence<RsaOtherPrimesInfo>'] = createSequenceConverter(
  converters.RsaOtherPrimesInfo);

converters.JsonWebKey = createDictionaryConverter(
  'JsonWebKey', [
    simpleDomStringKey('kty'),
    simpleDomStringKey('use'),
    {
      key: 'key_ops',
      converter: converters['sequence<DOMString>'],
    },
    simpleDomStringKey('alg'),
    {
      key: 'ext',
      converter: converters.boolean,
    },
    simpleDomStringKey('crv'),
    simpleDomStringKey('x'),
    simpleDomStringKey('y'),
    simpleDomStringKey('d'),
    simpleDomStringKey('n'),
    simpleDomStringKey('e'),
    simpleDomStringKey('p'),
    simpleDomStringKey('q'),
    simpleDomStringKey('dp'),
    simpleDomStringKey('dq'),
    simpleDomStringKey('qi'),
    simpleDomStringKey('pub'),
    simpleDomStringKey('priv'),
    {
      key: 'oth',
      converter: converters['sequence<RsaOtherPrimesInfo>'],
    },
    simpleDomStringKey('k'),
  ]);

converters.HkdfParams = createDictionaryConverter(
  'HkdfParams', [
    ...new SafeArrayIterator(dictAlgorithm),
    {
      key: 'hash',
      converter: converters.HashAlgorithmIdentifier,
      validator: (V, dict) => ensureSHA(V, 'HkdfParams'),
      required: true,
    },
    {
      key: 'salt',
      converter: converters.BufferSource,
      required: true,
    },
    {
      key: 'info',
      converter: converters.BufferSource,
      required: true,
    },
  ]);

converters.CShakeParams = createDictionaryConverter(
  'CShakeParams', [
    ...new SafeArrayIterator(dictAlgorithm),
    {
      key: 'length',
      converter: (V, opts) =>
        converters['unsigned long'](V, { ...opts, enforceRange: true }),
      validator: (V, opts) => {
        // The Web Crypto spec allows for SHAKE output length that are not multiples of
        // 8. We don't.
        if (V % 8)
          throw lazyDOMException('Unsupported CShakeParams length', 'NotSupportedError');
      },
      required: true,
    },
    {
      key: 'functionName',
      converter: converters.BufferSource,
      validator: validateZeroLength('CShakeParams.functionName'),
    },
    {
      key: 'customization',
      converter: converters.BufferSource,
      validator: validateZeroLength('CShakeParams.customization'),
    },
  ]);

converters.Pbkdf2Params = createDictionaryConverter(
  'Pbkdf2Params', [
    ...new SafeArrayIterator(dictAlgorithm),
    {
      key: 'hash',
      converter: converters.HashAlgorithmIdentifier,
      validator: (V, dict) => ensureSHA(V, 'Pbkdf2Params'),
      required: true,
    },
    {
      key: 'iterations',
      converter: (V, opts) =>
        converters['unsigned long'](V, { ...opts, enforceRange: true }),
      validator: (V, dict) => {
        if (V === 0)
          throw lazyDOMException('iterations cannot be zero', 'OperationError');
      },
      required: true,
    },
    {
      key: 'salt',
      converter: converters.BufferSource,
      required: true,
    },
  ]);

converters.AesDerivedKeyParams = createDictionaryConverter(
  'AesDerivedKeyParams', [
    ...new SafeArrayIterator(dictAlgorithm),
    {
      key: 'length',
      converter: (V, opts) =>
        converters['unsigned short'](V, { ...opts, enforceRange: true }),
      validator: AESLengthValidator,
      required: true,
    },
  ]);

converters.AesCbcParams = createDictionaryConverter(
  'AesCbcParams', [
    ...new SafeArrayIterator(dictAlgorithm),
    {
      key: 'iv',
      converter: converters.BufferSource,
      validator: (V, dict) => validateByteLength(V, 'algorithm.iv', 16),
      required: true,
    },
  ]);

converters.AeadParams = createDictionaryConverter(
  'AeadParams', [
    ...new SafeArrayIterator(dictAlgorithm),
    {
      key: 'iv',
      converter: converters.BufferSource,
      validator: (V, dict) => {
        switch (StringPrototypeToLowerCase(dict.name)) {
          case 'chacha20-poly1305':
            validateByteLength(V, 'algorithm.iv', 12);
            break;
          case 'aes-gcm':
            validateMaxBufferLength(V, 'algorithm.iv');
            break;
          case 'aes-ocb':
            if (V.byteLength > 15) {
              throw lazyDOMException(
                'AES-OCB algorithm.iv must be no more than 15 bytes',
                'OperationError');
            }
            break;
        }
      },
      required: true,
    },
    {
      key: 'tagLength',
      converter: (V, opts) =>
        converters.octet(V, { ...opts, enforceRange: true }),
      validator: (V, dict) => {
        switch (StringPrototypeToLowerCase(dict.name)) {
          case 'chacha20-poly1305':
            if (V !== 128) {
              throw lazyDOMException(
                `${V} is not a valid ChaCha20-Poly1305 tag length`,
                'OperationError');
            }
            break;
          case 'aes-gcm':
            if (!ArrayPrototypeIncludes([32, 64, 96, 104, 112, 120, 128], V)) {
              throw lazyDOMException(
                `${V} is not a valid AES-GCM tag length`,
                'OperationError');
            }
            break;
          case 'aes-ocb':
            if (!ArrayPrototypeIncludes([64, 96, 128], V)) {
              throw lazyDOMException(
                `${V} is not a valid AES-OCB tag length`,
                'OperationError');
            }
            break;
        }
      },
    },
    {
      key: 'additionalData',
      converter: converters.BufferSource,
      validator: (V, dict) => validateMaxBufferLength(V, 'algorithm.additionalData'),
    },
  ]);

converters.AesCtrParams = createDictionaryConverter(
  'AesCtrParams', [
    ...new SafeArrayIterator(dictAlgorithm),
    {
      key: 'counter',
      converter: converters.BufferSource,
      validator: (V, dict) => validateByteLength(V, 'algorithm.counter', 16),
      required: true,
    },
    {
      key: 'length',
      converter: (V, opts) =>
        converters.octet(V, { ...opts, enforceRange: true }),
      validator: (V, dict) => {
        if (V === 0 || V > 128)
          throw lazyDOMException(
            'AES-CTR algorithm.length must be between 1 and 128',
            'OperationError');
      },
      required: true,
    },
  ]);

converters.CryptoKey = createInterfaceConverter(
  'CryptoKey', CryptoKey.prototype);

converters.EcdhKeyDeriveParams = createDictionaryConverter(
  'EcdhKeyDeriveParams', [
    ...new SafeArrayIterator(dictAlgorithm),
    {
      key: 'public',
      converter: converters.CryptoKey,
      validator: (V, dict) => {
        if (V.type !== 'public')
          throw lazyDOMException(
            'algorithm.public must be a public key', 'InvalidAccessError');

        if (StringPrototypeToLowerCase(V.algorithm.name) !== StringPrototypeToLowerCase(dict.name))
          throw lazyDOMException(
            'key algorithm mismatch',
            'InvalidAccessError');
      },
      required: true,
    },
  ]);

converters.ContextParams = createDictionaryConverter(
  'ContextParams', [
    ...new SafeArrayIterator(dictAlgorithm),
    {
      key: 'context',
      converter: converters.BufferSource,
      validator(V, dict) {
        let { 0: major, 1: minor } = process.versions.openssl.split('.');
        major = NumberParseInt(major, 10);
        minor = NumberParseInt(minor, 10);
        if (major > 3 || (major === 3 && minor >= 2)) {
          this.validator = undefined;
        } else {
          this.validator = validateZeroLength('ContextParams.context');
          this.validator(V, dict);
        }
      },
    },
  ]);

converters.Argon2Params = createDictionaryConverter(
  'Argon2Params', [
    ...new SafeArrayIterator(dictAlgorithm),
    {
      key: 'nonce',
      converter: converters.BufferSource,
      required: true,
    },
    {
      key: 'parallelism',
      converter: (V, opts) =>
        converters['unsigned long'](V, { ...opts, enforceRange: true }),
      validator: (V, dict) => {
        if (V === 0 || V > MathPow(2, 24) - 1) {
          throw lazyDOMException(
            'parallelism must be > 0 and < 16777215',
            'OperationError');
        }
      },
      required: true,
    },
    {
      key: 'memory',
      converter: (V, opts) =>
        converters['unsigned long'](V, { ...opts, enforceRange: true }),
      validator: (V, dict) => {
        if (V < 8 * dict.parallelism) {
          throw lazyDOMException(
            'memory must be at least 8 times the degree of parallelism',
            'OperationError');
        }
      },
      required: true,
    },
    {
      key: 'passes',
      converter: (V, opts) =>
        converters['unsigned long'](V, { ...opts, enforceRange: true }),
      required: true,
    },
    {
      key: 'version',
      converter: (V, opts) =>
        converters.octet(V, { ...opts, enforceRange: true }),
      validator: (V, dict) => {
        if (V !== 0x13) {
          throw lazyDOMException(
            `${V} is not a valid Argon2 version`,
            'OperationError');
        }
      },
    },
    {
      key: 'secretValue',
      converter: converters.BufferSource,
    },
    {
      key: 'associatedData',
      converter: converters.BufferSource,
    },
  ]);

function validateMacKeyLength(parameterName, zeroError) {
  return (V) => {
    if (V === 0)
      throw lazyDOMException(`${parameterName} cannot be 0`, zeroError);

    // The Web Crypto spec allows for key lengths that are not multiples of 8. We don't.
    if (V % 8)
      throw lazyDOMException(`Unsupported ${parameterName}`, 'NotSupportedError');
  };
}

for (const { 0: name, 1: zeroError } of [['KmacKeyGenParams', 'OperationError'], ['KmacImportParams', 'DataError']]) {
  converters[name] = createDictionaryConverter(
    name, [
      ...new SafeArrayIterator(dictAlgorithm),
      {
        key: 'length',
        converter: (V, opts) =>
          converters['unsigned long'](V, { ...opts, enforceRange: true }),
        validator: validateMacKeyLength(`${name}.length`, zeroError),
      },
    ]);
}

converters.KmacParams = createDictionaryConverter(
  'KmacParams', [
    ...new SafeArrayIterator(dictAlgorithm),
    {
      key: 'length',
      converter: (V, opts) =>
        converters['unsigned long'](V, { ...opts, enforceRange: true }),
      validator: (V, opts) => {
        // The Web Crypto spec allows for KMAC output length that are not multiples of 8. We don't.
        if (V % 8)
          throw lazyDOMException('Unsupported KmacParams length', 'NotSupportedError');
      },
      required: true,
    },
    {
      key: 'customization',
      converter: converters.BufferSource,
    },
  ]);

module.exports = {
  converters,
  requiredArguments,
};
