Skip to content

Commit

Permalink
feat(router): made baseUrl optional to ignore server matching
Browse files Browse the repository at this point in the history
  • Loading branch information
Chris Miaskowski committed Oct 19, 2018
1 parent ebb6d2c commit 91669a8
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 34 deletions.
11 changes: 7 additions & 4 deletions packages/http-server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import { createInstance, IHttpConfig } from '@stoplight/prism-http';
// TODO: this is a trivial example, scratch code

const prism = createInstance({
config: async ({ query }) => {
config: async ({ url }) => {
const config: IHttpConfig = {};

if (query && query.__code) {
if (url.query && url.query.__code) {
config.mock = {
code: query.__code,
code: url.query.__code,
};
}

Expand All @@ -21,7 +21,10 @@ const prism = createInstance({
});

app.get('*', async (req: any, res: any) => {
const response = await prism.process({ method: req.method, host: req.host, path: req.path });
const response = await prism.process({
method: req.method,
url: { baseUrl: req.host, path: req.path },
});

if (response.data) {
res.send(response.data);
Expand Down
89 changes: 88 additions & 1 deletion packages/http/src/router/__tests__/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { pickOneHttpMethod, pickSetOfHttpMethods, randomPath } from './utils';

const chance = new Chance();

function createResource(method: string, path: string, servers: IServer[] = []): IHttpOperation {
function createResource(method: string, path: string, servers?: IServer[]): IHttpOperation {
return {
id: chance.guid(),
method,
Expand Down Expand Up @@ -41,6 +41,23 @@ describe('http router', () => {

describe('given a resource', () => {
test('should not match if no server defined', () => {
const method = pickOneHttpMethod();
const path = randomPath();
const resourcePromise = router.route({
resources: [createResource(method, path, [])],
input: {
method,
url: {
baseUrl: '',
path,
},
},
});

return expect(resourcePromise).rejects.toBe(NO_SERVER_CONFIGURATION_PROVIDED_ERROR);
});

test('should not match if no servers arrays provided', () => {
const method = pickOneHttpMethod();
const path = randomPath();
const resourcePromise = router.route({
Expand Down Expand Up @@ -128,6 +145,60 @@ describe('http router', () => {
expect(resource).toBe(expectedResource);
});

test('given a templated matching server and matched concrete path should match', async () => {
const url = 'http://{host}/v1';
const path = randomPath({ includeTemplates: false });
const expectedResource = createResource(method, path, [
{
url,
variables: {
host: {
default: 'stoplight.io',
},
},
},
]);
const resource = await router.route({
resources: [expectedResource],
input: {
method,
url: {
baseUrl: 'http://stoplight.io/v1',
path,
},
},
});

expect(resource).toBe(expectedResource);
});

test('given a templated matching server and matched templated path should match', async () => {
const url = 'http://{host}/v1';
const path = '/{x}/b';
const expectedResource = createResource(method, path, [
{
url,
variables: {
host: {
default: 'stoplight.io',
},
},
},
]);
const resource = await router.route({
resources: [expectedResource],
input: {
method,
url: {
baseUrl: 'http://stoplight.io/v1',
path: '/a/b',
},
},
});

expect(resource).toBe(expectedResource);
});

test('given a concrete matching server and matched templated path should match', async () => {
const url = chance.url();
const templatedPath = '/a/{b}/c';
Expand Down Expand Up @@ -292,6 +363,22 @@ describe('http router', () => {

expect(resource).toBe(expectedResource);
});

test('given no baseUrl and a server url it should ignore servers and match by path', async () => {
const path = randomPath({ includeTemplates: false });
const expectedResource = createResource(method, path, [{ url: 'www.stoplight.io/v1' }]);
const resource = await router.route({
resources: [expectedResource],
input: {
method,
url: {
path,
},
},
});

expect(resource).toBe(expectedResource);
});
});
});
});
Expand Down
1 change: 0 additions & 1 deletion packages/http/src/router/errors.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export const ROUTE_DISAMBIGUATION_ERROR = new Error('Could not disambiguate the given route.');
export const NO_RESOURCE_PROVIDED_ERROR = new Error('Route not resolved, no resource provided.');
export const NONE_METHOD_MATCHED_ERROR = new Error('Route not resolved, none method matched.');
export const NONE_PATH_MATCHED_ERROR = new Error('Route not resolved, none path matched.');
Expand Down
59 changes: 33 additions & 26 deletions packages/http/src/router/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { IRouter } from '@stoplight/prism-core';
import { IHttpOperation } from '@stoplight/types';
import { IHttpOperation, IServer } from '@stoplight/types';

import { IHttpConfig, IHttpRequest } from '../types';
import {
Expand All @@ -8,7 +8,6 @@ import {
NONE_METHOD_MATCHED_ERROR,
NONE_PATH_MATCHED_ERROR,
NONE_SERVER_MATCHED_ERROR,
ROUTE_DISAMBIGUATION_ERROR,
} from './errors';
import { matchBaseUrl } from './matchBaseUrl';
import { matchPath } from './matchPath';
Expand All @@ -18,6 +17,7 @@ export const router: IRouter<IHttpOperation, IHttpRequest, IHttpConfig> = {
route: async ({ resources, input }) => {
const matches = [];
const { path: requestPath, baseUrl: requestBaseUrl } = input.url;
const ignoreServers: boolean = requestBaseUrl === undefined;

if (!resources.length) {
throw NO_RESOURCE_PROVIDED_ERROR;
Expand All @@ -26,38 +26,31 @@ export const router: IRouter<IHttpOperation, IHttpRequest, IHttpConfig> = {
let noServerProvided: boolean = true;
let noneMethodMatched: boolean = true;
let nonePathMatched: boolean = true;
let noneServerMatched: boolean = true;
let noneServerMatched: boolean = !ignoreServers;

for (const resource of resources) {
if (!matchByMethod(input, resource)) continue;
noneMethodMatched = false;

const pathMatch = matchPath(requestPath, resource.path);
const serverMatches = [];

const { servers = [] } = resource;

if (servers.length) noServerProvided = false;
if (pathMatch !== MatchType.NOMATCH) nonePathMatched = false;

for (const server of servers) {
const tempServerMatch = matchBaseUrl(server, requestBaseUrl);
if (tempServerMatch !== MatchType.NOMATCH) {
serverMatches.push(tempServerMatch);
}
let serverMatch: MatchType | null = null;

if (!ignoreServers) {
serverMatch = matchServer(servers, requestBaseUrl as string);
if (serverMatch) noneServerMatched = false;
}

const serverMatch = disambiguateServers(serverMatches);

if (serverMatch) {
noneServerMatched = false;
if (pathMatch !== MatchType.NOMATCH) {
matches.push({
pathMatch,
serverMatch,
resource,
});
}
if (pathMatch !== MatchType.NOMATCH) {
matches.push({
pathMatch,
serverMatch,
resource,
});
}
}

Expand All @@ -81,6 +74,18 @@ export const router: IRouter<IHttpOperation, IHttpRequest, IHttpConfig> = {
},
};

function matchServer(servers: IServer[], requestBaseUrl: string) {
const serverMatches = [];
for (const server of servers) {
const tempServerMatch = matchBaseUrl(server, requestBaseUrl);
if (tempServerMatch !== MatchType.NOMATCH) {
serverMatches.push(tempServerMatch);
}
}

return disambiguateServers(serverMatches);
}

function matchByMethod(request: IHttpRequest, operation: IHttpOperation): boolean {
return operation.method.toLowerCase() === request.method.toLowerCase();
}
Expand All @@ -96,15 +101,17 @@ function disambiguateMatches(matches: IMatch[]): IHttpOperation {
// then fallback to first
matches[0];

if (!matchResult) {
throw ROUTE_DISAMBIGUATION_ERROR;
}

return matchResult.resource;
}

function areServerAndPath(match: IMatch, serverType: MatchType, pathType: MatchType) {
return match.serverMatch === serverType && match.pathMatch === pathType;
const serverMatch = match.serverMatch;
if (serverMatch === null) {
// server match will only be null if server matching is disabled.
// therefore skip comparison.
return match.pathMatch === pathType;
}
return serverMatch === serverType && match.pathMatch === pathType;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/http/src/router/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export type Nullable<T> = T | null;

export interface IMatch {
resource: IHttpOperation;
serverMatch: MatchType;
serverMatch: MatchType | null;
pathMatch: MatchType;
}

Expand Down
2 changes: 1 addition & 1 deletion packages/http/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export interface IHttpConfig extends IPrismConfig {
export interface IHttpRequest {
method: IHttpMethod;
url: {
baseUrl: string;
baseUrl?: string;
path: string;
query?: {
[name: string]: string;
Expand Down

0 comments on commit 91669a8

Please sign in to comment.