<commit_before>'use strict';

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

var assign = require('lodash.assign');
var Bluebird = require('bluebird');
var debug = require('debug')('xo:api');
var forEach = require('lodash.foreach');
var isArray = require('lodash.isarray');
var isFunction = require('lodash.isfunction');
var isObject = require('lodash.isobject');
var isString = require('lodash.isstring');
var keys = require('lodash.keys');
var pick = require('lodash.pick');
var requireTree = require('require-tree');
var schemaInspector = require('schema-inspector');

var apiErrors = require('./api-errors');
var coroutine = require('./fibers-utils').$coroutine;
var InvalidParameters = require('./api-errors').InvalidParameters;
var NoSuchMethod = require('./api-errors').NoSuchMethod;
var Unauthorized = require('./api-errors').Unauthorized;
var wait = require('./fibers-utils').$wait;

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

function $deprecated(fn)
{
	return function (session, req) {
		console.warn(req.method +' is deprecated!');

		return fn.apply(this, arguments);
	};
}

var wrap = function (val) {
	return function () {
		return val;
	};
};

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

// TODO: Helper functions that could be written:
// - checkParams(req.params, param1, ..., paramN)

var helpers = {};

helpers.checkPermission = function (permission)
{
	// TODO: Handle token permission.

	var userId = this.session.get('user_id', undefined);

	if (undefined === userId)
	{
		throw new Unauthorized();
	}

	if (!permission)
	{
		return;
	}

	var user = wait(this.users.first(userId));
	// The user MUST exist at this time.

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

// Checks and returns parameters.
helpers.getParams = function (schema) {
	var params = this.request.params;

	schema = {
		type: 'object',
		properties: schema,
	};

	var result = schemaInspector.validate(schema, params);

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

	return params;
};

helpers.getUserPublicProperties = function (user) {
	// Handles both properties and wrapped models.
	var properties = user.properties || user;

	return pick(properties, 'id', 'email', 'permission');
};

helpers.getServerPublicProperties = function (server) {
	// Handles both properties and wrapped models.
	var properties = server.properties || server;

	return pick(properties, 'id', 'host', 'username');
};

// Deprecated!
var errorClasses = {
	ALREADY_AUTHENTICATED: apiErrors.AlreadyAuthenticated,
	INVALID_CREDENTIAL: apiErrors.InvalidCredential,
	INVALID_PARAMS: apiErrors.InvalidParameters,
	NO_SUCH_OBJECT: apiErrors.NoSuchObject,
	NOT_IMPLEMENTED: apiErrors.NotImplementd,
};
helpers.throw = function (errorId, data) {
	throw new (errorClasses[errorId])(data);
};

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

function Api(xo)
{
	if ( !(this instanceof Api) )
	{
		return new Api(xo);
	}

	this.xo = xo;
}

var execHelper = coroutine(function (session, request) {
	var ctx = Object.create(this.xo);
	assign(ctx, helpers, {
		session: session,
		request: request,
	});

	var method = this.getMethod(request.method);

	if (!method)
	{
		console.warn('Invalid method: '+ request.method);
		throw new NoSuchMethod(request.method);
	}

	if ('permission' in method)
	{
		helpers.checkPermission.call(ctx, method.permission);
	}

	if (method.params)
	{
		helpers.getParams.call(ctx, method.params);
	}

	return method.call(ctx, request.params);
});

Api.prototype.exec = function (session, request) {
	var method = request.method;

	debug('%s(...)', method);

	return Bluebird.try(execHelper, [session, request], this).then(
		function (result) {
			debug('%s(...) → %s', method, typeof result);
			return result;
		},
		function (error) {
			debug('Error: %s(...) → %s', method, error);
			throw error;
		}<commit_msg>Fix: NoSuchMethod → MethodNotFound.<commit_after>'use strict';

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

var assign = require('lodash.assign');
var Bluebird = require('bluebird');
var debug = require('debug')('xo:api');
var forEach = require('lodash.foreach');
var isArray = require('lodash.isarray');
var isFunction = require('lodash.isfunction');
var isObject = require('lodash.isobject');
var isString = require('lodash.isstring');
var keys = require('lodash.keys');
var pick = require('lodash.pick');
var requireTree = require('require-tree');
var schemaInspector = require('schema-inspector');

var apiErrors = require('./api-errors');
var coroutine = require('./fibers-utils').$coroutine;
var InvalidParameters = require('./api-errors').InvalidParameters;
var MethodNotFound = require('./api-errors').MethodNotFound;
var Unauthorized = require('./api-errors').Unauthorized;
var wait = require('./fibers-utils').$wait;

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

function $deprecated(fn)
{
	return function (session, req) {
		console.warn(req.method +' is deprecated!');

		return fn.apply(this, arguments);
	};
}

var wrap = function (val) {
	return function () {
		return val;
	};
};

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

// TODO: Helper functions that could be written:
// - checkParams(req.params, param1, ..., paramN)

var helpers = {};

helpers.checkPermission = function (permission)
{
	// TODO: Handle token permission.

	var userId = this.session.get('user_id', undefined);

	if (undefined === userId)
	{
		throw new Unauthorized();
	}

	if (!permission)
	{
		return;
	}

	var user = wait(this.users.first(userId));
	// The user MUST exist at this time.

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

// Checks and returns parameters.
helpers.getParams = function (schema) {
	var params = this.request.params;

	schema = {
		type: 'object',
		properties: schema,
	};

	var result = schemaInspector.validate(schema, params);

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

	return params;
};

helpers.getUserPublicProperties = function (user) {
	// Handles both properties and wrapped models.
	var properties = user.properties || user;

	return pick(properties, 'id', 'email', 'permission');
};

helpers.getServerPublicProperties = function (server) {
	// Handles both properties and wrapped models.
	var properties = server.properties || server;

	return pick(properties, 'id', 'host', 'username');
};

// Deprecated!
var errorClasses = {
	ALREADY_AUTHENTICATED: apiErrors.AlreadyAuthenticated,
	INVALID_CREDENTIAL: apiErrors.InvalidCredential,
	INVALID_PARAMS: apiErrors.InvalidParameters,
	NO_SUCH_OBJECT: apiErrors.NoSuchObject,
	NOT_IMPLEMENTED: apiErrors.NotImplementd,
};
helpers.throw = function (errorId, data) {
	throw new (errorClasses[errorId])(data);
};

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

function Api(xo)
{
	if ( !(this instanceof Api) )
	{
		return new Api(xo);
	}

	this.xo = xo;
}

var execHelper = coroutine(function (session, request) {
	var ctx = Object.create(this.xo);
	assign(ctx, helpers, {
		session: session,
		request: request,
	});

	var method = this.getMethod(request.method);

	if (!method)
	{
		console.warn('Invalid method: '+ request.method);
		throw new MethodNotFound(request.method);
	}

	if ('permission' in method)
	{
		helpers.checkPermission.call(ctx, method.permission);
	}

	if (method.params)
	{
		helpers.getParams.call(ctx, method.params);
	}

	return method.call(ctx, request.params);
});

Api.prototype.exec = function (session, request) {
	var method = request.method;

	debug('%s(...)', method);

	return Bluebird.try(execHelper, [session, request], this).then(
		function (result) {
			debug('%s(...) → %s', method, typeof result);
			return result;
		},
		function (error) {
			debug('Error: %s(...) → %s', method, error);
			throw error;
		}
{'fraction_non_alphanumeric': 0.21464456166792265, 'fraction_numerical': 0.00025119316754584274, 'mean_word_length': 4.929262844378258, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, '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': '1937609', 'n_tokens_mistral': 2519, 'n_tokens_neox': 2405, 'n_words': 579}