feat: add server config validation
This commit is contained in:
104
plugin/core/config.ts
Normal file
104
plugin/core/config.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
export interface YonexusServerConfig {
|
||||
followerIdentifiers: string[];
|
||||
notifyBotToken: string;
|
||||
adminUserId: string;
|
||||
listenHost?: string;
|
||||
listenPort: number;
|
||||
publicWsUrl?: string;
|
||||
}
|
||||
|
||||
export class YonexusServerConfigError extends Error {
|
||||
readonly issues: string[];
|
||||
|
||||
constructor(issues: string[]) {
|
||||
super(`Invalid Yonexus.Server config: ${issues.join("; ")}`);
|
||||
this.name = "YonexusServerConfigError";
|
||||
this.issues = issues;
|
||||
}
|
||||
}
|
||||
|
||||
function isNonEmptyString(value: unknown): value is string {
|
||||
return typeof value === "string" && value.trim().length > 0;
|
||||
}
|
||||
|
||||
function normalizeOptionalString(value: unknown): string | undefined {
|
||||
if (value === undefined || value === null) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const trimmed = value.trim();
|
||||
return trimmed.length > 0 ? trimmed : undefined;
|
||||
}
|
||||
|
||||
function isValidPort(value: unknown): value is number {
|
||||
return Number.isInteger(value) && value >= 1 && value <= 65535;
|
||||
}
|
||||
|
||||
function isValidWsUrl(value: string): boolean {
|
||||
try {
|
||||
const url = new URL(value);
|
||||
return url.protocol === "ws:" || url.protocol === "wss:";
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function validateYonexusServerConfig(raw: unknown): YonexusServerConfig {
|
||||
const source = raw as Record<string, unknown> | null;
|
||||
const issues: string[] = [];
|
||||
|
||||
const rawIdentifiers = source?.followerIdentifiers;
|
||||
const followerIdentifiers = Array.isArray(rawIdentifiers)
|
||||
? rawIdentifiers
|
||||
.filter((value): value is string => typeof value === "string")
|
||||
.map((value) => value.trim())
|
||||
.filter((value) => value.length > 0)
|
||||
: [];
|
||||
|
||||
if (!Array.isArray(rawIdentifiers) || followerIdentifiers.length === 0) {
|
||||
issues.push("followerIdentifiers must contain at least one non-empty identifier");
|
||||
}
|
||||
|
||||
if (new Set(followerIdentifiers).size !== followerIdentifiers.length) {
|
||||
issues.push("followerIdentifiers must not contain duplicates");
|
||||
}
|
||||
|
||||
const notifyBotToken = source?.notifyBotToken;
|
||||
if (!isNonEmptyString(notifyBotToken)) {
|
||||
issues.push("notifyBotToken is required");
|
||||
}
|
||||
|
||||
const adminUserId = source?.adminUserId;
|
||||
if (!isNonEmptyString(adminUserId)) {
|
||||
issues.push("adminUserId is required");
|
||||
}
|
||||
|
||||
const listenPort = source?.listenPort;
|
||||
if (!isValidPort(listenPort)) {
|
||||
issues.push("listenPort must be an integer between 1 and 65535");
|
||||
}
|
||||
|
||||
const listenHost = normalizeOptionalString(source?.listenHost) ?? "0.0.0.0";
|
||||
const publicWsUrl = normalizeOptionalString(source?.publicWsUrl);
|
||||
|
||||
if (publicWsUrl !== undefined && !isValidWsUrl(publicWsUrl)) {
|
||||
issues.push("publicWsUrl must be a valid ws:// or wss:// URL when provided");
|
||||
}
|
||||
|
||||
if (issues.length > 0) {
|
||||
throw new YonexusServerConfigError(issues);
|
||||
}
|
||||
|
||||
return {
|
||||
followerIdentifiers,
|
||||
notifyBotToken: notifyBotToken.trim(),
|
||||
adminUserId: adminUserId.trim(),
|
||||
listenHost,
|
||||
listenPort,
|
||||
publicWsUrl
|
||||
};
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
export { validateYonexusServerConfig, YonexusServerConfigError } from "./core/config.js";
|
||||
export type { YonexusServerConfig } from "./core/config.js";
|
||||
|
||||
export interface YonexusServerPluginManifest {
|
||||
readonly name: "Yonexus.Server";
|
||||
readonly version: string;
|
||||
|
||||
Reference in New Issue
Block a user