From 911719784d22028afca23037a0defad0f8a64562 Mon Sep 17 00:00:00 2001 From: Will Osborne Date: Mon, 22 Apr 2024 11:57:38 +0100 Subject: [PATCH 1/4] Add support for additional properties at top level --- cli/src/commands/generate/generate.spec.ts | 54 +++++- cli/src/commands/generate/generate.ts | 25 ++- cli/test_fixtures/additional-props.json | 184 +++++++++++++++++++++ 3 files changed, 261 insertions(+), 2 deletions(-) create mode 100644 cli/test_fixtures/additional-props.json diff --git a/cli/src/commands/generate/generate.spec.ts b/cli/src/commands/generate/generate.spec.ts index 709a15fdd..9bf72c331 100644 --- a/cli/src/commands/generate/generate.spec.ts +++ b/cli/src/commands/generate/generate.spec.ts @@ -21,7 +21,8 @@ const { getPropertyValue, instantiateNodes, instantiateRelationships, - instantiateNodeInterfaces + instantiateNodeInterfaces, + instantiateAdditionalTopLevelProperties } = exportedForTesting; describe('getPropertyValue', () => { @@ -73,6 +74,8 @@ describe('getPropertyValue', () => { }); }); + + function getSamplePatternNode(properties: any): any { return { properties: { @@ -313,6 +316,55 @@ describe('instantiateRelationships', () => { }); }); +describe('instantiateAdditionalTopLevelProperties', () => { + it('instantiate an additional top level array property', () => { + const pattern = { + properties: { + 'extra-property': { + type: 'array' + } + } + }; + + expect(instantiateAdditionalTopLevelProperties(pattern)) + .toEqual({ + 'extra-property': [ + '{{ EXTRA_PROPERTY }}' + ] + }); + }); + + it('instantiate an additional top level const property', () => { + const pattern = { + properties: { + 'extra-property': { + const: 'value here' + } + } + }; + + expect(instantiateAdditionalTopLevelProperties(pattern)) + .toEqual({ + 'extra-property': 'value here' + }); + }); + + it('instantiate an additional top level string property', () => { + const pattern = { + properties: { + 'extra-property': { + 'type': 'string' + } + } + }; + + expect(instantiateAdditionalTopLevelProperties(pattern)) + .toEqual({ + 'extra-property': '{{ EXTRA_PROPERTY }}' + }); + }); +}); + describe('runGenerate', () => { let tempDirectoryPath; diff --git a/cli/src/commands/generate/generate.ts b/cli/src/commands/generate/generate.ts index 396801171..e13639d5e 100644 --- a/cli/src/commands/generate/generate.ts +++ b/cli/src/commands/generate/generate.ts @@ -132,11 +132,32 @@ function instantiateRelationships(pattern: any): any { return outputRelationships; } +function instantiateAdditionalTopLevelProperties(pattern: any): any { + const properties = pattern?.properties; + if (!properties) { + console.error('Warning: pattern has no properties defined.'); + return []; + } + + const extraProperties = {}; + for (const [additionalProperty, detail] of Object.entries(properties)) { + // additional properties only + if (['nodes', 'relationships'].includes(additionalProperty)) { + continue; + } + + extraProperties[additionalProperty] = getPropertyValue(additionalProperty, detail); + } + + return extraProperties; +} + export const exportedForTesting = { getPropertyValue, instantiateNodes, instantiateRelationships, - instantiateNodeInterfaces + instantiateNodeInterfaces, + instantiateAdditionalTopLevelProperties }; export function generate(patternPath: string, debug: boolean): CALMInstantiation { @@ -144,10 +165,12 @@ export function generate(patternPath: string, debug: boolean): CALMInstantiation const pattern = loadFile(patternPath); const outputNodes = instantiateNodes(pattern); const relationshipNodes = instantiateRelationships(pattern); + const additionalProperties = instantiateAdditionalTopLevelProperties(pattern); const final = { 'nodes': outputNodes, 'relationships': relationshipNodes, + ...additionalProperties // object spread operator to insert additional props at top level }; return final; diff --git a/cli/test_fixtures/additional-props.json b/cli/test_fixtures/additional-props.json new file mode 100644 index 000000000..fe6ea1b7c --- /dev/null +++ b/cli/test_fixtures/additional-props.json @@ -0,0 +1,184 @@ +{ + "$schema": "https://raw.githubusercontent.com/finos-labs/architecture-as-code/main/calm/draft/2024-03/meta/calm.json", + "$id": "https://raw.githubusercontent.com/finos-labs/architecture-as-code/main/calm/pattern/api-gateway", + "title": "API Gateway Pattern", + "type": "object", + "properties": { + "nodes": { + "type": "array", + "minItems": 3, + "prefixItems": [ + { + "$ref": "https://raw.githubusercontent.com/finos-labs/architecture-as-code/main/calm/draft/2024-03/meta/core.json#/defs/node", + "properties": { + "ingress-host": { + "type": "string" + }, + "ingress-port": { + "type": "integer" + }, + "well-known-endpoint": { + "type": "string" + }, + "description": { + "const": "The API Gateway used to verify authorization and access to downstream system" + }, + "type": { + "const": "system" + }, + "name": { + "const": "API Gateway" + }, + "unique-id": { + "const": "api-gateway" + } + }, + "required": [ + "ingress-host", + "ingress-port", + "well-known-endpoint" + ] + }, + { + "$ref": "https://raw.githubusercontent.com/finos-labs/architecture-as-code/main/calm/draft/2024-03/meta/core.json#/defs/node", + "properties": { + "description": { + "const": "The API Consumer making an authenticated and authorized request" + }, + "type": { + "const": "system" + }, + "name": { + "const": "Python Based API Consumer" + }, + "unique-id": { + "const": "api-consumer" + } + } + }, + { + "$ref": "https://raw.githubusercontent.com/finos-labs/architecture-as-code/main/calm/draft/2024-03/meta/core.json#/defs/node", + "properties": { + "host": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "description": { + "const": "The API Producer serving content" + }, + "type": { + "const": "system" + }, + "name": { + "const": "Java Based API Producer" + }, + "unique-id": { + "const": "api-producer" + } + }, + "required": [ + "host", + "port" + ] + }, + { + "$ref": "https://raw.githubusercontent.com/finos-labs/architecture-as-code/main/calm/draft/2024-03/meta/core.json#/defs/node", + "properties": { + "description": { + "const": "The Identity Provider used to verify the bearer token" + }, + "type": { + "const": "system" + }, + "name": { + "const": "Identity Provider" + }, + "unique-id": { + "const": "idp" + } + } + } + ] + }, + "relationships": { + "type": "array", + "minItems": 3, + "prefixItems": [ + { + "$ref": "https://raw.githubusercontent.com/finos-labs/architecture-as-code/main/calm/draft/2024-03/meta/core.json#/defs/relationship", + "properties": { + "unique-id": { + "const": "api-consumer-api-gateway" + }, + "relationship-type": { + "const": { + "connects": { + "source": "api-consumer", + "destination": "api-gateway" + } + } + }, + "parties": { + }, + "protocol": { + "const": "HTTPS" + }, + "authentication": { + "const": "OAuth2" + } + } + }, + { + "$ref": "https://raw.githubusercontent.com/finos-labs/architecture-as-code/main/calm/draft/2024-03/meta/core.json#/defs/relationship", + "properties": { + "unique-id": { + "const": "api-gateway-idp" + }, + "relationship-type": { + "const": { + "connects": { + "source": "api-gateway", + "destination": "idp" + } + } + }, + "protocol": { + "const": "HTTPS" + } + } + }, + { + "$ref": "https://raw.githubusercontent.com/finos-labs/architecture-as-code/main/calm/draft/2024-03/meta/core.json#/defs/relationship", + "properties": { + "unique-id": { + "const": "api-gateway-api-producer" + }, + "relationship-type": { + "const": { + "connects": { + "source": "api-gateway", + "destination": "api-producer" + } + } + }, + "protocol": { + "const": "HTTPS" + } + } + } + ] + }, + "extra-property-constant": { + "const": "constant value" + }, + "extra-property-array": { + "type": "array" + } + }, + "required": [ + "nodes", + "relationships" + ] +} From bfe47bc590815c836fba39cb013299ced39ceec5 Mon Sep 17 00:00:00 2001 From: Will Osborne Date: Mon, 22 Apr 2024 13:18:23 +0100 Subject: [PATCH 2/4] Instantiate extra properties as if they were nodes --- cli/src/commands/generate/generate.spec.ts | 36 ++++++++++++++++------ cli/src/commands/generate/generate.ts | 36 ++++++++++++---------- 2 files changed, 46 insertions(+), 26 deletions(-) diff --git a/cli/src/commands/generate/generate.spec.ts b/cli/src/commands/generate/generate.spec.ts index 9bf72c331..2519c2baf 100644 --- a/cli/src/commands/generate/generate.spec.ts +++ b/cli/src/commands/generate/generate.spec.ts @@ -321,46 +321,62 @@ describe('instantiateAdditionalTopLevelProperties', () => { const pattern = { properties: { 'extra-property': { - type: 'array' + properties: { + values: { + type: 'array' + } + } } } }; expect(instantiateAdditionalTopLevelProperties(pattern)) .toEqual({ - 'extra-property': [ - '{{ EXTRA_PROPERTY }}' - ] + 'extra-property': { + values: [ '{{ VALUES }}' ] + } }); }); it('instantiate an additional top level const property', () => { const pattern = { properties: { - 'extra-property': { - const: 'value here' + 'extra': { + properties: { + 'extra-property': { + const: 'value here' + } + } } } }; expect(instantiateAdditionalTopLevelProperties(pattern)) .toEqual({ - 'extra-property': 'value here' + 'extra': { + 'extra-property': 'value here' + } }); }); it('instantiate an additional top level string property', () => { const pattern = { properties: { - 'extra-property': { - 'type': 'string' + 'extra': { + properties: { + 'extra-property': { + 'type': 'string' + } + } } } }; expect(instantiateAdditionalTopLevelProperties(pattern)) .toEqual({ - 'extra-property': '{{ EXTRA_PROPERTY }}' + extra: { + 'extra-property': '{{ EXTRA_PROPERTY }}' + } }); }); }); diff --git a/cli/src/commands/generate/generate.ts b/cli/src/commands/generate/generate.ts index e13639d5e..b417d7d85 100644 --- a/cli/src/commands/generate/generate.ts +++ b/cli/src/commands/generate/generate.ts @@ -26,7 +26,7 @@ function getStringPlaceholder(name: string): string { return '{{ ' + name.toUpperCase().replaceAll('-', '_') + ' }}'; } -function getPropertyValue(keyName: string, detail: any) : any { +function getPropertyValue(keyName: string, detail: any): any { if ('const' in detail) { return detail['const']; } @@ -41,8 +41,8 @@ function getPropertyValue(keyName: string, detail: any) : any { return -1; } if (propertyType === 'array') { - return [ - getStringPlaceholder(keyName) + return [ + getStringPlaceholder(keyName) ]; } } @@ -72,6 +72,20 @@ function instantiateNodeInterfaces(detail: any): any[] { return interfaces; } +function instantiateNode(node: any): any { + const out = {}; + for (const [key, detail] of Object.entries(node['properties'])) { + if (key === 'interfaces') { + const interfaces = instantiateNodeInterfaces(detail); + out['interfaces'] = interfaces; + } + else { + out[key] = getPropertyValue(key, detail); + } + } + return out; +} + function instantiateNodes(pattern: any): any { const nodes = pattern?.properties?.nodes?.prefixItems; if (!nodes) { @@ -88,18 +102,7 @@ function instantiateNodes(pattern: any): any { continue; } - const out = {}; - for (const [key, detail] of Object.entries(node['properties'])) { - if (key === 'interfaces') { - const interfaces = instantiateNodeInterfaces(detail); - out['interfaces'] = interfaces; - } - else { - out[key] = getPropertyValue(key, detail); - } - } - - outputNodes.push(out); + outputNodes.push(instantiateNode(node)); } return outputNodes; } @@ -146,7 +149,8 @@ function instantiateAdditionalTopLevelProperties(pattern: any): any { continue; } - extraProperties[additionalProperty] = getPropertyValue(additionalProperty, detail); + // TODO + extraProperties[additionalProperty] = instantiateNode(detail); } return extraProperties; From 5ffafcf25bfcdb2436b4361e54ab45f5d4d170a7 Mon Sep 17 00:00:00 2001 From: Will Osborne Date: Mon, 22 Apr 2024 13:42:27 +0100 Subject: [PATCH 3/4] Lint --- cli/src/commands/generate/generate.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/commands/generate/generate.spec.ts b/cli/src/commands/generate/generate.spec.ts index 2519c2baf..544c6a451 100644 --- a/cli/src/commands/generate/generate.spec.ts +++ b/cli/src/commands/generate/generate.spec.ts @@ -374,7 +374,7 @@ describe('instantiateAdditionalTopLevelProperties', () => { expect(instantiateAdditionalTopLevelProperties(pattern)) .toEqual({ - extra: { + extra: { 'extra-property': '{{ EXTRA_PROPERTY }}' } }); From c462006c738d5c703c41d9b8cab465b98c292026 Mon Sep 17 00:00:00 2001 From: Will Osborne Date: Mon, 22 Apr 2024 14:38:10 +0100 Subject: [PATCH 4/4] Fix log line --- cli/src/commands/generate/generate.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/src/commands/generate/generate.ts b/cli/src/commands/generate/generate.ts index b417d7d85..52a1fc767 100644 --- a/cli/src/commands/generate/generate.ts +++ b/cli/src/commands/generate/generate.ts @@ -51,7 +51,7 @@ function getPropertyValue(keyName: string, detail: any): any { function instantiateNodeInterfaces(detail: any): any[] { const interfaces = []; if (!('prefixItems' in detail)) { - console.error('No items in interfaces block.'); + logger.error('No items in interfaces block.'); return []; } @@ -138,7 +138,7 @@ function instantiateRelationships(pattern: any): any { function instantiateAdditionalTopLevelProperties(pattern: any): any { const properties = pattern?.properties; if (!properties) { - console.error('Warning: pattern has no properties defined.'); + logger.error('Warning: pattern has no properties defined.'); return []; }