Skip to content

Commit 8669ab9

Browse files
committed
refactor: simplify XML parser error handling by removing unnecessary MAX_FAILURES logic
- Remove circuit breaker pattern with MAX_FAILURES counter - Use immediate fallback for any parse error, not just addChild errors - Simplify error handling logic as retrying the same XML parse multiple times is unnecessary - Update tests to reflect the simplified error handling approach
1 parent 6f73937 commit 8669ab9

File tree

2 files changed

+27
-80
lines changed

2 files changed

+27
-80
lines changed

src/core/tools/__tests__/multiApplyDiffTool.test.ts

Lines changed: 9 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ describe("multiApplyDiffTool", () => {
155155
})
156156

157157
describe("Fallback parsing", () => {
158-
it("should use fallback parser after repeated failures", () => {
158+
it("should use fallback parser immediately on any failure", () => {
159159
const xml = `<args>
160160
<file>
161161
<path>test.txt</path>
@@ -166,14 +166,9 @@ describe("multiApplyDiffTool", () => {
166166
</file>
167167
</args>`
168168

169-
// Simulate multiple failures to trigger fallback
170-
let callCount = 0
169+
// Mock parseXml to simulate immediate fallback on any error
171170
vi.mocked(parseXml).mockImplementation(() => {
172-
callCount++
173-
if (callCount <= 3) {
174-
throw new Error("Cannot read properties of undefined (reading 'addChild')")
175-
}
176-
// After 3 failures, the fallback should be used
171+
// Fallback should be used immediately
177172
return {
178173
file: {
179174
path: "test.txt",
@@ -185,12 +180,7 @@ describe("multiApplyDiffTool", () => {
185180
}
186181
})
187182

188-
// First 3 calls should fail
189-
for (let i = 0; i < 3; i++) {
190-
expect(() => parseXml(xml)).toThrow()
191-
}
192-
193-
// Fourth call should succeed with fallback
183+
// First call should succeed with fallback
194184
const result = parseXml(xml) as any
195185
expect(result).toBeDefined()
196186
expect(result.file.path).toBe("test.txt")
@@ -246,28 +236,16 @@ describe("multiApplyDiffTool", () => {
246236
}
247237
})
248238

249-
it("should track parse failure count for circuit breaker", () => {
250-
// This tests the circuit breaker pattern
251-
let failureCount = 0
252-
const MAX_FAILURES = 3
253-
239+
it("should immediately use fallback on parse failure", () => {
240+
// This tests the immediate fallback pattern
254241
const simulateParseFailure = () => {
255-
failureCount++
256-
if (failureCount >= MAX_FAILURES) {
257-
// Should trigger fallback
258-
return "fallback_result"
259-
}
260-
throw new Error("Parse failed")
242+
// Should immediately trigger fallback on any error
243+
return "fallback_result"
261244
}
262245

263-
// First two failures
264-
expect(() => simulateParseFailure()).toThrow()
265-
expect(() => simulateParseFailure()).toThrow()
266-
267-
// Third failure triggers fallback
246+
// First failure immediately triggers fallback
268247
const result = simulateParseFailure()
269248
expect(result).toBe("fallback_result")
270-
expect(failureCount).toBe(3)
271249
})
272250
})
273251

src/utils/xml.ts

Lines changed: 18 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,14 @@
11
import { XMLParser } from "fast-xml-parser"
22

33
/**
4-
* Encapsulated XML parser with circuit breaker pattern
4+
* Encapsulated XML parser with fallback mechanism
55
*
66
* This dual-parser system handles interference from external XML parsers (like xml2js)
77
* that may be loaded globally by other VSCode extensions. When the primary parser
88
* (fast-xml-parser) fails due to external interference, it automatically falls back
99
* to a regex-based parser.
10-
*
11-
* Note: This parser instance should not be used concurrently as parseFailureCount
12-
* is not thread-safe. However, this is not an issue in practice since JavaScript
13-
* is single-threaded.
1410
*/
1511
class XmlParserWithFallback {
16-
private parseFailureCount = 0
17-
private readonly MAX_FAILURES = 3
1812
private readonly MAX_XML_SIZE = 10 * 1024 * 1024 // 10MB limit for fallback parser
1913

2014
/**
@@ -118,14 +112,7 @@ class XmlParserWithFallback {
118112
stopNodes: _stopNodes,
119113
})
120114

121-
const result = parser.parse(xmlString)
122-
123-
// Reset failure count on success
124-
if (this.parseFailureCount > 0) {
125-
this.parseFailureCount = 0
126-
}
127-
128-
return result
115+
return parser.parse(xmlString)
129116
} catch (error) {
130117
// Enhance error message for better debugging
131118
// Handle cases where error might not be an Error instance (e.g., strings, objects)
@@ -140,41 +127,23 @@ class XmlParserWithFallback {
140127
errorMessage = "Unknown error"
141128
}
142129

143-
// Check for xml2js specific error patterns - IMMEDIATELY use fallback
144-
if (errorMessage.includes("addChild")) {
145-
// Don't wait for multiple failures - use fallback immediately for addChild errors
146-
try {
147-
const result = this.fallbackXmlParse(xmlString)
148-
return result
149-
} catch (fallbackError) {
150-
const fallbackErrorMsg = fallbackError instanceof Error ? fallbackError.message : "Unknown error"
151-
// Still throw the error but make it clear we tried the fallback
152-
throw new Error(
153-
`XML parsing failed with fallback parser. Fallback parser also failed: ${fallbackErrorMsg}`,
154-
)
155-
}
130+
// Try fallback parser for any parsing error
131+
// This handles both xml2js interference (addChild errors) and other parse failures
132+
try {
133+
const result = this.fallbackXmlParse(xmlString)
134+
return result
135+
} catch (fallbackError) {
136+
const fallbackErrorMsg = fallbackError instanceof Error ? fallbackError.message : "Unknown error"
137+
// Provide context about which error was from xml2js interference
138+
const isXml2jsError = errorMessage.includes("addChild")
139+
const errorContext = isXml2jsError
140+
? "XML parsing failed due to external parser interference (xml2js)."
141+
: "XML parsing failed."
142+
143+
throw new Error(
144+
`${errorContext} Fallback parser also failed. Original: ${errorMessage}, Fallback: ${fallbackErrorMsg}`,
145+
)
156146
}
157-
158-
// For other errors, also consider using fallback after repeated failures
159-
this.parseFailureCount++
160-
161-
if (this.parseFailureCount >= this.MAX_FAILURES) {
162-
try {
163-
const result = this.fallbackXmlParse(xmlString)
164-
// Reset counter on successful fallback
165-
this.parseFailureCount = 0
166-
return result
167-
} catch (fallbackError) {
168-
// Reset counter after fallback attempt
169-
this.parseFailureCount = 0
170-
const fallbackErrorMsg = fallbackError instanceof Error ? fallbackError.message : "Unknown error"
171-
throw new Error(
172-
`XML parsing failed with both parsers. Original: ${errorMessage}, Fallback: ${fallbackErrorMsg}`,
173-
)
174-
}
175-
}
176-
177-
throw new Error(`Failed to parse XML: ${errorMessage}`)
178147
}
179148
}
180149
}

0 commit comments

Comments
 (0)