/**
 * Failed to minify the file using Terser v5.39.0. Serving the original version.
 * Original file: /npm/@jaredwray/mockhttp@1.5.1/dist/index.mjs
 *
 * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
 */
import process$1 from "node:process";
import * as fsPromises from "node:fs/promises";
import path, { join } from "node:path";
import fastifyCookie from "@fastify/cookie";
import fastifyHelmet from "@fastify/helmet";
import fastifyRateLimit from "@fastify/rate-limit";
import fastifyStatic from "@fastify/static";
import { fastifySwagger } from "@fastify/swagger";
import { detect } from "detect-port";
import Fastify from "fastify";
import { Hookified } from "hookified";
import * as crypto$1 from "node:crypto";
import crypto, { randomBytes, randomInt, randomUUID, timingSafeEqual } from "node:crypto";
import { Buffer as Buffer$1 } from "node:buffer";
import { readFileSync } from "node:fs";
import { brotliCompressSync, deflateSync, gzipSync } from "node:zlib";
import { escape } from "html-escaper";
import { fastifySwaggerUi } from "@fastify/swagger-ui";
//#region src/certificate.ts
function encodeLength(length) {
	if (length < 128) return Buffer.from([length]);
	const bytes = [];
	let temp = length;
	while (temp > 0) {
		bytes.unshift(temp & 255);
		temp >>= 8;
	}
	return Buffer.from([128 | bytes.length, ...bytes]);
}
function encodeTlv(tag, value) {
	return Buffer.concat([
		Buffer.from([tag]),
		encodeLength(value.length),
		value
	]);
}
function encodeSequence(...elements) {
	return encodeTlv(48, Buffer.concat(elements));
}
function encodeSet(...elements) {
	return encodeTlv(49, Buffer.concat(elements));
}
function encodeInteger(value) {
	let buf;
	if (typeof value === "number") if (value === 0) buf = Buffer.from([0]);
	else {
		const bytes = [];
		let temp = value;
		while (temp > 0) {
			bytes.unshift(temp & 255);
			temp >>= 8;
		}
		if (bytes[0] & 128) bytes.unshift(0);
		buf = Buffer.from(bytes);
	}
	else if (value.length > 0 && value[0] & 128) buf = Buffer.concat([Buffer.from([0]), value]);
	else buf = value;
	return encodeTlv(2, buf);
}
function encodeBitString(value) {
	return encodeTlv(3, Buffer.concat([Buffer.from([0]), value]));
}
function encodeOctetString(value) {
	return encodeTlv(4, value);
}
function encodeOid(oid) {
	const bytes = [];
	bytes.push(40 * oid[0] + oid[1]);
	for (let i = 2; i < oid.length; i++) {
		let component = oid[i];
		if (component < 128) bytes.push(component);
		else {
			const encoded = [];
			encoded.push(component & 127);
			component >>= 7;
			while (component > 0) {
				encoded.push(component & 127 | 128);
				component >>= 7;
			}
			encoded.reverse();
			bytes.push(...encoded);
		}
	}
	return encodeTlv(6, Buffer.from(bytes));
}
function encodeUtf8String(value) {
	return encodeTlv(12, Buffer.from(value, "utf8"));
}
/**
* Encode a date as ASN.1 time. Uses UTCTime (tag 0x17) for years 1950-2049
* and GeneralizedTime (tag 0x18) for years >= 2050, per RFC 5280 Section 4.1.2.5.
*/
function encodeTime(date) {
	const fullYear = date.getUTCFullYear();
	const timePart = `${pad2(date.getUTCMonth() + 1)}${pad2(date.getUTCDate())}${pad2(date.getUTCHours())}${pad2(date.getUTCMinutes())}${pad2(date.getUTCSeconds())}Z`;
	if (fullYear >= 2050) {
		const str = `${fullYear}${timePart}`;
		return encodeTlv(24, Buffer.from(str, "ascii"));
	}
	const str = `${pad2(fullYear % 100)}${timePart}`;
	return encodeTlv(23, Buffer.from(str, "ascii"));
}
function encodeContextSpecific(tag, value) {
	return encodeTlv(160 | tag, value);
}
function pad2(n) {
	return n.toString().padStart(2, "0");
}
const OID_SHA256_WITH_RSA = [
	1,
	2,
	840,
	113549,
	1,
	1,
	11
];
const OID_COMMON_NAME = [
	2,
	5,
	4,
	3
];
const OID_SUBJECT_ALT_NAME = [
	2,
	5,
	29,
	17
];
function buildAlgorithmIdentifier() {
	return encodeSequence(encodeOid(OID_SHA256_WITH_RSA), encodeTlv(5, Buffer.alloc(0)));
}
function buildName(cn) {
	return encodeSequence(encodeSet(encodeSequence(encodeOid(OID_COMMON_NAME), encodeUtf8String(cn))));
}
function buildValidity(notBefore, notAfter) {
	return encodeSequence(encodeTime(notBefore), encodeTime(notAfter));
}
function encodeIpAddress(ip) {
	if (ip.includes(".")) {
		const parts = ip.split(".").map(Number);
		return Buffer.from(parts);
	}
	const expanded = expandIpv6(ip);
	const buf = Buffer.alloc(16);
	const groups = expanded.split(":");
	for (let i = 0; i < 8; i++) {
		const val = Number.parseInt(groups[i], 16);
		buf.writeUInt16BE(val, i * 2);
	}
	return buf;
}
function expandIpv6(ip) {
	if (ip.includes("::")) {
		const [left, right] = ip.split("::");
		const leftGroups = left ? left.split(":") : [];
		const rightGroups = right ? right.split(":") : [];
		const missing = 8 - leftGroups.length - rightGroups.length;
		const middle = Array.from({ length: missing }).fill("0000");
		return [
			...leftGroups,
			...middle,
			...rightGroups
		].map((g) => g.padStart(4, "0")).join(":");
	}
	return ip.split(":").map((g) => g.padStart(4, "0")).join(":");
}
function buildSubjectAltNameExtension(altNames) {
	const names = [];
	for (const alt of altNames) if (alt.type === "dns") names.push(encodeTlv(130, Buffer.from(alt.value, "ascii")));
	else names.push(encodeTlv(135, encodeIpAddress(alt.value)));
	const sanValue = encodeSequence(...names);
	return encodeSequence(encodeOid(OID_SUBJECT_ALT_NAME), encodeOctetString(sanValue));
}
function buildExtensions(altNames) {
	return encodeContextSpecific(3, encodeSequence(buildSubjectAltNameExtension(altNames)));
}
function buildTbsCertificate(serialNumber, issuerCn, notBefore, notAfter, subjectCn, publicKeyDer, altNames) {
	return encodeSequence(encodeContextSpecific(0, encodeInteger(2)), encodeInteger(serialNumber), buildAlgorithmIdentifier(), buildName(issuerCn), buildValidity(notBefore, notAfter), buildName(subjectCn), publicKeyDer, buildExtensions(altNames));
}
function derToPem(der, label) {
	const base64 = der.toString("base64");
	const lines = [];
	for (let i = 0; i < base64.length; i += 64) lines.push(base64.slice(i, i + 64));
	return `-----BEGIN ${label}-----\n${lines.join("\n")}\n-----END ${label}-----\n`;
}
/**
* Generate a self-signed certificate using only Node.js built-in crypto.
* Returns PEM-encoded certificate and private key strings.
*/
function generateCertificate(options) {
	const commonName = options?.commonName ?? "localhost";
	const validityDays = options?.validityDays ?? 365;
	const keySize = options?.keySize ?? 2048;
	const altNames = options?.altNames ?? [
		{
			type: "dns",
			value: "localhost"
		},
		{
			type: "ip",
			value: "127.0.0.1"
		},
		{
			type: "ip",
			value: "::1"
		}
	];
	const { publicKey, privateKey } = crypto$1.generateKeyPairSync("rsa", {
		modulusLength: keySize,
		publicKeyEncoding: {
			type: "spki",
			format: "der"
		},
		privateKeyEncoding: {
			type: "pkcs8",
			format: "pem"
		}
	});
	const notBefore = /* @__PURE__ */ new Date();
	const notAfter = /* @__PURE__ */ new Date();
	notAfter.setDate(notAfter.getDate() + validityDays);
	const serialBytes = crypto$1.randomBytes(16);
	serialBytes[0] &= 127;
	let start = 0;
	/* v8 ignore start -- depends on random data producing leading zeros */
	while (start < serialBytes.length - 1 && serialBytes[start] === 0) start++;
	const tbsCertificate = buildTbsCertificate(serialBytes.subarray(start), commonName, notBefore, notAfter, commonName, publicKey, altNames);
	const signer = crypto$1.createSign("SHA256");
	signer.update(tbsCertificate);
	const signature = signer.sign(privateKey);
	return {
		cert: derToPem(encodeSequence(tbsCertificate, buildAlgorithmIdentifier(), encodeBitString(signature)), "CERTIFICATE"),
		key: privateKey
	};
}
/**
* Generate a self-signed certificate and write PEM files to disk.
* Returns the same CertificateResult as generateCertificate().
*/
async function generateCertificateFiles(options) {
	const result = generateCertificate(options);
	await fsPromises.writeFile(options.certPath, result.cert, "utf8");
	await fsPromises.writeFile(options.keyPath, result.key, "utf8");
	return result;
}
//#endregion
//#region src/fastify-config.ts
function getFastifyConfig(logging = true) {
	return { logger: logging ? { transport: {
		target: "pino-pretty",
		options: {
			colorize: true,
			translateTime: true,
			ignore: "pid,hostname",
			singleLine: true
		}
	} } : false };
}
//#endregion
//#region src/routes/anything/index.ts
const anythingSchema = {
	description: "Will return anything that you send it",
	tags: ["Anything"],
	response: { 200: {
		type: "object",
		properties: {
			args: {
				type: "object",
				description: "Route parameters",
				additionalProperties: true
			},
			data: {
				type: "object",
				description: "Request body data",
				additionalProperties: true
			},
			files: {
				type: "object",
				description: "Uploaded files",
				additionalProperties: true
			},
			form: {
				type: "object",
				description: "Form data",
				additionalProperties: true
			},
			headers: {
				type: "object",
				description: "Request headers",
				additionalProperties: true
			},
			json: {
				type: "object",
				description: "Parsed JSON body",
				additionalProperties: true
			},
			method: {
				type: "string",
				description: "HTTP method used"
			},
			origin: {
				type: "string",
				description: "Origin of the request"
			},
			url: {
				type: "string",
				description: "Request URL"
			}
		},
		additionalProperties: false
	} }
};
const anythingRoute = (fastify) => {
	const handler = async (request, reply) => {
		const response = {
			args: request.query,
			data: request.body ?? {},
			files: request.raw?.files ?? {},
			form: request.body ?? {},
			headers: request.headers,
			json: request.body ?? {},
			method: request.method,
			origin: request.headers.origin ?? request.headers.host ?? "",
			url: request.url
		};
		await reply.send(response);
	};
	const noValidation = {
		schema: anythingSchema,
		validatorCompiler: () => () => true
	};
	fastify.get("/anything", noValidation, handler);
	fastify.post("/anything", noValidation, handler);
	fastify.put("/anything", noValidation, handler);
	fastify.patch("/anything", noValidation, handler);
	fastify.delete("/anything", noValidation, handler);
};
//#endregion
//#region src/routes/auth/basic.ts
const parseBasic$1 = (header) => {
	if (!header || typeof header !== "string") return;
	const spaceIndex = header.indexOf(" ");
	if (spaceIndex === -1) return;
	const scheme = header.slice(0, spaceIndex);
	const value = header.slice(spaceIndex + 1);
	if (scheme.toLowerCase() !== "basic" || !value) return;
	try {
		const decoded = Buffer$1.from(value, "base64").toString("utf8");
		const idx = decoded.indexOf(":");
		if (idx === -1) return;
		const username = decoded.slice(0, idx);
		const password = decoded.slice(idx + 1);
		if (username === "") return;
		return {
			username,
			password
		};
	} catch {
		/* v8 ignore next -- @preserve */
		return;
	}
};
const safeCompare = (a, b) => {
	if (a.length !== b.length) return false;
	try {
		return timingSafeEqual(Buffer$1.from(a, "utf8"), Buffer$1.from(b, "utf8"));
	} catch {
		/* v8 ignore next -- @preserve */
		return false;
	}
};
const basicAuthSchema = {
	description: "HTTP Basic authentication. Succeeds only if the user/pass provided in the path matches the Basic Authorization header.",
	tags: ["Auth"],
	params: {
		type: "object",
		properties: {
			user: { type: "string" },
			passwd: { type: "string" }
		},
		required: ["user", "passwd"]
	},
	response: {
		200: {
			type: "object",
			properties: {
				authenticated: { type: "boolean" },
				user: { type: "string" }
			},
			required: ["authenticated", "user"]
		},
		401: {
			type: "object",
			properties: { message: { type: "string" } },
			required: ["message"]
		}
	}
};
const basicAuthRoute = (fastify) => {
	fastify.get("/basic-auth/:user/:passwd", { schema: basicAuthSchema }, async (request, reply) => {
		const { user, passwd } = request.params;
		if (!user || typeof user !== "string" || typeof passwd !== "string") {
			reply.header("WWW-Authenticate", "Basic realm=\"mockhttp\"");
			return reply.status(401).send({ message: "Unauthorized" });
		}
		const parsed = parseBasic$1(request.headers.authorization);
		if (!parsed || !safeCompare(parsed.username, user) || !safeCompare(parsed.password, passwd)) {
			reply.header("WWW-Authenticate", "Basic realm=\"mockhttp\"");
			return reply.status(401).send({ message: "Unauthorized" });
		}
		return reply.send({
			authenticated: true,
			user
		});
	});
};
//#endregion
//#region src/routes/auth/bearer.ts
const parseBearer = (header) => {
	if (!header) return;
	const [scheme, token] = header.split(" ");
	if (!scheme || scheme.toLowerCase() !== "bearer" || !token) return;
	return token;
};
const bearerAuthSchema = {
	description: "HTTP Bearer authentication. Succeeds if any Bearer token is present unless ?required=true is provided.",
	tags: ["Auth"],
	querystring: {
		type: "object",
		properties: { required: {
			type: "boolean",
			default: true
		} }
	},
	response: {
		200: {
			type: "object",
			properties: {
				authenticated: { type: "boolean" },
				token: { type: "string" }
			},
			required: ["authenticated", "token"]
		},
		401: {
			type: "object",
			properties: { message: { type: "string" } },
			required: ["message"]
		}
	}
};
const bearerAuthRoute = (fastify) => {
	fastify.get("/bearer", { schema: bearerAuthSchema }, async (request, reply) => {
		const token = parseBearer(request.headers.authorization);
		const required = request.query?.required ?? true;
		if (!token && required) {
			reply.header("WWW-Authenticate", "Bearer");
			return reply.status(401).send({ message: "Unauthorized" });
		}
		if (!token) return reply.send({
			authenticated: false,
			token: ""
		});
		return reply.send({
			authenticated: true,
			token
		});
	});
};
//#endregion
//#region src/routes/auth/digest.ts
const makeNonce = () => crypto.randomBytes(16).toString("hex");
const algorithms = new Set([
	"MD5",
	"md5",
	"SHA-256",
	"sha-256",
	"SHA-512",
	"sha-512"
]);
const hashAlg = (alg) => {
	if (!alg) return "md5";
	const a = alg.toLowerCase();
	if (a === "md5") return "md5";
	if (a === "sha-256") return "sha256";
	if (a === "sha-512") return "sha512";
	return "md5";
};
const h = (algo, data) => crypto.createHash(algo).update(data).digest("hex");
const parseDigest = (header) => {
	if (!header) return;
	const spaceIndex = header.indexOf(" ");
	/* v8 ignore next -- @preserve */
	if (spaceIndex === -1) return;
	const scheme = header.slice(0, spaceIndex);
	const rest = header.slice(spaceIndex + 1);
	if (!scheme || scheme.toLowerCase() !== "digest" || !rest) return;
	const out = {};
	const parts = [];
	let current = "";
	let inQuotes = false;
	for (const char of rest) if (char === "\"") {
		inQuotes = !inQuotes;
		current += char;
	} else if (char === "," && !inQuotes) {
		if (current.trim()) parts.push(current.trim());
		current = "";
	} else current += char;
	if (current.trim()) parts.push(current.trim());
	if (parts.length === 0) return out;
	for (const p of parts) {
		const equalIndex = p.indexOf("=");
		if (equalIndex === -1) continue;
		const k = p.slice(0, equalIndex).trim();
		let value = p.slice(equalIndex + 1).trim();
		if (value.startsWith("\"") && value.endsWith("\"")) value = value.slice(1, -1);
		out[k] = value;
	}
	return out;
};
const digestAuthSchema = {
	description: "HTTP Digest authentication. Mirrors httpbin behavior for testing clients.",
	tags: ["Auth"],
	params: {
		type: "object",
		properties: {
			qop: { type: "string" },
			user: { type: "string" },
			passwd: { type: "string" },
			algorithm: { type: "string" },
			stale_after: { type: "string" }
		},
		required: [
			"qop",
			"user",
			"passwd"
		]
	}
};
const digestAuthRoute = (fastify) => {
	fastify.get("/digest-auth/:qop/:user/:passwd", { schema: digestAuthSchema }, async (request, reply) => {
		const { user, passwd } = request.params;
		const realm = "mockhttp";
		const nonce = makeNonce();
		const auth = parseDigest(request.headers.authorization);
		if (!auth) {
			reply.header("WWW-Authenticate", `Digest realm="${realm}", qop="auth,auth-int", nonce="${nonce}", opaque="${makeNonce()}"`);
			return reply.status(401).send({ message: "Unauthorized" });
		}
		if (auth.username !== user) {
			reply.header("WWW-Authenticate", `Digest realm="${realm}", qop="auth,auth-int", nonce="${nonce}", opaque="${makeNonce()}"`);
			return reply.status(401).send({ message: "Unauthorized" });
		}
		const method = "GET";
		const uri = auth.uri ?? request.url.replace(/^https?:\/\/[^/]+/, "");
		const algo = hashAlg(auth.algorithm);
		const ha1 = h(algo, `${user}:${realm}:${passwd}`);
		const ha2 = h(algo, `${method}:${uri}`);
		let expected = "";
		expected = auth.qop ? h(algo, `${ha1}:${auth.nonce}:${auth.nc}:${auth.cnonce}:${auth.qop}:${ha2}`) : h(algo, `${ha1}:${auth.nonce}:${ha2}`);
		if (!auth.response || auth.response !== expected) {
			reply.header("WWW-Authenticate", `Digest realm="${realm}", qop="auth,auth-int", nonce="${nonce}", opaque="${makeNonce()}"`);
			return reply.status(401).send({ message: "Unauthorized" });
		}
		return reply.send({
			authenticated: true,
			user
		});
	});
	fastify.get("/digest-auth/:qop/:user/:passwd/:algorithm", { schema: digestAuthSchema }, async (request, reply) => {
		const { user, passwd, algorithm } = request.params;
		const realm = "mockhttp";
		const algoHeader = algorithms.has(String(algorithm)) ? String(algorithm) : "MD5";
		const nonce = makeNonce();
		const auth = parseDigest(request.headers.authorization);
		if (!auth) {
			reply.header("WWW-Authenticate", `Digest realm="${realm}", qop="auth,auth-int", algorithm=${algoHeader}, nonce="${nonce}", opaque="${makeNonce()}"`);
			return reply.status(401).send({ message: "Unauthorized" });
		}
		if (auth.username !== user) {
			reply.header("WWW-Authenticate", `Digest realm="${realm}", qop="auth,auth-int", algorithm=${algoHeader}, nonce="${nonce}", opaque="${makeNonce()}"`);
			return reply.status(401).send({ message: "Unauthorized" });
		}
		const method = "GET";
		const uri = auth.uri ?? request.url.replace(/^https?:\/\/[^/]+/, "");
		const algo = hashAlg(auth.algorithm ?? algoHeader);
		const ha1 = h(algo, `${user}:${realm}:${passwd}`);
		const ha2 = h(algo, `${method}:${uri}`);
		const expected = auth.qop ? h(algo, `${ha1}:${auth.nonce}:${auth.nc}:${auth.cnonce}:${auth.qop}:${ha2}`) : h(algo, `${ha1}:${auth.nonce}:${ha2}`);
		if (!auth.response || auth.response !== expected) {
			reply.header("WWW-Authenticate", `Digest realm="${realm}", qop="auth,auth-int", algorithm=${algoHeader}, nonce="${nonce}", opaque="${makeNonce()}"`);
			return reply.status(401).send({ message: "Unauthorized" });
		}
		return reply.send({
			authenticated: true,
			user
		});
	});
};
//#endregion
//#region src/routes/auth/hidden-basic.ts
const parseBasic = (header) => {
	if (!header) return;
	const [scheme, value] = header.split(" ");
	if (!scheme || scheme.toLowerCase() !== "basic" || !value) return;
	try {
		const decoded = Buffer$1.from(value, "base64").toString("utf8");
		const idx = decoded.indexOf(":");
		if (idx === -1) return;
		return {
			username: decoded.slice(0, idx),
			password: decoded.slice(idx + 1)
		};
	} catch {
		/* v8 ignore next -- @preserve */
		return;
	}
};
const hiddenBasicAuthSchema = {
	description: "HTTP Basic authentication with docs hidden. Mirrors /basic-auth but hides from schema.",
	tags: ["Auth"],
	hide: true,
	params: {
		type: "object",
		properties: {
			user: { type: "string" },
			passwd: { type: "string" }
		},
		required: ["user", "passwd"]
	}
};
const hiddenBasicAuthRoute = (fastify) => {
	fastify.get("/hidden-basic-auth/:user/:passwd", { schema: hiddenBasicAuthSchema }, async (request, reply) => {
		const { user, passwd } = request.params;
		const parsed = parseBasic(request.headers.authorization);
		if (!parsed || parsed.username !== user || parsed.password !== passwd) {
			reply.header("WWW-Authenticate", "Basic realm=\"mockhttp\"");
			return reply.status(401).send({ message: "Unauthorized" });
		}
		return reply.send({
			authenticated: true,
			user
		});
	});
};
//#endregion
//#region src/routes/cookies/delete.ts
const cookiesDeleteSchema = {
	description: "Delete a cookie",
	tags: ["Cookies"],
	querystring: {
		type: "object",
		properties: { name: { type: "string" } },
		required: ["name"],
		additionalProperties: false
	},
	response: { 204: { type: "null" } }
};
const deleteCookieRoute = (fastify) => {
	fastify.delete("/cookies", { schema: cookiesDeleteSchema }, async (request, reply) => {
		const { name } = request.query;
		reply.clearCookie(name, { path: "/" });
		return reply.status(204).send();
	});
};
//#endregion
//#region src/routes/cookies/get.ts
const getCookiesRouteSchema = {
	description: "Return cookies from the request",
	tags: ["Cookies"],
	response: { 200: {
		type: "object",
		properties: { cookies: {
			type: "object",
			additionalProperties: { type: "string" }
		} },
		required: ["cookies"]
	} }
};
const getCookiesRoute = (fastify) => {
	fastify.get("/cookies", { schema: getCookiesRouteSchema }, async (request, reply) => {
		reply.type("application/json");
		await reply.send({ cookies: request.cookies });
	});
};
//#endregion
//#region src/routes/cookies/post.ts
const postCookieRouteSchema = {
	description: "Set a cookie",
	tags: ["Cookies"],
	body: {
		type: "object",
		properties: {
			name: { type: "string" },
			value: { type: "string" },
			expires: {
				type: "string",
				format: "date-time"
			}
		},
		required: ["name", "value"],
		additionalProperties: false
	},
	response: { 200: {
		type: "object",
		properties: { cookies: {
			type: "object",
			additionalProperties: { type: "string" }
		} },
		required: ["cookies"]
	} }
};
const postCookieRoute = (fastify) => {
	fastify.post("/cookies", { schema: postCookieRouteSchema }, async (request, reply) => {
		const { name, value, expires } = request.body;
		if (expires) {
			const date = new Date(expires);
			reply.setCookie(name, value, {
				path: "/",
				expires: date
			});
		} else reply.setCookie(name, value, { path: "/" });
		return reply.status(200).send();
	});
};
//#endregion
//#region src/routes/dynamic-data/base64.ts
const base64Schema = {
	description: "Decodes base64url-encoded string",
	tags: ["Dynamic Data"],
	params: {
		type: "object",
		properties: { value: {
			type: "string",
			description: "Base64 encoded value to decode"
		} },
		required: ["value"]
	},
	response: {
		200: {
			type: "string",
			description: "Decoded value"
		},
		400: {
			type: "object",
			properties: { error: { type: "string" } }
		}
	}
};
function isValidBase64(str) {
	if (!/^[A-Za-z0-9+/\-_]*={0,2}$/.test(str)) return false;
	return str.replace(/=+$/, "").length % 4 !== 1;
}
const base64Route = (fastify) => {
	fastify.get("/base64/:value", { schema: base64Schema }, async (request, reply) => {
		const { value } = request.params;
		if (!isValidBase64(value)) return reply.code(400).send({ error: "Incorrect Base64 data" });
		const normalizedValue = value.replace(/-/g, "+").replace(/_/g, "/");
		const decoded = Buffer.from(normalizedValue, "base64").toString("utf-8");
		return reply.header("Content-Type", "text/html; charset=utf-8").send(decoded);
	});
};
//#endregion
//#region src/routes/dynamic-data/bytes.ts
const bytesSchema = {
	description: "Returns n random bytes generated with given seed",
	tags: ["Dynamic Data"],
	params: {
		type: "object",
		properties: { n: {
			type: "string",
			description: "Number of bytes to generate"
		} },
		required: ["n"]
	},
	querystring: {
		type: "object",
		properties: { seed: {
			type: "string",
			description: "Seed for random number generation"
		} }
	},
	response: {
		200: {
			type: "string",
			description: "Random binary data"
		},
		400: {
			type: "object",
			properties: { error: { type: "string" } }
		}
	}
};
function seededRandom$1(seed) {
	let state = seed;
	return () => {
		state = state * 1103515245 + 12345 & 2147483647;
		return state / 2147483647;
	};
}
function generateSeededBytes$1(n, seed) {
	const random = seededRandom$1(seed);
	const bytes = Buffer.alloc(n);
	for (let i = 0; i < n; i++) bytes[i] = Math.floor(random() * 256);
	return bytes;
}
const MAX_BYTES$3 = 100 * 1024;
const bytesRoute = (fastify) => {
	fastify.get("/bytes/:n", { schema: bytesSchema }, async (request, reply) => {
		const n = Number.parseInt(request.params.n, 10);
		if (Number.isNaN(n) || n < 0) return reply.code(400).send({ error: "n must be a non-negative integer" });
		const limitedN = Math.min(n, MAX_BYTES$3);
		const seedParam = request.query.seed;
		let bytes;
		if (seedParam !== void 0) {
			const seed = Number.parseInt(seedParam, 10);
			if (Number.isNaN(seed)) return reply.code(400).send({ error: "seed must be an integer" });
			bytes = generateSeededBytes$1(limitedN, seed);
		} else bytes = randomBytes(limitedN);
		return reply.header("Content-Type", "application/octet-stream").header("Content-Length", bytes.length).send(bytes);
	});
};
//#endregion
//#region src/routes/dynamic-data/delay.ts
const delaySchema = {
	description: "Returns a delayed response (max of 10 seconds)",
	tags: ["Dynamic Data"],
	params: {
		type: "object",
		properties: { delay: {
			type: "string",
			description: "Delay in seconds (max 10)"
		} },
		required: ["delay"]
	},
	querystring: {
		type: "object",
		additionalProperties: true
	},
	response: {
		200: {
			type: "object",
			properties: {
				args: { type: "object" },
				data: { type: "string" },
				files: { type: "object" },
				form: { type: "object" },
				headers: { type: "object" },
				origin: { type: "string" },
				url: { type: "string" }
			}
		},
		400: {
			type: "object",
			properties: { error: { type: "string" } }
		}
	}
};
const MAX_DELAY$1 = 10;
function sleep$2(ms) {
	return new Promise((resolve) => setTimeout(resolve, ms));
}
const delayRoute = (fastify) => {
	const handler = async (request, reply) => {
		const delay = Number.parseFloat(request.params.delay);
		if (Number.isNaN(delay) || delay < 0) return reply.code(400).send({ error: "delay must be a non-negative number" });
		await sleep$2(Math.min(delay, MAX_DELAY$1) * 1e3);
		const { protocol } = request;
		/* v8 ignore next -- @preserve */
		const host = request.headers.host || "localhost";
		return {
			args: request.query || {},
			data: "",
			files: {},
			form: {},
			headers: request.headers,
			origin: request.ip,
			url: `${protocol}://${host}${request.url}`
		};
	};
	fastify.get("/delay/:delay", { schema: delaySchema }, handler);
	fastify.post("/delay/:delay", { schema: delaySchema }, handler);
	fastify.put("/delay/:delay", { schema: delaySchema }, handler);
	fastify.patch("/delay/:delay", { schema: delaySchema }, handler);
	fastify.delete("/delay/:delay", { schema: delaySchema }, handler);
};
//#endregion
//#region src/routes/dynamic-data/drip.ts
const dripSchema = {
	description: "Drips data over a duration after an optional initial delay, then (optionally) returns with the given status code",
	tags: ["Dynamic Data"],
	querystring: {
		type: "object",
		properties: {
			duration: {
				type: "string",
				description: "Duration in seconds over which to drip the data (default: 2)"
			},
			numbytes: {
				type: "string",
				description: "Number of bytes to send (default: 10)"
			},
			code: {
				type: "string",
				description: "HTTP status code to return (default: 200)"
			},
			delay: {
				type: "string",
				description: "Initial delay in seconds before starting (default: 2)"
			}
		}
	},
	response: {
		200: {
			type: "string",
			description: "Dripped data"
		},
		400: {
			type: "object",
			properties: { error: { type: "string" } }
		}
	}
};
function sleep$1(ms) {
	return new Promise((resolve) => setTimeout(resolve, ms));
}
const DEFAULT_DURATION = 2;
const DEFAULT_NUMBYTES = 10;
const DEFAULT_CODE = 200;
const DEFAULT_DELAY = 2;
const MAX_DURATION = 10;
const MAX_DELAY = 10;
const MAX_BYTES$2 = 10 * 1024 * 1024;
const dripRoute = (fastify) => {
	fastify.get("/drip", { schema: dripSchema }, async (request, reply) => {
		/* v8 ignore next 2 -- @preserve */
		const duration = request.query.duration ? Number.parseFloat(request.query.duration) : DEFAULT_DURATION;
		const numbytes = request.query.numbytes ? Number.parseInt(request.query.numbytes, 10) : DEFAULT_NUMBYTES;
		/* v8 ignore next 2 -- @preserve */
		const code = request.query.code ? Number.parseInt(request.query.code, 10) : DEFAULT_CODE;
		/* v8 ignore next 3 -- @preserve */
		const delay = request.query.delay ? Number.parseFloat(request.query.delay) : DEFAULT_DELAY;
		if (Number.isNaN(duration) || duration < 0) return reply.code(400).send({ error: "duration must be a non-negative number" });
		if (Number.isNaN(numbytes) || numbytes < 0) return reply.code(400).send({ error: "numbytes must be a non-negative integer" });
		if (Number.isNaN(code) || code < 100 || code > 599) return reply.code(400).send({ error: "code must be a valid HTTP status code" });
		if (Number.isNaN(delay) || delay < 0) return reply.code(400).send({ error: "delay must be a non-negative number" });
		const actualDuration = Math.min(duration, MAX_DURATION);
		const actualDelay = Math.min(delay, MAX_DELAY);
		const actualBytes = Math.min(numbytes, MAX_BYTES$2);
		if (actualDelay > 0) await sleep$1(actualDelay * 1e3);
		reply.raw.writeHead(code, {
			"Content-Type": "application/octet-stream",
			"Content-Length": actualBytes.toString()
		});
		if (actualBytes === 0) {
			reply.raw.end();
			return;
		}
		const intervalMs = actualBytes > 1 ? actualDuration * 1e3 / (actualBytes - 1) : 0;
		const byte = Buffer.from("*");
		for (let i = 0; i < actualBytes; i++) {
			reply.raw.write(byte);
			if (i < actualBytes - 1 && intervalMs > 0) await sleep$1(intervalMs);
		}
		reply.raw.end();
	});
};
//#endregion
//#region src/routes/dynamic-data/links.ts
const linksSchema = {
	description: "Generate a page containing n links to other pages which do the same",
	tags: ["Dynamic Data"],
	params: {
		type: "object",
		properties: {
			n: {
				type: "string",
				description: "Number of links to generate"
			},
			offset: {
				type: "string",
				description: "Starting offset for pagination (default: 0)"
			}
		},
		required: ["n"]
	},
	response: {
		200: {
			type: "string",
			description: "HTML page with links"
		},
		400: {
			type: "object",
			properties: { error: { type: "string" } }
		}
	}
};
const MAX_LINKS = 200;
const linksRoute = (fastify) => {
	fastify.get("/links/:n/:offset?", { schema: linksSchema }, async (request, reply) => {
		const n = Number.parseInt(request.params.n, 10);
		const offset = request.params.offset ? Number.parseInt(request.params.offset, 10) : 0;
		if (Number.isNaN(n) || n < 0) return reply.code(400).send({ error: "n must be a non-negative integer" });
		if (Number.isNaN(offset) || offset < 0) return reply.code(400).send({ error: "offset must be a non-negative integer" });
		const limitedN = Math.min(n, MAX_LINKS);
		const links = [];
		for (let i = 0; i < limitedN; i++) if (i === offset) links.push(`${i} `);
		else links.push(`<a href="/links/${limitedN}/${i}">${i}</a> `);
		const html = `<html>
<head>
<title>Links</title>
</head>
<body>
${links.join("")}
</body>
</html>`;
		return reply.header("Content-Type", "text/html; charset=utf-8").send(html);
	});
};
//#endregion
//#region src/routes/dynamic-data/range.ts
const rangeSchema = {
	description: "Streams n random bytes generated, and supports HTTP Range requests",
	tags: ["Dynamic Data"],
	params: {
		type: "object",
		properties: { numbytes: {
			type: "string",
			description: "Number of bytes available"
		} },
		required: ["numbytes"]
	},
	querystring: {
		type: "object",
		properties: { duration: {
			type: "string",
			description: "Delay before sending response in seconds"
		} }
	},
	response: {
		200: {
			type: "string",
			description: "Binary data"
		},
		206: {
			type: "string",
			description: "Partial binary data"
		},
		400: {
			type: "object",
			properties: { error: { type: "string" } }
		},
		416: {
			type: "object",
			properties: { error: { type: "string" } }
		}
	}
};
const MAX_BYTES$1 = 100 * 1024;
function sleep(ms) {
	return new Promise((resolve) => setTimeout(resolve, ms));
}
function generateBytes(n) {
	const alphabet = "abcdefghijklmnopqrstuvwxyz";
	const bytes = Buffer.alloc(n);
	for (let i = 0; i < n; i++) bytes[i] = alphabet.charCodeAt(i % 26);
	return bytes;
}
function parseRangeHeader(rangeHeader, totalSize) {
	const match = rangeHeader.match(/^bytes=(.+)$/);
	if (!match) return null;
	const ranges = [];
	const parts = match[1].split(",");
	for (const part of parts) {
		const trimmedPart = part.trim();
		if (trimmedPart.startsWith("-")) {
			const suffix = Number.parseInt(trimmedPart.slice(1), 10);
			/* v8 ignore next -- @preserve */
			if (Number.isNaN(suffix)) return null;
			ranges.push({
				start: Math.max(0, totalSize - suffix),
				end: totalSize - 1
			});
		} else if (trimmedPart.endsWith("-")) {
			const start = Number.parseInt(trimmedPart.slice(0, -1), 10);
			/* v8 ignore next -- @preserve */
			if (Number.isNaN(start)) return null;
			ranges.push({
				start,
				end: totalSize - 1
			});
		} else {
			const [startStr, endStr] = trimmedPart.split("-");
			const start = Number.parseInt(startStr, 10);
			const end = Number.parseInt(endStr, 10);
			/* v8 ignore next -- @preserve */
			if (Number.isNaN(start) || Number.isNaN(end)) return null;
			ranges.push({
				start,
				end: Math.min(end, totalSize - 1)
			});
		}
	}
	for (const range of ranges) if (range.start > range.end || range.start >= totalSize) return null;
	return ranges;
}
const rangeRoute = (fastify) => {
	fastify.get("/range/:numbytes", { schema: rangeSchema }, async (request, reply) => {
		const numbytes = Number.parseInt(request.params.numbytes, 10);
		if (Number.isNaN(numbytes) || numbytes < 0) return reply.code(400).send({ error: "numbytes must be a non-negative integer" });
		const limitedBytes = Math.min(numbytes, MAX_BYTES$1);
		if (request.query.duration) {
			const duration = Number.parseFloat(request.query.duration);
			if (!Number.isNaN(duration) && duration > 0) await sleep(Math.min(duration, 10) * 1e3);
		}
		const allBytes = generateBytes(limitedBytes);
		const rangeHeader = request.headers.range;
		if (!rangeHeader) return reply.header("Content-Type", "application/octet-stream").header("Content-Length", allBytes.length).header("Accept-Ranges", "bytes").send(allBytes);
		const ranges = parseRangeHeader(rangeHeader, limitedBytes);
		if (!ranges || ranges.length === 0) return reply.code(416).header("Content-Range", `bytes */${limitedBytes}`).send({ error: "Range Not Satisfiable" });
		if (ranges.length === 1) {
			const range = ranges[0];
			const chunk = allBytes.subarray(range.start, range.end + 1);
			return reply.code(206).header("Content-Type", "application/octet-stream").header("Content-Length", chunk.length).header("Accept-Ranges", "bytes").header("Content-Range", `bytes ${range.start}-${range.end}/${limitedBytes}`).send(chunk);
		}
		const boundary = "3d6b6a416f9b5";
		const parts = [];
		for (const range of ranges) {
			const chunk = allBytes.subarray(range.start, range.end + 1);
			const header = `\r\n--${boundary}\r\nContent-Type: application/octet-stream\r\nContent-Range: bytes ${range.start}-${range.end}/${limitedBytes}\r\n\r\n`;
			parts.push(Buffer.from(header));
			parts.push(chunk);
		}
		parts.push(Buffer.from(`\r\n--${boundary}--\r\n`));
		const multipartBody = Buffer.concat(parts);
		return reply.code(206).header("Content-Type", `multipart/byteranges; boundary=${boundary}`).header("Content-Length", multipartBody.length).send(multipartBody);
	});
};
//#endregion
//#region src/routes/dynamic-data/stream.ts
const streamSchema = {
	description: "Stream n JSON responses",
	tags: ["Dynamic Data"],
	params: {
		type: "object",
		properties: { n: {
			type: "string",
			description: "Number of JSON responses to stream (max 100)"
		} },
		required: ["n"]
	},
	response: {
		200: {
			type: "string",
			description: "Newline-delimited JSON responses"
		},
		400: {
			type: "object",
			properties: { error: { type: "string" } }
		}
	}
};
const MAX_LINES = 100;
const streamRoute = (fastify) => {
	fastify.get("/stream/:n", { schema: streamSchema }, async (request, reply) => {
		const n = Number.parseInt(request.params.n, 10);
		if (Number.isNaN(n) || n < 0) return reply.code(400).send({ error: "n must be a non-negative integer" });
		const lines = Math.min(n, MAX_LINES);
		const { protocol } = request;
		/* v8 ignore next -- @preserve */
		const host = request.headers.host || "localhost";
		reply.raw.writeHead(200, {
			"Content-Type": "application/json",
			"Transfer-Encoding": "chunked"
		});
		for (let i = 0; i < lines; i++) {
			const data = {
				id: i,
				args: request.query || {},
				headers: request.headers,
				origin: request.ip,
				url: `${protocol}://${host}${request.url}`
			};
			reply.raw.write(`${JSON.stringify(data)}\n`);
		}
		reply.raw.end();
	});
};
//#endregion
//#region src/routes/dynamic-data/stream-bytes.ts
const streamBytesSchema = {
	description: "Streams n random bytes generated with given seed, in chunks of chunk_size per packet",
	tags: ["Dynamic Data"],
	params: {
		type: "object",
		properties: { n: {
			type: "string",
			description: "Number of bytes to stream"
		} },
		required: ["n"]
	},
	querystring: {
		type: "object",
		properties: {
			seed: {
				type: "string",
				description: "Seed for random number generation"
			},
			chunk_size: {
				type: "string",
				description: "Size of each chunk (default: 10240)"
			}
		}
	},
	response: {
		200: {
			type: "string",
			description: "Streamed random binary data"
		},
		400: {
			type: "object",
			properties: { error: { type: "string" } }
		}
	}
};
function seededRandom(seed) {
	let state = seed;
	return () => {
		state = state * 1103515245 + 12345 & 2147483647;
		return state / 2147483647;
	};
}
function generateSeededBytes(n, seed) {
	const random = seededRandom(seed);
	const bytes = Buffer.alloc(n);
	for (let i = 0; i < n; i++) bytes[i] = Math.floor(random() * 256);
	return bytes;
}
const MAX_BYTES = 100 * 1024;
const DEFAULT_CHUNK_SIZE = 10240;
const streamBytesRoute = (fastify) => {
	fastify.get("/stream-bytes/:n", { schema: streamBytesSchema }, async (request, reply) => {
		const n = Number.parseInt(request.params.n, 10);
		if (Number.isNaN(n) || n < 0) return reply.code(400).send({ error: "n must be a non-negative integer" });
		const limitedN = Math.min(n, MAX_BYTES);
		const chunkSize = request.query.chunk_size ? Number.parseInt(request.query.chunk_size, 10) : DEFAULT_CHUNK_SIZE;
		if (Number.isNaN(chunkSize) || chunkSize <= 0) return reply.code(400).send({ error: "chunk_size must be a positive integer" });
		const seedParam = request.query.seed;
		let allBytes;
		if (seedParam !== void 0) {
			const seed = Number.parseInt(seedParam, 10);
			if (Number.isNaN(seed)) return reply.code(400).send({ error: "seed must be an integer" });
			allBytes = generateSeededBytes(limitedN, seed);
		} else allBytes = randomBytes(limitedN);
		reply.raw.writeHead(200, {
			"Content-Type": "application/octet-stream",
			"Transfer-Encoding": "chunked"
		});
		let offset = 0;
		while (offset < allBytes.length) {
			const chunk = allBytes.subarray(offset, offset + chunkSize);
			reply.raw.write(chunk);
			offset += chunkSize;
		}
		reply.raw.end();
	});
};
//#endregion
//#region src/routes/dynamic-data/uuid.ts
const uuidSchema = {
	description: "Return a UUID4",
	tags: ["Dynamic Data"],
	response: { 200: {
		type: "object",
		properties: { uuid: {
			type: "string",
			format: "uuid"
		} },
		required: ["uuid"]
	} }
};
const uuidRoute = (fastify) => {
	fastify.get("/uuid", { schema: uuidSchema }, async () => ({ uuid: randomUUID() }));
};
//#endregion
//#region src/routes/http-methods/delete.ts
const deleteSchema = {
	description: "Handles a DELETE request and returns request information",
	tags: ["HTTP Methods"],
	response: { 200: {
		type: "object",
		properties: {
			method: { type: "string" },
			headers: {
				type: "object",
				additionalProperties: { type: "string" }
			},
			body: { oneOf: [
				{
					type: "object",
					additionalProperties: true
				},
				{ type: "string" },
				{ type: "null" }
			] }
		},
		required: ["method", "headers"]
	} }
};
const deleteRoute = (fastify) => {
	fastify.delete("/delete", { schema: deleteSchema }, async (request) => ({
		method: "DELETE",
		headers: request.headers,
		body: request.body
	}));
};
//#endregion
//#region src/routes/http-methods/get.ts
const getSchema = {
	description: "Returns request information for GET requests",
	tags: ["HTTP Methods"],
	querystring: {
		type: "object",
		additionalProperties: true,
		properties: {},
		description: "Query parameters sent in the request"
	},
	headers: {
		type: "object",
		additionalProperties: true,
		properties: {},
		description: "Headers sent in the request"
	},
	response: { 200: {
		type: "object",
		properties: {
			method: { type: "string" },
			headers: {
				type: "object",
				additionalProperties: { type: "string" }
			},
			queryParams: {
				type: "object",
				additionalProperties: { type: "string" }
			}
		},
		required: [
			"method",
			"headers",
			"queryParams"
		]
	} }
};
const getRoute = (fastify) => {
	fastify.get("/get", { schema: getSchema }, async (request, _reply) => ({
		method: "GET",
		headers: request.headers,
		queryParams: request.query
	}));
};
//#endregion
//#region src/routes/http-methods/patch.ts
const patchSchema = {
	description: "Handles a PATCH request and returns request information",
	tags: ["HTTP Methods"],
	body: {
		type: "object",
		additionalProperties: true,
		description: "The body of the PATCH request"
	},
	response: { 200: {
		type: "object",
		properties: {
			method: { type: "string" },
			headers: {
				type: "object",
				additionalProperties: { type: "string" }
			},
			body: {
				type: "object",
				additionalProperties: true
			}
		},
		required: [
			"method",
			"headers",
			"body"
		]
	} }
};
const patchRoute = (fastify) => {
	fastify.patch("/patch", { schema: patchSchema }, async (request) => ({
		method: "PATCH",
		headers: request.headers,
		body: request.body
	}));
};
//#endregion
//#region src/routes/http-methods/post.ts
const postSchema = {
	description: "Handles a POST request and returns request information",
	tags: ["HTTP Methods"],
	body: {
		type: "object",
		additionalProperties: true,
		description: "The body of the POST request"
	},
	response: { 200: {
		type: "object",
		properties: {
			method: { type: "string" },
			headers: {
				type: "object",
				additionalProperties: { type: "string" }
			},
			body: {
				type: "object",
				additionalProperties: true
			}
		},
		required: [
			"method",
			"headers",
			"body"
		]
	} }
};
const postRoute = (fastify) => {
	fastify.post("/post", { schema: postSchema }, async (request) => ({
		method: "POST",
		headers: request.headers,
		body: request.body
	}));
};
//#endregion
//#region src/routes/http-methods/put.ts
const putSchema = {
	description: "Handles a PUT request and returns request information",
	tags: ["HTTP Methods"],
	body: {
		type: "object",
		additionalProperties: true,
		description: "The body of the PUT request"
	},
	response: { 200: {
		type: "object",
		properties: {
			method: { type: "string" },
			headers: {
				type: "object",
				additionalProperties: { type: "string" }
			},
			body: {
				type: "object",
				additionalProperties: true
			}
		},
		required: [
			"method",
			"headers",
			"body"
		]
	} }
};
const putRoute = (fastify) => {
	fastify.put("/put", { schema: putSchema }, async (request) => ({
		method: "PUT",
		headers: request.headers,
		body: request.body
	}));
};
//#endregion
//#region src/routes/images/index.ts
const imageSchema = {
	description: "Returns an image based on Accept header (supports jpeg, png, svg, webp)",
	tags: ["Images"],
	headers: {
		type: "object",
		properties: { accept: {
			type: "string",
			description: "Accept header for content negotiation"
		} }
	},
	response: { 200: {
		description: "Image file",
		type: "string",
		format: "binary"
	} }
};
const jpegSchema = {
	description: "Returns a JPEG image",
	tags: ["Images"],
	response: { 200: {
		description: "JPEG image",
		type: "string",
		format: "binary"
	} }
};
const pngSchema = {
	description: "Returns a PNG image",
	tags: ["Images"],
	response: { 200: {
		description: "PNG image",
		type: "string",
		format: "binary"
	} }
};
const svgSchema = {
	description: "Returns an SVG image",
	tags: ["Images"],
	response: { 200: {
		description: "SVG image",
		type: "string",
		format: "binary"
	} }
};
const webpSchema = {
	description: "Returns a WEBP image",
	tags: ["Images"],
	response: { 200: {
		description: "WEBP image",
		type: "string",
		format: "binary"
	} }
};
const imageRoutes = (fastify) => {
	const publicPath = join(process.cwd(), "public");
	fastify.get("/image", { schema: imageSchema }, async (request, reply) => {
		const accept = request.headers.accept || "";
		let file = "logo.png";
		let contentType = "image/png";
		if (accept.includes("image/jpeg") || accept.includes("image/jpg")) {
			file = "logo.jpg";
			contentType = "image/jpeg";
		} else if (accept.includes("image/svg+xml") || accept.includes("image/svg")) {
			file = "logo.svg";
			contentType = "image/svg+xml";
		} else if (accept.includes("image/webp")) {
			file = "logo.webp";
			contentType = "image/webp";
		} else if (accept.includes("image/png")) {
			file = "logo.png";
			contentType = "image/png";
		}
		try {
			const imageBuffer = readFileSync(join(publicPath, file));
			reply.type(contentType);
			return imageBuffer;
		} catch (error) {
			/* v8 ignore next -- @preserve */
			reply.code(500);
			/* v8 ignore next -- @preserve */
			return { error: `${error}` };
		}
	});
	fastify.get("/image/jpeg", { schema: jpegSchema }, async (_request, reply) => {
		try {
			const imageBuffer = readFileSync(join(publicPath, "logo.jpg"));
			reply.type("image/jpeg");
			return imageBuffer;
		} catch (error) {
			/* v8 ignore next -- @preserve */
			reply.code(500);
			/* v8 ignore next -- @preserve */
			return { error: `${error}` };
		}
	});
	fastify.get("/image/png", { schema: pngSchema }, async (_request, reply) => {
		try {
			const imageBuffer = readFileSync(join(publicPath, "logo.png"));
			reply.type("image/png");
			return imageBuffer;
		} catch (error) {
			/* v8 ignore next -- @preserve */
			reply.code(500);
			/* v8 ignore next -- @preserve */
			return { error: `${error}` };
		}
	});
	fastify.get("/image/svg", { schema: svgSchema }, async (_request, reply) => {
		try {
			const imageBuffer = readFileSync(join(publicPath, "logo.svg"));
			reply.type("image/svg+xml");
			return imageBuffer;
		} catch (error) {
			/* v8 ignore next -- @preserve */
			reply.code(500);
			/* v8 ignore next -- @preserve */
			return { error: `${error}` };
		}
	});
	fastify.get("/image/webp", { schema: webpSchema }, async (_request, reply) => {
		try {
			const imageBuffer = readFileSync(join(publicPath, "logo.webp"));
			reply.type("image/webp");
			return imageBuffer;
		} catch (error) {
			/* v8 ignore next -- @preserve */
			reply.code(500);
			/* v8 ignore next -- @preserve */
			return { error: `${error}` };
		}
	});
};
//#endregion
//#region src/routes/index.ts
const indexRoute = (fastify) => {
	fastify.get("/", { schema: { hide: true } }, async (_request, reply) => {
		await reply.type("text/html").send(`
	<!doctype html>
<html>
  <head>
    <title>{ mockhttp }</title>
    <meta charset="utf-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1" />
    <meta name="description" content="MockHttp - A powerful HTTP mock server and httpbin replacement for API testing. Built with Fastify, TypeScript, and Docker support." />
    <meta name="keywords" content="mock, http, httpbin, testing, api, fastify, docker, typescript, rest, mock-server, api-testing, http-testing, httpbin-alternative" />
    <meta property="og:title" content="MockHttp - HTTP Mock Server" />
    <meta property="og:description" content="A powerful HTTP mock server and httpbin replacement for API testing. Built with Fastify, TypeScript, and Docker support." />
    <meta property="og:type" content="website" />
    <meta property="og:url" content="https://mockhttp.org" />
    <meta property="og:image" content="https://mockhttp.org/logo.png" />
    <meta property="og:site_name" content="MockHttp" />
    <meta name="twitter:card" content="summary" />
    <meta name="twitter:title" content="MockHttp - HTTP Mock Server" />
    <meta name="twitter:description" content="A powerful HTTP mock server and httpbin replacement for API testing." />
    <meta name="twitter:image" content="https://mockhttp.org/logo.png" />
      <style>
      :root {
  --scalar-custom-header-height: 70px;
}
.custom-header {
  height: var(--scalar-custom-header-height);
  background-color: var(--scalar-background-1);
  box-shadow: inset 0 -1px 0 var(--scalar-border-color);
  color: var(--scalar-color-1);
  font-size: var(--scalar-font-size-2);
  padding: 0 18px;
  position: sticky;
  justify-content: space-between;
  top: 0;
  z-index: 100;
}
.custom-header,
.custom-header nav {
  display: flex;
  align-items: center;
  gap: 18px;
}

.custom-header img {
  height: var(--scalar-custom-header-height);
  object-fit: contain;
}

.custom-header a:hover {
  color: var(--scalar-color-2);
}

.github-corner {
  fill: var(--scalar-border-color) !important;
}

      </style>
  </head>
  <body>
  <header class="custom-header scalar-app">
    <a href="/"><img src="/logo.svg" alt="{ mockhttp }" /></a>
    <a href="https://github.com/jaredwray/mockhttp" class="github-corner" aria-label="View source on GitHub">
      <svg width="70" height="70" viewBox="0 0 250 250" style="color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true">
      <path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
      <path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path>
      <path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a><style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style>
  </header>

    <script
      id="api-reference"
      data-url="/docs/json"><\/script>

    <script src="/scalar/browser/standalone.js"><\/script>
  </body>
</html>
`);
	});
};
//#endregion
//#region src/routes/redirects/absolute-redirect.ts
const absoluteRedirectSchema = {
	tags: ["Redirects"],
	description: "Redirects the request to the target URL using an absolute URL",
	params: {
		type: "object",
		properties: { value: {
			type: "integer",
			minimum: 1
		} },
		required: ["value"]
	},
	headers: {
		type: "object",
		properties: { host: { type: "string" } },
		required: ["host"]
	},
	response: { 302: { type: "string" } }
};
const absoluteRedirectRoute = (fastify) => {
	fastify.get("/absolute-redirect/:value", { schema: absoluteRedirectSchema }, async (request, reply) => {
		const { value } = request.params;
		const { host } = request.headers;
		const { protocol } = request;
		let url = `${protocol}://${host}/get`;
		let html = `
        <title>Redirecting...</title>
        <h1>Redirecting...</h1>
        <p>You should be redirected automatically to target URL: <a href="${url}">${url}</a>.  If not click the link.</p>
        `;
		if (value > 1) {
			url = `${protocol}://${host}/absolute-redirect/${value - 1}`;
			html = `
            <title>Redirecting...</title>
            <h1>Redirecting...</h1>
            <p>You should be redirected automatically to target URL: <a href="${url}">${url}</a>.  If not click the link.</p>
            `;
		}
		await reply.header("Location", url).status(302).type("text/html").send(html);
	});
};
//#endregion
//#region src/routes/redirects/redirect-to.ts
const redirectToSchema = {
	tags: ["Redirects"],
	description: "Redirects the request to the target URL",
	querystring: {
		type: "object",
		properties: {
			url: {
				type: "string",
				format: "uri",
				description: "The URL to redirect to"
			},
			status_code: {
				type: "string",
				pattern: "^[1-5][0-9][0-9]$",
				description: "The HTTP status code to use for redirection"
			}
		},
		required: ["url"],
		additionalProperties: false
	}
};
const redirectToRoute = (fastify) => {
	const handler = async (request, reply) => {
		const { url, status_code } = request.query;
		const statusCode = status_code ? Number(status_code) : 302;
		await reply.redirect(url, statusCode);
	};
	fastify.get("/redirect-to", { schema: redirectToSchema }, handler);
	fastify.post("/redirect-to", { schema: redirectToSchema }, handler);
	fastify.put("/redirect-to", { schema: redirectToSchema }, handler);
	fastify.patch("/redirect-to", { schema: redirectToSchema }, handler);
	fastify.delete("/redirect-to", { schema: redirectToSchema }, handler);
};
//#endregion
//#region src/routes/redirects/relative-redirect.ts
const relativeRedirectSchema = {
	tags: ["Redirects"],
	description: "Redirects the request to the target URL using an relative URL",
	params: {
		type: "object",
		properties: { value: {
			type: "integer",
			minimum: 1
		} },
		required: ["value"]
	},
	headers: {
		type: "object",
		properties: { host: { type: "string" } },
		required: ["host"]
	},
	response: { 302: { type: "string" } }
};
const relativeRedirectRoute = (fastify) => {
	fastify.get("/relative-redirect/:value", { schema: relativeRedirectSchema }, async (request, reply) => {
		const { value } = request.params;
		let url = "/get";
		let html = `
        <title>Redirecting...</title>
        <h1>Redirecting...</h1>
        <p>You should be redirected automatically to target URL: <a href="${url}">${url}</a>.  If not click the link.</p>
        `;
		if (value > 1) {
			url = `/relative-redirect/${value - 1}`;
			html = `
            <title>Redirecting...</title>
            <h1>Redirecting...</h1>
            <p>You should be redirected automatically to target URL: <a href="${url}">${url}</a>.  If not click the link.</p>
            `;
		}
		await reply.header("Location", url).status(302).type("text/html").send(html);
	});
};
//#endregion
//#region src/routes/request-inspection/headers.ts
const headersSchema = {
	description: "Returns all headers of the client request",
	tags: ["Request Inspection"],
	response: { 200: {
		type: "object",
		properties: { headers: {
			type: "object",
			additionalProperties: { type: "string" },
			description: "All headers of the client request"
		} },
		required: ["headers"]
	} }
};
const headersRoute = (fastify) => {
	fastify.get("/headers", { schema: headersSchema }, async (request) => {
		const { headers } = request;
		return { headers };
	});
};
//#endregion
//#region src/routes/request-inspection/ip.ts
const ipSchema = {
	description: "Returns the IP address of the client",
	tags: ["Request Inspection"],
	response: { 200: {
		type: "object",
		properties: { ip: {
			type: "string",
			description: "The IP address of the client"
		} },
		required: ["ip"]
	} }
};
const ipRoute = (fastify) => {
	fastify.get("/ip", { schema: ipSchema }, async (request) => {
		const cloudflareIp = request.headers["cf-connecting-ip"];
		const forwardedIp = request.headers["x-forwarded-for"];
		const remoteIp = request.ip;
		return { ip: cloudflareIp ?? (typeof forwardedIp === "string" ? forwardedIp.split(",")[0].trim() : remoteIp) };
	});
};
//#endregion
//#region src/routes/request-inspection/user-agent.ts
const userAgentSchema = {
	description: "Returns the User-Agent header of the client request",
	tags: ["Request Inspection"],
	response: { 200: {
		type: "object",
		properties: { userAgent: {
			type: "string",
			description: "The User-Agent header of the client request"
		} },
		required: ["userAgent"]
	} }
};
const userAgentRoute = (fastify) => {
	fastify.get("/user-agent", { schema: userAgentSchema }, async (request) => {
		return { userAgent: request.headers["user-agent"] };
	});
};
//#endregion
//#region src/routes/response-formats/index.ts
const plainSchema = {
	description: "Returns random plain text content",
	tags: ["Response Formats"],
	response: { 200: {
		type: "string",
		description: "Random plain text content"
	} }
};
const textSchema = {
	description: "Returns random text content",
	tags: ["Response Formats"],
	response: { 200: {
		type: "string",
		description: "Random text content"
	} }
};
const htmlSchema = {
	description: "Returns random HTML content",
	tags: ["Response Formats"],
	response: { 200: {
		type: "string",
		description: "Random HTML content"
	} }
};
const xmlSchema = {
	description: "Returns random XML content",
	tags: ["Response Formats"],
	response: { 200: {
		type: "string",
		description: "Random XML content"
	} }
};
const jsonSchema = {
	description: "Returns random JSON content",
	tags: ["Response Formats"],
	response: { 200: {
		type: "object",
		description: "Random JSON content",
		additionalProperties: true
	} }
};
const denySchema = {
	description: "Returns page denied by robots.txt rules",
	tags: ["Response Formats"],
	response: { 403: {
		type: "string",
		description: "Page denied by robots.txt rules"
	} }
};
const gzipSchema = {
	description: "Returns GZip-encoded data",
	tags: ["Response Formats"],
	response: { 200: {
		type: "string",
		description: "GZip-encoded content"
	} }
};
const deflateSchema = {
	description: "Returns Deflate-encoded data",
	tags: ["Response Formats"],
	response: { 200: {
		type: "string",
		description: "Deflate-encoded content"
	} }
};
const brotliSchema = {
	description: "Returns Brotli-encoded data",
	tags: ["Response Formats"],
	response: { 200: {
		type: "string",
		description: "Brotli-encoded content"
	} }
};
const randomTexts = [
	"The quick brown fox jumps over the lazy dog.",
	"Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
	"To be or not to be, that is the question.",
	"All work and no play makes Jack a dull boy.",
	"The only way to do great work is to love what you do.",
	"In the beginning was the Word, and the Word was with God.",
	"Once upon a time in a galaxy far, far away...",
	"It was the best of times, it was the worst of times.",
	"The journey of a thousand miles begins with a single step.",
	"Knowledge is power. France is bacon."
];
const randomHtmlContent = [
	`<!DOCTYPE html>
<html lang="en">
<head>
    <title>Sample Page</title>
</head>
<body>
    <h1>Welcome to MockHTTP</h1>
    <p>This is a sample HTML page with random content.</p>
</body>
</html>`,
	`<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Test Document</title>
</head>
<body>
    <header>
        <h1>Test Page</h1>
        <nav>
            <ul>
                <li><a href="#">Home</a></li>
                <li><a href="#">About</a></li>
                <li><a href="#">Contact</a></li>
            </ul>
        </nav>
    </header>
    <main>
        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
    </main>
</body>
</html>`,
	`<!DOCTYPE html>
<html lang="en">
<head>
    <title>Article Page</title>
    <style>
        body { font-family: Arial, sans-serif; }
        article { max-width: 800px; margin: 0 auto; }
    </style>
</head>
<body>
    <article>
        <h1>The Journey Begins</h1>
        <p>Once upon a time in a digital world...</p>
        <blockquote>
            "The only way to do great work is to love what you do."
        </blockquote>
    </article>
</body>
</html>`,
	`<!DOCTYPE html>
<html>
<head>
    <title>Form Example</title>
</head>
<body>
    <h2>Contact Form</h2>
    <form action="/submit" method="post">
        <label for="name">Name:</label>
        <input type="text" id="name" name="name" required>
        <br>
        <label for="email">Email:</label>
        <input type="email" id="email" name="email" required>
        <br>
        <button type="submit">Submit</button>
    </form>
</body>
</html>`,
	`<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Page</title>
</head>
<body>
    <div class="container">
        <h1>Responsive Design</h1>
        <p>This page adapts to different screen sizes.</p>
        <img src="placeholder.jpg" alt="Placeholder" style="max-width: 100%; height: auto;">
    </div>
</body>
</html>`
];
const randomXmlContent = [
	`<?xml version="1.0" encoding="UTF-8"?>
<root>
    <message>Hello from MockHTTP</message>
    <timestamp>${(/* @__PURE__ */ new Date()).toISOString()}</timestamp>
    <status>success</status>
</root>`,
	`<?xml version="1.0" encoding="UTF-8"?>
<book>
    <title>The Great Gatsby</title>
    <author>F. Scott Fitzgerald</author>
    <year>1925</year>
    <publisher>Charles Scribner's Sons</publisher>
    <isbn>978-0-7432-7356-5</isbn>
</book>`,
	`<?xml version="1.0" encoding="UTF-8"?>
<users>
    <user id="1">
        <name>John Doe</name>
        <email>john@example.com</email>
        <role>admin</role>
    </user>
    <user id="2">
        <name>Jane Smith</name>
        <email>jane@example.com</email>
        <role>user</role>
    </user>
</users>`,
	`<?xml version="1.0" encoding="UTF-8"?>
<product>
    <id>12345</id>
    <name>Wireless Mouse</name>
    <description>Ergonomic wireless mouse with 3-year battery life</description>
    <price currency="USD">29.99</price>
    <stock>150</stock>
    <category>Electronics</category>
</product>`,
	`<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>Mock RSS Feed</title>
        <link>http://example.com/rss</link>
        <description>Sample RSS feed from MockHTTP</description>
        <item>
            <title>First Article</title>
            <link>http://example.com/article1</link>
            <description>This is the first article in our feed</description>
            <pubDate>${(/* @__PURE__ */ new Date()).toUTCString()}</pubDate>
        </item>
    </channel>
</rss>`
];
const randomJsonContent = [
	() => ({
		message: "Hello from MockHTTP",
		timestamp: (/* @__PURE__ */ new Date()).toISOString(),
		status: "success",
		code: 200
	}),
	() => ({
		user: {
			id: 1,
			name: "John Doe",
			email: "john@example.com",
			role: "admin",
			createdAt: "2024-01-15T10:30:00Z"
		},
		permissions: [
			"read",
			"write",
			"delete"
		]
	}),
	() => ({
		products: [{
			id: 101,
			name: "Laptop",
			price: 999.99,
			stock: 50
		}, {
			id: 102,
			name: "Mouse",
			price: 29.99,
			stock: 200
		}],
		total: 2,
		page: 1
	}),
	() => ({ config: {
		theme: "dark",
		language: "en",
		notifications: {
			email: true,
			push: false,
			sms: false
		},
		privacy: {
			profile: "public",
			activity: "friends"
		}
	} }),
	() => ({ metrics: {
		cpu: 45.2,
		memory: 78.5,
		disk: 62.3,
		network: {
			inbound: "1.2MB/s",
			outbound: "0.8MB/s"
		},
		uptime: "15 days",
		services: [
			{
				name: "api",
				status: "healthy"
			},
			{
				name: "database",
				status: "healthy"
			},
			{
				name: "cache",
				status: "warning"
			}
		]
	} })
];
const responseFormatRoutes = (fastify) => {
	const plainHandler = async (_request, reply) => {
		const text = randomTexts[Math.floor(Math.random() * randomTexts.length)];
		reply.type("text/plain");
		return text;
	};
	fastify.get("/plain", { schema: plainSchema }, plainHandler);
	fastify.post("/plain", { schema: plainSchema }, plainHandler);
	fastify.put("/plain", { schema: plainSchema }, plainHandler);
	fastify.patch("/plain", { schema: plainSchema }, plainHandler);
	fastify.delete("/plain", { schema: plainSchema }, plainHandler);
	const textHandler = async (_request, reply) => {
		const text = randomTexts[Math.floor(Math.random() * randomTexts.length)];
		reply.type("text/plain");
		return text;
	};
	fastify.get("/text", { schema: textSchema }, textHandler);
	fastify.post("/text", { schema: textSchema }, textHandler);
	fastify.put("/text", { schema: textSchema }, textHandler);
	fastify.patch("/text", { schema: textSchema }, textHandler);
	fastify.delete("/text", { schema: textSchema }, textHandler);
	const htmlHandler = async (_request, reply) => {
		const html = randomHtmlContent[Math.floor(Math.random() * randomHtmlContent.length)];
		reply.type("text/html");
		return html;
	};
	fastify.get("/html", { schema: htmlSchema }, htmlHandler);
	fastify.post("/html", { schema: htmlSchema }, htmlHandler);
	fastify.put("/html", { schema: htmlSchema }, htmlHandler);
	fastify.patch("/html", { schema: htmlSchema }, htmlHandler);
	fastify.delete("/html", { schema: htmlSchema }, htmlHandler);
	const xmlHandler = async (_request, reply) => {
		const xml = randomXmlContent[Math.floor(Math.random() * randomXmlContent.length)];
		reply.type("application/xml");
		return xml;
	};
	fastify.get("/xml", { schema: xmlSchema }, xmlHandler);
	fastify.post("/xml", { schema: xmlSchema }, xmlHandler);
	fastify.put("/xml", { schema: xmlSchema }, xmlHandler);
	fastify.patch("/xml", { schema: xmlSchema }, xmlHandler);
	fastify.delete("/xml", { schema: xmlSchema }, xmlHandler);
	const jsonHandler = async (_request, reply) => {
		const jsonGenerator = randomJsonContent[Math.floor(Math.random() * randomJsonContent.length)];
		const json = jsonGenerator();
		reply.type("application/json");
		return json;
	};
	fastify.get("/json", { schema: jsonSchema }, jsonHandler);
	fastify.post("/json", { schema: jsonSchema }, jsonHandler);
	fastify.put("/json", { schema: jsonSchema }, jsonHandler);
	fastify.patch("/json", { schema: jsonSchema }, jsonHandler);
	fastify.delete("/json", { schema: jsonSchema }, jsonHandler);
	const denyHandler = async (_request, reply) => {
		const denyMessages = [
			`<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>403 Forbidden</title>
</head><body>
<h1>Forbidden</h1>
<p>Access denied by robots.txt rules.</p>
<p>The requested resource is disallowed by robots.txt.</p>
</body></html>`,
			`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>403 - Access Denied</title>
</head>
<body>
<h1>403 Forbidden</h1>
<p>This page is blocked by robots.txt rules.</p>
<p>User-agent: *<br>Disallow: ${_request.url}</p>
</body>
</html>`,
			`<!DOCTYPE html>
<html>
<head>
<title>403 - Access Denied</title>
<style>body { font-family: Arial, sans-serif; margin: 40px; }</style>
</head>
<body>
<h2>403 - Access Denied by robots.txt</h2>
<p>The robots.txt file prevents access to this resource.</p>
<hr>
<small>MockHTTP Server</small>
</body>
</html>`
		];
		const html = denyMessages[Math.floor(Math.random() * denyMessages.length)];
		reply.code(403);
		reply.type("text/html");
		return html;
	};
	fastify.get("/deny", { schema: denySchema }, denyHandler);
	fastify.post("/deny", { schema: denySchema }, denyHandler);
	fastify.put("/deny", { schema: denySchema }, denyHandler);
	fastify.patch("/deny", { schema: denySchema }, denyHandler);
	fastify.delete("/deny", { schema: denySchema }, denyHandler);
	const gzipHandler = async (_request, reply) => {
		const sampleData = [
			{
				message: "This is GZip compressed data from MockHTTP",
				timestamp: (/* @__PURE__ */ new Date()).toISOString(),
				compression: "gzip",
				originalSize: 0,
				compressedSize: 0
			},
			{
				title: "Sample Document",
				content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ".repeat(50),
				metadata: {
					author: "MockHTTP Server",
					format: "gzip",
					version: "1.0"
				}
			},
			{
				data: Array.from({ length: 100 }, (_, i) => ({
					id: i + 1,
					value: `Item ${i + 1}`,
					description: `This is item number ${i + 1} in the dataset`
				})),
				info: "Large dataset compressed with GZip"
			}
		];
		const data = sampleData[Math.floor(Math.random() * sampleData.length)];
		const jsonString = JSON.stringify(data);
		if (data.originalSize !== void 0) data.originalSize = Buffer.byteLength(jsonString);
		const compressed = gzipSync(jsonString);
		if (data.compressedSize !== void 0) {
			const updatedData = {
				...data,
				compressedSize: compressed.length
			};
			const updatedCompressed = gzipSync(JSON.stringify(updatedData));
			reply.header("Content-Encoding", "gzip");
			reply.header("Content-Type", "application/json");
			reply.type("application/octet-stream");
			return updatedCompressed;
		}
		reply.header("Content-Encoding", "gzip");
		reply.header("Content-Type", "application/json");
		reply.type("application/octet-stream");
		return compressed;
	};
	fastify.get("/gzip", { schema: gzipSchema }, gzipHandler);
	fastify.post("/gzip", { schema: gzipSchema }, gzipHandler);
	fastify.put("/gzip", { schema: gzipSchema }, gzipHandler);
	fastify.patch("/gzip", { schema: gzipSchema }, gzipHandler);
	fastify.delete("/gzip", { schema: gzipSchema }, gzipHandler);
	const deflateHandler = async (_request, reply) => {
		const sampleData = [
			{
				message: "This is Deflate compressed data from MockHTTP",
				timestamp: (/* @__PURE__ */ new Date()).toISOString(),
				compression: "deflate",
				originalSize: 0,
				compressedSize: 0
			},
			{
				title: "Deflate Compressed Document",
				content: "The deflate algorithm is used for compression. ".repeat(40),
				details: {
					algorithm: "DEFLATE",
					method: "zlib deflate",
					server: "MockHTTP"
				}
			},
			{
				items: Array.from({ length: 50 }, (_, i) => ({
					index: i,
					name: `Entry ${i}`,
					data: `This is test data for entry number ${i}`,
					timestamp: (/* @__PURE__ */ new Date(Date.now() - i * 1e6)).toISOString()
				})),
				metadata: {
					compression: "deflate",
					count: 50
				}
			}
		];
		const data = sampleData[Math.floor(Math.random() * sampleData.length)];
		const jsonString = JSON.stringify(data);
		if (data.originalSize !== void 0) data.originalSize = Buffer.byteLength(jsonString);
		const compressed = deflateSync(jsonString);
		if (data.compressedSize !== void 0) {
			const updatedData = {
				...data,
				compressedSize: compressed.length
			};
			const updatedCompressed = deflateSync(JSON.stringify(updatedData));
			reply.header("Content-Encoding", "deflate");
			reply.header("Content-Type", "application/json");
			reply.type("application/octet-stream");
			return updatedCompressed;
		}
		reply.header("Content-Encoding", "deflate");
		reply.header("Content-Type", "application/json");
		reply.type("application/octet-stream");
		return compressed;
	};
	fastify.get("/deflate", { schema: deflateSchema }, deflateHandler);
	fastify.post("/deflate", { schema: deflateSchema }, deflateHandler);
	fastify.put("/deflate", { schema: deflateSchema }, deflateHandler);
	fastify.patch("/deflate", { schema: deflateSchema }, deflateHandler);
	fastify.delete("/deflate", { schema: deflateSchema }, deflateHandler);
	const brotliHandler = async (_request, reply) => {
		const sampleData = [
			{
				message: "This is Brotli compressed data from MockHTTP",
				timestamp: (/* @__PURE__ */ new Date()).toISOString(),
				compression: "br",
				algorithm: "Brotli",
				originalSize: 0,
				compressedSize: 0
			},
			{
				title: "Brotli Compression Example",
				description: "Brotli is a generic-purpose lossless compression algorithm that compresses data using a combination of a modern variant of the LZ77 algorithm, Huffman coding and 2nd order context modeling.",
				content: "Brotli compression typically achieves 20-26% better compression ratios than gzip. ".repeat(10),
				metadata: {
					encoding: "br",
					quality: 11,
					server: "MockHTTP"
				}
			},
			{ benchmark: {
				title: "Compression Benchmark Data",
				results: Array.from({ length: 75 }, (_, i) => ({
					id: `test-${i}`,
					name: `Benchmark Test ${i}`,
					value: Math.random() * 1e3,
					unit: "ms",
					timestamp: (/* @__PURE__ */ new Date(Date.now() - i * 6e4)).toISOString()
				})),
				summary: {
					compression: "brotli",
					totalTests: 75,
					encoding: "br"
				}
			} }
		];
		const data = sampleData[Math.floor(Math.random() * sampleData.length)];
		const jsonString = JSON.stringify(data);
		if (data.originalSize !== void 0) data.originalSize = Buffer.byteLength(jsonString);
		const compressed = brotliCompressSync(jsonString);
		if (data.compressedSize !== void 0) {
			const updatedData = {
				...data,
				compressedSize: compressed.length
			};
			const updatedCompressed = brotliCompressSync(JSON.stringify(updatedData));
			reply.header("Content-Encoding", "br");
			reply.header("Content-Type", "application/json");
			reply.type("application/octet-stream");
			return updatedCompressed;
		}
		reply.header("Content-Encoding", "br");
		reply.header("Content-Type", "application/json");
		reply.type("application/octet-stream");
		return compressed;
	};
	fastify.get("/brotli", { schema: brotliSchema }, brotliHandler);
	fastify.post("/brotli", { schema: brotliSchema }, brotliHandler);
	fastify.put("/brotli", { schema: brotliSchema }, brotliHandler);
	fastify.patch("/brotli", { schema: brotliSchema }, brotliHandler);
	fastify.delete("/brotli", { schema: brotliSchema }, brotliHandler);
};
//#endregion
//#region src/routes/response-inspection/cache.ts
const cacheGetSchema = {
	description: "Handles cache validation and retrieval.",
	tags: ["Response Inspection"],
	response: {
		200: {
			type: "string",
			description: "The cached content or resource."
		},
		304: {
			type: "null",
			description: "Indicates the resource has not been modified."
		}
	}
};
const cacheSetSchema = {
	description: "Sets a Cache-Control header for n seconds.",
	tags: ["Response Inspection"],
	params: {
		type: "object",
		properties: { value: {
			type: "integer",
			description: "Number of seconds to cache the resource."
		} },
		required: ["value"]
	},
	response: { 200: {
		type: "string",
		description: "Confirmation message for setting cache."
	} }
};
const cacheRoutes = (fastify) => {
	fastify.get("/cache", { schema: cacheGetSchema }, async (request, reply) => {
		const ifModifiedSince = request.headers["if-modified-since"];
		const ifNoneMatch = request.headers["if-none-match"];
		if (ifModifiedSince ?? ifNoneMatch) {
			await reply.status(304).send();
			return;
		}
		await reply.send("Cache content or resource response here.");
	});
	fastify.get("/cache/:value", { schema: cacheSetSchema }, async (request, reply) => {
		const { value } = request.params;
		await reply.header("Cache-Control", `max-age=${value}`).send(`Cache-Control set for ${value} seconds.`);
	});
};
//#endregion
//#region src/routes/response-inspection/etag.ts
const etagSchema = {
	description: "Handles ETag-based conditional requests.",
	tags: ["Response Inspection"],
	params: {
		type: "object",
		properties: { etag: {
			type: "string",
			description: "The ETag value for the resource."
		} },
		required: ["etag"]
	},
	response: {
		200: {
			type: "string",
			description: "Resource content for matching ETag."
		},
		304: {
			type: "null",
			description: "Indicates the resource has not been modified."
		},
		412: {
			type: "null",
			description: "Indicates a precondition failed due to mismatched ETag."
		}
	}
};
const etagRoutes = (fastify) => {
	fastify.get("/etag/:etag", { schema: etagSchema }, async (request, reply) => {
		const { etag } = request.params;
		const ifNoneMatch = request.headers["if-none-match"];
		const ifMatch = request.headers["if-match"];
		if (ifNoneMatch === etag) {
			await reply.status(304).send();
			return;
		}
		if (ifMatch && ifMatch !== etag) {
			await reply.status(412).send();
			return;
		}
		await reply.header("ETag", etag).send("Resource content for matching ETag.");
	});
};
//#endregion
//#region src/routes/response-inspection/response-headers.ts
const responseHeadersSchema = {
	description: "Returns a set of response headers based on the query string.",
	tags: ["Response Inspection"],
	querystring: {
		type: "object",
		additionalProperties: true,
		description: "Freeform query string parameters to be added as response headers."
	},
	response: { 200: {
		type: "object",
		additionalProperties: true,
		description: "Response headers as per query string parameters."
	} }
};
const responseHeadersRoutes = (fastify) => {
	fastify.get("/response-headers", { schema: responseHeadersSchema }, async (request, reply) => {
		const queryParameters = request.query;
		for (const [key, value] of Object.entries(queryParameters)) reply.header(key, escape(value));
		const cleanedResponse = Object.fromEntries(Object.entries(queryParameters).map(([key, value]) => [key, escape(value)]));
		await reply.send(cleanedResponse);
	});
	fastify.post("/response-headers", { schema: responseHeadersSchema }, async (request, reply) => {
		const queryParameters = request.query ?? {};
		const body = request.body ?? {};
		for (const [key, value] of Object.entries(queryParameters)) reply.header(key, escape(value));
		for (const [key, value] of Object.entries(body)) reply.header(key, escape(value));
		const cleanedResponse = Object.fromEntries(Object.entries({
			...queryParameters,
			...body
		}).map(([key, value]) => [key, escape(value)]));
		await reply.send(cleanedResponse);
	});
};
//#endregion
//#region src/routes/sitemap.ts
const sitemapRoute = (fastify) => {
	fastify.get("/sitemap.xml", { schema: { hide: true } }, async (_request, reply) => {
		try {
			const host = _request.headers.host ?? "mockhttp.org";
			const xml = `<?xml version="1.0" encoding="UTF-8"?>
                            <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
                                <url>
                                    <loc>${_request.headers["x-forwarded-proto"]?.includes("https") ? "https" : "http"}://${host}/</loc>
                                    <changefreq>monthly</changefreq>
                                    <priority>1.0</priority>
                                </url>
                        </urlset>`;
			await reply.type("text/xml").send(xml);
		} catch (error) {
			/* v8 ignore next -- @preserve */
			fastify.log.error(error);
			/* v8 ignore next -- @preserve */
			await reply.status(500).send({ error: "Internal Server Error" });
		}
	});
};
//#endregion
//#region src/routes/status-codes/index.ts
const statusCodes = [
	{
		code: 100,
		message: "Continue"
	},
	{
		code: 101,
		message: "Switching Protocols"
	},
	{
		code: 102,
		message: "Processing"
	},
	{
		code: 103,
		message: "Early Hints"
	},
	{
		code: 200,
		message: "OK"
	},
	{
		code: 201,
		message: "Created"
	},
	{
		code: 202,
		message: "Accepted"
	},
	{
		code: 203,
		message: "Non-Authoritative Information"
	},
	{
		code: 204,
		message: "No Content"
	},
	{
		code: 205,
		message: "Reset Content"
	},
	{
		code: 206,
		message: "Partial Content"
	},
	{
		code: 207,
		message: "Multi-Status"
	},
	{
		code: 208,
		message: "Already Reported"
	},
	{
		code: 226,
		message: "IM Used"
	},
	{
		code: 300,
		message: "Multiple Choices"
	},
	{
		code: 301,
		message: "Moved Permanently"
	},
	{
		code: 302,
		message: "Found"
	},
	{
		code: 303,
		message: "See Other"
	},
	{
		code: 304,
		message: "Not Modified"
	},
	{
		code: 305,
		message: "Use Proxy"
	},
	{
		code: 306,
		message: "Switch Proxy"
	},
	{
		code: 307,
		message: "Temporary Redirect"
	},
	{
		code: 308,
		message: "Permanent Redirect"
	},
	{
		code: 400,
		message: "Bad Request"
	},
	{
		code: 401,
		message: "Unauthorized"
	},
	{
		code: 402,
		message: "Payment Required"
	},
	{
		code: 403,
		message: "Forbidden"
	},
	{
		code: 404,
		message: "Not Found"
	},
	{
		code: 405,
		message: "Method Not Allowed"
	},
	{
		code: 406,
		message: "Not Acceptable"
	},
	{
		code: 407,
		message: "Proxy Authentication Required"
	},
	{
		code: 408,
		message: "Request Timeout"
	},
	{
		code: 409,
		message: "Conflict"
	},
	{
		code: 410,
		message: "Gone"
	},
	{
		code: 411,
		message: "Length Required"
	},
	{
		code: 412,
		message: "Precondition Failed"
	},
	{
		code: 413,
		message: "Payload Too Large"
	},
	{
		code: 414,
		message: "URI Too Long"
	},
	{
		code: 415,
		message: "Unsupported Media Type"
	},
	{
		code: 416,
		message: "Range Not Satisfiable"
	},
	{
		code: 417,
		message: "Expectation Failed"
	},
	{
		code: 418,
		message: "I'm a teapot"
	},
	{
		code: 421,
		message: "Misdirected Request"
	},
	{
		code: 422,
		message: "Unprocessable Entity"
	},
	{
		code: 423,
		message: "Locked"
	},
	{
		code: 424,
		message: "Failed Dependency"
	},
	{
		code: 425,
		message: "Too Early"
	},
	{
		code: 426,
		message: "Upgrade Required"
	},
	{
		code: 428,
		message: "Precondition Required"
	},
	{
		code: 429,
		message: "Too Many Requests"
	},
	{
		code: 431,
		message: "Request Header Fields Too Large"
	},
	{
		code: 451,
		message: "Unavailable For Legal Reasons"
	},
	{
		code: 500,
		message: "Internal Server Error"
	},
	{
		code: 501,
		message: "Not Implemented"
	},
	{
		code: 502,
		message: "Bad Gateway"
	},
	{
		code: 503,
		message: "Service Unavailable"
	},
	{
		code: 504,
		message: "Gateway Timeout"
	},
	{
		code: 505,
		message: "HTTP Version Not Supported"
	},
	{
		code: 506,
		message: "Variant Also Negotiates"
	},
	{
		code: 507,
		message: "Insufficient Storage"
	},
	{
		code: 508,
		message: "Loop Detected"
	},
	{
		code: 510,
		message: "Not Extended"
	},
	{
		code: 511,
		message: "Network Authentication Required"
	}
];
const statusSchema = {
	description: "Return status code or random status code if more than one are given ",
	tags: ["Status Codes"],
	params: {
		type: "object",
		properties: { code: {
			type: "string",
			description: "HTTP status code to be returned"
		} }
	},
	response: { 200: {
		type: "object",
		properties: { status: { type: "string" } }
	} }
};
const statusCodeRoute = (fastify) => {
	const handler = async (request, reply) => {
		let { code } = request.params;
		if (!code || !statusCodes.map((status) => status.code).includes(Number(code))) code = statusCodes[randomInt(0, statusCodes.length)].code.toString();
		await reply.code(Number(code)).send({ status: `Response with status code ${code}` });
	};
	fastify.get("/status/:code?", { schema: statusSchema }, handler);
	fastify.post("/status/:code?", { schema: statusSchema }, handler);
	fastify.put("/status/:code?", { schema: statusSchema }, handler);
	fastify.patch("/status/:code?", { schema: statusSchema }, handler);
	fastify.delete("/status/:code?", { schema: statusSchema }, handler);
};
const fastifySwaggerConfig = { openapi: {
	info: {
		title: "Mock HTTP API",
		description: `
A simple HTTP server that can be used to mock HTTP responses for testing purposes. Inspired by [httpbin](https://httpbin.org/) and built using \`nodejs\` and \`fastify\` with the idea of running it via https://mockhttp.org, via docker \`jaredwray/mockhttp\`, or nodejs \`npm install jaredwray/mockhttp\`.

* [GitHub Repository](https://github.com/jaredwray/mockhttp)
* [Docker Image](https://hub.docker.com/r/jaredwray/mockhttp)
* [NPM Package](https://www.npmjs.com/package/jaredwray/mockhttp)

# About mockhttp.org 

[mockhttp.org](https://mockhttp.org) is a free service that runs this codebase and allows you to use it for testing purposes. It is a simple way to mock HTTP responses for testing purposes. Ran via [Cloudflare](https://cloudflare.com) and [Google Cloud Run](https://cloud.google.com/run/) across 7 regions globally and can do millions of requests per second.
`,
		version: "1.5.1"
	},
	consumes: ["application/json"],
	produces: ["application/json"]
} };
const registerSwaggerUi = async (fastify) => {
	await fastify.register(fastifySwaggerUi, {
		routePrefix: "/docs",
		uiConfig: {
			docExpansion: "none",
			deepLinking: false
		},
		uiHooks: {
			onRequest(_request, _reply, next) {
				next();
			},
			preHandler(_request, _reply, next) {
				next();
			}
		},
		staticCSP: true,
		transformSpecification: (swaggerObject, _request, _reply) => swaggerObject,
		transformSpecificationClone: true
	});
};
//#endregion
//#region src/tap-manager.ts
/**
* Manages HTTP response injections for testing and mocking
*/
var TapManager = class {
	_injections;
	constructor() {
		this._injections = /* @__PURE__ */ new Map();
	}
	/**
	* Get all active injection taps
	*/
	get injections() {
		return this._injections;
	}
	/**
	* Check if there are any active injections
	*/
	get hasInjections() {
		return this._injections.size > 0;
	}
	/**
	* Inject a custom response for requests matching the given criteria
	* @param response - The response configuration or a function that returns it
	* @param matcher - Optional criteria to match requests
	* @returns A tap object that can be used to remove the injection
	*/
	inject(response, matcher) {
		const id = randomUUID();
		const tap = {
			id,
			response,
			matcher
		};
		this._injections.set(id, tap);
		return tap;
	}
	/**
	* Remove an injection by its tap object or ID
	* @param tapOrId - The tap object or tap ID to remove
	* @returns true if the injection was removed, false if it wasn't found
	*/
	removeInjection(tapOrId) {
		const id = typeof tapOrId === "string" ? tapOrId : tapOrId.id;
		return this._injections.delete(id);
	}
	/**
	* Find the first injection that matches the given request
	* @param request - The Fastify request object
	* @returns The matching injection tap, or undefined if no match
	*/
	matchRequest(request) {
		for (const tap of this._injections.values()) if (this.requestMatches(request, tap.matcher)) return tap;
	}
	/**
	* Clear all injections
	*/
	clear() {
		this._injections.clear();
	}
	/**
	* Check if a request matches the given matcher criteria
	* @param request - The Fastify request object
	* @param matcher - The matcher criteria (undefined matches all requests)
	* @returns true if the request matches
	*/
	requestMatches(request, matcher) {
		if (!matcher) return true;
		if (matcher.url) {
			if (!this.urlMatches(request.url, matcher.url)) return false;
		}
		if (matcher.hostname) {
			if (request.hostname !== matcher.hostname) return false;
		}
		if (matcher.method) {
			if (request.method.toUpperCase() !== matcher.method.toUpperCase()) return false;
		}
		if (matcher.headers) {
			for (const [key, value] of Object.entries(matcher.headers)) if (request.headers[key.toLowerCase()] !== value) return false;
		}
		return true;
	}
	/**
	* Check if a URL matches a pattern (supports wildcards with *)
	* @param url - The URL to test
	* @param pattern - The pattern to match against
	* @returns true if the URL matches the pattern
	*/
	urlMatches(url, pattern) {
		const urlPath = url.split("?")[0];
		if (pattern === urlPath) return true;
		if (pattern.includes("*")) {
			const regexPattern = pattern.split("*").map((part) => part.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(".*");
			return new RegExp(`^${regexPattern}$`).test(urlPath);
		}
		return false;
	}
};
//#endregion
//#region src/mock-http.ts
var MockHttp = class extends Hookified {
	_port = 3e3;
	_host = "0.0.0.0";
	_autoDetectPort = true;
	_helmet = true;
	_apiDocs = true;
	_logging = true;
	_httpBin = {
		httpMethods: true,
		redirects: true,
		requestInspection: true,
		responseInspection: true,
		responseFormats: true,
		statusCodes: true,
		cookies: true,
		anything: true,
		auth: true,
		images: true,
		dynamicData: true
	};
	_rateLimit = {
		max: 1e3,
		timeWindow: "1 minute",
		allowList: ["127.0.0.1", "::1"]
	};
	_http2 = false;
	_http1 = true;
	_https;
	_httpsCredentials;
	_server = Fastify();
	_taps = new TapManager();
	constructor(options) {
		super(options?.hookOptions);
		if (options?.port) this._port = options.port;
		if (options?.host) this._host = options.host;
		if (options?.helmet !== void 0) this._helmet = options.helmet;
		if (options?.apiDocs !== void 0) this._apiDocs = options.apiDocs;
		if (options?.httpBin !== void 0) this._httpBin = options.httpBin;
		if (options?.rateLimit !== void 0) if (options.rateLimit === false) this._rateLimit = void 0;
		else this._rateLimit = options.rateLimit;
		if (options?.logging !== void 0) this._logging = options.logging;
		if (options?.http2 !== void 0) this._http2 = options.http2;
		if (options?.http1 !== void 0) this._http1 = options.http1;
		if (options?.https !== void 0) if (options.https === true) this._https = { autoGenerate: true };
		else if (options.https === false) this._https = void 0;
		else this._https = options.https;
	}
	/**
	* The port to listen on. If not provided, defaults to 3000 and detects the next available port if in use.
	* @default 3000
	*/
	get port() {
		return this._port;
	}
	/**
	* The port to listen on. If not provided, defaults to 3000 and detects the next available port if in use.
	* @default 3000
	*/
	set port(port) {
		this._port = port;
	}
	/**
	* The host to listen on. If not provided, defaults to 'localhost'.
	* @default 'localhost'
	*/
	get host() {
		return this._host;
	}
	/**
	* The host to listen on. If not provided, defaults to 'localhost'.
	* @default 'localhost'
	*/
	set host(host) {
		this._host = host;
	}
	/**
	* Whether to automatically detect the next available port if the provided port is in use. Defaults to true.
	* @default true
	*/
	get autoDetectPort() {
		return this._autoDetectPort;
	}
	/**
	* Whether to automatically detect the next available port if the provided port is in use. Defaults to true.
	* @default true
	*/
	set autoDetectPort(autoDetectPort) {
		this._autoDetectPort = autoDetectPort;
	}
	/**
	* Whether to use Helmet for security headers. Defaults to true.
	* @default true
	*/
	get helmet() {
		return this._helmet;
	}
	/**
	* Whether to use Helmet for security headers. Defaults to true.
	* @default true
	*/
	set helmet(helmet) {
		this._helmet = helmet;
	}
	/**
	* Whether to use Swagger for API documentation. Defaults to true.
	* @default true
	*/
	get apiDocs() {
		return this._apiDocs;
	}
	/**
	* Whether to use Swagger for API documentation. Defaults to true.
	* @default true
	*/
	set apiDocs(apiDocs) {
		this._apiDocs = apiDocs;
	}
	/**
	* Whether to enable logging. Defaults to true.
	* @default true
	*/
	get logging() {
		return this._logging;
	}
	/**
	* Whether to enable logging. Defaults to true.
	* @default true
	*/
	set logging(logging) {
		this._logging = logging;
	}
	/**
	* HTTPS configuration. Set to true to auto-generate a self-signed certificate,
	* or provide an HttpsOptions object with cert/key or auto-generation options.
	*/
	get https() {
		return this._https;
	}
	/**
	* HTTPS configuration. Set to true to auto-generate a self-signed certificate,
	* or provide an HttpsOptions object with cert/key or auto-generation options.
	*/
	set https(value) {
		if (value === true) this._https = { autoGenerate: true };
		else if (value === false || value === void 0) this._https = void 0;
		else this._https = value;
	}
	/**
	* Whether the server is running in HTTPS mode.
	*/
	get isHttps() {
		return this._httpsCredentials !== void 0;
	}
	/**
	* Whether HTTP/2 support is enabled.
	* @default false
	*/
	get http2() {
		return this._http2;
	}
	/**
	* Whether HTTP/2 support is enabled.
	* @default false
	*/
	set http2(http2) {
		this._http2 = http2;
	}
	/**
	* Whether to allow HTTP/1.1 fallback when using HTTP/2 with HTTPS.
	* @default true
	*/
	get http1() {
		return this._http1;
	}
	/**
	* Whether to allow HTTP/1.1 fallback when using HTTP/2 with HTTPS.
	* @default true
	*/
	set http1(http1) {
		this._http1 = http1;
	}
	/**
	* HTTP Bin options. Defaults to all enabled.
	*/
	get httpBin() {
		return this._httpBin;
	}
	/**
	* HTTP Bin options. Defaults to all enabled.
	*/
	set httpBin(httpBinary) {
		this._httpBin = httpBinary;
	}
	/**
	* Rate limiting options. Defaults to 1000 requests per minute (localhost excluded).
	* Set to undefined to disable rate limiting, or provide custom options to configure.
	* @default { max: 1000, timeWindow: "1 minute", allowList: ["127.0.0.1", "::1"] }
	*/
	get rateLimit() {
		return this._rateLimit;
	}
	/**
	* Rate limiting options. Defaults to 1000 requests per minute (localhost excluded).
	* Set to undefined to disable rate limiting, or provide custom options to configure.
	*
	* Note: Changing this property requires restarting the server (close() then start()) for changes to take effect.
	* @default { max: 1000, timeWindow: "1 minute", allowList: ["127.0.0.1", "::1"] }
	*/
	set rateLimit(rateLimit) {
		this._rateLimit = rateLimit;
	}
	/**
	* The Fastify server instance.
	*/
	get server() {
		return this._server;
	}
	/**
	* The Fastify server instance.
	*/
	set server(server) {
		this._server = server;
	}
	/**
	* The TapManager instance for managing injection taps.
	*/
	get taps() {
		return this._taps;
	}
	/**
	* The TapManager instance for managing injection taps.
	*/
	set taps(taps) {
		this._taps = taps;
	}
	/**
	* Start the Fastify server. If the server is already running, it will be closed and restarted.
	*/
	async start() {
		this._httpsCredentials = void 0;
		if (this._https) this._httpsCredentials = await this.resolveHttpsCredentials(this._https);
		try {
			/* v8 ignore next -- @preserve */
			if (this._server) await this._server.close();
			const fastifyOptions = { ...getFastifyConfig(this._logging) };
			if (this._http2) fastifyOptions.http2 = true;
			if (this._httpsCredentials) {
				const httpsOptions = {
					key: this._httpsCredentials.key,
					cert: this._httpsCredentials.cert
				};
				if (this._http2) httpsOptions.allowHTTP1 = this._http1;
				fastifyOptions.https = httpsOptions;
			}
			this._server = Fastify(fastifyOptions);
			this._server.addHook("onRequest", async (request, reply) => {
				const matchedTap = this._taps.matchRequest(request);
				if (matchedTap) {
					const { response, statusCode = 200, headers } = typeof matchedTap.response === "function" ? matchedTap.response(request) : matchedTap.response;
					reply.code(statusCode);
					if (headers) for (const [key, value] of Object.entries(headers)) reply.header(key, value);
					return reply.send(response);
				}
			});
			await this._server.register(fastifyStatic, {
				root: path.resolve("./node_modules/@scalar/api-reference/dist"),
				prefix: "/scalar"
			});
			await this.server.register(fastifyStatic, {
				root: path.resolve("./public"),
				decorateReply: false
			});
			await this._server.register(sitemapRoute);
			if (this._helmet) await this._server.register(fastifyHelmet, { contentSecurityPolicy: { directives: {
				defaultSrc: ["'self'"],
				scriptSrc: [
					"'self'",
					"'unsafe-inline'",
					"'wasm-unsafe-eval'"
				],
				styleSrc: ["'self'", "'unsafe-inline'"],
				imgSrc: [
					"'self'",
					"data:",
					"https:"
				],
				fontSrc: ["'self'", "data:"],
				connectSrc: ["'self'"],
				frameSrc: ["'self'"],
				objectSrc: ["'none'"],
				upgradeInsecureRequests: []
			} } });
			if (this._rateLimit) await this._server.register(fastifyRateLimit, this._rateLimit);
			if (this._apiDocs) await this.registerApiDocs();
			const { httpMethods, redirects, requestInspection, responseInspection, responseFormats, statusCodes, cookies, anything, auth, images, dynamicData } = this._httpBin;
			if (httpMethods) await this.registerHttpMethods();
			if (statusCodes) await this.registerStatusCodeRoutes();
			if (requestInspection) await this.registerRequestInspectionRoutes();
			if (responseInspection) await this.registerResponseInspectionRoutes();
			if (responseFormats) await this.registerResponseFormatRoutes();
			if (redirects) await this.registerRedirectRoutes();
			if (cookies) await this.registerCookieRoutes();
			if (anything) await this.registerAnythingRoutes();
			if (auth) await this.registerAuthRoutes();
			if (images) await this.registerImageRoutes();
			if (dynamicData) await this.registerDynamicDataRoutes();
			if (this._autoDetectPort) {
				const originalPort = this._port;
				this._port = await this.detectPort();
				if (originalPort !== this._port) this._server.log.info(`Port ${originalPort} is in use, detected next available port: ${this._port}`);
			}
			await this._server.listen({
				port: this._port,
				host: this._host
			});
		} catch (error) {
			/* v8 ignore next -- @preserve */
			this._server.log.error(error);
		}
	}
	/**
	* Close the Fastify server.
	*/
	async close() {
		await this._server.close();
	}
	/**
	* Detect the next available port to run on.
	* @returns The port that is available to run on
	*/
	async detectPort() {
		const { port } = this;
		return detect(port);
	}
	/**
	* This will register the API documentation routes including openapi and swagger ui.
	* @param fastifyInstance - the server instance to register the routes on.
	*/
	async registerApiDocs(fastifyInstance) {
		const fastify = fastifyInstance ?? this._server;
		await fastify.register(fastifySwagger, fastifySwaggerConfig);
		await registerSwaggerUi(fastify);
		await fastify.register(indexRoute);
	}
	/**
	* Register the HTTP methods routes.
	* @param fastifyInstance - the server instance to register the routes on.
	*/
	async registerHttpMethods(fastifyInstance) {
		const fastify = fastifyInstance ?? this._server;
		await fastify.register(getRoute);
		await fastify.register(postRoute);
		await fastify.register(deleteRoute);
		await fastify.register(putRoute);
		await fastify.register(patchRoute);
	}
	/**
	* Register the status code routes.
	* @param fastifyInstance - the server instance to register the routes on.
	*/
	async registerStatusCodeRoutes(fastifyInstance) {
		await (fastifyInstance ?? this._server).register(statusCodeRoute);
	}
	/**
	* Register the request inspection routes.
	* @param fastifyInstance - the server instance to register the routes on.
	*/
	async registerRequestInspectionRoutes(fastifyInstance) {
		const fastify = fastifyInstance ?? this._server;
		await fastify.register(ipRoute);
		await fastify.register(headersRoute);
		await fastify.register(userAgentRoute);
	}
	/**
	* Register the response inspection routes.
	* @param fastifyInstance - the server instance to register the routes on.
	*/
	async registerResponseInspectionRoutes(fastifyInstance) {
		const fastify = fastifyInstance ?? this._server;
		await fastify.register(cacheRoutes);
		await fastify.register(etagRoutes);
		await fastify.register(responseHeadersRoutes);
	}
	async registerResponseFormatRoutes(fastifyInstance) {
		await (fastifyInstance ?? this._server).register(responseFormatRoutes);
	}
	/**
	* Register the redirect routes.
	* @param fastifyInstance - the server instance to register the routes on.
	*/
	async registerRedirectRoutes(fastifyInstance) {
		const fastify = fastifyInstance ?? this._server;
		await fastify.register(absoluteRedirectRoute);
		await fastify.register(relativeRedirectRoute);
		await fastify.register(redirectToRoute);
	}
	/**
	* Register the cookie routes.
	* @param fastifyInstance - the server instance to register the routes on.
	*/
	async registerCookieRoutes(fastifyInstance) {
		const fastify = fastifyInstance ?? this._server;
		await fastify.register(fastifyCookie);
		await fastify.register(getCookiesRoute);
		await fastify.register(postCookieRoute);
		await fastify.register(deleteCookieRoute);
	}
	/**
	* Register the anything routes.
	* @param fastifyInstance - the server instance to register the routes on.
	*/
	async registerAnythingRoutes(fastifyInstance) {
		await (fastifyInstance ?? this._server).register(anythingRoute);
	}
	/**
	* Register the auth routes.
	* @param fastifyInstance - the server instance to register the routes on.
	*/
	async registerAuthRoutes(fastifyInstance) {
		const fastify = fastifyInstance ?? this._server;
		await fastify.register(basicAuthRoute);
		await fastify.register(hiddenBasicAuthRoute);
		await fastify.register(bearerAuthRoute);
		await fastify.register(digestAuthRoute);
	}
	/**
	* Register the image routes.
	* @param fastifyInstance - the server instance to register the routes on.
	*/
	async registerImageRoutes(fastifyInstance) {
		await (fastifyInstance ?? this._server).register(imageRoutes);
	}
	/**
	* Register the dynamic data routes.
	* @param fastifyInstance - the server instance to register the routes on.
	*/
	async registerDynamicDataRoutes(fastifyInstance) {
		const fastify = fastifyInstance ?? this._server;
		await fastify.register(uuidRoute);
		await fastify.register(bytesRoute);
		await fastify.register(streamBytesRoute);
		await fastify.register(delayRoute);
		await fastify.register(base64Route);
		await fastify.register(streamRoute);
		await fastify.register(rangeRoute);
		await fastify.register(dripRoute);
		await fastify.register(linksRoute);
	}
	async resolveHttpsCredentials(options) {
		if (options.cert && options.key) return {
			cert: await this.loadPemValue(options.cert),
			key: await this.loadPemValue(options.key)
		};
		if ((options.cert || options.key) && options.autoGenerate !== true) throw new Error("HTTPS options must include both 'cert' and 'key'. Only one was provided.");
		if (options.autoGenerate !== false) {
			const result = generateCertificate(options.certificateOptions);
			return {
				cert: result.cert,
				key: result.key
			};
		}
		throw new Error("HTTPS is enabled but no certificate was provided and autoGenerate is false.");
	}
	async loadPemValue(value) {
		if (value.startsWith("-----")) return value;
		return fsPromises.readFile(value, "utf8");
	}
};
//#endregion
//#region src/index.ts
const start = async () => {
	const mockHttp = new MockHttp();
	/* v8 ignore next -- @preserve */
	if (process$1.env.PORT) mockHttp.port = Number.parseInt(process$1.env.PORT, 10);
	/* v8 ignore next -- @preserve */
	if (process$1.env.HOST) mockHttp.host = process$1.env.HOST;
	/* v8 ignore next -- @preserve */
	if (process$1.env.LOGGING === "false") mockHttp.logging = false;
	/* v8 ignore next -- @preserve */
	if (process$1.env.HTTP2 === "true") mockHttp.http2 = true;
	await mockHttp.start();
	return mockHttp;
};
/* v8 ignore next -- @preserve */
if (import.meta.url === `file://${process$1.argv[1]}`) await start();
//#endregion
export { MockHttp as default, MockHttp as mockhttp, generateCertificate, generateCertificateFiles, start };
