import { describe, it } from "@effect/vitest" import { deepStrictEqual, strictEqual } from "@effect/vitest/utils" import { escapeToken, unescapeToken } from "effect/JsonPointer" describe("JsonPointer", () => { describe("escapeToken", () => { describe("basic escaping", () => { it("escapes tilde (~) to ~0", () => { deepStrictEqual(escapeToken("a~b"), "a~0b") deepStrictEqual(escapeToken("~"), "~0") deepStrictEqual(escapeToken("~~"), "~0~0") }) it("escapes forward slash (/) to ~1", () => { deepStrictEqual(escapeToken("a/b"), "a~1b") deepStrictEqual(escapeToken("/"), "~1") deepStrictEqual(escapeToken("//"), "~1~1") }) it("escapes both tilde and slash in same string", () => { deepStrictEqual(escapeToken("a~b/c"), "a~0b~1c") deepStrictEqual(escapeToken("path/to~key"), "path~1to~0key") deepStrictEqual(escapeToken("~a/b~c/d~"), "~0a~1b~0c~1d~0") }) }) describe("order of operations", () => { it("replaces ~ before / to prevent double-escaping", () => { const token = "a~b/c" const result = escapeToken(token) deepStrictEqual(result, "a~0b~1c") deepStrictEqual(result.includes("~0~1"), false) deepStrictEqual(result.includes("~1~0"), false) }) it("handles ~ followed by / correctly", () => { deepStrictEqual(escapeToken("a~/b"), "a~0~1b") }) it("handles / followed by ~ correctly", () => { deepStrictEqual(escapeToken("a/~b"), "a~1~0b") }) it("handles consecutive special characters", () => { deepStrictEqual(escapeToken("~~"), "~0~0") deepStrictEqual(escapeToken("//"), "~1~1") deepStrictEqual(escapeToken("~/"), "~0~1") deepStrictEqual(escapeToken("/~"), "~1~0") }) }) describe("edge cases", () => { it("returns empty string unchanged", () => { deepStrictEqual(escapeToken(""), "") strictEqual(escapeToken(""), "") }) it("returns input unchanged when no special characters", () => { deepStrictEqual(escapeToken("abc"), "abc") deepStrictEqual(escapeToken("hello world"), "hello world") deepStrictEqual(escapeToken("123"), "123") }) it("handles strings with only tildes", () => { deepStrictEqual(escapeToken("~~~"), "~0~0~0") deepStrictEqual(escapeToken("a~b~c"), "a~0b~0c") }) it("handles strings with only slashes", () => { deepStrictEqual(escapeToken("///"), "~1~1~1") deepStrictEqual(escapeToken("a/b/c"), "a~1b~1c") }) it("handles multiple occurrences of same character", () => { deepStrictEqual(escapeToken("a~b~c~d"), "a~0b~0c~0d") deepStrictEqual(escapeToken("a/b/c/d"), "a~1b~1c~1d") }) it("handles special sequences that could be confused", () => { deepStrictEqual(escapeToken("~01"), "~001") deepStrictEqual(escapeToken("~10"), "~010") deepStrictEqual(escapeToken("~00"), "~000") deepStrictEqual(escapeToken("~11"), "~011") }) }) describe("unicode and other characters", () => { it("passes through unicode characters unchanged", () => { deepStrictEqual(escapeToken("héllo"), "héllo") deepStrictEqual(escapeToken("世界"), "世界") deepStrictEqual(escapeToken("🚀"), "🚀") }) it("passes through other special characters unchanged", () => { deepStrictEqual(escapeToken("a.b"), "a.b") deepStrictEqual(escapeToken("a-b"), "a-b") deepStrictEqual(escapeToken("a_b"), "a_b") deepStrictEqual(escapeToken("a@b"), "a@b") deepStrictEqual(escapeToken("a#b"), "a#b") deepStrictEqual(escapeToken("a$b"), "a$b") deepStrictEqual(escapeToken("a%b"), "a%b") deepStrictEqual(escapeToken("a&b"), "a&b") deepStrictEqual(escapeToken("a*b"), "a*b") deepStrictEqual(escapeToken("a+b"), "a+b") deepStrictEqual(escapeToken("a=b"), "a=b") deepStrictEqual(escapeToken("a?b"), "a?b") deepStrictEqual(escapeToken("a!b"), "a!b") }) it("handles mixed unicode and special characters", () => { deepStrictEqual(escapeToken("héllo~world/test"), "héllo~0world~1test") deepStrictEqual(escapeToken("世界/🌍~key"), "世界~1🌍~0key") }) }) describe("immutability", () => { it("does not mutate input string", () => { const original = "a~b/c" const originalCopy = "a~b/c" escapeToken(original) deepStrictEqual(original, originalCopy) strictEqual(original, originalCopy) }) it("returns a different token value when escaping changes the input", () => { const input = "a~b" const result = escapeToken(input) strictEqual(result === input, false) deepStrictEqual(result, "a~0b") }) }) describe("RFC 6901 examples", () => { it("handles examples from documentation", () => { deepStrictEqual(escapeToken("name/alias"), "name~1alias") deepStrictEqual(escapeToken("path/to~key"), "path~1to~0key") }) }) }) describe("unescapeToken", () => { describe("basic unescaping", () => { it("unescapes ~0 to ~", () => { deepStrictEqual(unescapeToken("a~0b"), "a~b") deepStrictEqual(unescapeToken("~0"), "~") deepStrictEqual(unescapeToken("~0~0"), "~~") }) it("unescapes ~1 to /", () => { deepStrictEqual(unescapeToken("a~1b"), "a/b") deepStrictEqual(unescapeToken("~1"), "/") deepStrictEqual(unescapeToken("~1~1"), "//") }) it("unescapes both ~0 and ~1 in same string", () => { deepStrictEqual(unescapeToken("a~0b~1c"), "a~b/c") deepStrictEqual(unescapeToken("path~1to~0key"), "path/to~key") deepStrictEqual(unescapeToken("~0a~1b~0c~1d~0"), "~a/b~c/d~") }) }) describe("order of operations", () => { it("replaces ~1 before ~0 to prevent incorrect decoding", () => { const token = "a~1b~0c" const result = unescapeToken(token) deepStrictEqual(result, "a/b~c") }) it("handles ~01 sequence correctly (unescapes to ~1)", () => { deepStrictEqual(unescapeToken("~01"), "~1") }) it("handles ~10 sequence correctly (unescapes to /0)", () => { deepStrictEqual(unescapeToken("~10"), "/0") }) it("handles ~00 sequence correctly (unescapes to ~0)", () => { deepStrictEqual(unescapeToken("~00"), "~0") }) it("handles ~11 sequence correctly (unescapes to /1)", () => { deepStrictEqual(unescapeToken("~11"), "/1") }) }) describe("edge cases", () => { it("returns empty string unchanged", () => { deepStrictEqual(unescapeToken(""), "") strictEqual(unescapeToken(""), "") }) it("returns input unchanged when no escaped sequences", () => { deepStrictEqual(unescapeToken("abc"), "abc") deepStrictEqual(unescapeToken("hello world"), "hello world") deepStrictEqual(unescapeToken("123"), "123") }) it("handles strings with only ~0 sequences", () => { deepStrictEqual(unescapeToken("~0~0~0"), "~~~") deepStrictEqual(unescapeToken("a~0b~0c"), "a~b~c") }) it("handles strings with only ~1 sequences", () => { deepStrictEqual(unescapeToken("~1~1~1"), "///") deepStrictEqual(unescapeToken("a~1b~1c"), "a/b/c") }) it("handles multiple occurrences of same sequence", () => { deepStrictEqual(unescapeToken("a~0b~0c~0d"), "a~b~c~d") deepStrictEqual(unescapeToken("a~1b~1c~1d"), "a/b/c/d") }) it("decodes escaped sequences at token boundaries and suffixes", () => { deepStrictEqual(unescapeToken("~0"), "~") deepStrictEqual(unescapeToken("~1"), "/") deepStrictEqual(unescapeToken("a~0"), "a~") deepStrictEqual(unescapeToken("a~1"), "a/") }) }) describe("unicode and other characters", () => { it("passes through unicode characters unchanged", () => { deepStrictEqual(unescapeToken("héllo"), "héllo") deepStrictEqual(unescapeToken("世界"), "世界") deepStrictEqual(unescapeToken("🚀"), "🚀") }) it("passes through other special characters unchanged", () => { deepStrictEqual(unescapeToken("a.b"), "a.b") deepStrictEqual(unescapeToken("a-b"), "a-b") deepStrictEqual(unescapeToken("a_b"), "a_b") deepStrictEqual(unescapeToken("a@b"), "a@b") }) it("handles mixed unicode and escaped sequences", () => { deepStrictEqual(unescapeToken("héllo~0world~1test"), "héllo~world/test") deepStrictEqual(unescapeToken("世界~1🌍~0key"), "世界/🌍~key") }) }) describe("immutability", () => { it("does not mutate input string", () => { const original = "a~0b~1c" const originalCopy = "a~0b~1c" unescapeToken(original) deepStrictEqual(original, originalCopy) strictEqual(original, originalCopy) }) it("returns a different token value when unescaping changes the input", () => { const input = "a~0b" const result = unescapeToken(input) strictEqual(result === input, false) deepStrictEqual(result, "a~b") }) }) describe("RFC 6901 examples", () => { it("handles examples from documentation", () => { deepStrictEqual(unescapeToken("name~1alias"), "name/alias") deepStrictEqual(unescapeToken("path~1to~0key"), "path/to~key") }) }) }) describe("round-trip", () => { it("unescapeToken(escapeToken(token)) === token for basic cases", () => { const cases = [ "abc", "a~b", "a/b", "a~b/c", "path/to~key", "name/alias", "~", "/", "~~", "//", "~/", "/~" ] for (const token of cases) { const escaped = escapeToken(token) const unescaped = unescapeToken(escaped) deepStrictEqual(unescaped, token, `Failed for token: ${token}`) } }) it("unescapeToken(escapeToken(token)) === token for edge cases", () => { const cases = [ "", "~01", "~10", "~00", "~11", "a~01b", "a~10b", "a~00b", "a~11b" ] for (const token of cases) { const escaped = escapeToken(token) const unescaped = unescapeToken(escaped) deepStrictEqual(unescaped, token, `Failed for token: ${token}`) } }) it("unescapeToken(escapeToken(token)) === token for unicode", () => { const cases = [ "héllo", "世界", "🚀", "héllo~world/test", "世界/🌍~key" ] for (const token of cases) { const escaped = escapeToken(token) const unescaped = unescapeToken(escaped) deepStrictEqual(unescaped, token, `Failed for token: ${token}`) } }) it("unescapeToken(escapeToken(token)) === token for complex sequences", () => { const cases = [ "a~b/c~d/e", "path/to~key/value", "~a/b~c/d~", "a~/b/c~d", "a/~b/c~d" ] for (const token of cases) { const escaped = escapeToken(token) const unescaped = unescapeToken(escaped) deepStrictEqual(unescaped, token, `Failed for token: ${token}`) } }) it("handles multiple round-trips", () => { const token = "a~b/c~d" let current = token for (let i = 0; i < 5; i++) { current = escapeToken(current) current = unescapeToken(current) deepStrictEqual(current, token) } }) }) })