Skip to content

Commit 407af67

Browse files
fix: wrap error from fast-xml-parser when invalid xml parsed (#518)
* fix: wrap error from fast-xml-parser when invalid xml parsed * chore: adding types for SDR * chore: fix test titles * chore: remove METADATA_SUPPORT.md change * chore: adding types for SDR * chore: adding types for SDR * chore: better argument names
1 parent b7ee5cf commit 407af67

File tree

4 files changed

+57
-6
lines changed

4 files changed

+57
-6
lines changed

METADATA_SUPPORT.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,7 @@ v55 introduces the following new types. Here's their current level of support
482482

483483
|Metadata Type|Support|Notes|
484484
|:---|:---|:---|
485+
|Experience|undefined|undefined|
485486

486487
## Additional Types
487488

src/i18n/i18n.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,5 @@ export const messages = {
6565
error_no_job_id: 'The %s operation is missing a job ID. Initialize an operation with an ID, or start a new job.',
6666
tapi_deploy_component_limit_error: 'This deploy method only supports deploying one metadata component at a time',
6767
warn_unresolved_source_for_components: 'The following components will not be deployed due to unresolved source: %s',
68+
invalid_xml_parsing: 'error parsing %s due to:\n message: %s\n line: %s\n code: %s',
6869
};

src/resolve/sourceComponent.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
66
*/
77
import { basename, join } from 'path';
8-
import { parse } from 'fast-xml-parser';
8+
import { parse, validate } from 'fast-xml-parser';
99
import { get, getString, JsonMap } from '@salesforce/ts-types';
1010
import { baseName, normalizeToArray, parseMetadataXml, trimUntil } from '../utils';
1111
import { DEFAULT_PACKAGE_ROOT_SFDX } from '../common';
1212
import { SfdxFileFormat } from '../convert';
1313
import { MetadataType } from '../registry';
14-
import { TypeInferenceError } from '../errors';
14+
import { LibraryError, TypeInferenceError } from '../errors';
1515
import { DestructiveChangesType } from '../collections';
1616
import { filePathsFromMetadataComponent } from '../utils/filePathGenerator';
1717
import { MetadataComponent, VirtualDirectory } from './types';
@@ -116,17 +116,17 @@ export class SourceComponent implements MetadataComponent {
116116
public async parseXml<T = JsonMap>(xmlFilePath?: string): Promise<T> {
117117
const xml = xmlFilePath ?? this.xml;
118118
if (xml) {
119-
const contents = await this.tree.readFile(xml);
120-
return this.parse<T>(contents.toString());
119+
const contents = (await this.tree.readFile(xml)).toString();
120+
return this.parseAndValidateXML(contents, xml);
121121
}
122122
return {} as T;
123123
}
124124

125125
public parseXmlSync<T = JsonMap>(xmlFilePath?: string): T {
126126
const xml = xmlFilePath ?? this.xml;
127127
if (xml) {
128-
const contents = this.tree.readFileSync(xml);
129-
return this.parse<T>(contents.toString());
128+
const contents = this.tree.readFileSync(xml).toString();
129+
return this.parseAndValidateXML(contents, xml);
130130
}
131131
return {} as T;
132132
}
@@ -232,6 +232,24 @@ export class SourceComponent implements MetadataComponent {
232232
}
233233
}
234234

235+
private parseAndValidateXML<T = JsonMap>(contents: string, path: string): T {
236+
try {
237+
return this.parse<T>(contents);
238+
} catch (e) {
239+
// only attempt validating once there's an error to avoid the performance hit of validating every file
240+
const validation = validate(contents);
241+
if (validation !== true) {
242+
throw new LibraryError('invalid_xml_parsing', [
243+
path,
244+
validation.err.msg,
245+
validation.err.line.toString(),
246+
validation.err.code,
247+
]);
248+
}
249+
throw e;
250+
}
251+
}
252+
235253
private getDecomposedChildren(dirPath: string): SourceComponent[] {
236254
const children: SourceComponent[] = [];
237255
for (const fsPath of this.walk(dirPath)) {

test/resolve/sourceComponent.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,37 @@ describe('SourceComponent', () => {
117117
});
118118
});
119119

120+
it('should handle improperly formatted xml and throw a helpful message (async)', async () => {
121+
const component = COMPONENT;
122+
env
123+
.stub(component.tree, 'readFile')
124+
.resolves(Buffer.from('</CustomLabels xmlns="http://soap.sforce.com/2006/04/metadata"></CustomLabels>'));
125+
try {
126+
await component.parseXml();
127+
} catch (e) {
128+
expect(e.message).to.include(`error parsing ${component.xml} due to:`);
129+
expect(e.message).to.include("message: Closing tag 'CustomLabels' can't have attributes or invalid starting.");
130+
expect(e.message).to.include('line: 1');
131+
expect(e.message).to.include('code: InvalidTag');
132+
}
133+
});
134+
135+
it('should handle improperly formatted xml and throw a helpful message (sync)', async () => {
136+
const component = COMPONENT;
137+
env
138+
.stub(component.tree, 'readFile')
139+
.resolves(Buffer.from('</CustomLabels xmlns="http://soap.sforce.com/2006/04/metadata"></CustomLabels>'));
140+
141+
try {
142+
component.parseXmlSync();
143+
} catch (e) {
144+
expect(e.message).to.include(`error parsing ${component.xml} due to:`);
145+
expect(e.message).to.include("message: Closing tag 'CustomLabels' can't have attributes or invalid starting.");
146+
expect(e.message).to.include('line: 1');
147+
expect(e.message).to.include('code: InvalidTag');
148+
}
149+
});
150+
120151
it('should parse the child components xml content to js object', async () => {
121152
const component = new SourceComponent({
122153
name: 'nondecomposedchild',

0 commit comments

Comments
 (0)