feat: simple variable substitution support

fixes #565
This commit is contained in:
Roman Hotsiy 2018-07-23 12:00:29 +03:00
parent 59e05fbe75
commit 9d6b30c55a
No known key found for this signature in database
GPG Key ID: 5CB7B3ACABA57CB0
3 changed files with 88 additions and 7 deletions

View File

@ -234,6 +234,24 @@ describe('Utils', () => {
expect(res).toEqual([{ url: 'http://base.com/sandbox/test', description: '' }]); expect(res).toEqual([{ url: 'http://base.com/sandbox/test', description: '' }]);
}); });
it('should correcly resolve url with server relative path', () => {
const res = normalizeServers('http://base.com/subpath/spec.yaml', [
{
url: '/sandbox/test',
},
]);
expect(res).toEqual([{ url: 'http://base.com/sandbox/test', description: '' }]);
});
it('should correcly resolve url with relative path', () => {
const res = normalizeServers('http://base.com/subpath/spec.yaml', [
{
url: 'sandbox/test',
},
]);
expect(res).toEqual([{ url: 'http://base.com/subpath/sandbox/test', description: '' }]);
});
it('should prefer server host over spec`s one', () => { it('should prefer server host over spec`s one', () => {
const res = normalizeServers('http://base.com/spec.yaml', [ const res = normalizeServers('http://base.com/spec.yaml', [
{ {
@ -261,5 +279,35 @@ describe('Utils', () => {
]); ]);
expect(res).toEqual([{ url: 'https://base.com/sandbox/test', description: 'test' }]); expect(res).toEqual([{ url: 'https://base.com/sandbox/test', description: 'test' }]);
}); });
it('should expand variables', () => {
const servers = normalizeServers('', [
{
url: '{protocol}{host}{basePath}',
variables: {
protocol: {
default: 'http://',
},
host: {
default: '127.0.0.1',
},
basePath: {
default: '/path/to/endpoint',
},
},
},
{
url: 'http://127.0.0.2:{port}',
variables: {},
},
{
url: 'http://127.0.0.3',
},
]);
expect(servers[0].url).toEqual('http://127.0.0.1/path/to/endpoint');
expect(servers[1].url).toEqual('http://127.0.0.2:{port}');
expect(servers[2].url).toEqual('http://127.0.0.3');
});
}); });
}); });

View File

@ -1,4 +1,5 @@
import slugify from 'slugify'; import slugify from 'slugify';
import { format, parse } from 'url';
/** /**
* Maps over array passing `isLast` bool to iterator as the second arguemnt * Maps over array passing `isLast` bool to iterator as the second arguemnt
@ -133,3 +134,30 @@ export function safeSlugify(value: string): string {
.replace(/-+$/, '') .replace(/-+$/, '')
); // Trim - from end of text ); // Trim - from end of text
} }
export function isAbsoluteUrl(url: string) {
return /(?:^[a-z][a-z0-9+.-]*:|\/\/)/i.test(url);
}
/**
* simple resolve URL which doesn't break on strings with url fragments
* e.g. resolveUrl('http://test.com:{port}', 'path') results in http://test.com:{port}/path
*/
export function resolveUrl(url: string, to: string) {
let res;
if (to.startsWith('//')) {
const { protocol: specProtocol } = parse(url);
res = `${specProtocol}${to}`;
} else if (isAbsoluteUrl(to)) {
res = to;
} else if (!to.startsWith('/')) {
res = stripTrailingSlash(url) + '/' + to;
} else {
const urlObj = parse(url);
res = format({
...urlObj,
pathname: to,
});
}
return stripTrailingSlash(res);
}

View File

@ -1,5 +1,4 @@
import { dirname } from 'path'; import { dirname } from 'path';
import { parse as urlParse, resolve as resolveUrl } from 'url';
import { OpenAPIParser } from '../services/OpenAPIParser'; import { OpenAPIParser } from '../services/OpenAPIParser';
import { import {
@ -11,7 +10,7 @@ import {
Referenced, Referenced,
} from '../types'; } from '../types';
import { IS_BROWSER } from './dom'; import { IS_BROWSER } from './dom';
import { isNumeric, stripTrailingSlash } from './helpers'; import { isNumeric, resolveUrl } from './helpers';
function isWildcardStatusCode(statusCode: string | number): statusCode is string { function isWildcardStatusCode(statusCode: string | number): statusCode is string {
return typeof statusCode === 'string' && /\dxx/i.test(statusCode); return typeof statusCode === 'string' && /\dxx/i.test(statusCode);
@ -240,6 +239,13 @@ export function mergeSimilarMediaTypes(types: Dict<OpenAPIMediaType>): Dict<Open
return mergedTypes; return mergedTypes;
} }
function expandVariables(url: string, variables: object = {}) {
return url.replace(
/(?:{)(\w+)(?:})/g,
(match, name) => (variables[name] && variables[name].default) || match,
);
}
export function normalizeServers( export function normalizeServers(
specUrl: string | undefined, specUrl: string | undefined,
servers: OpenAPIServer[], servers: OpenAPIServer[],
@ -254,17 +260,16 @@ export function normalizeServers(
}, },
]; ];
} }
const { protocol: specProtocol } = urlParse(baseUrl);
function normalizeUrl(url: string): string { function normalizeUrl(url: string, variables: object | undefined): string {
url = resolveUrl(baseUrl, url); url = expandVariables(url, variables);
return stripTrailingSlash(url.startsWith('//') ? `${specProtocol}${url}` : url); return resolveUrl(baseUrl, url);
} }
return servers.map(server => { return servers.map(server => {
return { return {
...server, ...server,
url: normalizeUrl(server.url), url: normalizeUrl(server.url, server.variables),
description: server.description || '', description: server.description || '',
}; };
}); });