Skip to content

Commit

Permalink
Normalizing rest / paths
Browse files Browse the repository at this point in the history
Fix propagation of rest in toPath
  • Loading branch information
JAForbes committed Feb 11, 2024
1 parent 16594f4 commit 4c69c9c
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 22 deletions.
3 changes: 2 additions & 1 deletion lib/fromPath.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Either } from "./either.js";
import { normalizeRest } from "./normalize.js";

type Mode =
| "initialize"
Expand Down Expand Up @@ -200,7 +201,7 @@ export function safe(
if (rest) {
score = Math.max(0, score - 1);
}
return { type: "Either", tag: "Right", value: { rest, value, score } };
return { type: "Either", tag: "Right", value: { rest: normalizeRest(rest), value, score } };
}

export function unsafe( _path: string, _pattern: string): { rest: string, value: Record<string,string>, score: number } {
Expand Down
11 changes: 5 additions & 6 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { Either } from "./either.js";
import * as ParsePath from "./fromPath.js";
import { normalizePathSegment, normalizeRest } from "./normalize.js";

export type Patterns = string | string[];

Expand Down Expand Up @@ -88,7 +89,6 @@ function otherwise(tags:string[]) {
)
}


export function type<N extends string, D extends Definition>(
type: N,
routes: D
Expand Down Expand Up @@ -134,7 +134,8 @@ export function type<N extends string, D extends Definition>(
}

if (bestPath) {
return { type: "Either", tag: "Right", value: bestPath.join("/") };
const value = normalizePathSegment(bestPath.concat(route.value.rest).join("/"))
return { type: "Either", tag: "Right", value };
} else if (errors.length) {
return { type: "Either", tag: "Left", value: new Error(errors[0]) };
} else {
Expand Down Expand Up @@ -178,7 +179,7 @@ export function type<N extends string, D extends Definition>(
}
} else {
if (bestRoute == null || result.value.score > bestRank) {
bestRoute = { type, tag, value: { ...result.value.value, rest: result.value.rest } };
bestRoute = api[tag]({ ...result.value.value, rest: result.value.rest })
bestRank = result.value.score;
}
}
Expand Down Expand Up @@ -230,9 +231,7 @@ export function type<N extends string, D extends Definition>(

for (const [tag, of] of Object.entries(routes)) {
api[tag] = (value:any = {}) => {
if ( !value.rest ) {
value.rest = '/'
}
value.rest = normalizeRest(value.rest)
return { type, tag, value }
};

Expand Down
25 changes: 25 additions & 0 deletions lib/normalize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

export function normalizePathSegment(s: string): string{
if( s == '' || s == '/' ) {
return '/'
} else if ( s.startsWith('/') && s.endsWith('/') ) {
return s.slice(0, -1)
} else if ( !s.startsWith('/') && s.endsWith('/') ) {
return '/' + s.slice(0, -1)
} else {
return s
}
}

export function normalizeRest(s:string): string {
if ( !s ) {
s = ''
}
if (s.at(0) == '/') {
s = s.slice(1)
}
if (s.at(-1) == '/') {
s = s.slice(0,-1)
}
return s
}
24 changes: 12 additions & 12 deletions test/fromPath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ test("fromPath: simple", () => {
.sort((a, b) => b.value.score - a.value.score)
.map((x) => x.value)[0];

assert(best.rest == "/", "most specific wins");
assert(best.rest == "", "most specific wins");

assert.deepEqual(best.value, { nam: "james" }, "Expected parsed URL");
});
Expand All @@ -45,8 +45,8 @@ test("fromPath: rest", () => {
.sort((a, b) => b.value.score - a.value.score)
.map((x) => x.value);

assert(sorted[0].rest == "/", "most specific wins");
assert(sorted[1].rest == "/extra", "rest has expected value");
assert.equal(sorted[0].rest, "", "most specific wins");
assert.equal(sorted[1].rest, "extra", "rest has expected value");
assert.deepEqual(
sorted[1].value,
{ name: "james" },
Expand All @@ -58,34 +58,34 @@ test("fromPath: garbage", () => {
assert.deepEqual(safe("", ""), {
type: "Either",
tag: "Right",
value: { rest: "/", value: {}, score: 0 },
value: { rest: "", value: {}, score: 0 },
});
assert.deepEqual(safe("////", ""), {
type: "Either",
tag: "Right",
value: { rest: "/", value: {}, score: 0 },
value: { rest: "", value: {}, score: 0 },
});
assert.deepEqual(safe("", "////////"), {
type: "Either",
tag: "Right",
value: { rest: "/", value: {}, score: 0 },
value: { rest: "", value: {}, score: 0 },
});
assert.deepEqual(safe("///////", "////////"), {
type: "Either",
tag: "Right",
value: { rest: "/", value: {}, score: 0 },
value: { rest: "", value: {}, score: 0 },
});

assert.deepEqual(safe("a", ":a"), {
type: "Either",
tag: "Right",
value: { rest: "/", value: { a: "a" }, score: 1 },
value: { rest: "", value: { a: "a" }, score: 1 },
});

assert.deepEqual(safe(":::::", ":a"), {
type: "Either",
tag: "Right",
value: { rest: "/", value: { a: ":::::" }, score: 1 },
value: { rest: "", value: { a: ":::::" }, score: 1 },
});

// @ts-expect-error
Expand Down Expand Up @@ -124,10 +124,10 @@ test("fromPath: complex", () => {
assert.match(fail.message, /:c/);
}
assert.deepEqual(success, [
{ rest: "/a", value: {}, score: 0 },
{ rest: "/", value: {}, score: 3 },
{ rest: "a", value: {}, score: 0 },
{ rest: "", value: {}, score: 3 },
{
rest: "/and/something/extra",
rest: "and/something/extra",
value: { a: "welcome", b: "james", c: "you", g: "cool" },
score: 19,
},
Expand Down
52 changes: 49 additions & 3 deletions test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ test("index: basic", () => {
assert.deepEqual(Example.A({ a_id: "hello" }), {
type: "Example",
tag: "A",
value: { a_id: "hello", rest: "/" },
value: { a_id: "hello", rest: "" },
});
assert.deepEqual(Example.B({ b_id: "hello" }), {
type: "Example",
tag: "B",
value: { b_id: "hello", rest: "/" },
value: { b_id: "hello", rest: "" },
});

assert.equal(Example.definition.A({ a_id: "" }), "/a/:a_id");
Expand Down Expand Up @@ -72,7 +72,7 @@ test("index: fromPath", () => {

assert.throws(() => Example.fromPath("/"), /Expected literal/);
assert.deepEqual(
Example.C({ c_id: "cool", rest: "/" }),
Example.C({ c_id: "cool", rest: "" }),
Example.fromPath("/c/cool")
);
assert.deepEqual(
Expand Down Expand Up @@ -148,3 +148,49 @@ test("index: matchOr", () => {
assert.deepEqual(res, Example.A({ a_id: "notcool" }));
}
});

test('index: rest', () => {
const Odin = superouter.type('Odin', {
Home: (_: Record<string,string>) => '/',
Organization: (_: Record<string, string>) => '/admin/organizations'
})

assert.equal(Odin.toPath(Odin.Organization({ rest: '1' })), '/admin/organizations/1')
assert.equal(Odin.toPath(Odin.Organization({ rest: '/1' })), '/admin/organizations/1')
assert.equal(Odin.toPath(Odin.Organization({ rest: '/1/' })), '/admin/organizations/1')
assert.equal(Odin.toPath(Odin.Organization({ rest: '1/' })), '/admin/organizations/1')

const Orgs = superouter.type('Orgs', {
List: (_: { organization_id: string }) => `/:organization_id`,
Group: (_: { organization_id: string, group_id: string }) => `/:organization_id/groups/:group_id`
})

{
const parentPath = Odin.toPath(Odin.Organization({ rest: '1' }))

const child = Orgs.fromPath(parentPath.replace(`/admin/organizations`, ''))

assert.equal(Orgs.toPath(child), '/1')
}
{
const originalUrl = `/admin/organizations/1/groups/2`

const originalRoute = Odin.fromPath(originalUrl)

assert.deepEqual(originalRoute, Odin.Organization({ rest: '1/groups/2' }))

const parentPath = Odin.toPath(originalRoute)

assert.equal(originalUrl, parentPath)

const prefix = `/admin/organizations`

const child = Orgs.fromPath(parentPath.replace(prefix, ''))

assert.equal(Orgs.toPath(child), '/1/groups/2')

assert.equal(prefix + Orgs.toPath(child), originalUrl)


}
})

0 comments on commit 4c69c9c

Please sign in to comment.