function generateSingleApi()

in scripts/utils/generateApis.js [171:428]


function generateSingleApi (version, spec, common) {
  const release = version.charAt(0)
  const api = Object.keys(spec)[0]
  const name = api
    .replace(/\.([a-z])/g, k => k[1].toUpperCase())
    .replace(/_([a-z])/g, k => k[1].toUpperCase())

  const { paths } = spec[api].url
  const { params } = spec[api]
  const acceptedQuerystring = []
  const required = []

  const methods = paths.reduce((acc, val) => {
    for (const method of val.methods) {
      if (!acc.includes(method)) acc.push(method)
    }
    return acc
  }, [])
  const parts = paths.reduce((acc, val) => {
    if (!val.parts) return acc
    for (const part of Object.keys(val.parts)) {
      if (!acc.includes(part)) acc.push(part)
    }
    return acc
  }, [])

  // get the required parts from the url
  // if the url has at least one static path,
  // then there are not required parts of the url
  let allParts = []
  for (const path of paths) {
    if (path.parts) {
      allParts.push(Object.keys(path.parts))
    } else {
      allParts = []
      break
    }
  }
  if (allParts.length > 0) {
    intersect(...allParts).forEach(r => required.push(r))
  }

  for (const key in params) {
    if (params[key].required) {
      required.push(key)
    }

    acceptedQuerystring.push(key)
    if (deprecatedParameters[release] && deprecatedParameters[release][key]) {
      acceptedQuerystring.push(deprecatedParameters[release][key])
    }
  }

  for (const key in spec[api]) {
    const k = spec[api][key]
    if (k && k.required) {
      required.push(key)
    }
  }
  if (common && common.params) {
    for (const key in common.params) {
      acceptedQuerystring.push(key)
    }
  }

  const code = `
  function ${name}Api (params, options, callback) {
    ;[params, options, callback] = normalizeArguments(params, options, callback)

    ${genRequiredChecks()}

    ${genUrlValidation(paths, api)}

    let { ${genQueryBlacklist(false)}, ...querystring } = params
    querystring = snakeCaseKeys(acceptedQuerystring, snakeCase, querystring)

    let path = ''
    ${buildPath(api)}

    // build request object
    const request = {
      method,
      path,
      ${genBody(api, methods, spec[api].body, spec)}
      querystring
    }

    return this.transport.request(request, options, callback)
  }
  `.trim() // always call trim to avoid newlines

  return {
    name,
    code,
    acceptedQuerystring: acceptedQuerystring,
    snakeCase: genSnakeCaseMap(),
    documentation: generateDocumentation(spec[api], api)
  }

  function genRequiredChecks (param) {
    const code = required
      .map(_genRequiredCheck)
      .concat(_noBody())
      .filter(Boolean)

    if (code.length) {
      code.unshift('// check required parameters')
    }

    return code.join('\n        ')

    function _genRequiredCheck (param) {
      const camelCased = param[0] === '_'
        ? '_' + param.slice(1).replace(/_([a-z])/g, k => k[1].toUpperCase())
        : param.replace(/_([a-z])/g, k => k[1].toUpperCase())

      if (param === camelCased) {
        const check = `
          if (params['${param}'] == null) {
            const err = new this[kConfigurationError]('Missing required parameter: ${param}')
            return handleError(err, callback)
          }
        `
        return check.trim()
      } else {
        const check = `
          if (params['${param}'] == null && params['${camelCased}'] == null) {
            const err = new this[kConfigurationError]('Missing required parameter: ${param} or ${camelCased}')
            return handleError(err, callback)
          }
        `
        return check.trim()
      }
    }

    function _noBody () {
      const check = `
        if (params.body != null) {
          const err = new this[kConfigurationError]('This API does not require a body')
          return handleError(err, callback)
        }
      `
      return spec[api].body === null ? check.trim() : ''
    }
  }

  function genSnakeCaseMap () {
    const toCamelCase = str => {
      return str[0] === '_'
        ? '_' + str.slice(1).replace(/_([a-z])/g, k => k[1].toUpperCase())
        : str.replace(/_([a-z])/g, k => k[1].toUpperCase())
    }

    return acceptedQuerystring.reduce((acc, val, index) => {
      if (toCamelCase(val) !== val) {
        acc[toCamelCase(val)] = val
      }
      return acc
    }, {})
  }

  function genQueryBlacklist (addQuotes = true) {
    const toCamelCase = str => {
      return str[0] === '_'
        ? '_' + str.slice(1).replace(/_([a-z])/g, k => k[1].toUpperCase())
        : str.replace(/_([a-z])/g, k => k[1].toUpperCase())
    }

    const blacklist = ['method', 'body']
    parts.forEach(p => {
      const camelStr = toCamelCase(p)
      if (camelStr !== p) blacklist.push(`${camelStr}`)
      blacklist.push(`${p}`)
    })
    return addQuotes ? blacklist.map(q => `'${q}'`) : blacklist
  }

  function buildPath () {
    const toCamelCase = str => {
      return str[0] === '_'
        ? '_' + str.slice(1).replace(/_([a-z])/g, k => k[1].toUpperCase())
        : str.replace(/_([a-z])/g, k => k[1].toUpperCase())
    }

    const genAccessKey = str => {
      const camelStr = toCamelCase(str)
      return camelStr === str
        ? str
        : `${str} || ${camelStr}`
    }

    const genCheck = path => {
      return path
        .split('/')
        .filter(Boolean)
        .map(p => p.startsWith('{') ? `(${genAccessKey(p.slice(1, -1))}) != null` : false)
        .filter(Boolean)
        .join(' && ')
    }

    const genPath = path => {
      path = path
        .split('/')
        .filter(Boolean)
        .map(p => p.startsWith('{') ? `encodeURIComponent(${genAccessKey(p.slice(1, -1))})` : `'${p}'`)
        .join(' + \'/\' + ')
      return path.length > 0 ? ('\'/\' + ' + path) : '\'/\''
    }

    let hasStaticPath = false
    let sortedPaths = paths
      // some legacy API have mutliple statis paths
      // this filter removes them
      .filter(p => {
        if (p.path.includes('{')) return true
        if (hasStaticPath === false && p.deprecated == null) {
          hasStaticPath = true
          return true
        }
        return false
      })
      // sort by number of parameters (desc)
      .sort((a, b) => Object.keys(b.parts || {}).length - Object.keys(a.parts || {}).length)

    const allDeprecated = paths.filter(path => path.deprecated != null)
    if (allDeprecated.length === paths.length) sortedPaths = [paths[0]]

    let code = ''
    for (let i = 0; i < sortedPaths.length; i++) {
      const { path, methods } = sortedPaths[i]
      if (sortedPaths.length === 1) {
        code += `if (method == null) method = ${generatePickMethod(methods)}
          path = ${genPath(path)}
        `
      } else if (i === 0) {
        code += `if (${genCheck(path)}) {
            if (method == null) method = ${generatePickMethod(methods)}
            path = ${genPath(path)}
          }
        `
      } else if (i === sortedPaths.length - 1) {
        code += ` else {
            if (method == null) method = ${generatePickMethod(methods)}
            path = ${genPath(path)}
          }
        `
      } else {
        code += ` else if (${genCheck(path)}) {
            if (method == null) method = ${generatePickMethod(methods)}
            path = ${genPath(path)}
          }
        `
      }
    }

    return code
  }
}