diff --git a/lib/consts.dart b/lib/consts.dart index 10ae65ea9..fd1c5bb49 100644 --- a/lib/consts.dart +++ b/lib/consts.dart @@ -143,7 +143,8 @@ enum CodegenLanguage { enum ImportFormat { curl("cURL"), postman("Postman Collection v2.1"), - insomnia("Insomnia v4"); + insomnia("Insomnia v4"), + openapi("OpenAPI v3.1"); const ImportFormat(this.label); final String label; diff --git a/lib/importer/importer.dart b/lib/importer/importer.dart index 4d93d9bec..d92676290 100644 --- a/lib/importer/importer.dart +++ b/lib/importer/importer.dart @@ -13,6 +13,7 @@ class Importer { .toList(), ImportFormat.postman => PostmanIO().getHttpRequestModelList(content), ImportFormat.insomnia => InsomniaIO().getHttpRequestModelList(content), + ImportFormat.openapi => OpenApiIO().getHttpRequestModelList(content), }; } } diff --git a/packages/apidash_core/lib/import_export/import_export.dart b/packages/apidash_core/lib/import_export/import_export.dart index 33762e50c..0ac1cc2c6 100644 --- a/packages/apidash_core/lib/import_export/import_export.dart +++ b/packages/apidash_core/lib/import_export/import_export.dart @@ -1,3 +1,4 @@ export 'curl_io.dart'; export 'postman_io.dart'; export 'insomnia_io.dart'; +export 'openapi_io.dart'; diff --git a/packages/apidash_core/lib/import_export/openapi_io.dart b/packages/apidash_core/lib/import_export/openapi_io.dart new file mode 100644 index 000000000..3a1249a6d --- /dev/null +++ b/packages/apidash_core/lib/import_export/openapi_io.dart @@ -0,0 +1,146 @@ +import 'package:openapi_spec/openapi_spec.dart'; +import '../consts.dart'; +import '../models/models.dart'; +import '../utils/utils.dart'; +import 'package:seed/seed.dart'; + +class OpenApiIO { + (String?, HttpRequestModel) oasOperationToHttpRequestModel(String url, Operation? operation, String methodName, OpenApi oas) { + HTTPVerb method = HTTPVerb.values.byName((methodName)); + List parameters = operation?.parameters ?? []; + List params = []; + List isParamEnabledList = []; + Map? res = operation?.responses ?? oas.components?.responses; + List isHeaderEnabledList = []; + List headers = []; + RequestBody? requestBody = operation?.requestBody; + Map? content = requestBody?.content; + ContentType bodyContentType = ContentType.json; + List formData = []; + String body = ""; + + + // parameters + // FIX: https://github.com/tazatechnology/openapi_spec/issues/76 + for (var param in parameters) { + var name = param.name ?? ""; + var value = param.example ?? ""; + var activeQuery = param.deprecated ?? true; + params.add(NameValueModel(name: name, value: value)); + isParamEnabledList.add(activeQuery); + } + + // headers + // FIX: https://github.com/tazatechnology/openapi_spec/issues/76 + res?.map((status, response) { + return MapEntry( + response.headers?.map((headerName, headerValue) { + isHeaderEnabledList.add(headerValue != null ? true : false); + headers.add(NameValueModel(name: headerName, value: headerValue)); + return const MapEntry(null, null); + }), + null + ); + }); + + + // RequestBody TODO: + content?.map((contentTypeStr, mediaType) { + if (mediaType.schema?.ref != null) { + var schemaas = oas.components?.schemas; + var referencedSchemaDefinition = schemaas?[mediaType.schema?.ref]; + var properties = referencedSchemaDefinition?.toJson()['properties']; + var bodyContent = properties.map((field, inputDetails) { + // bodyContentType = getContentTypeFromContentTypeStr(contentTypeStr) ?? kDefaultContentType; + // FIX: https://github.com/foss42/apidash/issues/771 + // TODO: reqBody content defines references to json && xml && formdata; suggestion - implement a bodyContentGenerator(); + // Note: body could be a NameValueModel so json && xml && formdata can be shown on APIDASH + bodyContentType = kDefaultContentType; + return MapEntry(field, inputDetails['example']); + }); + body = bodyContent.toString(); + } else if (mediaType.examples != null) { + mediaType.examples?.map((str, value) { + body = value.value.toString(); + bodyContentType = getContentTypeFromContentTypeStr(contentTypeStr) ?? kDefaultContentType; + // TODO: more than one example in openapi spec + return const MapEntry(null, null); + }); + } else { + body = ""; + } + + // FIX: + if (bodyContentType == ContentType.formdata) { + var name = mediaType.schema?.title ?? ""; + FormDataType formDataType; + try { + formDataType = FormDataType.values.byName(mediaType.schema?.runtimeType.toString() ?? ""); + } catch (e) { + formDataType = FormDataType.text; + } + var value = switch (formDataType) { + FormDataType.text => mediaType.schema.toString() ?? "", + FormDataType.file => mediaType.schema.toString() ?? "" + }; + formData.add(FormDataModel( + name: name, + value: value, + type: formDataType + )); + } + return const MapEntry(null, null); + }); + + return (operation?.id, HttpRequestModel( + method: method, + url: url, + params: params, + isParamEnabledList: isParamEnabledList, + isHeaderEnabledList: isHeaderEnabledList, + headers: headers, + body: body, + bodyContentType: bodyContentType, + formData: formData + )); + } + + List<(String?, HttpRequestModel)>? getHttpRequestModelList(String content) { + content = content.trim(); + try { + + const format = OpenApiFormat.yaml; + final oas = OpenApi.fromString(source: content, format: format); + final oasPaths = oas.paths ?? oas.components?.pathItems; + List<(String?, HttpRequestModel)> httpRequestModelList = []; + + // Note: Schemas could be defined directly in the reqbody or in the components or maybe both + // when schema is defined only in the components a reference to the scheme present in the components is provided + oasPaths?.map((url, pathItem) { + if (pathItem.get != null) { + httpRequestModelList.add(oasOperationToHttpRequestModel(url, pathItem.get, "get", oas)); + } + if (pathItem.post != null) { + httpRequestModelList.add(oasOperationToHttpRequestModel(url, pathItem.post, "post", oas)); + } + if (pathItem.put != null) { + httpRequestModelList.add(oasOperationToHttpRequestModel(url, pathItem.put, "put", oas)); + } + if (pathItem.delete != null) { + httpRequestModelList.add(oasOperationToHttpRequestModel(url, pathItem.delete, "delete", oas)); + } + if (pathItem.patch != null) { + httpRequestModelList.add(oasOperationToHttpRequestModel(url, pathItem.patch, "patch", oas)); + } + if (pathItem.head != null) { + httpRequestModelList.add(oasOperationToHttpRequestModel(url, pathItem.head, "head", oas)); + } + return const MapEntry(null, null); + }); + return httpRequestModelList; + } catch (e) { + print("$e"); + return null; + } + } +} diff --git a/packages/apidash_core/pubspec.yaml b/packages/apidash_core/pubspec.yaml index 13aaea079..786c9ab99 100644 --- a/packages/apidash_core/pubspec.yaml +++ b/packages/apidash_core/pubspec.yaml @@ -23,6 +23,9 @@ dependencies: path: ../postman seed: ^0.0.3 xml: ^6.3.0 + # currently checkedout tags/v0.7.24 + openapi_spec: + path: ../openapi_spec dev_dependencies: flutter_test: diff --git a/packages/apidash_core/pubspec_overrides.yaml b/packages/apidash_core/pubspec_overrides.yaml index 61081fc76..8bad0c308 100644 --- a/packages/apidash_core/pubspec_overrides.yaml +++ b/packages/apidash_core/pubspec_overrides.yaml @@ -1,10 +1,12 @@ # melos_managed_dependency_overrides: curl_parser,insomnia_collection,postman,seed dependency_overrides: curl_parser: - path: ../curl_parser + path: ..\\curl_parser insomnia_collection: - path: ../insomnia_collection + path: ..\\insomnia_collection postman: - path: ../postman + path: ..\\postman seed: - path: ../seed + path: ..\\seed + openapi_spec: + path: ..\\openapi_spec diff --git a/packages/openapi_spec b/packages/openapi_spec new file mode 160000 index 000000000..ee320961c --- /dev/null +++ b/packages/openapi_spec @@ -0,0 +1 @@ +Subproject commit ee320961c7a7e398f594c473ac39fcb102baaa3f diff --git a/pubspec.lock b/pubspec.lock index 9881558db..e5ce4017d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -223,6 +223,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.0" + cli_completion: + dependency: transitive + description: + name: cli_completion + sha256: "158deec74a75cdc69bce061645fea08f94190dd6833f988f517c2dfcb45e9117" + url: "https://pub.dev" + source: hosted + version: "0.5.0" cli_launcher: dependency: transitive description: @@ -937,6 +945,14 @@ packages: url: "https://pub.dev" source: hosted version: "7.3.0" + mason_logger: + dependency: transitive + description: + name: mason_logger + sha256: "1fdf5c76870eb6fc3611ed6fbae1973a3794abe581ea5e22e68af2f73c688b93" + url: "https://pub.dev" + source: hosted + version: "0.2.16" matcher: dependency: transitive description: @@ -1048,6 +1064,13 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.2+1" + openapi_spec: + dependency: transitive + description: + path: "packages/openapi_spec" + relative: true + source: path + version: "0.7.24" package_config: dependency: transitive description: @@ -1263,6 +1286,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.2" + recase: + dependency: transitive + description: + name: recase + sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213 + url: "https://pub.dev" + source: hosted + version: "4.1.0" riverpod: dependency: "direct main" description: