Skip to content

Commit

Permalink
feat(router): add matchPath function
Browse files Browse the repository at this point in the history
SL-72
  • Loading branch information
Chris Miaskowski committed Oct 17, 2018
1 parent 2dc3f8b commit 7292957
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 48 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
},
"devDependencies": {
"@stoplight/types": "1.0.x",
"@types/chance": "^1.0.1",
"@types/jest": "23.3.x",
"@types/node": "10.11.x",
"chance": "^1.0.16",
"jest": "23.6.x",
"ts-jest": "23.10.x",
"tslint": "5.11.x",
Expand All @@ -34,7 +36,7 @@
"jest": {
"verbose": false,
"testMatch": [
"<rootDir>/packages/*/src/**/__tests__/*.(ts|js)?(x)"
"<rootDir>/packages/*/src/**/__tests__/*spec.(ts|js)?(x)"
],
"transform": {
"^.+\\.[tj]sx?$": "ts-jest"
Expand Down
8 changes: 4 additions & 4 deletions packages/http/src/router/__tests__/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Chance } from 'chance';
import { router } from '../index';
import { oneRandomHttpMethod, randomUrl, randomPath, pickSetOfHttpMethods, pickOneHttpMethod } from '@stoplight/prism-http/router/__tests__/utils';
import { randomUrl, randomPath, pickSetOfHttpMethods, pickOneHttpMethod } from '@stoplight/prism-http/router/__tests__/utils';

const chance = new Chance();

Expand All @@ -10,7 +10,7 @@ describe('http router', () => {
const resource = await router.route({
resources: [],
input: {
method: oneRandomHttpMethod(),
method: pickOneHttpMethod(),
url: randomUrl()
}
});
Expand All @@ -23,13 +23,13 @@ describe('http router', () => {
const resource = await router.route({
resources: [{
id: chance.guid(),
method: oneRandomHttpMethod(),
method: pickOneHttpMethod(),
path: randomPath(),
responses: [],
servers: []
}],
input: {
method: oneRandomHttpMethod(),
method: pickOneHttpMethod(),
url: randomUrl()
}
});
Expand Down
51 changes: 16 additions & 35 deletions packages/http/src/router/__tests__/matchPath.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,7 @@ describe('matchPath()', () => {
includeTemplates: false,
});

expect(matchPath(path, { path }).toBeTruthy();
});

test('any conrecte request path should match same length templated path', () => {
// e.g. /a/b/c should match /a/{x}/c
const pathFragments = chance.natural({ min: 1, max: 6 });
const trailingSlash = chance.bool();
const requestPath = randomPath({
pathFragments,
includeTemplates: false,
trailingSlash
});
const operationPath = randomPath({
pathFragments,
includeTemplates: true,
trailingSlash
});

expect(matchPath(requestPath, { path: operationPath })).toBeTruthy();
expect(matchPath(path, { path })).toBeTruthy();
});

test('none request path should not match path with less fragments', () => {
Expand All @@ -68,7 +50,7 @@ describe('matchPath()', () => {
expect(matchPath(requestPath, { path: operationPath })).toBeFalsy();
});

test('none request path should not match concrete path with more fragments', () => {
test('none request path should match concrete path with more fragments', () => {
// e.g. /a/b should not match /a/b/c
// e.g. /a/b/ should not match /a/b/c
const requestPath = randomPath({
Expand All @@ -84,49 +66,48 @@ describe('matchPath()', () => {
});

test('reqest path should match a templated path and resolve variables', () => {
expect(matchPath('/a'), { '/{a}'}).toEqual([
expect(matchPath('/a', { path: '/{a}'})).toEqual([
{ name: 'a', value: 'a' }
]);

expect(matchPath('/a/b'), { '/{a}/{b}'}).toEqual([
expect(matchPath('/a/b', { path: '/{a}/{b}'})).toEqual([
{ name: 'a', value: 'a' },
{ name: 'b', value: 'b' },
]);

expect(matchPath('/a/b'), { '/a/{b}'}).toEqual([
expect(matchPath('/a/b', { path: '/a/{b}'})).toEqual([
{ name: 'b', value: 'b' },
]);
});

test('request path should match a template path and resolve undefined variables', () => {
expect(matchPath('/'), { '/{a}'}).toEqual([
{ name: 'a', value: undefined },
expect(matchPath('/', { path: '/{a}'})).toEqual([
{ name: 'a', value: '' },
]);

expect(matchPath('//'), { '/{a}/'}).toEqual([
{ name: 'a', value: undefined },
expect(matchPath('//', { path: '/{a}/'})).toEqual([
{ name: 'a', value: '' },
]);

expect(matchPath('//b'), { '/{a}/{b}'}).toEqual([
{ name: 'a', value: undefined },
expect(matchPath('//b', { path: '/{a}/{b}'})).toEqual([
{ name: 'a', value: '' },
{ name: 'b', value: 'b' },
]);

expect(matchPath('/a/'), { '/{a}/{b}'}).toEqual([
expect(matchPath('/a/', { path: '/{a}/{b}'})).toEqual([
{ name: 'a', value: 'a' },
{ name: 'b', value: undefined },
{ name: 'b', value: '' },
]);

expect(matchPath('//'), { '/{a}/{b}'}).toEqual([
{ name: 'a', value: undefined },
{ name: 'b', value: undefined },
expect(matchPath('//', { path: '/{a}/{b}'})).toEqual([
{ name: 'a', value: '' },
{ name: 'b', value: '' },
]);
});

test('none path should match templated operation with more path fragments', () => {
// e.g. `/a/b` should not match /{x}/{y}/{z}
// e.g. `/a` should not match /{x}/{y}/{z}
const trailingSlash = chance.bool();
const requestPath = randomPath({
pathFragments: chance.natural({ min: 1, max: 3 }),
includeTemplates: false,
Expand Down
4 changes: 2 additions & 2 deletions packages/http/src/router/__tests__/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export function randomArray<T>(itemGenerator: () => T, length: number = 1): T[]
const defaultRandomPathOptions = {
pathFragments: 3,
includeTemplates: true,
leadingSlash: true
leadingSlash: true,
};

interface RandomPathOptions {
Expand All @@ -34,7 +34,7 @@ interface RandomPathOptions {
}

export function randomPath(opts: RandomPathOptions = defaultRandomPathOptions): string {
opts = Object.assign(opts, defaultRandomPathOptions);
opts = Object.assign({}, defaultRandomPathOptions, opts);
const randomPathFragments = randomArray(
() => (opts.includeTemplates && chance.coin() === 'heads') ? `{${chance.word()}}` : chance.word(),
opts.pathFragments
Expand Down
52 changes: 46 additions & 6 deletions packages/http/src/router/matchPath.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,48 @@
import { IHttpOperation } from "@stoplight/types/http";
function fragmentarize(path: string): string[] {
return path.split('/').slice(1);
}

function getTemplateParamName(pathFragment: string) {
const match = pathFragment.match(/^{(.*)}$/);
return match && match[1];
}

/**
* @returns `true` if matched concrete, `false` if not matched, `path param values` if matched templated
*/
export function matchPath(requestPath: string, operation: { path: string }): boolean | { name: string, value: string }[] {
const operationPath = operation.path;
if (!requestPath.startsWith('/')) {
throw new Error('The request path must start with a slash.');
}
if (!operationPath.startsWith('/')) {
throw new Error('The operation path must start with a slash.');
}
const operationPathFragments = fragmentarize(operationPath);
const requestPathFragments = fragmentarize(requestPath);

if (operationPathFragments.length < requestPathFragments.length
|| operationPathFragments.length > requestPathFragments.length) {
return false;
}

const params = [];
while (requestPathFragments.length) {
const requestPathFragment = requestPathFragments.shift();
const operationPathFragment = operationPathFragments.shift();

const paramName = getTemplateParamName(operationPathFragment as string);

if (paramName === null && operationPathFragment !== requestPathFragment) {
// if concrete fragment and fragments are different return false
return false;
} else if (paramName !== null) {
params.push({
name: paramName as string,
value: requestPathFragment as string,
});
}
}

// returns true if matched concrete
// returns false if not matched
// returns path param values if matched templated
export function matchPath(requestPath: string, operation: Partial<IHttpOperation>): boolean | [ { name: string, value: string } ] {
throw new Error('not implemented');
return params;
}
10 changes: 10 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@
resolved "https://registry.yarnpkg.com/@stoplight/types/-/types-1.0.1.tgz#4a1c49c494f66e46efce4da30edcb03fd94617c8"
integrity sha512-5eobPkXsxJDP3q3IUT+iucRwiKwnJlaIRQY7bA42p6O3IyV9URBPuzfuMQNlyrDj2tt6StNEuYo6ReQxWFCHXg==

"@types/chance@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@types/chance/-/chance-1.0.1.tgz#c10703020369602c40dd9428cc6e1437027116df"
integrity sha512-jtV6Bv/j+xk4gcXeLlESwNc/m/I/dIZA0xrt29g0uKcjyPob8iisj/5z0ARE+Ldfx4MxjNFNECG0z++J7zJgqg==

"@types/[email protected]":
version "23.3.5"
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-23.3.5.tgz#870a1434208b60603745bfd214fc3fc675142364"
Expand Down Expand Up @@ -546,6 +551,11 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.0:
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"

chance@^1.0.16:
version "1.0.16"
resolved "https://registry.yarnpkg.com/chance/-/chance-1.0.16.tgz#bd61912716b0010c3dca8e3948a960efcaa7bb1b"
integrity sha512-2bgDHH5bVfAXH05SPtjqrsASzZ7h90yCuYT2z4mkYpxxYvJXiIydBFzVieVHZx7wLH1Ag2Azaaej2/zA1XUrNQ==

chownr@^1.0.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494"
Expand Down

0 comments on commit 7292957

Please sign in to comment.