Source: utils/odata.es.js

/**
 * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 */

import {
	FUNCTIONAL_OPERATORS,
	NOT_OPERATORS,
	PROPERTY_TYPES,
	RELATIONAL_OPERATORS,
} from './constants.es';

/**
 * Gets the type of the property from the property name.
 * @param {string} propertyName The property name to find.
 * @param {array} properties The list of defined properties to search in.
 * @returns {string} The property type.
 */
const getTypeByPropertyName = (propertyName, properties) => {
	let type = null;

	if (propertyName && properties) {
		const property = properties.find(
			(property) => property.name === propertyName
		);

		type = property ? property.type : null;
	}

	return type;
};

/**
 * Decides whether to add quotes to value.
 * @param {boolean | string} value
 * @param {boolean | date | number | string} type
 * @returns {string}
 */
function valueParser(value, type) {
	let parsedValue;

	switch (type) {
		case PROPERTY_TYPES.BOOLEAN:
		case PROPERTY_TYPES.DATE:
		case PROPERTY_TYPES.DATE_TIME:
		case PROPERTY_TYPES.INTEGER:
		case PROPERTY_TYPES.DOUBLE:
			parsedValue = value;
			break;
		case PROPERTY_TYPES.COLLECTION:
		case PROPERTY_TYPES.STRING:
		default:
			parsedValue = `'${value}'`;
			break;
	}

	return parsedValue;
}

/**
 * Recursively traverses the criteria object to build an oData filter query
 * string. Properties is required to parse the correctly with or without quotes
 * and formatting the query differently for certain types like collection.
 * @param {object} criteria The criteria object.
 * @param {string} queryConjunction The conjunction name value to be used in the
 * query.
 * @param {array} properties The list of property objects. See
 * ContributorBuilder for valid property object shape.
 * @returns An OData query string built from the criteria object.
 */
function buildQueryString(criteria, queryConjunction, properties) {
	return criteria.filter(Boolean).reduce((queryString, criterion, index) => {
		const {
			conjunctionName,
			items,
			operatorName,
			propertyName,
			value,
		} = criterion;

		if (index > 0) {
			queryString = queryString.concat(` ${queryConjunction} `);
		}

		if (conjunctionName) {
			queryString = queryString.concat(
				`(${buildQueryString(items, conjunctionName, properties)})`
			);
		}
		else {
			const type =
				criterion.type ||
				getTypeByPropertyName(propertyName, properties);

			const parsedValue = valueParser(value, type);

			if (isValueType(RELATIONAL_OPERATORS, operatorName)) {
				if (type === PROPERTY_TYPES.COLLECTION) {
					queryString = queryString.concat(
						`${propertyName}/any(c:c ${operatorName} ${parsedValue})`
					);
				}
				else {
					queryString = queryString.concat(
						`${propertyName} ${operatorName} ${parsedValue}`
					);
				}
			}
			else if (isValueType(FUNCTIONAL_OPERATORS, operatorName)) {
				if (type === PROPERTY_TYPES.COLLECTION) {
					queryString = queryString.concat(
						`${propertyName}/any(c:${operatorName}(c, ${parsedValue}))`
					);
				}
				else {
					queryString = queryString.concat(
						`${operatorName}(${propertyName}, ${parsedValue})`
					);
				}
			}
			else if (isValueType(NOT_OPERATORS, operatorName)) {
				const baseOperator = operatorName.replace(/not-/g, '');

				const baseExpression = [
					{
						operatorName: baseOperator,
						propertyName,
						type,
						value,
					},
				];

				// Not is wrapped in a group to simplify AST parsing.

				queryString = queryString.concat(
					`(not (${buildQueryString(
						baseExpression,
						conjunctionName,
						properties
					)}))`
				);
			}
		}

		return queryString;
	}, '');
}

/**
 * Checks if the value is a certain type.
 * @param {object} types A map of supported types.
 * @param {*} value The value to validate.
 * @returns {boolean}
 */
function isValueType(types, value) {
	return Object.values(types).includes(value);
}

export {buildQueryString};