@httpx/dsn-parser
Version:
DSN & JDBC string parser with query params support in a tiny and modern package.
143 lines (142 loc) • 4.95 kB
JavaScript
//#region src/dsn-parser.type.ts
const errorReasons = {
EMPTY_DSN: "DSN cannot be empty",
INVALID_ARGUMENT: "DSN must be a string",
INVALID_PORT: "Invalid port",
PARSE_ERROR: "Cannot parse DSN"
};
//#endregion
//#region src/dsn-parser.util.ts
const createErrorResult = (reason, msg) => {
return {
message: msg ?? errorReasons[reason],
reason,
success: false
};
};
const isNonEmptyString = (value, trim = true) => {
return typeof value === "string" && (trim ? value.trim() : value).length > 0;
};
const parsableNumberRegexp = /^-?\d{1,16}$/;
const isParsableNumber = (value) => {
return typeof value === "string" && parsableNumberRegexp.test(value);
};
const isValidNetworkPort = (port) => {
return !Number.isNaN(port) && port < 65536 && port > 0;
};
const removeUndefined = (obj) => {
const definedObj = {};
for (const key in obj) if (obj[key] !== void 0) definedObj[key] = obj[key];
return definedObj;
};
const mergeDsnOverrides = (parsedDsn, overrides) => {
const merged = {};
const { params, ...restDsn } = parsedDsn;
for (const [key, value] of Object.entries(restDsn)) merged[key] = key in overrides ? overrides[key] : value;
merged.params = params;
return merged;
};
//#endregion
//#region src/query-param-parser.ts
const defaultOptions$1 = {
parseBooleans: true,
parseNumbers: true,
setTrueForUndefinedValues: true
};
const parseQueryParams = (queryParams, options) => {
const { parseBooleans, parseNumbers, setTrueForUndefinedValues } = {
...defaultOptions$1,
...options
};
const defaultValue = setTrueForUndefinedValues ? true : null;
return queryParams.split("&").filter((v) => v.trim().length > 0).reduce((acc, keyValuePair) => {
const [key, value = null] = keyValuePair.split("=");
let val;
if (typeof value === "string") if (parseNumbers && isParsableNumber(value)) val = Number.parseInt(value, 10);
else val = parseBooleans && ["false", "true"].includes(value) ? value === "true" : decodeURIComponent(value);
else val = defaultValue;
return {
...acc,
[key]: val
};
}, {});
};
//#endregion
//#region src/parse-dsn.ts
const dsnRegexp = /^(?<driver>([\w+-]{1,40})):\/\/((?<user>[^/:]{1,200})?(:(?<pass>.{0,200}))?@)?(?<host>[^/:]{1,200}?)(:(?<port>\d+)?)?(\/(?<db>([\w#$.@-])+))?(\?(?<params>.{1,1000}))?$/;
const defaultOptions = {
lowercaseDriver: false,
overrides: {}
};
const parseDsn = (dsn, options) => {
if (!isNonEmptyString(dsn)) return createErrorResult(typeof dsn === "string" ? "EMPTY_DSN" : "INVALID_ARGUMENT");
const { lowercaseDriver, overrides = {} } = {
...defaultOptions,
...options
};
const matches = dsnRegexp.exec(dsn);
if (!matches?.groups) return createErrorResult("PARSE_ERROR");
const parsed = {};
for (const [key, value] of Object.entries(matches.groups)) if (typeof value === "string") switch (key) {
case "driver":
parsed.driver = lowercaseDriver ? value.toLowerCase() : value;
break;
case "port":
parsed.port = Number.parseInt(value, 10);
break;
case "params":
parsed.params = parseQueryParams(value);
break;
default: parsed[key] = value;
}
const val = removeUndefined(mergeDsnOverrides(parsed, overrides));
if (val?.port && !isValidNetworkPort(val.port)) return createErrorResult("INVALID_PORT", `Invalid port: ${val.port}`);
return {
success: true,
value: val
};
};
//#endregion
//#region src/assert-parsable-dsn.ts
/**
* @throws Error when not parsable
*/
const assertParsableDsn = (dsn, msg) => {
if (typeof dsn !== "string") throw new TypeError("dsn: must be a string.");
const parsed = parseDsn(dsn);
if (!parsed.success) throw new Error(msg ?? `${parsed.message} (${parsed.reason})`);
};
//#endregion
//#region src/convert-jdbc-to-dsn.ts
/**
* Convert JDBC URL to DSN format.
*
* @example
* ```typescript
* const jdbc = 'sqlserver://localhost:1433;database=my-db;authentication=default;user=sa;password=pass03$;encrypt=true;trustServerCertificate=true';
* const dsn = convertJdbcToDsn(jdbc);
* // dsn is 'sqlserver://localhost:1433?database=my-db&authentication=default&user=sa&password=pass03$&encrypt=true&trustServerCertificate=true
* ```
* @throws TypeError .
*/
const convertJdbcToDsn = (jdbc) => {
if (typeof jdbc !== "string") throw new TypeError("jdbc param must be a string");
const [part1, ...rest] = jdbc.split(";");
return [part1, rest ? rest.join("&") : void 0].filter(Boolean).join("?");
};
//#endregion
//#region src/is-parsable-dsn.ts
const isParsableDsn = (dsn) => {
return parseDsn(dsn).success;
};
//#endregion
//#region src/parse-dsn-or-throw.ts
const parseDsnOrThrow = (dsn, options) => {
const parsedOrError = parseDsn(dsn, options);
if (parsedOrError.success) return parsedOrError.value;
const pfx = options?.errorMsgPrefix ?? `Can't parse dsn`;
throw new Error(`${pfx}: ${parsedOrError.message} (${parsedOrError.reason})`);
};
//#endregion
export { assertParsableDsn, convertJdbcToDsn, isParsableDsn, parseDsn, parseDsnOrThrow };
//# sourceMappingURL=index.js.map