diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 46a9b053..211eaf06 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -129,21 +129,28 @@ flutter test test/widgets/codegen_previewer_test.dart
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
+## Platform-specific Additional Instructions
-### Network Connection Issues on macOS
+### macOS
-If you encounter a network connection error similar to the following while running your Flutter app on macOS:
+Add below keys to `macos/Runner/DebugProfile.entitlements` and `macos/Runner/Release.entitlements`.
+
+```
+ com.apple.security.network.server
+
+ com.apple.security.network.client
+
+ com.apple.security.files.downloads.read-write
+
+ com.apple.security.files.user-selected.read-write
+
+```
+
+If not added, you can 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 c2760f5d..f6f73d62 100644
--- a/README.md
+++ b/README.md
@@ -123,19 +123,28 @@ API Dash can be downloaded from the links below:
API Dash currently supports API integration code generation for the following languages/libraries.
-| Language | Library |
-| ---------------------- | ------------- |
-| cURL | |
-| HAR | |
-| Dart | `http` |
-| JavaScript | `axios` |
-| JavaScript | `fetch` |
-| JavaScript (`node.js`) | `axios` |
-| JavaScript (`node.js`) | `fetch` |
-| Python | `http.client` |
-| Python | `requests` |
-| Kotlin | `okhttp3` |
-| Java | `okhttp3` |
+| Language | Library | Comment/Issues |
+| ---------------------- | ------------- | ------- |
+| cURL | | |
+| HAR | | |
+| Dart | `http` | |
+| Dart | `dio` | |
+| Go | `net/http` | |
+| JavaScript | `axios` | |
+| JavaScript | `fetch` | |
+| JavaScript (`node.js`) | `axios` | |
+| JavaScript (`node.js`) | `fetch` | |
+| Python | `requests` | |
+| Python | `http.client` | |
+| Kotlin | `okhttp3` | |
+| Rust | `reqwest` | |
+| Rust | `ureq` | |
+| Rust | `Actix Client` | |
+| Java | `asynchttpclient` | https://github.com/foss42/apidash/issues/136 |
+| Java | `HttpClient` | https://github.com/foss42/apidash/issues/137 |
+| Java | `okhttp3` | |
+| Julia | `HTTP` | https://github.com/foss42/apidash/issues/154 |
+| PHP | `guzzle` | https://github.com/foss42/apidash/issues/143 |
We welcome contributions to support other programming languages/libraries/frameworks. Please check out more details [here](https://github.com/foss42/apidash/discussions/80).
diff --git a/lib/codegen/codegen.dart b/lib/codegen/codegen.dart
index 5e093532..7c66b88f 100644
--- a/lib/codegen/codegen.dart
+++ b/lib/codegen/codegen.dart
@@ -5,6 +5,7 @@ import 'dart/http.dart';
import 'dart/dio.dart';
import 'go/http.dart';
import 'kotlin/okhttp.dart';
+import 'php/guzzle.dart';
import 'python/http_client.dart';
import 'python/requests.dart';
import 'rust/actix.dart';
@@ -17,6 +18,7 @@ import 'others/curl.dart';
import 'julia/http.dart';
import 'java/okhttp.dart';
import 'java/async_http_client.dart';
+import 'java/httpclient.dart';
class Codegen {
String? getCode(
@@ -44,6 +46,8 @@ class Codegen {
return DartHttpCodeGen().getCode(rM);
case CodegenLanguage.dartDio:
return DartDioCodeGen().getCode(rM);
+ case CodegenLanguage.goHttp:
+ return GoHttpCodeGen().getCode(rM);
case CodegenLanguage.jsAxios:
return AxiosCodeGen().getCode(rM);
case CodegenLanguage.jsFetch:
@@ -52,10 +56,16 @@ class Codegen {
return AxiosCodeGen(isNodeJs: true).getCode(rM);
case CodegenLanguage.nodejsFetch:
return FetchCodeGen(isNodeJs: true).getCode(rM);
+ case CodegenLanguage.javaAsyncHttpClient:
+ return JavaAsyncHttpClientGen().getCode(rM);
+ case CodegenLanguage.javaHttpClient:
+ return JavaHttpClientCodeGen().getCode(rM);
+ case CodegenLanguage.javaOkHttp:
+ return JavaOkHttpCodeGen().getCode(rM);
+ case CodegenLanguage.juliaHttp:
+ return JuliaHttpClientCodeGen().getCode(rM);
case CodegenLanguage.kotlinOkHttp:
return KotlinOkHttpCodeGen().getCode(rM);
- case CodegenLanguage.javaOkHttp:
- return JavaOkHttpCodeGen().getCode(rM);
case CodegenLanguage.pythonHttpClient:
return PythonHttpClientCodeGen()
.getCode(rM, boundary: boundary ?? getNewUuid());
@@ -67,12 +77,8 @@ class Codegen {
return RustReqwestCodeGen().getCode(rM);
case CodegenLanguage.rustUreq:
return RustUreqCodeGen().getCode(rM, boundary: boundary);
- case CodegenLanguage.goHttp:
- return GoHttpCodeGen().getCode(rM);
- case CodegenLanguage.juliaHttp:
- return JuliaHttpClientCodeGen().getCode(rM);
- case CodegenLanguage.javaAsyncHttpClient:
- return JavaAsyncHttpClientGen().getCode(rM);
+ case CodegenLanguage.phpGuzzle:
+ return PhpGuzzleCodeGen().getCode(rM);
}
}
}
diff --git a/lib/codegen/go/http.dart b/lib/codegen/go/http.dart
index 4bdecf48..a93d47e0 100644
--- a/lib/codegen/go/http.dart
+++ b/lib/codegen/go/http.dart
@@ -99,7 +99,7 @@ func main() {
var templateStart = jj.Template(kTemplateStart);
result += templateStart.render({
- "hasBody": requestModel.hasData,
+ "hasBody": requestModel.hasBody,
"hasFormData": requestModel.hasFormData,
"hasFileInFormData": requestModel.hasFileInFormData,
});
@@ -144,7 +144,7 @@ func main() {
});
var headersList = requestModel.enabledRequestHeaders;
- if (headersList != null || requestModel.hasData) {
+ if (headersList != null || requestModel.hasBody) {
var headers = requestModel.enabledHeadersMap;
if (requestModel.hasJsonData || requestModel.hasTextData) {
headers.putIfAbsent(kHeaderContentType,
diff --git a/lib/codegen/java/httpclient.dart b/lib/codegen/java/httpclient.dart
new file mode 100644
index 00000000..037a9a87
--- /dev/null
+++ b/lib/codegen/java/httpclient.dart
@@ -0,0 +1,183 @@
+import 'dart:convert';
+import 'package:jinja/jinja.dart' as jj;
+import 'package:apidash/utils/utils.dart'
+ show getValidRequestUri, requestModelToHARJsonRequest, stripUriParams;
+import '../../models/request_model.dart';
+import 'package:apidash/consts.dart';
+
+class JavaHttpClientCodeGen {
+ final String kTemplateStart = """
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpHeaders;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse.BodyHandlers;
+
+public class JavaHttpClientExample {
+
+ public static void main(String[] args) throws IOException, InterruptedException {
+ HttpClient client = HttpClient.newHttpClient();
+""";
+ final String kTemplateUrl = '''
+
+ String url = "{{url}}";
+
+''';
+
+ final String kTemplateUrlQuery = '''
+
+ String url = "{{url}}";
+ try {
+ URI uri = new URI(url);
+ url = uri.resolve("{{params}}").toString();
+ } catch (URISyntaxException e) {
+ e.printStackTrace();
+ }
+
+''';
+
+ String kTemplateRequestBody = '''
+
+ String body = "{{body}}";
+
+''';
+
+ final String kStringRequestStart = """
+
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create(url))
+""";
+
+ final String kTemplateRequestEnd = """
+ .{{method}}({{body}})
+ .build();
+
+ HttpResponse response = client.send(request, BodyHandlers.ofString());
+ System.out.println(response.statusCode());
+ System.out.println(response.body());
+ }
+}
+\n
+""";
+
+ String? getCode(
+ RequestModel requestModel,
+ ) {
+ try {
+ String result = "";
+ bool hasQuery = false;
+ bool hasBody = false;
+ bool hasJsonBody = false;
+
+ var rec = getValidRequestUri(
+ requestModel.url,
+ requestModel.enabledRequestParams,
+ );
+ Uri? uri = rec.$1;
+
+ if (uri != null) {
+ String url = stripUriParams(uri);
+
+ if (uri.hasQuery) {
+ var params = uri.queryParameters;
+ if (params.isNotEmpty) {
+ hasQuery = true;
+ var templateParams = jj.Template(kTemplateUrlQuery);
+ result += templateParams.render({"url": url, "params": uri.query});
+ }
+ }
+
+ if (!hasQuery) {
+ var templateUrl = jj.Template(kTemplateUrl);
+ result += templateUrl.render({"url": url});
+ }
+ var rM = requestModel.copyWith(url: url);
+
+ var harJson = requestModelToHARJsonRequest(rM, useEnabled: true);
+
+ var method = requestModel.method;
+ var requestBody = requestModel.requestBody;
+ if (requestModel.hasFormData &&
+ requestModel.formDataMapList.isNotEmpty &&
+ kMethodsWithBody.contains(method)) {
+ var formDataList = requestModel.formDataMapList;
+ result += """
+ StringBuilder formData = new StringBuilder();
+ formData.append(""";
+
+ for (var formDataMap in formDataList) {
+ result += '"""${formDataMap['name']}=${formDataMap['value']}&""",';
+ }
+
+ result = result.substring(0, result.length - 1);
+ result += ");\n";
+ hasBody = true;
+ } else if (kMethodsWithBody.contains(method) && requestBody != null) {
+ var contentLength = utf8.encode(requestBody).length;
+ if (contentLength > 0) {
+ var templateBody = jj.Template(kTemplateRequestBody);
+ hasBody = true;
+ hasJsonBody =
+ requestBody.startsWith("{") && requestBody.endsWith("}");
+ if (harJson["postData"]?["text"] != null) {
+ result += templateBody.render({
+ "body": kEncoder.convert(harJson["postData"]["text"]).substring(
+ 1, kEncoder.convert(harJson["postData"]["text"]).length - 1)
+ });
+ }
+ }
+ }
+
+ result = kTemplateStart + result;
+ result += kStringRequestStart;
+
+ var headersList = requestModel.enabledRequestHeaders;
+ var contentType = requestModel.requestBodyContentType.header;
+ if (hasBody &&
+ !requestModel.enabledHeadersMap.containsKey('Content-Type')) {
+ result =
+ """$result .header("Content-Type", "$contentType")\n""";
+ }
+ if (headersList != null) {
+ var headers = requestModel.enabledHeadersMap;
+ if (headers.isNotEmpty) {
+ result += getHeaders(headers, hasJsonBody);
+ }
+ }
+
+ var templateRequestEnd = jj.Template(kTemplateRequestEnd);
+
+ if (kMethodsWithBody.contains(method)) {
+ result += templateRequestEnd.render({
+ "method": method.name.toUpperCase(),
+ "body": hasBody
+ ? "BodyPublishers.ofString(body)"
+ : "BodyPublishers.noBody()"
+ });
+ } else {
+ result += templateRequestEnd
+ .render({"method": method.name.toUpperCase(), "body": ""});
+ }
+ }
+ return result;
+ } catch (e) {
+ return null;
+ }
+ }
+
+ String getHeaders(Map headers, hasJsonBody) {
+ String result = "";
+ for (final k in headers.keys) {
+ if (k.toLowerCase() == 'authorization') {
+ result = """$result .header("$k", "${headers[k]}")\n""";
+ } else {
+ result = """$result .header("$k", "${headers[k]}")\n""";
+ }
+ }
+ return result;
+ }
+}
diff --git a/lib/codegen/js/axios.dart b/lib/codegen/js/axios.dart
index 71456d6e..301ecf46 100644
--- a/lib/codegen/js/axios.dart
+++ b/lib/codegen/js/axios.dart
@@ -1,4 +1,3 @@
-import 'dart:convert';
import 'package:jinja/jinja.dart' as jj;
import 'package:apidash/utils/utils.dart'
show padMultilineString, requestModelToHARJsonRequest, stripUrlParams;
@@ -10,12 +9,14 @@ class AxiosCodeGen {
final bool isNodeJs;
- String kStringImportNode = """{% if isNodeJs %}import axios from 'axios';
+ String kStringImportNode = """import axios from 'axios';
+{%if hasFileInFormData -%}
+import fs from 'fs'
+{% endif %}
-{% endif %}{% if hasFormData and isNodeJs %}const fs = require('fs');{% endif %}
""";
- String kTemplateStart = """let config = {
+ String kTemplateStart = """const config = {
url: '{{url}}',
method: '{{method}}'
""";
@@ -37,59 +38,28 @@ class AxiosCodeGen {
};
axios(config)
- .then(function (response) {
- // handle success
- console.log(response.status);
- console.log(response.data);
- })
- .catch(function (error) {
- // handle error
- console.log(error.response.status);
- console.log(error);
- });
+ .then(res => {
+ console.log(res.status);
+ console.log(res.data);
+ })
+ .catch(err => {
+ console.log(err);
+ });
""";
- String kMultiPartBodyTemplate = r'''
-
-
-async function buildFormData(fields) {
- var formdata = new FormData();
- for (const field of fields) {
- const name = field.name || '';
- const value = field.value || '';
- const type = field.type || 'text';
-
- if (type === 'text') {
- formdata.append(name, value);
- } else if (type === 'file') {
- formdata.append(name,{% if isNodeJs %} fs.createReadStream(value){% else %} fileInput.files[0],value{% endif %});
- }
- }
- return formdata;
-}
-
-
-''';
- var kGetFormDataTemplate = '''buildFormData({{fields_list}});
-''';
String? getCode(
RequestModel requestModel,
) {
try {
jj.Template kNodejsImportTemplate = jj.Template(kStringImportNode);
String importsData = kNodejsImportTemplate.render({
- "hasFormData": requestModel.hasFormData,
- "isNodeJs": isNodeJs,
+ "hasFileInFormData": requestModel.hasFileInFormData,
});
- String result = importsData;
- if (requestModel.hasFormData && requestModel.formDataMapList.isNotEmpty) {
- var templateMultiPartBody = jj.Template(kMultiPartBodyTemplate);
- var renderedMultiPartBody = templateMultiPartBody.render({
- "isNodeJs": isNodeJs,
- });
- result += renderedMultiPartBody;
- }
-
+ String result = isNodeJs
+ ? importsData
+ : requestModel.hasFileInFormData
+ ? "// refer https://github.com/foss42/apidash/issues/293#issuecomment-1997568083 for details regarding integration\n\n"
+ : "";
var harJson = requestModelToHARJsonRequest(
requestModel,
useEnabled: true,
@@ -126,17 +96,22 @@ async function buildFormData(fields) {
.render({"headers": padMultilineString(kEncoder.convert(m), 2)});
}
var templateBody = jj.Template(kTemplateBody);
-
if (requestModel.hasFormData && requestModel.formDataMapList.isNotEmpty) {
- var getFieldDataTemplate = jj.Template(kGetFormDataTemplate);
-
- result += templateBody.render({
- "body": getFieldDataTemplate.render({
- "fields_list": json.encode(requestModel.formDataMapList),
- })
- });
- }
- if (harJson["postData"]?["text"] != null) {
+ // Manually Create a JS Object
+ Map formParams = {};
+ int formFileCounter = 1;
+ for (var element in requestModel.formDataMapList) {
+ formParams["${element["name"]}"] = element["type"] == "text"
+ ? "${element["value"]}"
+ : isNodeJs
+ ? "fs.createReadStream(${element["value"]})"
+ : "fileInput$formFileCounter.files[0]";
+ if (element["type"] == "file") formFileCounter++;
+ }
+ var sanitizedJSObject = sanitzeJSObject(kEncoder.convert(formParams));
+ result += templateBody
+ .render({"body": padMultilineString(sanitizedJSObject, 2)});
+ } else if (harJson["postData"]?["text"] != null) {
result += templateBody
.render({"body": kEncoder.convert(harJson["postData"]["text"])});
}
@@ -146,4 +121,18 @@ async function buildFormData(fields) {
return null;
}
}
+
+ // escape function and variables in JS Object
+ String sanitzeJSObject(String jsObject) {
+ RegExp pattern = isNodeJs
+ ? RegExp(r'"fs\.createReadStream\((.*?)\)"')
+ : RegExp(r'"fileInput(\d+)\.files\[0\]"');
+
+ var sanitizedJSObject = jsObject.replaceAllMapped(pattern, (match) {
+ return isNodeJs
+ ? 'fs.createReadStream("${match.group(1)}")'
+ : 'fileInput${match.group(1)}.files[0]';
+ });
+ return sanitizedJSObject;
+ }
}
diff --git a/lib/codegen/kotlin/okhttp.dart b/lib/codegen/kotlin/okhttp.dart
index c717dc97..a3a2fb89 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}}{{importForFormData}}
+import okhttp3.Request{{importForQuery}}{{importForBody}}{{importForFormData}}{{importForFile}}
fun main() {
val client = OkHttpClient()
@@ -27,6 +27,12 @@ import okhttp3.MediaType.Companion.toMediaType""";
import okhttp3.MultipartBody""";
+ final String kStringImportForFile = """
+
+import java.io.File
+import okhttp3.RequestBody.Companion.asRequestBody
+import okhttp3.MediaType.Companion.toMediaType""";
+
final String kTemplateUrl = '''
val url = "{{url}}"
@@ -68,7 +74,7 @@ import okhttp3.MultipartBody""";
// Converting list of form data objects to kolin multi part data
String kFormDataBody = '''
val body = MultipartBody.Builder().setType(MultipartBody.FORM){% for item in formDataList %}{% if item.type == 'file' %}
- .addFormDataPart("{{item.name}}",null,File("{{item.value}}").asRequestBody("application/octet-stream".toMediaType()))
+ .addFormDataPart("{{item.name}}",File("{{item.value}}").name,File("{{item.value}}").asRequestBody("application/octet-stream".toMediaType()))
{% else %}.addFormDataPart("{{item.name}}","{{item.value}}")
{% endif %}{% endfor %}.build()
''';
@@ -81,6 +87,7 @@ import okhttp3.MultipartBody""";
bool hasQuery = false;
bool hasBody = false;
bool hasFormData = false;
+ bool hasFile = false;
var rec = getValidRequestUri(
requestModel.url,
@@ -111,8 +118,34 @@ import okhttp3.MultipartBody""";
hasFormData = true;
var formDataTemplate = jj.Template(kFormDataBody);
+ List