Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 40 additions & 2 deletions lib/dashbot/services/openapi_import_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, dynamic>;

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<dynamic> 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<String, dynamic> _payloadForOperation({
Expand Down
80 changes: 80 additions & 0 deletions test/dashbot/services/openapi_import_service_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down