Skip to content
This repository was archived by the owner on Jul 13, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 177 additions & 0 deletions src/__tests__/resolver.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1670,6 +1670,183 @@ describe('resolver', () => {
expect(result.errors[0].uri.toString()).toEqual(new URI('http://foo').toString());
});

/**
* Allows the consumer to provide a custom ref transformer to transform a fully resolved object.
*
*/
test('should support `transformDereferenceResult` hook', async () => {
const data = {
markdown: '# hello',
bar: {
hello: `{ "hello": "world" }`,
},
};

const source = {
definitions: {
foo: {
$ref: 'http://foo.com/foo.md#/markdown',
},
bar: {
$ref: 'http://foo.com/bar.json#/hello',
},
},
};

const reader: Types.IResolver = {
async resolve(ref: uri.URI): Promise<any> {
if (ref.path().split('.')[1] === 'md') {
return data;
}

return data.bar;
},
};

const resolver = new Resolver({
resolvers: {
http: reader,
},
transformDereferenceResult: async opts => {
if (opts.parentAuthority.path().split('.')[1] === 'md') {
opts.result = {
heading1: 'hello',
};
} else if (opts.parentAuthority.toString() === 'http://foo.com/bar.json' && opts.fragment === '/hello') {
// Can transform the result however you want
opts.result = {
pooh: 'bear',
};
}

return opts;
},
});

const result = await resolver.resolve(source);

expect(result.result).toEqual({
definitions: {
foo: {
heading1: 'hello',
},
bar: {
pooh: 'bear',
},
},
});
});

test('should pass `transformDereferenceResult` to child runners', async () => {
const data = {
foo: {
$ref: 'http://foo.com/hi',
},
hi: {
$ref: 'http://foo.com/bye',
},
bye: {
adios: true,
},
bar: {
hello: 'world',
},
};

const source = {
definitions: {
foo: {
$ref: 'http://foo.com/foo',
},
bar: {
$ref: 'http://foo.com/bar',
},
},
};

const reader: Types.IResolver = {
async resolve(ref: uri.URI): Promise<any> {
return data[ref.path().slice(1)];
},
};

let counter = 0;
const resolver = new Resolver({
resolvers: {
http: reader,
},
transformDereferenceResult: async opts => {
counter += 1;
return opts;
},
});

const result = await resolver.resolve(source);

expect(result.result).toEqual({
definitions: {
foo: {
adios: true,
},
bar: {
hello: 'world',
},
},
});

expect(counter).toEqual(5);
});

test('should support catching error in `transformDereferenceResult` hook', async () => {
const data = {
markdown: '# hello',
};

const source = {
definitions: {
foo: {
$ref: 'http://foo.com#/markdown',
},
},
};

const reader: Types.IResolver = {
async resolve(): Promise<any> {
return data;
},
};

const resolver = new Resolver({
resolvers: {
http: reader,
},
transformDereferenceResult: async opts => {
if (opts.parentAuthority.toString() === 'http://foo.com/') throw new Error('some transform error!');

return opts;
},
});

const result = await resolver.resolve(source);

expect(result.result).toEqual({
definitions: {
foo: '# hello',
},
});

expect({ ...result.errors[0], uri: undefined }).toEqual({
code: 'TRANSFORM_DEREFERENCED',
message:
"Error: Could not transform dereferenced result for 'http://foo.com/#/markdown' - Error: some transform error!",
pointerStack: [],
uriStack: [],
path: ['markdown'],
uri: undefined,
});
expect(result.errors[0].uri.toString()).toEqual(new URI('#/markdown').toString());
});

test('should pass context to transformRef and read', async () => {
let t1;
let t2;
Expand Down
3 changes: 3 additions & 0 deletions src/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export class Resolver {
protected getRef?: (key: string, val: any) => string | void;
protected transformRef?: (opts: Types.IRefTransformer, ctx: any) => uri.URI | any;
protected parseResolveResult?: (opts: Types.IUriParser) => Promise<Types.IUriParserResult>;
protected transformDereferenceResult?: (opts: Types.IDereferenceTransformer) => Promise<Types.ITransformerResult>;

constructor(opts: Types.IResolverOpts = {}) {
this.uriCache = opts.uriCache || new Cache();
Expand All @@ -29,6 +30,7 @@ export class Resolver {
this.dereferenceInline = typeof opts.dereferenceInline !== 'undefined' ? opts.dereferenceInline : true;
this.dereferenceRemote = typeof opts.dereferenceRemote !== 'undefined' ? opts.dereferenceRemote : true;
this.parseResolveResult = opts.parseResolveResult;
this.transformDereferenceResult = opts.transformDereferenceResult;
this.ctx = opts.ctx;
}

Expand All @@ -41,6 +43,7 @@ export class Resolver {
dereferenceInline: this.dereferenceInline,
dereferenceRemote: this.dereferenceRemote,
parseResolveResult: this.parseResolveResult,
transformDereferenceResult: this.transformDereferenceResult,
...opts,
ctx: Object.assign({}, this.ctx || {}, opts.ctx || {}),
});
Expand Down
36 changes: 36 additions & 0 deletions src/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ export class ResolveRunner implements Types.IResolveRunner {
public readonly getRef: (key: string, val: any) => string | void;
public readonly transformRef?: (opts: Types.IRefTransformer, ctx: any) => uri.URI | any;
public readonly parseResolveResult?: (opts: Types.IUriParser) => Promise<Types.IUriParserResult>;
public readonly transformDereferenceResult?: (
opts: Types.IDereferenceTransformer,
) => Promise<Types.ITransformerResult>;

private _source: any;

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

this.dereferenceRemote = typeof opts.dereferenceRemote !== 'undefined' ? opts.dereferenceRemote : true;
this.parseResolveResult = opts.parseResolveResult;
this.transformDereferenceResult = opts.transformDereferenceResult;
this.ctx = opts.ctx;

this.lookupUri = memoize(this.lookupUri, {
Expand Down Expand Up @@ -233,6 +237,37 @@ export class ResolveRunner implements Types.IResolveRunner {
resolved.result = this._source;
}

// support custom transformers
if (this.transformDereferenceResult) {
const ref = new URI(jsonPointer || '');
try {
const { result, error } = await this.transformDereferenceResult({
source: this.source,
result: resolved.result,
targetAuthority: ref,
parentAuthority: this.baseUri,
parentPath: targetPath,
fragment: ref.fragment(),
});

resolved.result = result;
if (error) {
throw new Error(`Could not transform dereferenced result for '${ref.toString()}' - ${String(error)}`);
}
} catch (e) {
resolved.errors.push({
code: 'TRANSFORM_DEREFERENCED',
message: `Error: Could not transform dereferenced result for '${this.baseUri.toString()}${
ref.fragment() !== '' ? `#${ref.fragment()}` : ``
}' - ${String(e)}`,
uri: ref,
uriStack: this.uriStack,
pointerStack: [],
path: targetPath,
});
}
}

return resolved;
}

Expand Down Expand Up @@ -340,6 +375,7 @@ export class ResolveRunner implements Types.IResolveRunner {
resolvers: this.resolvers,
transformRef: this.transformRef,
parseResolveResult: this.parseResolveResult,
transformDereferenceResult: this.transformDereferenceResult,
dereferenceRemote: this.dereferenceRemote,
dereferenceInline: this.dereferenceInline,
ctx: this.ctx,
Expand Down
28 changes: 27 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ export interface IResolverOpts {
*/
parseResolveResult?: (opts: IUriParser) => Promise<IUriParserResult>;

/**
* Hook to transform resolved object.
*
* For example, transform `OpenAPI` file to a `Hub Page`.
*/
transformDereferenceResult?: (opts: IDereferenceTransformer) => Promise<ITransformerResult>;

/** Should we resolve local pointers? true by default. */
dereferenceInline?: boolean;

Expand Down Expand Up @@ -126,6 +133,20 @@ export interface IUriParserResult {
error?: Error;
}

export interface IDereferenceTransformer {
result: any;
source: any;
fragment: string;
targetAuthority: uri.URI;
parentAuthority: uri.URI;
parentPath: string[];
}

export interface ITransformerResult {
result?: any;
error?: Error;
}

export interface IUriResult {
pointerStack: string[];
targetPath: string[];
Expand All @@ -147,7 +168,12 @@ export interface IRefTransformer extends IComputeRefOpts {
uri: uri.URI;
}

export type ResolverErrorCode = 'POINTER_MISSING' | 'RESOLVE_URI' | 'PARSE_URI' | 'RESOLVE_POINTER';
export type ResolverErrorCode =
| 'POINTER_MISSING'
| 'RESOLVE_URI'
| 'PARSE_URI'
| 'RESOLVE_POINTER'
| 'TRANSFORM_DEREFERENCED';
export interface IResolveError {
code: ResolverErrorCode;
message: string;
Expand Down
Loading