Skip to content
Open
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
7 changes: 6 additions & 1 deletion packages/parser/src/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,16 @@ export interface Resolver {
export interface ResolverOptions {
cache?: boolean;
resolvers?: Array<Resolver>;
resolveExternal?: boolean;
}

export function createResolver(options: ResolverOptions = {}): SpectralResolver {
// Default to true for backward compatibility
const resolveExternal = options.resolveExternal !== false;
const defaultResolvers = resolveExternal ? createDefaultResolvers() : [];

const availableResolvers: Array<Resolver> = [
...createDefaultResolvers(),
...defaultResolvers,
...(options.resolvers || [])
].map(r => ({
...r,
Expand Down
173 changes: 173 additions & 0 deletions packages/parser/test/external-ref-disabled.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import { Parser } from '../src/parser';

describe('external reference resolution disabled', function() {
it('should not resolve external http references when disabled in constructor', async function() {
const parser = new Parser({
__unstable: {
resolver: {
resolveExternal: false
}
}
});

const documentRaw = {
asyncapi: '2.0.0',
info: {
title: 'Test AsyncApi document',
version: '1.0',
},
channels: {
channel: {
publish: {
operationId: 'publish',
message: {
$ref: 'https://example.com/nonexistent-schema.json'
}
},
}
},
};

// When external resolution is disabled, validation should fail with an error about unresolved reference
const diagnostics = await parser.validate(documentRaw);

// Should have errors about unresolved references
expect(diagnostics.length).toBeGreaterThan(0);
const hasRefError = diagnostics.some(d =>
d.message?.toLowerCase().includes('reference') ||
d.message?.toLowerCase().includes('resolve') ||
d.message?.toLowerCase().includes('$ref') ||
d.message?.toLowerCase().includes('unable') ||
d.message?.toLowerCase().includes('unresolved')
);
expect(hasRefError).toBe(true);
});

it('should not resolve external https references when disabled in validate method', async function() {
const parser = new Parser();

const documentRaw = {
asyncapi: '2.0.0',
info: {
title: 'Test AsyncApi document',
version: '1.0',
},
channels: {
channel: {
publish: {
operationId: 'publish',
message: {
$ref: 'https://example.com/schema.json'
}
},
}
},
};

const diagnostics = await parser.validate(documentRaw, {
__unstable: {
resolver: {
resolveExternal: false
}
}
});

expect(diagnostics.length).toBeGreaterThan(0);
const hasRefError = diagnostics.some(d =>
d.message?.toLowerCase().includes('reference') ||
d.message?.toLowerCase().includes('resolve') ||
d.message?.toLowerCase().includes('unable') ||
d.message?.toLowerCase().includes('unresolved')
);
expect(hasRefError).toBe(true);
});

it('should not resolve external file references when disabled', async function() {
const parser = new Parser({
__unstable: {
resolver: {
resolveExternal: false
}
}
});

const documentRaw = {
asyncapi: '2.0.0',
info: {
title: 'Test AsyncApi document',
version: '1.0',
},
channels: {
channel: {
publish: {
operationId: 'publish',
message: {
$ref: './some-external-file.yaml'
}
},
}
},
};

const diagnostics = await parser.validate(documentRaw);
expect(diagnostics.length).toBeGreaterThan(0);
const hasRefError = diagnostics.some(d =>
d.message?.toLowerCase().includes('reference') ||
d.message?.toLowerCase().includes('resolve') ||
d.message?.toLowerCase().includes('unable') ||
d.message?.toLowerCase().includes('unresolved')
);
expect(hasRefError).toBe(true);
});

it('should still resolve internal references when external resolution is disabled', async function() {
const parser = new Parser({
__unstable: {
resolver: {
resolveExternal: false
}
}
});

const documentRaw = {
asyncapi: '2.0.0',
info: {
title: 'Test AsyncApi document',
version: '1.0',
},
channels: {
channel: {
publish: {
operationId: 'publish',
message: {
$ref: '#/components/messages/message'
}
},
}
},
components: {
messages: {
message: {
payload: {
type: 'string'
}
}
}
}
};

const { document, diagnostics } = await parser.parse(documentRaw);

// Document should be parsed successfully
expect(document).toBeDefined();
expect(document).toBeInstanceOf(Object);

// Internal reference should be resolved - the message should not have $ref anymore
const refMessage = document?.channels().get('channel')?.operations().get('publish')?.messages()[0];
expect(refMessage).toBeDefined();
expect(refMessage?.json('$ref' as any)).toBeUndefined();

// The message should have been resolved to the component
expect(refMessage?.json()).toEqual(document?.components().messages().get('message')?.json());
});
});