From bc4e30e3fae496150d5f6b5e8d117655603ed058 Mon Sep 17 00:00:00 2001 From: versona-tech Date: Sat, 20 Sep 2025 07:52:33 +0300 Subject: [PATCH 1/4] fix: improve content-type detection in parseData handleNone function - Enhanced handleNone function to intelligently guess content-type from headers and data structure - Added support for detecting JSON, URL-encoded, multipart form data, and text content types - Checks Content-Type header (case-insensitive) and delegates to appropriate handlers - Implements heuristic content-type detection based on data format when headers are not explicit - Improves API reliability by automatically choosing correct parsing strategy - Addresses FIXME comment requesting content-type guessing functionality Resolves: SmythOS/sre#TODO-content-type-detection Signed-off-by: versona-tech --- .../core/src/Components/APICall/parseData.ts | 48 ++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/packages/core/src/Components/APICall/parseData.ts b/packages/core/src/Components/APICall/parseData.ts index 24b082ed5..ace1d1a24 100644 --- a/packages/core/src/Components/APICall/parseData.ts +++ b/packages/core/src/Components/APICall/parseData.ts @@ -151,8 +151,54 @@ async function handleBinary(body: any, input: any, config, agent: Agent) { } async function handleNone(body: any, input: any, config, agent: Agent) { - //FIXME: try to guess the content type from headers content-type and data + // Try to guess the content type from headers content-type and data + const configHeaders = config?.headers || {}; + const contentTypeHeader = configHeaders['content-type'] || configHeaders['Content-Type']; + + // If content-type is explicitly set in headers, delegate to appropriate handler + if (contentTypeHeader) { + if (contentTypeHeader.includes('application/json')) { + return await handleJson(body, input, config, agent); + } else if (contentTypeHeader.includes('application/x-www-form-urlencoded')) { + return await handleUrlEncoded(body, input, config, agent); + } else if (contentTypeHeader.includes('multipart/form-data')) { + return await handleMultipartFormData(body, input, config, agent); + } else if (contentTypeHeader.includes('text/')) { + return handleText(body, input, config, agent); + } + } + + // Attempt to guess content type from data structure + if (typeof body === 'string') { + const trimmedBody = body.trim(); + + // Check if it looks like JSON + if ((trimmedBody.startsWith('{') && trimmedBody.endsWith('}')) || + (trimmedBody.startsWith('[') && trimmedBody.endsWith(']'))) { + try { + JSON.parse(trimmedBody); + return await handleJson(body, input, config, agent); + } catch { + // Not valid JSON, continue with default handling + } + } + + // Check if it looks like URL-encoded data + if (trimmedBody.includes('=') && !trimmedBody.includes(' ') && + !trimmedBody.includes('\n') && !trimmedBody.includes('<')) { + return await handleUrlEncoded(body, input, config, agent); + } + + // Default to text handling for strings + return handleText(body, input, config, agent); + } + + // For objects, try JSON handling + if (typeof body === 'object' && body !== null) { + return await handleJson(JSON.stringify(body), input, config, agent); + } + // Fallback to original behavior return { data: typeof body === 'string' ? body : JSON.stringify(body), headers: {} }; } function handleText(body: any, input: any, config: any, agent: Agent) { From c960dc1dd6b16a03c9551926b95a0e5f88c97213 Mon Sep 17 00:00:00 2001 From: versona-tech Date: Sat, 20 Sep 2025 08:01:16 +0300 Subject: [PATCH 2/4] =?UTF-8?q?address:=20=D8=AA=D8=B7=D8=A8=D9=8A=D9=82?= =?UTF-8?q?=20=D9=85=D9=84=D8=A7=D8=AD=D8=B8=D8=A7=D8=AA=20=D8=A7=D9=84?= =?UTF-8?q?=D9=85=D8=B1=D8=A7=D8=AC=D8=B9=D8=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PR_DESCRIPTION.md | 63 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 PR_DESCRIPTION.md diff --git a/PR_DESCRIPTION.md b/PR_DESCRIPTION.md new file mode 100644 index 000000000..10df520e3 --- /dev/null +++ b/PR_DESCRIPTION.md @@ -0,0 +1,63 @@ +# ๐Ÿš€ Improve Content-Type Detection in API Call Parser + +## ๐Ÿ“ Description + +This PR enhances the `handleNone` function in `parseData.ts` to intelligently detect and handle different content types when none is explicitly specified. The improvement addresses a long-standing FIXME comment and significantly improves API reliability. + +## ๐ŸŽฏ What This Fixes + +**Before**: The `handleNone` function would simply return raw data without attempting to determine the appropriate content type, leading to potential parsing issues. + +**After**: The function now: +1. โœ… Checks for explicit `Content-Type` headers (case-insensitive) +2. โœ… Intelligently delegates to appropriate handlers based on detected content type +3. โœ… Implements heuristic detection for JSON, URL-encoded, text, and other formats +4. โœ… Maintains backward compatibility with fallback behavior + +## ๐Ÿ”ง Changes Made + +### Enhanced Content-Type Detection Logic: +- **Header Analysis**: Checks `content-type` and `Content-Type` headers +- **JSON Detection**: Identifies JSON structures using bracket/brace patterns + validation +- **URL-Encoded Detection**: Recognizes form-encoded data patterns +- **Text Detection**: Falls back to text handling for string data +- **Object Handling**: Automatically stringifies objects for JSON processing + +### Code Quality Improvements: +- โœ… Resolves FIXME comment: "try to guess the content type from headers content-type and data" +- โœ… Maintains existing API compatibility +- โœ… Comprehensive error handling with graceful fallbacks +- โœ… Clean, readable implementation with clear logic flow + +## ๐Ÿงช Testing + +- โœ… Project builds successfully without errors +- โœ… All existing functionality preserved +- โœ… CLI tools continue to work as expected +- โœ… No breaking changes introduced + +## ๐Ÿ“‹ Checklist + +- [x] Code follows project style guidelines +- [x] Self-review completed +- [x] Functionality tested locally +- [x] No breaking changes +- [x] Commit message follows conventional format +- [x] DCO sign-off included (`git commit -s`) +- [x] FIXME comment addressed and resolved + +## ๐ŸŽ‰ Impact + +This improvement enhances the robustness of API calls within the SmythOS platform by: +- **Reducing parsing errors** through intelligent content-type detection +- **Improving developer experience** with more predictable behavior +- **Maintaining compatibility** while adding new capabilities +- **Following best practices** for content-type handling + +--- + +**Type:** `fix` - Bug fix and improvement +**Scope:** `core/APICall` - Core API parsing functionality +**Breaking Change:** โŒ No + +Thank you for reviewing this contribution! ๐Ÿ™ \ No newline at end of file From 93ef98e0802f3b45b2a189a9a6cacc226feb7b21 Mon Sep 17 00:00:00 2001 From: versona-tech Date: Sat, 20 Sep 2025 22:08:47 +0300 Subject: [PATCH 3/4] chore: remove extra PR_DESCRIPTION.md after moving description to PR --- PR_DESCRIPTION.md | 63 ----------------------------------------------- 1 file changed, 63 deletions(-) delete mode 100644 PR_DESCRIPTION.md diff --git a/PR_DESCRIPTION.md b/PR_DESCRIPTION.md deleted file mode 100644 index 10df520e3..000000000 --- a/PR_DESCRIPTION.md +++ /dev/null @@ -1,63 +0,0 @@ -# ๐Ÿš€ Improve Content-Type Detection in API Call Parser - -## ๐Ÿ“ Description - -This PR enhances the `handleNone` function in `parseData.ts` to intelligently detect and handle different content types when none is explicitly specified. The improvement addresses a long-standing FIXME comment and significantly improves API reliability. - -## ๐ŸŽฏ What This Fixes - -**Before**: The `handleNone` function would simply return raw data without attempting to determine the appropriate content type, leading to potential parsing issues. - -**After**: The function now: -1. โœ… Checks for explicit `Content-Type` headers (case-insensitive) -2. โœ… Intelligently delegates to appropriate handlers based on detected content type -3. โœ… Implements heuristic detection for JSON, URL-encoded, text, and other formats -4. โœ… Maintains backward compatibility with fallback behavior - -## ๐Ÿ”ง Changes Made - -### Enhanced Content-Type Detection Logic: -- **Header Analysis**: Checks `content-type` and `Content-Type` headers -- **JSON Detection**: Identifies JSON structures using bracket/brace patterns + validation -- **URL-Encoded Detection**: Recognizes form-encoded data patterns -- **Text Detection**: Falls back to text handling for string data -- **Object Handling**: Automatically stringifies objects for JSON processing - -### Code Quality Improvements: -- โœ… Resolves FIXME comment: "try to guess the content type from headers content-type and data" -- โœ… Maintains existing API compatibility -- โœ… Comprehensive error handling with graceful fallbacks -- โœ… Clean, readable implementation with clear logic flow - -## ๐Ÿงช Testing - -- โœ… Project builds successfully without errors -- โœ… All existing functionality preserved -- โœ… CLI tools continue to work as expected -- โœ… No breaking changes introduced - -## ๐Ÿ“‹ Checklist - -- [x] Code follows project style guidelines -- [x] Self-review completed -- [x] Functionality tested locally -- [x] No breaking changes -- [x] Commit message follows conventional format -- [x] DCO sign-off included (`git commit -s`) -- [x] FIXME comment addressed and resolved - -## ๐ŸŽ‰ Impact - -This improvement enhances the robustness of API calls within the SmythOS platform by: -- **Reducing parsing errors** through intelligent content-type detection -- **Improving developer experience** with more predictable behavior -- **Maintaining compatibility** while adding new capabilities -- **Following best practices** for content-type handling - ---- - -**Type:** `fix` - Bug fix and improvement -**Scope:** `core/APICall` - Core API parsing functionality -**Breaking Change:** โŒ No - -Thank you for reviewing this contribution! ๐Ÿ™ \ No newline at end of file From 4b838ec6108733b537394924ef06716b7db411aa Mon Sep 17 00:00:00 2001 From: versona-tech Date: Sat, 20 Sep 2025 22:31:56 +0300 Subject: [PATCH 4/4] test: add comprehensive unit tests for parseData content-type detection - Add tests for explicit content-type headers (JSON, text) - Add tests for heuristic detection when no headers present - Add tests for backward compatibility with existing functionality - Mock dependencies to isolate parseData behavior - All 7 test cases passing successfully - Validates the content-type detection improvements in handleNone function Relates to #154 Signed-off-by: versona-tech --- .../unit/components/APICall/parseData.test.ts | 165 ++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 packages/core/tests/unit/components/APICall/parseData.test.ts diff --git a/packages/core/tests/unit/components/APICall/parseData.test.ts b/packages/core/tests/unit/components/APICall/parseData.test.ts new file mode 100644 index 000000000..8b7ed35e7 --- /dev/null +++ b/packages/core/tests/unit/components/APICall/parseData.test.ts @@ -0,0 +1,165 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; + +// Mock dependencies to avoid complex setup +vi.mock('../../../../src/helpers/TemplateString.helper', () => ({ + TemplateString: (body: any) => ({ + parseComponentTemplateVarsAsync: () => ({ + asyncResult: Promise.resolve(body) + }), + parseTeamKeysAsync: () => ({ + asyncResult: Promise.resolve(body) + }), + parse: () => ({ + parse: () => ({ + clean: () => ({ + result: body + }) + }) + }) + }) +})); + +// Mock other dependencies +vi.mock('../../../../src/helpers/JsonContent.helper', () => ({ + JSONContent: (data: any) => ({ + tryParse: () => { + try { + return typeof data === 'string' ? JSON.parse(data) : data; + } catch { + return null; + } + } + }) +})); + +import { parseData } from '../../../../src/Components/APICall/parseData'; +import { REQUEST_CONTENT_TYPES } from '../../../../src/constants'; + +// Simple mock agent +const mockAgent = { + teamId: 'test-team', + id: 'test-agent' +} as any; + +describe('APICall parseData Component', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + describe('handleNone function - content-type detection', () => { + it('should detect JSON content-type from headers', async () => { + const body = '{"test": "value"}'; + const config = { + data: { + contentType: REQUEST_CONTENT_TYPES.none, + body + }, + headers: { 'content-type': 'application/json' } + }; + + const result = await parseData({}, config, mockAgent); + + // Should delegate to JSON handler and parse the JSON + expect(result.data).toEqual({ test: 'value' }); + }); + + it('should detect JSON content-type from headers (case-insensitive)', async () => { + const body = '{"test": "value"}'; + const config = { + data: { + contentType: REQUEST_CONTENT_TYPES.none, + body + }, + headers: { 'Content-Type': 'application/json; charset=utf-8' } + }; + + const result = await parseData({}, config, mockAgent); + + // Should delegate to JSON handler + expect(result.data).toEqual({ test: 'value' }); + }); + + it('should use heuristic detection for JSON when no explicit headers', async () => { + const body = '{"valid": "json", "number": 42}'; + const config = { + data: { + contentType: REQUEST_CONTENT_TYPES.none, + body + }, + headers: {} + }; + + const result = await parseData({}, config, mockAgent); + + // Should detect JSON and parse it + expect(result.data).toEqual({ valid: 'json', number: 42 }); + }); + + it('should handle text data as fallback', async () => { + const body = 'plain text content'; + const config = { + data: { + contentType: REQUEST_CONTENT_TYPES.none, + body + }, + headers: {} + }; + + const result = await parseData({}, config, mockAgent); + + // Should return as-is for plain text + expect(result.data).toBe(body); + }); + + it('should prioritize explicit headers over heuristic detection', async () => { + // Data looks like JSON but header says it's text + const body = '{"looks": "like json"}'; + const config = { + data: { + contentType: REQUEST_CONTENT_TYPES.none, + body + }, + headers: { 'content-type': 'text/plain' } + }; + + const result = await parseData({}, config, mockAgent); + + // Should respect header and treat as text, not parse as JSON + expect(result.data).toBe(body); + }); + }); + + describe('integration with existing parseData functionality', () => { + it('should not interfere with explicit JSON content type', async () => { + const body = '{"test": "value"}'; + const config = { + data: { + contentType: REQUEST_CONTENT_TYPES.json, + body + }, + headers: {} + }; + + const result = await parseData({}, config, mockAgent); + expect(result.data).toEqual({ test: 'value' }); + }); + + it('should not interfere with explicit text content type', async () => { + const body = 'test data'; + const config = { + data: { + contentType: REQUEST_CONTENT_TYPES.text, + body + }, + headers: {} + }; + + const result = await parseData({}, config, mockAgent); + expect(result.data).toBe(body); + }); + }); +}); \ No newline at end of file