diff --git a/cli/src/commands/generate/generate.spec.ts b/cli/src/commands/generate/generate.spec.ts index 709a15fd..544c6a45 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,71 @@ describe('instantiateRelationships', () => { }); }); +describe('instantiateAdditionalTopLevelProperties', () => { + it('instantiate an additional top level array property', () => { + const pattern = { + properties: { + 'extra-property': { + properties: { + values: { + type: 'array' + } + } + } + } + }; + + expect(instantiateAdditionalTopLevelProperties(pattern)) + .toEqual({ + 'extra-property': { + values: [ '{{ VALUES }}' ] + } + }); + }); + + it('instantiate an additional top level const property', () => { + const pattern = { + properties: { + 'extra': { + properties: { + 'extra-property': { + const: 'value here' + } + } + } + } + }; + + expect(instantiateAdditionalTopLevelProperties(pattern)) + .toEqual({ + 'extra': { + 'extra-property': 'value here' + } + }); + }); + + it('instantiate an additional top level string property', () => { + const pattern = { + properties: { + 'extra': { + properties: { + 'extra-property': { + 'type': 'string' + } + } + } + } + }; + + expect(instantiateAdditionalTopLevelProperties(pattern)) + .toEqual({ + extra: { + '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 39680117..52a1fc76 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) ]; } } @@ -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 []; } @@ -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; } @@ -132,11 +135,33 @@ function instantiateRelationships(pattern: any): any { return outputRelationships; } +function instantiateAdditionalTopLevelProperties(pattern: any): any { + const properties = pattern?.properties; + if (!properties) { + logger.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; + } + + // TODO + extraProperties[additionalProperty] = instantiateNode(detail); + } + + return extraProperties; +} + export const exportedForTesting = { getPropertyValue, instantiateNodes, instantiateRelationships, - instantiateNodeInterfaces + instantiateNodeInterfaces, + instantiateAdditionalTopLevelProperties }; export function generate(patternPath: string, debug: boolean): CALMInstantiation { @@ -144,10 +169,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 00000000..fe6ea1b7 --- /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" + ] +}