Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,65 @@ describe('interfaceIdExistsOnNode', () => {
expect(result[0].message).toBe(`Referenced interface with ID '${input.interfaces[1]}' was not defined on the node with ID '${input.node}'.`);
expect(result[0].path).toEqual(['/relationships/0/connects/destination']);
});

it('should return an empty array when the singular interface field exists on the node', () => {
const input = { node: 'node1', interface: 'intf1' };
const context = {
document: {
data: {
nodes: [
{
'unique-id': 'node1',
'interfaces': [
{'unique-id': 'intf1'}
]
}
]
}
}
};

const result = interfaceIdExistsOnNode(input, null, context);
expect(result).toEqual([]);
});

it('should return a message when the singular interface field references an interface not defined on the target node', () => {
const input = { node: 'node1', interface: 'intf2' };
const context = {
document: {
data: {
nodes: [
{
'unique-id': 'node1',
'interfaces': [
{'unique-id': 'intf1'}
]
}
]
}
},
path: ['/relationships/0/connects/destination']
};

const result = interfaceIdExistsOnNode(input, null, context);
expect(result.length).toBe(1);
expect(result[0].message).toBe(`Referenced interface with ID '${input.interface}' was not defined on the node with ID '${input.node}'.`);
expect(result[0].path).toEqual(['/relationships/0/connects/destination']);
});

it('should return a message when the singular interface field is used but the target node has no interfaces', () => {
const input = { node: 'node1', interface: 'intf1' };
const context = {
document: {
data: {
nodes: [{'unique-id': 'node1'}]
}
},
path: ['/relationships/0/connects/destination']
};

const result = interfaceIdExistsOnNode(input, null, context);
expect(result.length).toBe(1);
expect(result[0].message).toBe(`Node with unique-id ${input.node} has no interfaces defined, expected interfaces [${input.interface}].`);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,23 @@ import { difference } from 'lodash';

/**
* Checks that the input value exists as an interface with matching unique ID defined under a node in the document.
* Supports both "interface" (singular string) and "interfaces" (array) forms in connects relationships.
*/
export function interfaceIdExistsOnNode(input, _, context) {
if (!input || !input.interfaces) {
if (!input) {
return [];
}

// Support both "interface" (singular string) and "interfaces" (array) forms
const desiredInterfaces: string[] = [];
if (input.interfaces && Array.isArray(input.interfaces)) {
desiredInterfaces.push(...input.interfaces);
}
if (typeof input.interface === 'string') {
desiredInterfaces.push(input.interface);
}

if (desiredInterfaces.length === 0) {
return [];
}

Expand All @@ -24,7 +38,6 @@ export function interfaceIdExistsOnNode(input, _, context) {
}

// all of these must be present on the referenced node
const desiredInterfaces = input.interfaces;

const node = nodeMatch[0];

Expand Down
10 changes: 10 additions & 0 deletions shared/src/spectral/rules-architecture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,16 @@ const architectureRules: RulesetDefinition = {
function: interfaceIdExists
},
},

'referenced-interface-defined-in-architecture': {
description: 'Referenced interface (singular form) must be defined',
severity: 'error',
message: '{{error}}',
given: '$.relationships[*].relationship-type.connects.*.interface',
then: {
function: interfaceIdExists
},
},

'referenced-interfaces-defined-on-correct-node-in-architecture': {
description: 'Connects relationships must reference interfaces that exist on the correct nodes',
Expand Down