Created
October 14, 2020 19:55
-
-
Save nodkz/d2bb4e91ba8ca9fd3178d45b33d94357 to your computer and use it in GitHub Desktop.
Connection string
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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