Skip to content

Commit

Permalink
Fix URLPath absolute URL parsing
Browse files Browse the repository at this point in the history
This approach to ensureBase is more reliable than
trying to piecemeal set the origin. For example:

new URL("x:")
  • Loading branch information
samhh committed Apr 8, 2024
1 parent fb1b0b9 commit cc97614
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 19 deletions.
23 changes: 13 additions & 10 deletions src/URLPath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,15 @@ type URLPathSymbol = { readonly URLPathSymbol: unique symbol }
*/
export type URLPath = Newtype<URLPathSymbol, URL>

const phonyBase = "https://urlpath.fp-ts-std.samhh.com"
const phonyBase = new globalThis.URL("https://urlpath.fp-ts-std.samhh.com")

const ensureBase: Endomorphism<URL> = x =>
pipe(phonyBase, URL.clone, b => {
b.pathname = x.pathname
b.search = x.searchParams.toString()
b.hash = x.hash
return b
})

/**
* Check if a foreign value is a `URLPath`.
Expand All @@ -55,7 +63,7 @@ export const isURLPath: Refinement<unknown, URLPath> = (u): u is URLPath =>
// then, well, firstly I'm flattered. But secondly that's on them.
//
// Also nota bene that the origin check will only work with some protocols.
URL.isURL(u) && u.origin === phonyBase
URL.isURL(u) && u.origin === phonyBase.origin

/**
* Clone a `URLPath`.
Expand Down Expand Up @@ -92,7 +100,7 @@ export const clone: Endomorphism<URLPath> = over(URL.clone)
* @since 0.17.0
*/
export const fromURL = (x: URL): URLPath =>
pipe(new globalThis.URL(x.href, phonyBase), pack<URLPath>)
pipe(new globalThis.URL(x.href, phonyBase), ensureBase, pack<URLPath>)

/**
* Convert a `URLPath` to a `URL` with the provided `baseUrl`.
Expand Down Expand Up @@ -179,12 +187,7 @@ export const fromString =
// It should only throw some sort of `TypeError`:
// https://developer.mozilla.org/en-US/docs/Web/API/URL/URL
E.tryCatch(
() => {
const y = new globalThis.URL(x, phonyBase)
if (y.origin !== phonyBase)
throw new TypeError("Failed to retain phony base URL")
return y
},
() => ensureBase(new globalThis.URL(x, phonyBase)),
e => f(e as TypeError),
),
E.map(pack<URLPath>),
Expand Down Expand Up @@ -226,7 +229,7 @@ export const fromStringO: (x: string) => Option<URLPath> = flow(
* @since 0.17.0
*/
export const fromPathname = (x: string): URLPath => {
const y = new globalThis.URL("", phonyBase)
const y = URL.clone(phonyBase)
y.pathname = x
return pack(y)
}
Expand Down
21 changes: 12 additions & 9 deletions test/URLPath.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ describe("URLPath", () => {
expect(y.search).toEqual(x.search)
expect(y.hash).toEqual(x.hash)
})

it("does not retain the origin", () => {
const x = f(new URL("https://samhh.com/foo")) as unknown as URL
expect(x.href).toBe(`${phonyBase}/foo`)
})
})

describe("toURL", () => {
Expand Down Expand Up @@ -168,16 +173,14 @@ describe("URLPath", () => {
)
})

it("does not parse origins", () => {
const g = fromString(identity)

const e1 = unsafeUnwrapLeft(g("//x"))
expect(e1.name).toBe("TypeError")
expect(e1.message).toMatch(/phony/)
it("parses but does not retain origins", () => {
const g = flow(
fromString(identity),
E.map(x => (x as unknown as URL).href),
)

const e2 = unsafeUnwrapLeft(g("https://samhh.com/foo"))
expect(e2.name).toBe("TypeError")
expect(e2.message).toMatch(/phony/)
expect(g("//x")).toEqual(E.right(`${phonyBase}/`))
expect(g("https://samhh.com/foo")).toEqual(E.right(`${phonyBase}/foo`))

fc.assert(
fc.property(fc.string().map(f).filter(E.isRight), ({ right: x }) =>
Expand Down

0 comments on commit cc97614

Please sign in to comment.