-
-
Notifications
You must be signed in to change notification settings - Fork 96
/
srv.ts
117 lines (102 loc) Β· 2.89 KB
/
srv.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import { parseSrvUrl } from "./uri.ts";
import { ConnectOptions } from "../types.ts";
enum AllowedOption {
authSource = "authSource",
replicaSet = "replicaSet",
loadBalanced = "loadBalanced",
}
function isAllowedOption(key: unknown): key is AllowedOption {
return Object.values(AllowedOption).includes(key as AllowedOption);
}
interface Resolver {
resolveDns: typeof Deno.resolveDns;
}
interface SRVResolveResultOptions {
authSource?: string;
replicaSet?: string;
loadBalanced?: string;
}
interface SRVResolveResult {
servers: { host: string; port: number }[];
options: SRVResolveResultOptions;
}
class SRVError extends Error {
constructor(message?: string) {
super(message);
this.name = "SRVError";
}
}
export class Srv {
resolver: Resolver;
constructor(resolver = { resolveDns: Deno.resolveDns }) {
this.resolver = resolver;
}
async resolveSrvUrl(urlString: string): Promise<ConnectOptions> {
const options = parseSrvUrl(urlString);
const { srvServer, ...connectOptions } = options;
if (!srvServer) {
throw new SRVError(
`Could not parse srv server address from ${urlString}`,
);
}
const resolveResult = await this.resolve(srvServer);
return {
servers: resolveResult.servers,
// TODO: Check and throw on invalid options
...resolveResult.options,
...connectOptions,
};
}
async resolve(url: string): Promise<SRVResolveResult> {
const tokens = url.split(".");
if (tokens.length < 3) {
throw new SRVError(
`Expected url in format 'host.domain.tld', received ${url}`,
);
}
const srvRecord = await this.resolver.resolveDns(
`_mongodb._tcp.${url}`,
"SRV",
);
if (!(srvRecord?.length > 0)) {
throw new SRVError(
`Expected at least one SRV record, received ${srvRecord
?.length} for url ${url}`,
);
}
const txtRecords = await this.resolver.resolveDns(url, "TXT");
if (txtRecords?.length !== 1) {
throw new SRVError(
`Expected exactly one TXT record, received ${txtRecords
?.length} for url ${url}`,
);
}
const servers = srvRecord.map((record) => {
return {
host: record.target,
port: record.port,
};
});
const optionsUri = txtRecords[0].join("");
const options: { valid: SRVResolveResultOptions; illegal: string[] } = {
valid: {},
illegal: [],
};
optionsUri.split("&").forEach((option: string) => {
const [key, value] = option.split("=");
if (isAllowedOption(key) && !!value) options.valid[key] = value;
else options.illegal.push(option);
});
if (options.illegal.length !== 0) {
throw new SRVError(
`Illegal uri options: ${options.illegal}. Allowed options: ${
Object.values(AllowedOption)
}`,
);
}
return {
servers,
options: options.valid,
};
}
}