Source: basefilter.js

import { SDB_NULLSTR } from "./filterfunctions.js";

const SDB_BF_INVALID_SORT_COL = 'Column must be a non-empty string/cannot contain spaces/cannot begin with a number';
const SDB_BF_LIMIT_TYPE = 'Limit row number must be a positive integer value';
const SDB_BF_OFFSET_TYPE = 'Offset row number must be a positive integer value';
const SDB_BF_INVALID_WILDCARD = 'URL string placeholder must be a string, cannot contain slash (/)';
const SDB_BF_INVALID_NULLSTR = 'NULL string placeholder must be a string, cannot contain slash (/)'

/** 
 * Filter class for creating SlashDB-compatible URL strings.  Base class for `DataDiscoveryFilter` and `SQLPassThruFilter` classes.
 */
class BaseFilter {

   /**
   * A class for creating SlashDB-compatible URL strings
   * @param {string} [urlPlaceholder] - a string that contains a character or string to set for the placeholder query parameter (used to indicate what char/string
   * was used to replace '/' character in values contained in the URL that may contain the '/' character);  default is '__'
   */	
	constructor(urlPlaceholder = '__') {
		// will contain any query parameters that are set
		this.urlStringParams = {
			sort : { default: undefined, value: undefined },
			distinct : { default: false, value: false },
			limit: { default: undefined, value: undefined },
			offset: { default: undefined, value: undefined },
			transpose : { default: false, value: false },
			nil_visible: { default: false, value: false },
			nullStr: { default: '<null>', value: SDB_NULLSTR }	// SDB_NULLSTR can be changed using chgPlaceholder(null, value) - default is <null>
		};
	
		if (urlPlaceholder !== undefined) {
			if (typeof(urlPlaceholder) !== 'string' || urlPlaceholder.indexOf('/') > -1  || urlPlaceholder.trim().length < 1) {
				throw TypeError(SDB_BF_INVALID_WILDCARD);
			}
		}

		this._urlPlaceholder = urlPlaceholder;

		// for specifying which columns to return if desired
		this.returnColumns = undefined;
		
		// combines path and query parameters
		this.endpoint = null;
	}

	/**
	 * Sets the `placeholder` query string parameter; only used internally
	* @returns {string} an empty string, or a query parameter string for the placeholder query parameter 
	*/ 
	_urlPlaceholderFn() {
		return this._urlPlaceholder !== '__' ? `&placeholder=${this._urlPlaceholder}` : '';
	}	

		
	/**
	* Appends the URL with set of columns to return from request
	* @param {...string} columns - a list of column names (e.g. `'FirstName','LastName','Email'`)
	* @returns this object	
	*/ 
	cols(...columns) {
		this.returnColumns = this._columnArrayParser(...columns);
		return this.build();
	}

	/**
	* Set the sort query string parameter
	* @param {...string} columns - a list of column names to sort by (e.g. `'FirstName','LastName','Email'`)
	* @returns this object	
	*/ 
	sort(...columns) {
		this.urlStringParams['sort']['value'] = this._columnArrayParser(...columns);
		return this.build();
	}

	/**
	* Mark a column as descending for `sort()` method.  Not used in class; exposed externally as its own function in this module
	* @param {string} col - a column name to mark as descending
	* @returns {string} a column name that has been marked as descending for `sort()` method
	* @throws {TypeError} if column name given is not a string
	* @throws {SyntaxError} if column name given contains spaces or parses to a number
	*/ 
	_sort_desc(col) {
		
		if (typeof(col) !== 'string') {
			throw TypeError(SDB_BF_INVALID_SORT_COL);
		}
		if (!isNaN(parseInt(col[0])) || col.indexOf(' ') > -1) {
			throw SyntaxError(SDB_BF_INVALID_SORT_COL);
		}

		return `-${col}`;
	}

	/**
	* Mark a column as ascending for `sort()` method.  Not used in class; exposed externally as its own function in this module; note
	* that this method doesn't do any modifications to the column name, it's here just so developers have an explicit method
	* @param {string} col - a column name to mark as descending
	* @returns {string} a column name that has been marked as ascending for `sort()` method (effectively, no changes to input)
	* @throws {TypeError} if column name given is not a string
	* @throws {SyntaxError} if column name given contains spaces or parses to a number
	*/ 
	_sort_asc(col) {
		
		if (typeof(col) !== 'string') {
			throw TypeError(SDB_BF_INVALID_SORT_COL);
		}
		if (!isNaN(parseInt(col[0])) || col.indexOf(' ') > -1) {
			throw SyntaxError(SDB_BF_INVALID_SORT_COL);
		}

		return `${col}`;
	}	

	/**
	* Sets the `distinct` query string parameter
	* @param {boolean} [toggle] - sets the parameter if not provided; removes the parameter if set to false
	* @returns this object
	*/ 
	distinct(toggle = true) {
		if (toggle === true || toggle === false) {
			this.urlStringParams['distinct']['value'] = toggle === true;
		}
		return this.build();
	}

	/**
	* Sets the `limit` query string parameter
	* @param {number | boolean} [numRows] - sets the parameter with the value provided; removes the 
	* parameter if not provided or set to false
	* @returns this object	
	* @throws {TypeError} if value provided is not an integer or < 1
	*/ 	
	limit(numRows = false) {
		if (numRows) {
			if ( !Number.isInteger(numRows) || numRows < 1) {
				throw TypeError(SDB_BF_LIMIT_TYPE);
			}
		}
		this.urlStringParams['limit']['value'] = numRows !== false ? numRows : this.urlStringParams['limit']['default'];
		return this.build();
	}

	/**
	* Sets the `offset` query string parameter
	* @param {number | boolean} [numRows] - sets the parameter with the value provided; removes the
	* parameter if not provided or set to false
	* @returns this object	
	* @throws {TypeError} if value provided is not an integer or < 1
	*/ 		
	offset(numRows = false) {
		if (numRows) {
			if ( !Number.isInteger(numRows) || numRows < 1) {
				throw TypeError(SDB_BF_OFFSET_TYPE);
			}
		}
		this.urlStringParams['offset']['value'] = numRows !== false ? numRows : this.urlStringParams['offset']['default'];
		return this.build();
	}

	/**
	* Sets the `nullStr` query string parameter
	*
	* When using the composable filter functions, if the `chgPlaceHolder` function is invoked to change the default
	* `null` placeholder, any `BaseFilter`, `DataDiscoveryFilter`, and `SQLPassThruFilter` objects created after changing
	* the placeholder will have the `nullStr` query string parameter automatically set to the value passed to `chgPlaceHolder`.
	* @param {string} [nullString] - sets parameter with the value provided, resets to default if not given
	* @returns this object	
	* @throws {TypeError} if value provided is not a valid string
	*/ 		
	nullStr(nullString) {
		if (arguments.length > 0) {
			if ( typeof(nullString) !== 'string' || nullString.indexOf('/') > -1 ) {
					throw TypeError(SDB_BF_INVALID_NULLSTR);
				}
		}
		this.urlStringParams['nullStr']['value'] = arguments.length > 0 ? nullString : this.urlStringParams['nullStr']['default'];
		return this.build();
	}

	/**
	* Sets the `transpose` query string parameter
	* @param {boolean} [toggle] - sets parameter if not provided; removes the parameter if set to false
	* @returns this object	
	*/ 	
	transpose(toggle = true) {
		if (toggle === true || toggle === false) {
			this.urlStringParams['transpose']['value'] = toggle === true;
		}
		return this.build();
	}

	/**
	* Sets the `nil_visible` query string parameter
	* @param {boolean} [toggle] - sets the parameter if not provided; removes the parameter if set to false
	* @returns this object	
	*/ 	
	xmlNilVisible(toggle = true) {
		this.urlStringParams['nil_visible']['value'] = toggle === true;
		return this.build();
	}	

	/**
	* Parses out column names; used by `sort()` and `cols()` methods.  Not called directly.
	* @param {...string} columns - a comma delimited list of column names to parse (e.g. `'FirstName','LastName','Email'`)
	* @returns {undefined} if one column given and value of column is false (resets `sort()`/`cols()`)
	* @returns {string} string of column names separated by ','
	* @throws {TypeError} if no columns given, or if any column names are not strings, or are empty strings
	* @throws {SyntaxError} if any column names contain spaces, or parse to numbers
	*/ 
	_columnArrayParser(...columns) {
		let s = '';
		if (columns.length < 1) {
			throw TypeError(SDB_BF_INVALID_SORT_COL);
		}
		
		if (columns.length === 1 && columns[0] === false) {
			return undefined;
		}
		
		else {
			for (const col of columns) {

				if (typeof(col) !== 'string' || col.trim().length < 1) {
					throw TypeError(SDB_BF_INVALID_SORT_COL);
				}
				if (!isNaN(parseInt(col[0])) || col.indexOf(' ') > -1) {
					throw SyntaxError(SDB_BF_INVALID_SORT_COL);
				}

				s += `${col},`;
			}
			s = s.slice(0,s.length-1);
			return s;
		}
	}

	/**
	* Builds the URL endpoint string from the filter strings provided to the class and the query string parameters that have been set;
	* called at the end of most filter string methods and query string parameter methods
	* @returns {Object} the current instance of this class
	*/ 
	build() {
		let columns = this.returnColumns ? `/${this.returnColumns}` : '';

		let paramString = '';
		for (const p in this.urlStringParams) {

			if (typeof this.urlStringParams[p] === 'object' && this.urlStringParams[p] !== null ) {
				if (this.urlStringParams[p]['default'] !== this.urlStringParams[p]['value']) {
					paramString += `${p}=${this.urlStringParams[p]['value']}&` ;	
				}
	   		}
		}

		paramString = paramString.slice(0,paramString.length-1);	// chop trailing &
		paramString += this._urlPlaceholderFn();

		this.endpoint = paramString.length > 0 ? `${columns}?${paramString}` : `${columns}`;
		
		return this;
	}

	/**
	* Returns the URL endpoint string in this class created by `build()`
	* @returns {string} the URL endpoint string
	*/ 	
	str() {
		return this.endpoint;
	}
}

// break out the _sort_desc/asc functions in the BaseFilter so they can be used standalone
const desc = BaseFilter.prototype._sort_desc;
const asc = BaseFilter.prototype._sort_asc;

export { BaseFilter, desc, asc }
export { SDB_BF_INVALID_SORT_COL, SDB_BF_LIMIT_TYPE, SDB_BF_OFFSET_TYPE, SDB_BF_INVALID_NULLSTR }