Skip to content

Instantly share code, notes, and snippets.

@nodkz
Created October 14, 2020 19:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nodkz/d2bb4e91ba8ca9fd3178d45b33d94357 to your computer and use it in GitHub Desktop.
Save nodkz/d2bb4e91ba8ca9fd3178d45b33d94357 to your computer and use it in GitHub Desktop.
Connection string
export interface ConnectionStringHost {
host: string;
port?: number;
}
export interface ConnectionStringParameters {
scheme: string;
username?: string;
password?: string;
hosts: ConnectionStringHost[];
path: string[];
options?: any;
}
/**
* Takes a connection string object and returns a URI string of the form:
*
* scheme://[username[:password]@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[endpoint]][?options]
* @param connectionStringObject The object that describes connection string parameters
*/
export function connectionStringSerialize(
connectionStringObject: ConnectionStringParameters
): string {
if (!connectionStringObject.scheme) {
throw new Error(`Scheme not provided`);
}
let uri = connectionStringObject.scheme + '://';
if (connectionStringObject.username) {
uri += encodeURIComponent(connectionStringObject.username);
// Allow empty passwords
if (connectionStringObject.password) {
uri += ':' + encodeURIComponent(connectionStringObject.password);
}
uri += '@';
}
uri += _formatAddress(connectionStringObject);
// Only put a slash when there is an endpoint
if (Array.isArray(connectionStringObject.path)) {
const path = connectionStringObject.path
.filter((o) => o === null || o === undefined || o === '')
.map((o) => encodeURIComponent(o))
.join('/');
if (path) {
uri += '/' + path;
}
}
if (connectionStringObject.options && Object.keys(connectionStringObject.options).length > 0) {
uri +=
'?' +
Object.keys(connectionStringObject.options)
.map(
(option) =>
encodeURIComponent(option) +
'=' +
encodeURIComponent(connectionStringObject.options[option])
)
.join('&');
}
return uri;
}
/**
* Takes a connection string URI of form:
*
* scheme://[username[:password]@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[path]][?options]
*
* and returns an object of form:
*
* {
* scheme: string,
* username?: string,
* password?: string,
* hosts: [ { host: string, port?: number }, ... ],
* path?: string[],
* options?: object
* }
*
* Where scheme and hosts will always be present. Other fields will only be present in the result if they were
* present in the input.
* @param uri The connection string URI
*/
export function connectionStringParse(uri: string): ConnectionStringParameters {
const connectionStringParser = new RegExp(
'^\\s*' + // Optional whitespace padding at the beginning of the line
'([^:]+):\\/\\/' + // Scheme (Group 1)
'(?:([^:@,/?=&]*)' + // User (Group 2)
'(?::([^:@,/?=&]*))?@)?' + // Password (Group 3)
'([^@/?=&]+)' + // Host address(es) (Group 4)
'(?:\\/([^:@,?=&]+)?)?' + // Endpoint (Group 5)
'(?:\\?([^:@,/?]+)?)?' + // Options (Group 6)
'\\s*$', // Optional whitespace padding at the end of the line
'gi'
);
const connectionStringObject = {} as ConnectionStringParameters;
if (!uri || !uri.includes('://')) {
throw new Error(`No scheme found in URI ${uri}`);
}
const tokens = connectionStringParser.exec(uri);
if (Array.isArray(tokens)) {
connectionStringObject.scheme = tokens[1];
connectionStringObject.username = tokens[2] ? decodeURIComponent(tokens[2]) : tokens[2];
connectionStringObject.password = tokens[3] ? decodeURIComponent(tokens[3]) : tokens[3];
connectionStringObject.hosts = _parseAddress(tokens[4]);
connectionStringObject.path = tokens[5]
? tokens[5].split('/').map((o) => decodeURIComponent(o))
: [];
connectionStringObject.options = tokens[6] ? _parseOptions(tokens[6]) : tokens[6];
}
return connectionStringObject;
}
/**
* Formats the address portion of a connection string
* @param connectionStringObject The object that describes connection string parameters
*/
function _formatAddress(connectionStringObject: ConnectionStringParameters): string {
return connectionStringObject.hosts
.map(
(address) =>
encodeURIComponent(address.host) +
(address.port ? ':' + encodeURIComponent(address.port.toString(10)) : '')
)
.join(',');
}
/**
* Parses an address
* @param addresses The address(es) to process
*/
function _parseAddress(addresses: string): ConnectionStringHost[] {
return addresses.split(',').map((address) => {
const i = address.indexOf(':');
return (i >= 0
? { host: decodeURIComponent(address.substring(0, i)), port: +address.substring(i + 1) }
: { host: decodeURIComponent(address) }) as ConnectionStringHost;
});
}
/**
* Parses options
* @param options The options to process
*/
function _parseOptions(options: string): { [key: string]: string } {
const result: { [key: string]: string } = {};
options.split('&').forEach((option) => {
const i = option.indexOf('=');
if (i >= 0) {
result[decodeURIComponent(option.substring(0, i))] = decodeURIComponent(
option.substring(i + 1)
);
}
});
return result;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment