Skip to content

Commit 174802e

Browse files
committed
feat: resolve externalValue to value
1 parent c95931c commit 174802e

File tree

3 files changed

+220
-1
lines changed

3 files changed

+220
-1
lines changed

src/resolver.js

+20-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,20 @@ export function clearCache() {
2525
plugins.refs.clearCache();
2626
}
2727

28+
export function makeFetchRaw(http, opts = {}) {
29+
const { requestInterceptor, responseInterceptor } = opts;
30+
// Set credentials with 'http.withCredentials' value
31+
const credentials = http.withCredentials ? 'include' : 'same-origin';
32+
return (docPath) =>
33+
http({
34+
url: docPath,
35+
loadSpec: true,
36+
requestInterceptor,
37+
responseInterceptor,
38+
credentials,
39+
}).then((res) => res.text);
40+
}
41+
2842
export default function resolve(obj) {
2943
const {
3044
fetch,
@@ -66,8 +80,13 @@ export default function resolve(obj) {
6680

6781
// Build a json-fetcher ( ie: give it a URL and get json out )
6882
plugins.refs.fetchJSON = makeFetchJSON(http, { requestInterceptor, responseInterceptor });
83+
// Build a raw-fetcher ( ie: give it a URL and get raw text out )
84+
plugins.externalValue.fetchRaw = makeFetchRaw(http, {
85+
requestInterceptor,
86+
responseInterceptor,
87+
});
6988

70-
const plugs = [plugins.refs];
89+
const plugs = [plugins.refs, plugins.externalValue];
7190

7291
if (typeof parameterMacro === 'function') {
7392
plugs.push(plugins.parameters);

src/specmap/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import lib from './lib';
22
import refs from './lib/refs';
3+
import externalValue from './lib/external-value';
34
import allOf from './lib/all-of';
45
import parameters from './lib/parameters';
56
import properties from './lib/properties';
@@ -393,6 +394,7 @@ export default function mapSpec(opts) {
393394

394395
const plugins = {
395396
refs,
397+
externalValue,
396398
allOf,
397399
parameters,
398400
properties,

src/specmap/lib/external-value.js

+198
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
import { fetch } from 'cross-fetch';
2+
3+
import createError from './create-error';
4+
import lib from '.';
5+
6+
const externalValuesCache = {};
7+
8+
/**
9+
* Clears all external value caches.
10+
* @param {String} url (optional) the original externalValue value of the cache item to be cleared.
11+
* @api public
12+
*/
13+
function clearCache(url) {
14+
if (typeof url !== 'undefined') {
15+
delete externalValuesCache[url];
16+
} else {
17+
Object.keys(externalValuesCache).forEach((key) => {
18+
delete externalValuesCache[key];
19+
});
20+
}
21+
}
22+
23+
/**
24+
* Fetches a document.
25+
* @param {String} docPath the absolute URL of the document.
26+
* @return {Promise} a promise of the document content.
27+
* @api public
28+
*/
29+
const fetchRaw = (url) => fetch(url).then((res) => res.text);
30+
31+
const shouldResolveTestFn = [
32+
// OAS 3.0 Response Media Type Examples externalValue
33+
(path) =>
34+
// ["paths", *, *, "responses", *, "content", *, "examples", *, "externalValue"]
35+
path[0] === 'paths' &&
36+
path[3] === 'responses' &&
37+
path[5] === 'content' &&
38+
path[7] === 'examples' &&
39+
path[9] === 'externalValue',
40+
41+
// OAS 3.0 Request Body Media Type Examples externalValue
42+
(path) =>
43+
// ["paths", *, *, "requestBody", "content", *, "examples", *, "externalValue"]
44+
path[0] === 'paths' &&
45+
path[3] === 'requestBody' &&
46+
path[4] === 'content' &&
47+
path[6] === 'examples' &&
48+
path[8] === 'externalValue',
49+
50+
// OAS 3.0 Parameter Examples externalValue
51+
(path) =>
52+
// ["paths", *, "parameters", *, "examples", *, "externalValue"]
53+
path[0] === 'paths' &&
54+
path[2] === 'parameters' &&
55+
path[4] === 'examples' &&
56+
path[6] === 'externalValue',
57+
(path) =>
58+
// ["paths", *, *, "parameters", *, "examples", *, "externalValue"]
59+
path[0] === 'paths' &&
60+
path[3] === 'parameters' &&
61+
path[5] === 'examples' &&
62+
path[7] === 'externalValue',
63+
(path) =>
64+
// ["paths", *, "parameters", *, "content", *, "examples", *, "externalValue"]
65+
path[0] === 'paths' &&
66+
path[2] === 'parameters' &&
67+
path[4] === 'content' &&
68+
path[6] === 'examples' &&
69+
path[8] === 'externalValue',
70+
(path) =>
71+
// ["paths", *, *, "parameters", *, "content", *, "examples", *, "externalValue"]
72+
path[0] === 'paths' &&
73+
path[3] === 'parameters' &&
74+
path[5] === 'content' &&
75+
path[7] === 'examples' &&
76+
path[9] === 'externalValue',
77+
];
78+
79+
const shouldSkipResolution = (path) => !shouldResolveTestFn.some((fn) => fn(path));
80+
81+
const ExternalValueError = createError('ExternalValueError', function cb(message, extra, oriError) {
82+
this.originalError = oriError;
83+
Object.assign(this, extra || {});
84+
});
85+
86+
/**
87+
* This plugin resolves externalValue keys.
88+
* In order to do so it will use a cache in case the url was already requested.
89+
* It will use the fetchRaw method in order get the raw content hosted on specified url.
90+
* If successful retrieved it will replace the url with the actual value
91+
*/
92+
const plugin = {
93+
key: 'externalValue',
94+
plugin: (externalValue, _, fullPath) => {
95+
const parent = fullPath.slice(0, -1);
96+
97+
if (shouldSkipResolution(fullPath)) {
98+
return undefined;
99+
}
100+
101+
if (typeof externalValue !== 'string') {
102+
return new ExternalValueError('externalValue: must be a string', {
103+
externalValue,
104+
fullPath,
105+
});
106+
}
107+
108+
try {
109+
let externalValueOrPromise = getExternalValue(externalValue, fullPath);
110+
if (typeof externalValueOrPromise === 'undefined') {
111+
externalValueOrPromise = new ExternalValueError(
112+
`Could not resolve externalValue: ${externalValue}`,
113+
{
114+
externalValue,
115+
fullPath,
116+
}
117+
);
118+
}
119+
// eslint-disable-next-line no-underscore-dangle
120+
if (externalValueOrPromise.__value != null) {
121+
// eslint-disable-next-line no-underscore-dangle
122+
externalValueOrPromise = externalValueOrPromise.__value;
123+
} else {
124+
externalValueOrPromise = externalValueOrPromise.catch((e) => {
125+
throw wrapError(e, {
126+
externalValue,
127+
fullPath,
128+
});
129+
});
130+
}
131+
132+
if (externalValueOrPromise instanceof Error) {
133+
return [lib.remove(fullPath), externalValueOrPromise];
134+
}
135+
136+
const backupOriginalValuePatch = lib.add([...parent, '$externalValue'], externalValue);
137+
const valuePatch = lib.replace([...parent, 'value'], externalValueOrPromise);
138+
const cleanUpPatch = lib.remove(fullPath);
139+
return [backupOriginalValuePatch, valuePatch, cleanUpPatch];
140+
} catch (err) {
141+
return [
142+
lib.remove(fullPath),
143+
wrapError(err, {
144+
externalValue,
145+
fullPath,
146+
}),
147+
];
148+
}
149+
},
150+
};
151+
const mod = Object.assign(plugin, {
152+
wrapError,
153+
clearCache,
154+
ExternalValueError,
155+
fetchRaw,
156+
getExternalValue,
157+
});
158+
export default mod;
159+
160+
/**
161+
* Wraps an error as ExternalValueError.
162+
* @param {Error} e the error.
163+
* @param {Object} extra (optional) optional data.
164+
* @return {Error} an instance of ExternalValueError.
165+
* @api public
166+
*/
167+
function wrapError(e, extra) {
168+
let message;
169+
170+
if (e && e.response && e.response.body) {
171+
message = `${e.response.body.code} ${e.response.body.message}`;
172+
} else {
173+
message = e.message;
174+
}
175+
176+
return new ExternalValueError(`Could not resolve externalValue: ${message}`, extra, e);
177+
}
178+
179+
/**
180+
* Fetches and caches a ExternalValue.
181+
* @param {String} docPath the absolute URL of the document.
182+
* @return {Promise} a promise of the document content.
183+
* @api public
184+
*/
185+
function getExternalValue(url) {
186+
const val = externalValuesCache[url];
187+
if (val) {
188+
return lib.isPromise(val) ? val : Promise.resolve(val);
189+
}
190+
191+
// NOTE: we need to use `mod.fetchRaw` in order to be able to overwrite it.
192+
// Any tips on how to make this cleaner, please ping!
193+
externalValuesCache[url] = mod.fetchRaw(url).then((raw) => {
194+
externalValuesCache[url] = raw;
195+
return raw;
196+
});
197+
return externalValuesCache[url];
198+
}

0 commit comments

Comments
 (0)