+2
-29
@@ -9,3 +9,3 @@ /** | ||
| //----------------------------------------------------------------------------- | ||
| import { stringifyRequest } from "./util.js"; | ||
| import { NoRouteMatchedError } from "./util.js"; | ||
| import { isCorsSimpleRequest, CorsPreflightData, assertCorsResponse, processCorsResponse, validateCorsRequest, CORS_REQUEST_METHOD, CORS_REQUEST_HEADERS, CORS_ORIGIN, createCorsPreflightError, getUnsafeHeaders, createCorsError, } from "./cors.js"; | ||
@@ -25,29 +25,2 @@ import { createCustomRequest } from "./custom-request.js"; | ||
| /** | ||
| * Formats a message for when no route is matched. | ||
| * @param {Request} request The request that wasn't matched. | ||
| * @param {string|any|FormData|null} body The body of the request. | ||
| * @param {Trace[]} traces The traces from the servers. | ||
| * @returns {string} The formatted message. | ||
| */ | ||
| function formatNoRouteMatchedMessage(request, body, traces) { | ||
| return `No route matched for ${request.method} ${request.url}. | ||
| Full Request: | ||
| ${stringifyRequest(request, body)} | ||
| ${traces.length === 0 | ||
| ? "No partial matches found." | ||
| : "Partial matches:\n\n" + | ||
| traces | ||
| .map(trace => { | ||
| let traceMessage = `${trace.title}:`; | ||
| trace.messages.forEach(message => { | ||
| traceMessage += `\n ${message}`; | ||
| }); | ||
| return traceMessage; | ||
| }) | ||
| .join("\n\n")}`; | ||
| } | ||
| /** | ||
| * Creates a base URL from a URL or string. This is also validates | ||
@@ -349,3 +322,3 @@ * the URL to ensure it's valid. Empty strings are invalid. | ||
| // throw an error saying the route wasn't matched | ||
| throw new Error(formatNoRouteMatchedMessage(request, body, possibleTraces)); | ||
| throw new NoRouteMatchedError(request, body, possibleTraces); | ||
| } | ||
@@ -352,0 +325,0 @@ /** |
@@ -131,5 +131,15 @@ /** | ||
| /** | ||
| * Traces the details of a request pattern to see if it matches any routes. | ||
| * @param {RequestPattern|string} request The request pattern to check. | ||
| * @returns {{traces:Array<Trace>, matched:boolean}} The trace result with match status. | ||
| */ | ||
| traceCalled(request: RequestPattern | string): { | ||
| traces: Array<Trace>; | ||
| matched: boolean; | ||
| }; | ||
| /** | ||
| * Determines if a route has been called. | ||
| * @param {RequestPattern|string} request The request pattern to check. | ||
| * @returns {boolean} `true` if the route was called, `false` if not. | ||
| * @throws {Error} If the request pattern doesn't match any registered routes. | ||
| */ | ||
@@ -136,0 +146,0 @@ called(request: RequestPattern | string): boolean; |
+63
-4
@@ -5,3 +5,3 @@ /** | ||
| */ | ||
| /* global Response, FormData, setTimeout */ | ||
| /* global Request, Response, Headers, FormData, setTimeout */ | ||
| //----------------------------------------------------------------------------- | ||
@@ -12,3 +12,3 @@ // Imports | ||
| import { statusTexts } from "./http.js"; | ||
| import { getBody } from "./util.js"; | ||
| import { getBody, NoRouteMatchedError } from "./util.js"; | ||
| //----------------------------------------------------------------------------- | ||
@@ -476,7 +476,61 @@ // Type Definitions | ||
| /** | ||
| * Traces the details of a request pattern to see if it matches any routes. | ||
| * @param {RequestPattern|string} request The request pattern to check. | ||
| * @returns {{traces:Array<Trace>, matched:boolean}} The trace result with match status. | ||
| */ | ||
| traceCalled(request) { | ||
| const requestPattern = typeof request === "string" | ||
| ? { method: "GET", url: request } | ||
| : request; | ||
| assertValidRequestPattern(requestPattern); | ||
| // if the URL doesn't begin with the baseUrl then add it | ||
| if (!requestPattern.url.startsWith(this.baseUrl)) { | ||
| requestPattern.url = new URL(requestPattern.url, this.baseUrl).href; | ||
| } | ||
| const allTraces = []; | ||
| // Check matched routes and collect traces in a single pass | ||
| const matchedRoutes = this.#matchedRoutes; | ||
| for (let i = 0; i < matchedRoutes.length; i++) { | ||
| const route = matchedRoutes[i]; | ||
| const trace = route.traceMatches(requestPattern); | ||
| // If we found a match, return immediately without building traces | ||
| if (trace.matches) { | ||
| return { traces: [], matched: true }; | ||
| } | ||
| // Otherwise, store the trace for later use | ||
| allTraces.push({ ...trace, title: route.toString() }); | ||
| } | ||
| // Collect traces from unmatched routes | ||
| const unmatchedRoutes = this.#unmatchedRoutes; | ||
| for (let i = 0; i < unmatchedRoutes.length; i++) { | ||
| const route = unmatchedRoutes[i]; | ||
| const trace = route.traceMatches(requestPattern); | ||
| // Only store traces that don't match because this is likely an error | ||
| allTraces.push({ ...trace, title: route.toString() }); | ||
| } | ||
| // Filter out traces that only have basic URL mismatch (single message) | ||
| // to focus on meaningful partial matches | ||
| const meaningfulTraces = allTraces.filter(trace => trace.messages.length > 1); | ||
| return { traces: meaningfulTraces, matched: false }; | ||
| } | ||
| /** | ||
| * Determines if a route has been called. | ||
| * @param {RequestPattern|string} request The request pattern to check. | ||
| * @returns {boolean} `true` if the route was called, `false` if not. | ||
| * @throws {Error} If the request pattern doesn't match any registered routes. | ||
| */ | ||
| called(request) { | ||
| if (this.#routes.length === 0) { | ||
| throw new Error("No routes registered to match against."); | ||
| } | ||
| const { traces, matched } = this.traceCalled(request); | ||
| if (matched) { | ||
| return true; | ||
| } | ||
| // if one of the traces matches then the route hasn't been called yet | ||
| if (traces.some(trace => trace.matches)) { | ||
| return false; | ||
| } | ||
| // No routes match this pattern at all, so throw an error | ||
| // We need to create a minimal Request-like object for the error | ||
| const requestPattern = typeof request === "string" | ||
@@ -486,7 +540,12 @@ ? { method: "GET", url: request } | ||
| assertValidRequestPattern(requestPattern); | ||
| // if the URL doesn't being with the baseUrl then add it | ||
| // if the URL doesn't begin with the baseUrl then add it | ||
| if (!requestPattern.url.startsWith(this.baseUrl)) { | ||
| requestPattern.url = new URL(requestPattern.url, this.baseUrl).href; | ||
| } | ||
| return this.#matchedRoutes.some(route => route.matches(requestPattern)); | ||
| // Create a minimal Request-like object for the error | ||
| const mockRequest = new Request(requestPattern.url, { | ||
| method: requestPattern.method, | ||
| headers: new Headers(requestPattern.headers || {}), | ||
| }); | ||
| throw new NoRouteMatchedError(mockRequest, null, traces); | ||
| } | ||
@@ -493,0 +552,0 @@ /** |
+22
-0
@@ -34,1 +34,23 @@ /** | ||
| } | ||
| /** | ||
| * Represents an error that occurs when no route matched a request. | ||
| * @extends {Error} | ||
| */ | ||
| export class NoRouteMatchedError extends Error { | ||
| /** | ||
| * Creates a new NoRouteMatchedError instance. | ||
| * @param {Request} request The request that wasn't matched. | ||
| * @param {string|any|FormData|null} body The body of the request. | ||
| * @param {Array<{title: string, messages: Array<string>}>} traces The traces from the servers. | ||
| */ | ||
| constructor(request: Request, body: string | any | FormData | null, traces: Array<{ | ||
| title: string; | ||
| messages: Array<string>; | ||
| }>); | ||
| request: Request; | ||
| body: any; | ||
| traces: { | ||
| title: string; | ||
| messages: Array<string>; | ||
| }[]; | ||
| } |
+37
-0
@@ -61,2 +61,39 @@ /** | ||
| /** | ||
| * Represents an error that occurs when no route matched a request. | ||
| * @extends {Error} | ||
| */ | ||
| export class NoRouteMatchedError extends Error { | ||
| /** | ||
| * Creates a new NoRouteMatchedError instance. | ||
| * @param {Request} request The request that wasn't matched. | ||
| * @param {string|any|FormData|null} body The body of the request. | ||
| * @param {Array<{title: string, messages: Array<string>}>} traces The traces from the servers. | ||
| */ | ||
| constructor(request, body, traces) { | ||
| const message = `No route matched for ${request.method} ${request.url}. | ||
| Full Request: | ||
| ${stringifyRequest(request, body)} | ||
| ${traces.length === 0 | ||
| ? "No partial matches found." | ||
| : "Partial matches:\n\n" + | ||
| traces | ||
| .map(trace => { | ||
| let traceMessage = `${trace.title}:`; | ||
| trace.messages.forEach(message => { | ||
| traceMessage += `\n ${message}`; | ||
| }); | ||
| return traceMessage; | ||
| }) | ||
| .join("\n\n")}`; | ||
| super(message); | ||
| this.name = "NoRouteMatchedError"; | ||
| this.request = request; | ||
| this.body = body; | ||
| this.traces = traces; | ||
| } | ||
| } | ||
| /** | ||
| * Parses a URL and returns a URL object. This is used instead | ||
@@ -63,0 +100,0 @@ * of the URL constructor to provide a standard error message, |
+2
-2
| { | ||
| "name": "mentoss", | ||
| "version": "0.11.0", | ||
| "version": "0.12.0", | ||
| "description": "A utility to mock fetch requests and responses.", | ||
@@ -45,3 +45,3 @@ "type": "module", | ||
| "pretest": "npm run build", | ||
| "test:unit": "mocha --exit tests/**/*.*", | ||
| "test:unit": "mocha --exit --forbid-only tests/**/*.*", | ||
| "test:jsr": "npx jsr@latest publish --dry-run", | ||
@@ -48,0 +48,0 @@ "test": "npm run test:unit" |
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
154053
2.98%3552
2.93%10
11.11%