From 369a63f03f263d81f576fb9f38afd80cf3b95f96 Mon Sep 17 00:00:00 2001 From: Dan Hensby Date: Mon, 7 Aug 2023 21:56:45 +0200 Subject: [PATCH] feat: add connection string builder --- src/builder/index.ts | 49 +++++++++++++++++++++++++++++++ src/index.ts | 2 ++ test/builder/connection-string.ts | 21 +++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 src/builder/index.ts create mode 100644 test/builder/connection-string.ts diff --git a/src/builder/index.ts b/src/builder/index.ts new file mode 100644 index 0000000..1bb9c23 --- /dev/null +++ b/src/builder/index.ts @@ -0,0 +1,49 @@ +type ValidDataTypes = string | boolean | number | null | undefined | { toString(): string }; + +function isQuoted(val: string): boolean { + if (val[0] !== '{') { + return false; + } + for (let i = 1; i < val.length; i++) { + if (val[i] === '}') { + if (i + 1 === val.length) { + // if last char, then it's quoted properly + return true; + } else if (val[i + 1] !== '}') { + // the next char is no a `}` so there is no valid escaping here + return false; + } else { + // we are seeing an escaped `}`, so skip ahead + i++; + } + } + } + return false; +} + +function needsQuotes(val: string): boolean { + return !isQuoted(val) && !!val.match(/\[|]|{|}|\|\(|\)|,|;|\?|\*|=|!|@/)?.length; +} + +function encodeTuple(key: string, value: ValidDataTypes): [string, string] { + if (value === null || value === undefined) { + return [key, '']; + } + switch (typeof value) { + case 'boolean': + return [key, value ? 'Yes' : 'No']; + default: { + const strVal = (value as { toString(): string }).toString(); + if (needsQuotes(strVal)) { + return [key, `{${strVal.replace(/}/g, '}}')}}`]; + } + return [key, strVal]; + } + } +} + +export function buildConnectionString(data: Record): string { + return Object.entries(data).map(([key, value]) => { + return encodeTuple(key.trim(), value).join('='); + }).join(';'); +} diff --git a/src/index.ts b/src/index.ts index cf40537..7365372 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,8 @@ import parseConnectionString from './parser/connection-string'; import parseSqlConnectionString from './parser/sql-connection-string'; +export * from './builder'; + export { parseConnectionString, parseSqlConnectionString, diff --git a/test/builder/connection-string.ts b/test/builder/connection-string.ts new file mode 100644 index 0000000..dd012be --- /dev/null +++ b/test/builder/connection-string.ts @@ -0,0 +1,21 @@ +import { buildConnectionString } from '../../src/builder'; +import { expect } from 'chai'; + +describe('builder', () => { + it('builds a connection string', () => { + const built = buildConnectionString({ + strkey: 'value', + booltrue: true, + boolfalse: false, + numkey: 123, + needsquote: 'a}test', + quotedquote: '{quoted}}value}', + quoted: '{regular value}', + badlyquoted: '{no closing quote', + badclose: '{close}early', + nullkey: null, + undefinedkey: undefined, + }); + expect(built).to.equal('strkey=value;booltrue=Yes;boolfalse=No;numkey=123;needsquote={a}}test};quotedquote={quoted}}value};quoted={regular value};badlyquoted={{no closing quote};badclose={{close}}early};nullkey=;undefinedkey='); + }); +});