diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 00000000..04873dba
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,18 @@
+## PR Description
+
+_Add your description_
+
+## Related Issues
+
+- Related Issue #
+- Closes #
+
+### Checklist
+- [ ] I have gone through the [contributing guide](https://github.com/foss42/apidash/blob/main/CONTRIBUTING.md)
+- [ ] I have run the tests (`flutter test`) and all tests are passing
+
+## Added/updated tests?
+_We encourage you to add relevant test cases._
+
+- [ ] Yes
+- [ ] No, and this is why: _please replace this line with details on why tests have not been included_
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 140188ac..46a9b053 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -83,7 +83,7 @@ In case you have already setup Flutter, make sure to switch to `stable` branch a
1. Fork the project.
2. Create a clone of the forked project on your computer to run it locally.
3. Based on your desktop environment, enable Windows, macOS or Linux for the project. Select the same target device.
-4. This project uses [Records feature in Dart](https://github.com/dart-lang/language/blob/main/accepted/future-releases/records/records-feature-specification.md), so to run the project execute the following command:
+4. Run the project by executing the following command:
```
flutter run
@@ -128,3 +128,23 @@ flutter test test/widgets/codegen_previewer_test.dart
### How to add a new package to pubspec.yaml?
Instead of copy pasting from pub.dev, it is recommended that you use `flutter pub add package_name` to add a new package to `pubspec.yaml`. You can read more [here](https://docs.flutter.dev/packages-and-plugins/using-packages#adding-a-package-dependency-to-an-app-using-flutter-pub-add).
+
+## Troubleshooting Common Issues
+
+### Network Connection Issues on macOS
+
+If you encounter a network connection error similar to the following while running your Flutter app on macOS:
+
+```
+ClientException with SocketException: Connection failed (OS Error: Operation not permitted, errno = 1)
+```
+Add below key to `macos/Runner/DebugProfile.entitlements` and `macos/Runner/Release.entitlements`.
+
+```
+ com.apple.security.network.client
+
+```
+
+You can read more [here](https://docs.flutter.dev/platform-integration/macos/building#setting-up-entitlements)
+
+
diff --git a/README.md b/README.md
index 15dc46e1..d8b258e5 100644
--- a/README.md
+++ b/README.md
@@ -137,6 +137,7 @@ Here is the complete list of mimetypes that can be directly previewed in API Das
| File Type | Mimetype | Extension | Comment |
| --------- | -------------------------- | ----------------- | -------- |
| PDF | `application/pdf` | `.pdf` | |
+| CSV | `text/csv` | `.csv` | Can be improved |
| Image | `image/apng` | `.apng` | Animated |
| Image | `image/avif` | `.avif` | |
| Image | `image/bmp` | `.bmp` | |
@@ -177,14 +178,14 @@ Here is the complete list of mimetypes that are syntax highlighted in API Dash:
| ------------------ | --------- | ------------------------------------------------------------------------------------------------------------------ |
| `application/json` | `.json` | Other mimetypes like `application/geo+json`, `application/vcard+json` that are based on `json` are also supported. |
| `application/xml` | `.xml` | Other mimetypes like `application/xhtml+xml`, `application/vcard+xml` that are based on `xml` are also supported. |
-| `text/xml` | `.xml` | |
-| `application/yaml` | `.yaml` | Others - `application/x-yaml` or `application/x-yml` |
-| `text/yaml` | `.yaml` | Others - `text/yml` |
-| `application/sql` | `.sql` | |
-| `text/css` | `.css` | |
-| `text/html` | `.html` | Only syntax highlighting, no web preview. |
-| `text/javascript` | `.js` | |
-| `text/markdown` | `.md` | |
+| `text/xml` | `.xml` | |
+| `application/yaml` | `.yaml` | Others - `application/x-yaml` or `application/x-yml` |
+| `text/yaml` | `.yaml` | Others - `text/yml` |
+| `application/sql` | `.sql` | |
+| `text/css` | `.css` | |
+| `text/html` | `.html` | Only syntax highlighting, no web preview. |
+| `text/javascript` | `.js` | |
+| `text/markdown` | `.md` | |
## What's new in v0.3.0?
diff --git a/analysis_options.yaml b/analysis_options.yaml
index 1d5eafa4..9a1eabb4 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -3,8 +3,9 @@ include: package:flutter_lints/flutter.yaml
analyzer:
errors:
invalid_annotation_target: ignore
- enable-experiment:
- - records
+ exclude:
+ - "**/*.freezed.dart"
+ - "**/*.g.dart"
linter:
rules:
diff --git a/lib/codegen/codegen.dart b/lib/codegen/codegen.dart
index 2fb2f7ba..8061dc88 100644
--- a/lib/codegen/codegen.dart
+++ b/lib/codegen/codegen.dart
@@ -1,6 +1,7 @@
import 'package:apidash/codegen/rust/ureq.dart';
import 'package:apidash/models/models.dart' show RequestModel;
import 'package:apidash/consts.dart';
+import 'package:apidash/utils/utils.dart' show getNewUuid;
import 'dart/http.dart';
import 'dart/dio.dart';
import 'kotlin/okhttp.dart';
@@ -15,36 +16,45 @@ class Codegen {
String? getCode(
CodegenLanguage codegenLanguage,
RequestModel requestModel,
- String defaultUriScheme,
- ) {
+ String defaultUriScheme, {
+ String? boundary,
+ }) {
+ String url = requestModel.url;
+
+ if (url.isEmpty) {
+ url = kDefaultUri;
+ }
+ if (!url.contains("://") && url.isNotEmpty) {
+ url = "$defaultUriScheme://$url";
+ }
+ var rM = requestModel.copyWith(url: url);
+
switch (codegenLanguage) {
case CodegenLanguage.curl:
- return cURLCodeGen().getCode(requestModel, defaultUriScheme);
+ return cURLCodeGen().getCode(rM);
case CodegenLanguage.har:
- return HARCodeGen().getCode(requestModel, defaultUriScheme);
+ return HARCodeGen().getCode(rM, defaultUriScheme, boundary: boundary);
case CodegenLanguage.dartHttp:
- return DartHttpCodeGen().getCode(requestModel, defaultUriScheme);
+ return DartHttpCodeGen().getCode(rM);
case CodegenLanguage.dartDio:
- return DartDioCodeGen().getCode(requestModel, defaultUriScheme);
+ return DartDioCodeGen().getCode(rM);
case CodegenLanguage.jsAxios:
- return AxiosCodeGen().getCode(requestModel, defaultUriScheme);
+ return AxiosCodeGen().getCode(rM);
case CodegenLanguage.jsFetch:
- return FetchCodeGen().getCode(requestModel, defaultUriScheme);
+ return FetchCodeGen().getCode(rM);
case CodegenLanguage.nodejsAxios:
- return AxiosCodeGen(isNodeJs: true)
- .getCode(requestModel, defaultUriScheme);
+ return AxiosCodeGen(isNodeJs: true).getCode(rM);
case CodegenLanguage.nodejsFetch:
- return FetchCodeGen(isNodeJs: true)
- .getCode(requestModel, defaultUriScheme);
+ return FetchCodeGen(isNodeJs: true).getCode(rM);
case CodegenLanguage.kotlinOkHttp:
- return KotlinOkHttpCodeGen().getCode(requestModel, defaultUriScheme);
+ return KotlinOkHttpCodeGen().getCode(rM);
case CodegenLanguage.pythonHttpClient:
return PythonHttpClientCodeGen()
- .getCode(requestModel, defaultUriScheme);
+ .getCode(rM, boundary: boundary ?? getNewUuid());
case CodegenLanguage.pythonRequests:
- return PythonRequestsCodeGen().getCode(requestModel, defaultUriScheme);
+ return PythonRequestsCodeGen().getCode(rM, boundary: boundary);
case CodegenLanguage.rustUreq:
- return RustUreqCodeGen().getCode(requestModel, defaultUriScheme);
+ return RustUreqCodeGen().getCode(rM, boundary: boundary);
}
}
}
diff --git a/lib/codegen/codegen_utils.dart b/lib/codegen/codegen_utils.dart
new file mode 100644
index 00000000..2d7a1846
--- /dev/null
+++ b/lib/codegen/codegen_utils.dart
@@ -0,0 +1,15 @@
+String jsonToPyDict(String jsonString) {
+ Map replaceWithMap = {
+ "null": "None",
+ "true": "True",
+ "false": "False"
+ };
+ String pyDict = jsonString;
+ for (var k in replaceWithMap.keys) {
+ RegExp regExp = RegExp(k + r'(?=([^"]*"[^"]*")*[^"]*$)');
+ pyDict = pyDict.replaceAllMapped(regExp, (match) {
+ return replaceWithMap[match.group(0)] ?? match.group(0)!;
+ });
+ }
+ return pyDict;
+}
diff --git a/lib/codegen/dart/dio.dart b/lib/codegen/dart/dio.dart
index c4a7ed8e..c05b0b68 100644
--- a/lib/codegen/dart/dio.dart
+++ b/lib/codegen/dart/dio.dart
@@ -8,15 +8,10 @@ import 'shared.dart';
class DartDioCodeGen {
String? getCode(
RequestModel requestModel,
- String defaultUriScheme,
) {
try {
- String url = requestModel.url;
- if (!url.contains("://") && url.isNotEmpty) {
- url = "$defaultUriScheme://$url";
- }
final next = generatedDartCode(
- url: url,
+ url: requestModel.url,
method: requestModel.method,
queryParams: requestModel.enabledParamsMap,
headers: requestModel.enabledHeadersMap,
diff --git a/lib/codegen/dart/http.dart b/lib/codegen/dart/http.dart
index 3e423743..89e8062c 100644
--- a/lib/codegen/dart/http.dart
+++ b/lib/codegen/dart/http.dart
@@ -9,15 +9,10 @@ import 'shared.dart';
class DartHttpCodeGen {
String? getCode(
RequestModel requestModel,
- String defaultUriScheme,
) {
try {
- String url = requestModel.url;
- if (!url.contains("://") && url.isNotEmpty) {
- url = "$defaultUriScheme://$url";
- }
final next = generatedDartCode(
- url: url,
+ url: requestModel.url,
method: requestModel.method,
queryParams: requestModel.enabledParamsMap,
headers: {...requestModel.enabledHeadersMap},
@@ -53,7 +48,9 @@ class DartHttpCodeGen {
declareVar('uri').assign(refer('Uri.parse').call([literalString(url)]));
Expression? dataExp;
- if (kMethodsWithBody.contains(method) && (body?.isNotEmpty ?? false)) {
+ if (kMethodsWithBody.contains(method) &&
+ (body?.isNotEmpty ?? false) &&
+ contentType != ContentType.formdata) {
final strContent = CodeExpression(Code('r\'\'\'$body\'\'\''));
dataExp = declareVar('body', type: refer('String')).assign(strContent);
if (!hasContentTypeHeader) {
diff --git a/lib/codegen/js/axios.dart b/lib/codegen/js/axios.dart
index 1aeee285..71456d6e 100644
--- a/lib/codegen/js/axios.dart
+++ b/lib/codegen/js/axios.dart
@@ -12,7 +12,7 @@ class AxiosCodeGen {
String kStringImportNode = """{% if isNodeJs %}import axios from 'axios';
-{% endif %}{% if isFormDataRequest and isNodeJs %}const fs = require('fs');{% endif %}
+{% endif %}{% if hasFormData and isNodeJs %}const fs = require('fs');{% endif %}
""";
String kTemplateStart = """let config = {
@@ -73,18 +73,16 @@ async function buildFormData(fields) {
''';
String? getCode(
RequestModel requestModel,
- String defaultUriScheme,
) {
try {
jj.Template kNodejsImportTemplate = jj.Template(kStringImportNode);
String importsData = kNodejsImportTemplate.render({
- "isFormDataRequest": requestModel.isFormDataRequest,
+ "hasFormData": requestModel.hasFormData,
"isNodeJs": isNodeJs,
});
String result = importsData;
- if (requestModel.isFormDataRequest &&
- requestModel.formDataMapList.isNotEmpty) {
+ if (requestModel.hasFormData && requestModel.formDataMapList.isNotEmpty) {
var templateMultiPartBody = jj.Template(kMultiPartBodyTemplate);
var renderedMultiPartBody = templateMultiPartBody.render({
"isNodeJs": isNodeJs,
@@ -92,17 +90,14 @@ async function buildFormData(fields) {
result += renderedMultiPartBody;
}
- String url = requestModel.url;
- if (!url.contains("://") && url.isNotEmpty) {
- url = "$defaultUriScheme://$url";
- }
- var rM = requestModel.copyWith(url: url);
-
- var harJson = requestModelToHARJsonRequest(rM, useEnabled: true);
+ var harJson = requestModelToHARJsonRequest(
+ requestModel,
+ useEnabled: true,
+ );
var templateStart = jj.Template(kTemplateStart);
result += templateStart.render({
- "url": stripUrlParams(url),
+ "url": stripUrlParams(requestModel.url),
"method": harJson["method"].toLowerCase(),
});
@@ -118,22 +113,21 @@ async function buildFormData(fields) {
}
var headers = harJson["headers"];
- if (headers.isNotEmpty || requestModel.isFormDataRequest) {
+ if (headers.isNotEmpty || requestModel.hasFormData) {
var templateHeader = jj.Template(kTemplateHeader);
var m = {};
for (var i in headers) {
m[i["name"]] = i["value"];
}
- if (requestModel.isFormDataRequest) {
- m['Content-Type'] = 'multipart/form-data';
+ if (requestModel.hasFormData) {
+ m[kHeaderContentType] = 'multipart/form-data';
}
result += templateHeader
.render({"headers": padMultilineString(kEncoder.convert(m), 2)});
}
var templateBody = jj.Template(kTemplateBody);
- if (requestModel.isFormDataRequest &&
- requestModel.formDataMapList.isNotEmpty) {
+ if (requestModel.hasFormData && requestModel.formDataMapList.isNotEmpty) {
var getFieldDataTemplate = jj.Template(kGetFormDataTemplate);
result += templateBody.render({
diff --git a/lib/codegen/js/fetch.dart b/lib/codegen/js/fetch.dart
index 4ef2e551..2ff88a9b 100644
--- a/lib/codegen/js/fetch.dart
+++ b/lib/codegen/js/fetch.dart
@@ -12,7 +12,7 @@ class FetchCodeGen {
String kStringImportNode = """
import fetch from 'node-fetch';
-{% if isFormDataRequest %}const fs = require('fs');{% endif %}
+{% if hasFormData %}const fs = require('fs');{% endif %}
""";
@@ -73,29 +73,26 @@ fetch(url, options)
String? getCode(
RequestModel requestModel,
- String defaultUriScheme,
) {
try {
jj.Template kNodejsImportTemplate = jj.Template(kStringImportNode);
String importsData = kNodejsImportTemplate.render({
- "isFormDataRequest": requestModel.isFormDataRequest,
+ "hasFormData": requestModel.hasFormData,
});
String result = isNodeJs ? importsData : "";
- if (requestModel.isFormDataRequest) {
+ if (requestModel.hasFormData) {
var templateMultiPartBody = jj.Template(kMultiPartBodyTemplate);
result += templateMultiPartBody.render({
"isNodeJs": isNodeJs,
"fields_list": json.encode(requestModel.formDataMapList),
});
}
- String url = requestModel.url;
- if (!url.contains("://") && url.isNotEmpty) {
- url = "$defaultUriScheme://$url";
- }
- var rM = requestModel.copyWith(url: url);
- var harJson = requestModelToHARJsonRequest(rM, useEnabled: true);
+ var harJson = requestModelToHARJsonRequest(
+ requestModel,
+ useEnabled: true,
+ );
var templateStart = jj.Template(kTemplateStart);
result += templateStart.render({
@@ -108,8 +105,8 @@ fetch(url, options)
if (headers.isNotEmpty) {
var templateHeader = jj.Template(kTemplateHeader);
var m = {};
- if (requestModel.isFormDataRequest) {
- m["Content-Type"] = "multipart/form-data";
+ if (requestModel.hasFormData) {
+ m[kHeaderContentType] = "multipart/form-data";
}
for (var i in headers) {
m[i["name"]] = i["value"];
@@ -124,7 +121,7 @@ fetch(url, options)
result += templateBody.render({
"body": kEncoder.convert(harJson["postData"]["text"]),
});
- } else if (requestModel.isFormDataRequest) {
+ } else if (requestModel.hasFormData) {
var templateBody = jj.Template(kTemplateBody);
result += templateBody.render({
"body": 'payload',
diff --git a/lib/codegen/kotlin/okhttp.dart b/lib/codegen/kotlin/okhttp.dart
index 07e5a92b..c717dc97 100644
--- a/lib/codegen/kotlin/okhttp.dart
+++ b/lib/codegen/kotlin/okhttp.dart
@@ -7,7 +7,7 @@ import 'package:apidash/consts.dart';
class KotlinOkHttpCodeGen {
final String kTemplateStart = """import okhttp3.OkHttpClient
-import okhttp3.Request{{importForQuery}}{{importForBody}}
+import okhttp3.Request{{importForQuery}}{{importForBody}}{{importForFormData}}
fun main() {
val client = OkHttpClient()
@@ -23,6 +23,10 @@ import okhttp3.HttpUrl.Companion.toHttpUrl""";
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.MediaType.Companion.toMediaType""";
+ final String kStringImportForFormData = """
+
+import okhttp3.MultipartBody""";
+
final String kTemplateUrl = '''
val url = "{{url}}"
@@ -71,20 +75,15 @@ import okhttp3.MediaType.Companion.toMediaType""";
String? getCode(
RequestModel requestModel,
- String defaultUriScheme,
) {
try {
String result = "";
bool hasQuery = false;
bool hasBody = false;
-
- String url = requestModel.url;
- if (!url.contains("://") && url.isNotEmpty) {
- url = "$defaultUriScheme://$url";
- }
+ bool hasFormData = false;
var rec = getValidRequestUri(
- url,
+ requestModel.url,
requestModel.enabledRequestParams,
);
Uri? uri = rec.$1;
@@ -108,7 +107,8 @@ import okhttp3.MediaType.Companion.toMediaType""";
var method = requestModel.method;
var requestBody = requestModel.requestBody;
- if (requestModel.isFormDataRequest) {
+ if (requestModel.hasFormData) {
+ hasFormData = true;
var formDataTemplate = jj.Template(kFormDataBody);
result += formDataTemplate.render({
@@ -128,7 +128,8 @@ import okhttp3.MediaType.Companion.toMediaType""";
var templateStart = jj.Template(kTemplateStart);
var stringStart = templateStart.render({
"importForQuery": hasQuery ? kStringImportForQuery : "",
- "importForBody": hasBody ? kStringImportForBody : ""
+ "importForBody": hasBody ? kStringImportForBody : "",
+ "importForFormData": hasFormData ? kStringImportForFormData : ""
});
result = stringStart + result;
@@ -145,7 +146,7 @@ import okhttp3.MediaType.Companion.toMediaType""";
var templateRequestEnd = jj.Template(kTemplateRequestEnd);
result += templateRequestEnd.render({
"method": method.name.toLowerCase(),
- "hasBody": (hasBody || requestModel.isFormDataRequest) ? "body" : "",
+ "hasBody": (hasBody || requestModel.hasFormData) ? "body" : "",
});
}
return result;
diff --git a/lib/codegen/others/curl.dart b/lib/codegen/others/curl.dart
index 4ae610de..2ba59765 100644
--- a/lib/codegen/others/curl.dart
+++ b/lib/codegen/others/curl.dart
@@ -1,6 +1,7 @@
import 'package:jinja/jinja.dart' as jj;
import 'package:apidash/utils/utils.dart' show requestModelToHARJsonRequest;
import 'package:apidash/models/models.dart' show RequestModel;
+import 'package:apidash/consts.dart';
// ignore: camel_case_types
class cURLCodeGen {
@@ -11,7 +12,7 @@ class cURLCodeGen {
--header '{{name}}: {{value}}'
""";
String kTemplateFormData = """ \\
- --form '{{name}}: {{value}}'
+ --form '{{name}}={{value}}'
""";
String kTemplateBody = """ \\
@@ -20,25 +21,21 @@ class cURLCodeGen {
String? getCode(
RequestModel requestModel,
- String defaultUriScheme,
) {
try {
String result = "";
- String url = requestModel.url;
- if (!url.contains("://") && url.isNotEmpty) {
- url = "$defaultUriScheme://$url";
- }
- var rM = requestModel.copyWith(url: url);
-
- var harJson = requestModelToHARJsonRequest(rM, useEnabled: true);
+ var harJson = requestModelToHARJsonRequest(
+ requestModel,
+ useEnabled: true,
+ );
var templateStart = jj.Template(kTemplateStart);
result += templateStart.render({
"method": switch (harJson["method"]) {
"GET" => "",
"HEAD" => " --head",
- _ => " --request ${harJson["method"]} \\\n"
+ _ => " --request ${harJson["method"]} \\\n "
},
"url": harJson["url"],
});
@@ -46,33 +43,31 @@ class cURLCodeGen {
var headers = harJson["headers"];
if (headers.isNotEmpty) {
for (var item in headers) {
+ if (requestModel.hasFormData && item["name"] == kHeaderContentType) {
+ continue;
+ }
var templateHeader = jj.Template(kTemplateHeader);
result += templateHeader
.render({"name": item["name"], "value": item["value"]});
}
}
- if (harJson['formData'] != null) {
- var formDataList = harJson['formData'] as List