<commit_before>import createDebug from 'debug'
const debug = createDebug('xo:api')

import getKeys from 'lodash.keys'
import isFunction from 'lodash.isfunction'
import kindOf from 'kindof'
import ms from 'ms'
import schemaInspector from 'schema-inspector'

import {
  InvalidParameters,
  MethodNotFound,
  NoSuchObject,
  Unauthorized
} from './api-errors'
import {
  createRawObject,
  forEach
} from './utils'

// ===================================================================

// FIXME: this function is specific to XO and should not be defined in
// this file.
function checkPermission (method) {
  /* jshint validthis: true */

  const {permission} = method

  // No requirement.
  if (permission === undefined) {
    return
  }

  const {user} = this
  if (!user) {
    throw new Unauthorized()
  }

  // The only requirement is login.
  if (!permission) {
    return
  }

  if (!user.hasPermission(permission)) {
    throw new Unauthorized()
  }
}

// -------------------------------------------------------------------

function checkParams (method, params) {
  const schema = method.params
  if (!schema) {
    return
  }

  const result = schemaInspector.validate({
    type: 'object',
    properties: schema
  }, params)

  if (!result.valid) {
    throw new InvalidParameters(result.error)
  }
}

// -------------------------------------------------------------------

function resolveParams (method, params) {
  const resolve = method.resolve
  if (!resolve) {
    return params
  }

  const {user} = this
  if (!user) {
    throw new Unauthorized()
  }

  const userId = user.get('id')

  // Do not alter the original object.
  params = { ...params }

  const permissions = []
  forEach(resolve, ([param, types, permission = 'administrate'], key) => {
    const id = params[param]
    if (id === undefined) {
      return
    }

    const object = this.getObject(id, types)

    // This parameter has been handled, remove it.
    delete params[param]

    // Register this new value.
    params[key] = object

    permissions.push([ object.id, permission ])
  })

  return this.hasPermissions(userId, permissions).then(success => {
    if (success) {
      return params
    }

    throw new Unauthorized()
  })
}

// ===================================================================

function getMethodsInfo () {
  const methods = {}

  forEach(this.api._methods, function (method, name) {
    this[name] = {
      description: method.description,
      params: method.params || {},
      permission: method.permission
    }
  }, methods)

  return methods
}
getMethodsInfo.description = 'returns the signatures of all available API methods'

// -------------------------------------------------------------------

const getVersion = () => '0.1'
getVersion.description = 'API version (unstable)'

// -------------------------------------------------------------------

function listMethods () {
  return getKeys(this.api._methods)
}
listMethods.description = 'returns the name of all available API methods'

// -------------------------------------------------------------------

function methodSignature ({method: name}) {
  const method = this.api.getMethod(name)

  if (!method) {
    throw new NoSuchObject()
  }

  // Return an array for compatibility with XML-RPC.
  return [
    // XML-RPC require the name of the method.
    {
      name,
      description: method.description,
      params: method.params || {},
      permission: method.permission
    }
  ]
}
methodSignature.description = 'returns the signature of an API method'

// ===================================================================

export default class Api {
  constructor ({
    context,
    verboseLogsOnErrors
  } = {}) {
    this._methods = createRawObject()
    this._verboseLogsOnErrors = verboseLogsOnErrors
    this.context = context

    this.addMethods({
      system: {
        getMethodsInfo,
        getVersion,
        listMethods,
        methodSignature
      }
    })
  }

  addMethod (name, method) {
    this._methods[name] = method
  }

  addMethods (methods) {
    let base = ''
    forEach(methods, function addMethod (method, name) {
      name = base + name

      if (isFunction(method)) {
        this.addMethod(name, method)
        return
      }

      const oldBase = base
      base = name + '.'
      forEach(method, addMethod, this)
      base = oldBase
    }, this)
  }

  async call (session, name, params) {
    const startTime = Date.now()<commit_msg>Api#addMethod() returns a remover function.<commit_after>import createDebug from 'debug'
const debug = createDebug('xo:api')

import getKeys from 'lodash.keys'
import isFunction from 'lodash.isfunction'
import kindOf from 'kindof'
import ms from 'ms'
import schemaInspector from 'schema-inspector'

import {
  InvalidParameters,
  MethodNotFound,
  NoSuchObject,
  Unauthorized
} from './api-errors'
import {
  createRawObject,
  forEach,
  noop
} from './utils'

// ===================================================================

// FIXME: this function is specific to XO and should not be defined in
// this file.
function checkPermission (method) {
  /* jshint validthis: true */

  const {permission} = method

  // No requirement.
  if (permission === undefined) {
    return
  }

  const {user} = this
  if (!user) {
    throw new Unauthorized()
  }

  // The only requirement is login.
  if (!permission) {
    return
  }

  if (!user.hasPermission(permission)) {
    throw new Unauthorized()
  }
}

// -------------------------------------------------------------------

function checkParams (method, params) {
  const schema = method.params
  if (!schema) {
    return
  }

  const result = schemaInspector.validate({
    type: 'object',
    properties: schema
  }, params)

  if (!result.valid) {
    throw new InvalidParameters(result.error)
  }
}

// -------------------------------------------------------------------

function resolveParams (method, params) {
  const resolve = method.resolve
  if (!resolve) {
    return params
  }

  const {user} = this
  if (!user) {
    throw new Unauthorized()
  }

  const userId = user.get('id')

  // Do not alter the original object.
  params = { ...params }

  const permissions = []
  forEach(resolve, ([param, types, permission = 'administrate'], key) => {
    const id = params[param]
    if (id === undefined) {
      return
    }

    const object = this.getObject(id, types)

    // This parameter has been handled, remove it.
    delete params[param]

    // Register this new value.
    params[key] = object

    permissions.push([ object.id, permission ])
  })

  return this.hasPermissions(userId, permissions).then(success => {
    if (success) {
      return params
    }

    throw new Unauthorized()
  })
}

// ===================================================================

function getMethodsInfo () {
  const methods = {}

  forEach(this.api._methods, function (method, name) {
    this[name] = {
      description: method.description,
      params: method.params || {},
      permission: method.permission
    }
  }, methods)

  return methods
}
getMethodsInfo.description = 'returns the signatures of all available API methods'

// -------------------------------------------------------------------

const getVersion = () => '0.1'
getVersion.description = 'API version (unstable)'

// -------------------------------------------------------------------

function listMethods () {
  return getKeys(this.api._methods)
}
listMethods.description = 'returns the name of all available API methods'

// -------------------------------------------------------------------

function methodSignature ({method: name}) {
  const method = this.api.getMethod(name)

  if (!method) {
    throw new NoSuchObject()
  }

  // Return an array for compatibility with XML-RPC.
  return [
    // XML-RPC require the name of the method.
    {
      name,
      description: method.description,
      params: method.params || {},
      permission: method.permission
    }
  ]
}
methodSignature.description = 'returns the signature of an API method'

// ===================================================================

export default class Api {
  constructor ({
    context,
    verboseLogsOnErrors
  } = {}) {
    this._methods = createRawObject()
    this._verboseLogsOnErrors = verboseLogsOnErrors
    this.context = context

    this.addMethods({
      system: {
        getMethodsInfo,
        getVersion,
        listMethods,
        methodSignature
      }
    })
  }

  addMethod (name, method) {
    const methods = this._methods

    if (name in methods) {
      throw new Error(`API method ${name} already exists`)
    }

    methods[name] = method

    let unset = () => {
      delete methods[name]
      unset = noop
    }
    return () => unset()
  }

  addMethods (methods) {
    let base = ''
    forEach(methods, function addMethod (method, name) {
      name = base + name

      if (isFunction(method)) {
        this.addMethod(name, method)
        return
      }

      const oldBase = base
      base = name + '.'
      forEach(method, addMethod, this)
      base = oldBase
    }, this)
  }

  async call (session, name, params) {
    const startTime = Date.now()
{'fraction_non_alphanumeric': 0.21771576898118106, 'fraction_numerical': 0.00043261951113995244, 'mean_word_length': 2.211879124696075, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7073381', 'n_tokens_mistral': 2523, 'n_tokens_neox': 2433, 'n_words': 704}