diff --git a/packages/salto/src/parser/salto.ts b/packages/salto/src/parser/salto.ts index d5802ce8549..5b0abcc1677 100644 --- a/packages/salto/src/parser/salto.ts +++ b/packages/salto/src/parser/salto.ts @@ -1,7 +1,7 @@ import _ from 'lodash' import { - Type, ElemID, ObjectType, PrimitiveType, PrimitiveTypes, Field, Values, - isObjectType, isPrimitiveType, Element, isInstanceElement, InstanceElement, + Type, ElemID, ObjectType, PrimitiveType, PrimitiveTypes, Field, Values, isObjectType, + isPrimitiveType, Element, isInstanceElement, InstanceElement, isField, isElement, } from 'adapter-api' import { collections } from '@salto/lowerdash' import HCLParser, { @@ -212,7 +212,7 @@ export default class Parser { return { type: Keywords.LIST_DEFINITION, labels: [field.type.elemID.getFullName(), field.name], - attrs: field.getAnnotationsValues() || {}, + attrs: field.getAnnotationsValues(), blocks: [], } } @@ -221,78 +221,99 @@ export default class Parser { return { type: field.type.elemID.getFullName(), labels: [field.name], - attrs: field.getAnnotationsValues() || {}, + attrs: field.getAnnotationsValues(), blocks: [], } } - private static getAnnotationsBlock(element: PrimitiveType): HCLBlock { - return { + private static getAnnotationsBlock(element: Type): HCLBlock[] { + return _.isEmpty(element.annotations) ? [] : [{ type: Keywords.ANNOTATIONS_DEFINITION, labels: [], attrs: {}, - blocks: Object.keys(element.annotations) - .map(key => this.getFieldBlock(new Field(element.elemID, key, element.annotations[key]))), + blocks: Object.entries(element.annotations).map(([key, type]) => ({ + type: type.elemID.getFullName(), + labels: [key], + attrs: {}, + blocks: [], + })), + }] + } + + private static getElementBlock(elem: Element): HCLBlock { + if (isObjectType(elem)) { + return { + type: Keywords.TYPE_DEFINITION, + labels: [elem.elemID.getFullName()], + attrs: elem.getAnnotationsValues(), + blocks: this.getAnnotationsBlock(elem).concat( + Object.values(elem.fields).map(f => this.getBlock(f)) + ), + } + } + if (isPrimitiveType(elem)) { + return { + type: Keywords.TYPE_DEFINITION, + labels: [ + elem.elemID.getFullName(), + Keywords.TYPE_INHERITENCE_SEPARATOR, + getPrimitiveTypeName(elem.primitive), + ], + attrs: elem.getAnnotationsValues(), + blocks: this.getAnnotationsBlock(elem), + } + } + if (isInstanceElement(elem)) { + return { + type: elem.type.elemID.getFullName(), + labels: elem.elemID.isConfig() ? [] : [elem.elemID.name], + attrs: elem.value, + blocks: [], + } + } + // Without this exception the linter won't allow us to end the function + // without a return value + throw new Error('Unsupported element type') + } + + private static getBlock(value: Element | Values): HCLBlock { + if (isField(value)) { + return value.isList ? this.getListFieldBlock(value) : this.getFieldBlock(value) + } + if (isElement(value)) { + return this.getElementBlock(value) + } + // If we reach this point we are serializing values + return { + type: '', + labels: [], + attrs: value as Values, + blocks: [], } } /** * Serialize elements to blueprint * - * @param elements The elements to serialize + * @param elementsOrValues The element(s) or attributes to serialize * @returns A buffer with the elements serialized as a blueprint */ - public static async dump(elements: Element[]): Promise { - const blocks = elements.map(elem => { - if (isObjectType(elem)) { - // Clone the annotation values because we may delete some keys from there - const annotationsValues = _.cloneDeep(elem.getAnnotationsValues()) - return { - type: Keywords.TYPE_DEFINITION, - labels: [elem.elemID.getFullName()], - attrs: annotationsValues, - blocks: Object.values(elem.fields).map(field => ((field.isList) - ? this.getListFieldBlock(field) - : this.getFieldBlock(field))), - } - } - if (isPrimitiveType(elem)) { - return { - type: Keywords.TYPE_DEFINITION, - labels: [ - elem.elemID.getFullName(), - Keywords.TYPE_INHERITENCE_SEPARATOR, - getPrimitiveTypeName(elem.primitive), - ], - attrs: elem.getAnnotationsValues(), - blocks: Object.keys(elem.annotations).length > 0 ? [this.getAnnotationsBlock(elem)] : [], - } - } - if (isInstanceElement(elem)) { - const labels = elem.elemID.name === ElemID.CONFIG_INSTANCE_NAME - ? [] - : [elem.elemID.name] - - return { - type: elem.type.elemID.getFullName(), - labels, - attrs: elem.value, - blocks: [], - } - } - - // Without this exception the linter won't allow us to end the function - // without a return value - throw new Error('unsupported type') - }) - - const body: HCLBlock = { + static async dump(elementsOrValues: Element | Element[] | Values): Promise { + const wrapBlocks = (blocks: HCLBlock[]): HCLBlock => ({ type: '', labels: [], attrs: {}, - blocks: blocks.map(markBlockQuotes), - } + blocks, + }) + + // If we got a single element, put it in an array because we need to wrap it with an empty block + const elemListOrValues = isElement(elementsOrValues) ? [elementsOrValues] : elementsOrValues + + const body = _.isArray(elemListOrValues) + ? wrapBlocks(elemListOrValues.map(e => this.getBlock(e))) + : this.getBlock(elemListOrValues) + body.blocks = body.blocks.map(markBlockQuotes) return removeQuotes(await HCLParser.dump(body)) } } diff --git a/packages/salto/test/parser/salto.test.ts b/packages/salto/test/parser/salto.test.ts index 1a751b2f150..d1671e7135f 100644 --- a/packages/salto/test/parser/salto.test.ts +++ b/packages/salto/test/parser/salto.test.ts @@ -380,64 +380,88 @@ describe('Salto Dump', () => { } ) - let body: string + describe('dump elements', () => { + let body: string - beforeAll(async () => { - body = await Parser.dump([strType, numType, boolType, fieldType, model, instance, config]) - }) + beforeAll(async () => { + body = await Parser.dump([strType, numType, boolType, fieldType, model, instance, config]) + }) - it('dumps primitive types', () => { - expect(body).toMatch(/type salesforce_string is string {/) - expect(body).toMatch(/type salesforce_number is number {/) - expect(body).toMatch(/type salesforce_bool is boolean {/) - }) + it('dumps primitive types', () => { + expect(body).toMatch(/type salesforce_string is string {/) + expect(body).toMatch(/type salesforce_number is number {/) + expect(body).toMatch(/type salesforce_bool is boolean {/) + }) - it('dumps primitive field type annotations', () => { - expect(body).toMatch(/type salesforce_field is number {.*?annotations {.*?number alice {/s) - expect(body).toMatch(/type salesforce_field is number {.*?annotations {.*?number bob {/s) - expect(body).toMatch(/type salesforce_field is number {.*?annotations {.*?boolean tom {/s) - expect(body).toMatch(/type salesforce_field is number {.*?annotations {.*?string jerry {/s) - }) + it('dumps primitive field type annotations', () => { + expect(body).toMatch(/type salesforce_field is number {.*?annotations {.*?number alice {/s) + expect(body).toMatch(/type salesforce_field is number {.*?annotations {.*?number bob {/s) + expect(body).toMatch(/type salesforce_field is number {.*?annotations {.*?boolean tom {/s) + expect(body).toMatch(/type salesforce_field is number {.*?annotations {.*?string jerry {/s) + }) - it('dumps instance elements', () => { - expect(body).toMatch(/salesforce_test me {/) - }) + it('dumps instance elements', () => { + expect(body).toMatch(/salesforce_test me {/) + }) + + it('dumps config elements', () => { + expect(body).toMatch(/salesforce_test {/) + }) - it('dumps config elements', () => { - expect(body).toMatch(/salesforce_test {/) + describe('dumped model', () => { + it('has correct block type and label', () => { + expect(body).toMatch(/type salesforce_test {/) + }) + it('has complex attributes', () => { + expect(body).toMatch( + /lead_convert_settings = {\s*account = \[\s*{\s*input\s*=\s*"bla",\s*output\s*=\s*"foo"\s*}\s*\]\s*}/m, + ) + }) + it('has fields', () => { + expect(body).toMatch( + /salesforce_string name {\s+label = "Name"\s+}/m, + ) + expect(body).toMatch( + /salesforce_number num {/m, + ) + expect(body).toMatch( + /list salesforce_string list {/m + ) + }) + it('can be parsed back', async () => { + const { elements, errors } = await Parser.parse(Buffer.from(body), 'none') + expect(errors.length).toEqual(0) + expect(elements.length).toEqual(7) + expect(elements[0]).toEqual(strType) + expect(elements[1]).toEqual(numType) + expect(elements[2]).toEqual(boolType) + TestHelpers.expectTypesToMatch(elements[3] as Type, fieldType) + TestHelpers.expectTypesToMatch(elements[4] as Type, model) + TestHelpers.expectInstancesToMatch(elements[5] as InstanceElement, instance) + TestHelpers.expectInstancesToMatch(elements[6] as InstanceElement, config) + }) + }) }) + describe('dump field', () => { + let body: string - describe('dumped model', () => { - it('has correct block type and label', () => { - expect(body).toMatch(/type salesforce_test {/) + beforeAll(async () => { + body = await Parser.dump(model.fields.name) }) - it('has complex attributes', () => { - expect(body).toMatch( - /lead_convert_settings = {\s*account = \[\s*{\s*input\s*=\s*"bla",\s*output\s*=\s*"foo"\s*}\s*\]\s*}/m, - ) + + it('should contain only field', () => { + expect(body).toMatch(/^salesforce_string name {\s+label = "Name"\s+}$/m) }) - it('has fields', () => { - expect(body).toMatch( - /salesforce_string name {\s+label = "Name"\s+}/m, - ) - expect(body).toMatch( - /salesforce_number num {/m, - ) - expect(body).toMatch( - /list salesforce_string list {/m - ) + }) + describe('dump attribute', () => { + let body: string + + beforeAll(async () => { + body = await Parser.dump({ attr: 'value' }) }) - it('can be parsed back', async () => { - const { elements, errors } = await Parser.parse(Buffer.from(body), 'none') - expect(errors.length).toEqual(0) - expect(elements.length).toEqual(7) - expect(elements[0]).toEqual(strType) - expect(elements[1]).toEqual(numType) - expect(elements[2]).toEqual(boolType) - TestHelpers.expectTypesToMatch(elements[3] as Type, fieldType) - TestHelpers.expectTypesToMatch(elements[4] as Type, model) - TestHelpers.expectInstancesToMatch(elements[5] as InstanceElement, instance) - TestHelpers.expectInstancesToMatch(elements[6] as InstanceElement, config) + + it('should contain only attribute', () => { + expect(body).toMatch(/^attr\s+=\s+"value"$/m) }) }) })