Source: sqlpassthrufilter.js

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

const SDB_SPTF_INVALID_PARAM_FORMAT = 'Parameters must be given as an object of key/value pairs';
const SDB_SPTF_INVALID_PARAM_NAME = "Parameter name must be a non-empty string, cannot contain '/' character";
const SDB_SPTF_INVALID_PARAM_VALUE = "Parameter value must be a non-empty string, integer, or null, cannot contain '/'";
const SDB_SPTF_INVALID_XMLTYPE = "Parameter must be a non-empty string";

/** 
 * Class for creating URL strings for SlashDB SQL Pass-Thru functionality
 */
class SQLPassThruFilter extends BaseFilter {

   /**
   * Create a `SQLPassThruFilter` object for making SlashDB-compatible URL strings
   * @extends BaseFilter
   * @param {Object} [params] - optional object of key/value pairs representing SQL query parameters to instantiate this object with
   * @param {string} [urlPlaceholder] -  a string that contains a character(s) to set for the placeholder query parameter (used to indicate what character(s)
   * was used to replace '/' character in values contained in the URL that may contain the '/' character);  default is '__'
   */    	
	constructor(params = undefined, urlPlaceholder = '__') {
		super(urlPlaceholder)

		this.pathString = '';
		this.queryParams = {};

		if (params) {
			this.addParams(params);
		}

		this.urlStringParams = {
			...this.urlStringParams,
			count: { default: false, value: false },
			xmlType: { default: undefined, value: undefined },
		};

		this.build();
	}
	
	/**
	* Adds SQL query parameters to this object and stores info about them
	* @param {Object} params - an object of key/value pairs containing parameter names and values
	* @returns {SQLPassThruFilter} this object
	* @throws {SyntaxError} - if `params` parameter is not an object
	* @throws {SyntaxError} - if any of the `param` keys parse to numbers
	* @throws {TypeError} - if any of the `param` keys are not strings or contain spaces
	* @throws {TypeError} - if any of the `param` keys contain '/' character
	* @throws {TypeError} - if any of the `param` values are not strings or numbers, or are empty strings
	* @throws {TypeError} - if any of the `param` values contain '/' character
	*/ 	
	addParams(params) {

		if (! (typeof(params) === 'object' && !Array.isArray(params)) ){
			throw SyntaxError(SDB_SPTF_INVALID_PARAM_FORMAT);
		}

		for (const key in params) {

			if (!isNaN(parseInt(key)) ) {
				throw SyntaxError(SDB_SPTF_INVALID_PARAM_NAME);
			}

			if (typeof(key) !== 'string' || key.indexOf(' ') > -1) {
				throw TypeError(SDB_SPTF_INVALID_PARAM_NAME);
			}
	
			if (key.indexOf('/') > -1) {
				throw SyntaxError(SDB_SPTF_INVALID_PARAM_NAME);
			}

			if (params[key] === null) {
				params[key] = SDB_NULLSTR;
			}

			if (typeof(params[key]) !== 'number' && (typeof(params[key]) !== 'string')) {
				throw TypeError(SDB_SPTF_INVALID_PARAM_VALUE);
			}

			if ( typeof(params[key]) === 'string' && params[key].indexOf('/') > -1) {
				throw SyntaxError(SDB_SPTF_INVALID_PARAM_VALUE);
			}

			// update query parameter value if previously given
			// TODO add tests for this
			if (key in this.queryParams) {
				this.pathString = this.pathString.replace(`/${key}/${this.queryParams[key]}`, '');
			}

			this.queryParams[key] = params[key];
			this.pathString += `/${key}/${params[key]}`;
			this.pathString = this.pathString.replace('///','//');		// if multiple empty string values are given, '///' can appear - remove the extra '/'
		}

		return this.build();
	}

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

	/**
	* Sets the xmlType query string parameter
	* @param {string} [val] - value for the xmlType parameter; removes the query string parameter if not provided
	* @returns {SQLPassThruFilter} this object	
	* @throws {TypeError} if val paramter is not a string
	*/ 		
	xmlType(val = undefined) {
		if (val) {
			if (typeof(val) !== 'string') {
				throw TypeError(SDB_SPTF_INVALID_XMLTYPE);
			}
		}
		this.urlStringParams['xmlType']['value'] = val;
		return this.build();
	}

	/**
	* 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 {SQLPassThruFilter} this object
	*/ 	
	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 ? `/${this.pathString}${columns}?${paramString}` : `/${this.pathString}${columns}`;
		if (this.endpoint.startsWith('//')) {
			this.endpoint = this.endpoint.substring(1);
		}
		return this;
	}
}


export { SQLPassThruFilter }

// for testing only
export { SDB_SPTF_INVALID_PARAM_FORMAT, SDB_SPTF_INVALID_PARAM_NAME, SDB_SPTF_INVALID_PARAM_VALUE,SDB_SPTF_INVALID_XMLTYPE }