From 1e040e99320a534194397da34f2694ce1d7b270b Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Tue, 7 Jan 2025 18:42:21 +0000 Subject: [PATCH] Add method to determine if a URI is a template or not --- src/shared/uriTemplate.test.ts | 17 +++++++++++++++++ src/shared/uriTemplate.ts | 29 ++++++++++++++++++++++++----- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/shared/uriTemplate.test.ts b/src/shared/uriTemplate.test.ts index fc20121d..0e751311 100644 --- a/src/shared/uriTemplate.test.ts +++ b/src/shared/uriTemplate.test.ts @@ -1,6 +1,23 @@ import { UriTemplate } from "./uriTemplate.js"; describe("UriTemplate", () => { + describe("isTemplate", () => { + it("should return true for strings containing template expressions", () => { + expect(UriTemplate.isTemplate("{foo}")).toBe(true); + expect(UriTemplate.isTemplate("/users/{id}")).toBe(true); + expect(UriTemplate.isTemplate("http://example.com/{path}/{file}")).toBe(true); + expect(UriTemplate.isTemplate("/search{?q,limit}")).toBe(true); + }); + + it("should return false for strings without template expressions", () => { + expect(UriTemplate.isTemplate("")).toBe(false); + expect(UriTemplate.isTemplate("plain string")).toBe(false); + expect(UriTemplate.isTemplate("http://example.com/foo/bar")).toBe(false); + expect(UriTemplate.isTemplate("{}")).toBe(false); // Empty braces don't count + expect(UriTemplate.isTemplate("{ }")).toBe(false); // Just whitespace doesn't count + }); + }); + describe("simple string expansion", () => { it("should expand simple string variables", () => { const template = new UriTemplate("http://example.com/users/{username}"); diff --git a/src/shared/uriTemplate.ts b/src/shared/uriTemplate.ts index 900c14e7..47671fdd 100644 --- a/src/shared/uriTemplate.ts +++ b/src/shared/uriTemplate.ts @@ -1,6 +1,6 @@ // Claude-authored implementation of RFC 6570 URI Templates -type Variables = Record; +export type Variables = Record; const MAX_TEMPLATE_LENGTH = 1000000; // 1MB const MAX_VARIABLE_LENGTH = 1000000; // 1MB @@ -8,7 +8,22 @@ const MAX_TEMPLATE_EXPRESSIONS = 10000; const MAX_REGEX_LENGTH = 1000000; // 1MB export class UriTemplate { - private static validateLength(str: string, max: number, context: string): void { + /** + * Returns true if the given string contains any URI template expressions. + * A template expression is a sequence of characters enclosed in curly braces, + * like {foo} or {?bar}. + */ + static isTemplate(str: string): boolean { + // Look for any sequence of characters between curly braces + // that isn't just whitespace + return /\{[^}\s]+\}/.test(str); + } + + private static validateLength( + str: string, + max: number, + context: string, + ): void { if (str.length > max) { throw new Error( `${context} exceeds maximum length of ${max} characters (got ${str.length})`, @@ -60,7 +75,7 @@ export class UriTemplate { const exploded = expr.includes("*"); const names = this.getNames(expr); const name = names[0]; - + // Validate variable name length for (const name of names) { UriTemplate.validateLength( @@ -263,7 +278,11 @@ export class UriTemplate { } pattern += "$"; - UriTemplate.validateLength(pattern, MAX_REGEX_LENGTH, "Generated regex pattern"); + UriTemplate.validateLength( + pattern, + MAX_REGEX_LENGTH, + "Generated regex pattern", + ); const regex = new RegExp(pattern); const match = uri.match(regex); @@ -284,4 +303,4 @@ export class UriTemplate { return result; } -} \ No newline at end of file +}