Skip to content
This repository was archived by the owner on Jul 13, 2023. It is now read-only.

Commit 8cce1b9

Browse files
pytlesk4marbemac
authored andcommitted
feat: Add transform dereference result hook (#77)
* feat: add transform dereference result hook * chore: add tests * chore: update interface names * chore: pr feedback
1 parent 14befa3 commit 8cce1b9

File tree

5 files changed

+259
-49
lines changed

5 files changed

+259
-49
lines changed

src/__tests__/resolver.spec.ts

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1670,6 +1670,183 @@ describe('resolver', () => {
16701670
expect(result.errors[0].uri.toString()).toEqual(new URI('http://foo').toString());
16711671
});
16721672

1673+
/**
1674+
* Allows the consumer to provide a custom ref transformer to transform a fully resolved object.
1675+
*
1676+
*/
1677+
test('should support `transformDereferenceResult` hook', async () => {
1678+
const data = {
1679+
markdown: '# hello',
1680+
bar: {
1681+
hello: `{ "hello": "world" }`,
1682+
},
1683+
};
1684+
1685+
const source = {
1686+
definitions: {
1687+
foo: {
1688+
$ref: 'http://foo.com/foo.md#/markdown',
1689+
},
1690+
bar: {
1691+
$ref: 'http://foo.com/bar.json#/hello',
1692+
},
1693+
},
1694+
};
1695+
1696+
const reader: Types.IResolver = {
1697+
async resolve(ref: uri.URI): Promise<any> {
1698+
if (ref.path().split('.')[1] === 'md') {
1699+
return data;
1700+
}
1701+
1702+
return data.bar;
1703+
},
1704+
};
1705+
1706+
const resolver = new Resolver({
1707+
resolvers: {
1708+
http: reader,
1709+
},
1710+
transformDereferenceResult: async opts => {
1711+
if (opts.parentAuthority.path().split('.')[1] === 'md') {
1712+
opts.result = {
1713+
heading1: 'hello',
1714+
};
1715+
} else if (opts.parentAuthority.toString() === 'http://foo.com/bar.json' && opts.fragment === '/hello') {
1716+
// Can transform the result however you want
1717+
opts.result = {
1718+
pooh: 'bear',
1719+
};
1720+
}
1721+
1722+
return opts;
1723+
},
1724+
});
1725+
1726+
const result = await resolver.resolve(source);
1727+
1728+
expect(result.result).toEqual({
1729+
definitions: {
1730+
foo: {
1731+
heading1: 'hello',
1732+
},
1733+
bar: {
1734+
pooh: 'bear',
1735+
},
1736+
},
1737+
});
1738+
});
1739+
1740+
test('should pass `transformDereferenceResult` to child runners', async () => {
1741+
const data = {
1742+
foo: {
1743+
$ref: 'http://foo.com/hi',
1744+
},
1745+
hi: {
1746+
$ref: 'http://foo.com/bye',
1747+
},
1748+
bye: {
1749+
adios: true,
1750+
},
1751+
bar: {
1752+
hello: 'world',
1753+
},
1754+
};
1755+
1756+
const source = {
1757+
definitions: {
1758+
foo: {
1759+
$ref: 'http://foo.com/foo',
1760+
},
1761+
bar: {
1762+
$ref: 'http://foo.com/bar',
1763+
},
1764+
},
1765+
};
1766+
1767+
const reader: Types.IResolver = {
1768+
async resolve(ref: uri.URI): Promise<any> {
1769+
return data[ref.path().slice(1)];
1770+
},
1771+
};
1772+
1773+
let counter = 0;
1774+
const resolver = new Resolver({
1775+
resolvers: {
1776+
http: reader,
1777+
},
1778+
transformDereferenceResult: async opts => {
1779+
counter += 1;
1780+
return opts;
1781+
},
1782+
});
1783+
1784+
const result = await resolver.resolve(source);
1785+
1786+
expect(result.result).toEqual({
1787+
definitions: {
1788+
foo: {
1789+
adios: true,
1790+
},
1791+
bar: {
1792+
hello: 'world',
1793+
},
1794+
},
1795+
});
1796+
1797+
expect(counter).toEqual(5);
1798+
});
1799+
1800+
test('should support catching error in `transformDereferenceResult` hook', async () => {
1801+
const data = {
1802+
markdown: '# hello',
1803+
};
1804+
1805+
const source = {
1806+
definitions: {
1807+
foo: {
1808+
$ref: 'http://foo.com#/markdown',
1809+
},
1810+
},
1811+
};
1812+
1813+
const reader: Types.IResolver = {
1814+
async resolve(): Promise<any> {
1815+
return data;
1816+
},
1817+
};
1818+
1819+
const resolver = new Resolver({
1820+
resolvers: {
1821+
http: reader,
1822+
},
1823+
transformDereferenceResult: async opts => {
1824+
if (opts.parentAuthority.toString() === 'http://foo.com/') throw new Error('some transform error!');
1825+
1826+
return opts;
1827+
},
1828+
});
1829+
1830+
const result = await resolver.resolve(source);
1831+
1832+
expect(result.result).toEqual({
1833+
definitions: {
1834+
foo: '# hello',
1835+
},
1836+
});
1837+
1838+
expect({ ...result.errors[0], uri: undefined }).toEqual({
1839+
code: 'TRANSFORM_DEREFERENCED',
1840+
message:
1841+
"Error: Could not transform dereferenced result for 'http://foo.com/#/markdown' - Error: some transform error!",
1842+
pointerStack: [],
1843+
uriStack: [],
1844+
path: ['markdown'],
1845+
uri: undefined,
1846+
});
1847+
expect(result.errors[0].uri.toString()).toEqual(new URI('#/markdown').toString());
1848+
});
1849+
16731850
test('should pass context to transformRef and read', async () => {
16741851
let t1;
16751852
let t2;

src/resolver.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export class Resolver {
2020
protected getRef?: (key: string, val: any) => string | void;
2121
protected transformRef?: (opts: Types.IRefTransformer, ctx: any) => uri.URI | any;
2222
protected parseResolveResult?: (opts: Types.IUriParser) => Promise<Types.IUriParserResult>;
23+
protected transformDereferenceResult?: (opts: Types.IDereferenceTransformer) => Promise<Types.ITransformerResult>;
2324

2425
constructor(opts: Types.IResolverOpts = {}) {
2526
this.uriCache = opts.uriCache || new Cache();
@@ -29,6 +30,7 @@ export class Resolver {
2930
this.dereferenceInline = typeof opts.dereferenceInline !== 'undefined' ? opts.dereferenceInline : true;
3031
this.dereferenceRemote = typeof opts.dereferenceRemote !== 'undefined' ? opts.dereferenceRemote : true;
3132
this.parseResolveResult = opts.parseResolveResult;
33+
this.transformDereferenceResult = opts.transformDereferenceResult;
3234
this.ctx = opts.ctx;
3335
}
3436

@@ -41,6 +43,7 @@ export class Resolver {
4143
dereferenceInline: this.dereferenceInline,
4244
dereferenceRemote: this.dereferenceRemote,
4345
parseResolveResult: this.parseResolveResult,
46+
transformDereferenceResult: this.transformDereferenceResult,
4447
...opts,
4548
ctx: Object.assign({}, this.ctx || {}, opts.ctx || {}),
4649
});

src/runner.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ export class ResolveRunner implements Types.IResolveRunner {
3838
public readonly getRef: (key: string, val: any) => string | void;
3939
public readonly transformRef?: (opts: Types.IRefTransformer, ctx: any) => uri.URI | any;
4040
public readonly parseResolveResult?: (opts: Types.IUriParser) => Promise<Types.IUriParserResult>;
41+
public readonly transformDereferenceResult?: (
42+
opts: Types.IDereferenceTransformer,
43+
) => Promise<Types.ITransformerResult>;
4144

4245
private _source: any;
4346

@@ -74,6 +77,7 @@ export class ResolveRunner implements Types.IResolveRunner {
7477

7578
this.dereferenceRemote = typeof opts.dereferenceRemote !== 'undefined' ? opts.dereferenceRemote : true;
7679
this.parseResolveResult = opts.parseResolveResult;
80+
this.transformDereferenceResult = opts.transformDereferenceResult;
7781
this.ctx = opts.ctx;
7882

7983
this.lookupUri = memoize(this.lookupUri, {
@@ -233,6 +237,37 @@ export class ResolveRunner implements Types.IResolveRunner {
233237
resolved.result = this._source;
234238
}
235239

240+
// support custom transformers
241+
if (this.transformDereferenceResult) {
242+
const ref = new URI(jsonPointer || '');
243+
try {
244+
const { result, error } = await this.transformDereferenceResult({
245+
source: this.source,
246+
result: resolved.result,
247+
targetAuthority: ref,
248+
parentAuthority: this.baseUri,
249+
parentPath: targetPath,
250+
fragment: ref.fragment(),
251+
});
252+
253+
resolved.result = result;
254+
if (error) {
255+
throw new Error(`Could not transform dereferenced result for '${ref.toString()}' - ${String(error)}`);
256+
}
257+
} catch (e) {
258+
resolved.errors.push({
259+
code: 'TRANSFORM_DEREFERENCED',
260+
message: `Error: Could not transform dereferenced result for '${this.baseUri.toString()}${
261+
ref.fragment() !== '' ? `#${ref.fragment()}` : ``
262+
}' - ${String(e)}`,
263+
uri: ref,
264+
uriStack: this.uriStack,
265+
pointerStack: [],
266+
path: targetPath,
267+
});
268+
}
269+
}
270+
236271
return resolved;
237272
}
238273

@@ -340,6 +375,7 @@ export class ResolveRunner implements Types.IResolveRunner {
340375
resolvers: this.resolvers,
341376
transformRef: this.transformRef,
342377
parseResolveResult: this.parseResolveResult,
378+
transformDereferenceResult: this.transformDereferenceResult,
343379
dereferenceRemote: this.dereferenceRemote,
344380
dereferenceInline: this.dereferenceInline,
345381
ctx: this.ctx,

src/types.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ export interface IResolverOpts {
5151
*/
5252
parseResolveResult?: (opts: IUriParser) => Promise<IUriParserResult>;
5353

54+
/**
55+
* Hook to transform resolved object.
56+
*
57+
* For example, transform `OpenAPI` file to a `Hub Page`.
58+
*/
59+
transformDereferenceResult?: (opts: IDereferenceTransformer) => Promise<ITransformerResult>;
60+
5461
/** Should we resolve local pointers? true by default. */
5562
dereferenceInline?: boolean;
5663

@@ -126,6 +133,20 @@ export interface IUriParserResult {
126133
error?: Error;
127134
}
128135

136+
export interface IDereferenceTransformer {
137+
result: any;
138+
source: any;
139+
fragment: string;
140+
targetAuthority: uri.URI;
141+
parentAuthority: uri.URI;
142+
parentPath: string[];
143+
}
144+
145+
export interface ITransformerResult {
146+
result?: any;
147+
error?: Error;
148+
}
149+
129150
export interface IUriResult {
130151
pointerStack: string[];
131152
targetPath: string[];
@@ -147,7 +168,12 @@ export interface IRefTransformer extends IComputeRefOpts {
147168
uri: uri.URI;
148169
}
149170

150-
export type ResolverErrorCode = 'POINTER_MISSING' | 'RESOLVE_URI' | 'PARSE_URI' | 'RESOLVE_POINTER';
171+
export type ResolverErrorCode =
172+
| 'POINTER_MISSING'
173+
| 'RESOLVE_URI'
174+
| 'PARSE_URI'
175+
| 'RESOLVE_POINTER'
176+
| 'TRANSFORM_DEREFERENCED';
151177
export interface IResolveError {
152178
code: ResolverErrorCode;
153179
message: string;

0 commit comments

Comments
 (0)