From 465234477d196affeeade4f89ee23fe5a8bc529c Mon Sep 17 00:00:00 2001 From: Udhay-Adithya Date: Thu, 2 Oct 2025 03:49:26 +0530 Subject: [PATCH] fix: handle security fields with empty arrays --- .../services/openapi_import_service.dart | 42 +++++++++- .../services/openapi_import_service_test.dart | 80 +++++++++++++++++++ 2 files changed, 120 insertions(+), 2 deletions(-) diff --git a/lib/dashbot/services/openapi_import_service.dart b/lib/dashbot/services/openapi_import_service.dart index 954f649d1..b5bcc90b5 100644 --- a/lib/dashbot/services/openapi_import_service.dart +++ b/lib/dashbot/services/openapi_import_service.dart @@ -132,15 +132,53 @@ class OpenApiImportService { /// Try to parse a JSON or YAML OpenAPI spec string. /// Returns null if parsing fails. + /// + /// NOTE: There's a known issue with the openapi_spec package where + /// security fields containing empty arrays (e.g., "security": [[]]) + /// cause parsing failures. This method includes a workaround. static OpenApi? tryParseSpec(String source) { try { - // Let the library infer JSON/YAML return OpenApi.fromString(source: source, format: null); - } catch (_) { + } catch (e) { + // Try workaround for security field parsing issues + try { + final processedSource = _removeProblematicSecurityField(source); + if (processedSource != source) { + return OpenApi.fromString(source: processedSource, format: null); + } + } catch (_) { + // Workaround failed, fall through to return null + } return null; } } + /// Removes problematic security fields that cause parsing issues. + /// TODO: Remove this workaround once openapi_spec package fixes + /// the issue with security fields containing empty arrays. + static String _removeProblematicSecurityField(String source) { + try { + final spec = jsonDecode(source) as Map; + + if (spec.containsKey('security')) { + final security = spec['security']; + if (security is List && _hasEmptySecurityArrays(security)) { + spec.remove('security'); + return jsonEncode(spec); + } + } + + return source; + } catch (e) { + throw FormatException('Failed to preprocess OpenAPI spec: $e'); + } + } + + /// Checks if security list contains empty arrays that cause parsing issues. + static bool _hasEmptySecurityArrays(List security) { + return security.any((item) => item is List && item.isEmpty); + } + /// Build a single request payload from a path + method operation. /// The payload mirrors CurlImportService payload shape for reuse. static Map _payloadForOperation({ diff --git a/test/dashbot/services/openapi_import_service_test.dart b/test/dashbot/services/openapi_import_service_test.dart index 918f7fa3c..6dfdb699a 100644 --- a/test/dashbot/services/openapi_import_service_test.dart +++ b/test/dashbot/services/openapi_import_service_test.dart @@ -31,6 +31,86 @@ void main() { expect(summary, contains('POST')); }); + test('tryParseSpec handles problematic security field with empty arrays', + () { + const specWithEmptySecurityArray = ''' +{ + "openapi": "3.0.0", + "info": { + "title": "Cat Fact API", + "version": "1.0.0" + }, + "paths": { + "/fact": { + "get": { + "responses": { + "200": { + "description": "Success" + } + } + } + } + }, + "security": [[]] +}'''; + + final result = + OpenApiImportService.tryParseSpec(specWithEmptySecurityArray); + expect(result, isNotNull); + expect(result!.info.title, equals('Cat Fact API')); + expect(result.info.version, equals('1.0.0')); + expect(result.paths, isNotNull); + expect(result.paths!.keys, contains('/fact')); + }); + + test('tryParseSpec handles valid security field with actual requirements', + () { + const specWithRealSecurity = ''' +{ + "openapi": "3.0.0", + "info": { + "title": "Secured API", + "version": "1.0.0" + }, + "paths": { + "/secured": { + "get": { + "responses": { + "200": { + "description": "Success" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ] +}'''; + + final result = OpenApiImportService.tryParseSpec(specWithRealSecurity); + expect(result, isNotNull); + expect(result!.info.title, equals('Secured API')); + }); + + test('tryParseSpec returns null for invalid JSON', () { + const invalidSpec = 'not valid json'; + final result = OpenApiImportService.tryParseSpec(invalidSpec); + expect(result, isNull); + }); + + test('tryParseSpec returns null for non-OpenAPI JSON', () { + const nonOpenApiSpec = ''' +{ + "notOpenApi": true, + "someField": "value" +}'''; + final result = OpenApiImportService.tryParseSpec(nonOpenApiSpec); + expect(result, isNull); + }); + test('extractSpecMeta includes endpoints & baseUrl', () { final spec = OpenApiImportService.tryParseSpec(_specJson)!; final meta = OpenApiImportService.extractSpecMeta(spec);