Skip to content

Commit 088ab12

Browse files
committed
Merge branch 'main' into add-ui-tests
2 parents d2aee57 + 610e45c commit 088ab12

File tree

9 files changed

+1322
-110
lines changed

9 files changed

+1322
-110
lines changed

CONTRIBUTING.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,4 +154,16 @@ ClientException with SocketException: Connection failed (OS Error: Operation not
154154

155155
You can read more [here](https://docs.flutter.dev/platform-integration/macos/building#setting-up-entitlements)
156156

157+
### Android (Work in Progress)
157158

159+
Add the `multiDexEnabled true` line to the `defaultConfig` section at `android/app/build.gradle file`
160+
161+
```
162+
android {
163+
...
164+
defaultConfig {
165+
...
166+
multiDexEnabled true
167+
}
168+
}
169+
```

lib/codegen/codegen.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'php/guzzle.dart';
99
import 'python/http_client.dart';
1010
import 'python/requests.dart';
1111
import 'rust/actix.dart';
12+
import 'rust/curl_rust.dart';
1213
import 'rust/reqwest.dart';
1314
import 'rust/ureq.dart';
1415
import 'js/axios.dart';
@@ -73,6 +74,8 @@ class Codegen {
7374
return PythonRequestsCodeGen().getCode(rM, boundary: boundary);
7475
case CodegenLanguage.rustActix:
7576
return RustActixCodeGen().getCode(rM, boundary: boundary);
77+
case CodegenLanguage.rustCurl:
78+
return RustCurlCodeGen().getCode(rM);
7679
case CodegenLanguage.rustReqwest:
7780
return RustReqwestCodeGen().getCode(rM);
7881
case CodegenLanguage.rustUreq:

lib/codegen/rust/curl_rust.dart

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import 'package:jinja/jinja.dart' as jj;
2+
import 'package:apidash/utils/utils.dart'
3+
show getValidRequestUri, requestModelToHARJsonRequest;
4+
import 'package:apidash/models/models.dart' show RequestModel;
5+
import 'package:apidash/consts.dart';
6+
7+
class RustCurlCodeGen {
8+
final String kTemplateStart = """use curl::easy::Easy;
9+
{% if hasJsonBody %}use serde_json::json;
10+
{% endif %}{% if hasHeaders %}use curl::easy::List;
11+
{% endif %}
12+
fn main() {
13+
let mut easy = Easy::new();
14+
let mut data = Vec::new();
15+
16+
""";
17+
18+
String kTemplateUrl = """
19+
easy.url("{{url}}").unwrap();
20+
""";
21+
22+
String kTemplateMethod = """
23+
{% if method == 'get' or method == 'post' or method == 'put' %}
24+
easy.{{ method }}(true).unwrap();
25+
{% elif method == 'delete' %}
26+
easy.custom_request("DELETE").unwrap();
27+
{% elif method == 'patch' %}
28+
easy.custom_request("PATCH").unwrap();
29+
{% elif method == 'head' %}
30+
easy.nobody(true).unwrap();
31+
{% endif %}
32+
33+
""";
34+
35+
String kTemplateRawBody = """
36+
easy.post_fields_copy(r#"{{body}}"#.as_bytes()).unwrap();
37+
38+
39+
""";
40+
41+
String kTemplateJsonBody = """
42+
easy.post_fields_copy(json!({{body}}).to_string().as_bytes()).unwrap();
43+
44+
45+
""";
46+
47+
String kTemplateFormData = """
48+
let mut form = curl::easy::Form::new();
49+
{% for field in fields %}
50+
form.part("{{field.name}}")
51+
{% if field.type == "file" %}.file("{{field.value}}"){% else %}.contents(b"{{field.value}}"){% endif %}
52+
.add().unwrap();
53+
{% endfor %}
54+
easy.httppost(form).unwrap();
55+
""";
56+
57+
String kTemplateHeader = """
58+
{% if headers %}let mut list = List::new();{% for header, value in headers %}
59+
list.append("{{header}}: {{value}}").unwrap();{% endfor %}
60+
easy.http_headers(list).unwrap();
61+
{% endif %}
62+
63+
""";
64+
65+
final String kTemplateEnd = """
66+
{
67+
let mut transfer = easy.transfer();
68+
transfer.write_function(|new_data| {
69+
data.extend_from_slice(new_data);
70+
Ok(new_data.len())
71+
}).unwrap();
72+
transfer.perform().unwrap();
73+
}
74+
75+
let response_body = String::from_utf8_lossy(&data);
76+
77+
println!("Response body: {}", response_body);
78+
println!("Response code: {}", easy.response_code().unwrap());
79+
}""";
80+
81+
String? getCode(RequestModel requestModel) {
82+
try {
83+
String result = "";
84+
var requestBody = requestModel.requestBody;
85+
86+
String url = requestModel.url;
87+
88+
result += jj.Template(kTemplateStart).render({
89+
"hasJsonBody": requestModel.hasJsonData,
90+
"hasHeaders": (requestModel.enabledRequestHeaders != null &&
91+
requestModel.enabledRequestHeaders!.isNotEmpty) ||
92+
(requestModel.hasJsonData || requestModel.hasTextData)
93+
});
94+
95+
var rec = getValidRequestUri(
96+
url,
97+
requestModel.enabledRequestParams,
98+
);
99+
100+
Uri? uri = rec.$1;
101+
var harJson =
102+
requestModelToHARJsonRequest(requestModel, useEnabled: true);
103+
104+
var templateUrl = jj.Template(kTemplateUrl);
105+
result += templateUrl.render({"url": harJson["url"]});
106+
107+
var methodTemplate = jj.Template(kTemplateMethod);
108+
result += methodTemplate.render({"method": requestModel.method.name});
109+
110+
if (uri != null) {
111+
if (requestModel.hasTextData) {
112+
var templateBody = jj.Template(kTemplateRawBody);
113+
result += templateBody.render({"body": requestBody});
114+
} else if (requestModel.hasJsonData) {
115+
var templateBody = jj.Template(kTemplateJsonBody);
116+
result += templateBody.render({"body": requestBody});
117+
} else if (requestModel.hasFormData) {
118+
var templateFormData = jj.Template(kTemplateFormData);
119+
result += templateFormData.render({
120+
"fields": requestModel.formDataMapList,
121+
});
122+
}
123+
124+
var headersList = requestModel.enabledRequestHeaders;
125+
if (headersList != null || requestModel.hasBody) {
126+
var headers = requestModel.enabledHeadersMap;
127+
if (requestModel.hasJsonData || requestModel.hasTextData) {
128+
headers.putIfAbsent(kHeaderContentType,
129+
() => requestModel.requestBodyContentType.header);
130+
}
131+
if (headers.isNotEmpty) {
132+
var templateHeader = jj.Template(kTemplateHeader);
133+
result += templateHeader.render({
134+
"headers": headers,
135+
});
136+
}
137+
}
138+
139+
result += kTemplateEnd;
140+
}
141+
142+
return result;
143+
} catch (e) {
144+
return null;
145+
}
146+
}
147+
}

lib/consts.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@ enum CodegenLanguage {
284284
pythonHttpClient("Python (http.client)", "python", "py"),
285285
rustActix("Rust (Actix Client)", "rust", "rs"),
286286
rustReqwest("Rust (reqwest)", "rust", "rs"),
287+
rustCurl("Rust (curl-rust)", "rust", "rs"),
287288
rustUreq("Rust (ureq)", "rust", "rs"),
288289
javaOkHttp("Java (okhttp3)", "java", 'java'),
289290
javaAsyncHttpClient("Java (asynchttpclient)", "java", "java"),

lib/screens/home_page/editor_pane/details_card/request_pane/request_form_data.dart

Lines changed: 55 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -17,48 +17,67 @@ class FormDataWidget extends ConsumerStatefulWidget {
1717
class _FormDataBodyState extends ConsumerState<FormDataWidget> {
1818
late int seed;
1919
final random = Random.secure();
20-
late List<FormDataModel> rows;
20+
late List<FormDataModel> formRows;
21+
2122
@override
2223
void initState() {
2324
super.initState();
2425
seed = random.nextInt(kRandMax);
2526
}
2627

28+
void _onFieldChange(String selectedId) {
29+
ref.read(collectionStateNotifierProvider.notifier).update(
30+
selectedId,
31+
requestFormDataList: formRows.sublist(0, formRows.length - 1),
32+
);
33+
}
34+
2735
@override
2836
Widget build(BuildContext context) {
2937
final selectedId = ref.watch(selectedIdStateProvider);
30-
var formRows = ref.read(selectedRequestModelProvider)?.requestFormDataList;
31-
rows =
32-
formRows == null || formRows.isEmpty ? [kFormDataEmptyModel] : formRows;
38+
ref.watch(selectedRequestModelProvider
39+
.select((value) => value?.requestFormDataList?.length));
40+
var rF = ref.read(selectedRequestModelProvider)?.requestFormDataList;
41+
bool isFormDataEmpty = rF == null || rF.isEmpty;
42+
List<FormDataModel> rows = (isFormDataEmpty)
43+
? [
44+
kFormDataEmptyModel,
45+
]
46+
: rF;
47+
formRows = isFormDataEmpty ? rows : rows + [kFormDataEmptyModel];
3348

3449
DaviModel<FormDataModel> daviModelRows = DaviModel<FormDataModel>(
35-
rows: rows,
50+
rows: formRows,
3651
columns: [
3752
DaviColumn(
3853
cellPadding: kpsV5,
3954
name: 'Key',
4055
grow: 4,
4156
cellBuilder: (_, row) {
4257
int idx = row.index;
58+
bool isLast = idx + 1 == formRows.length;
4359
return Theme(
4460
data: Theme.of(context),
4561
child: FormDataField(
4662
keyId: "$selectedId-$idx-form-v-$seed",
47-
initialValue: rows[idx].name,
63+
initialValue: formRows[idx].name,
4864
hintText: " Add Key",
4965
onChanged: (value) {
50-
rows[idx] = rows[idx].copyWith(
51-
name: value,
52-
);
66+
formRows[idx] = formRows[idx].copyWith(name: value);
67+
if (isLast) formRows.add(kFormDataEmptyModel);
5368
_onFieldChange(selectedId!);
5469
},
5570
colorScheme: Theme.of(context).colorScheme,
56-
formDataType: rows[idx].type,
71+
formDataType: formRows[idx].type,
5772
onFormDataTypeChanged: (value) {
58-
rows[idx] = rows[idx].copyWith(
73+
bool hasChanged = formRows[idx].type != value;
74+
formRows[idx] = formRows[idx].copyWith(
5975
type: value ?? FormDataType.text,
6076
);
61-
rows[idx] = rows[idx].copyWith(value: "");
77+
formRows[idx] = formRows[idx].copyWith(value: "");
78+
if (idx == formRows.length - 1 && hasChanged) {
79+
formRows.add(kFormDataEmptyModel);
80+
}
6281
setState(() {});
6382
_onFieldChange(selectedId!);
6483
},
@@ -84,7 +103,8 @@ class _FormDataBodyState extends ConsumerState<FormDataWidget> {
84103
cellPadding: kpsV5,
85104
cellBuilder: (_, row) {
86105
int idx = row.index;
87-
return rows[idx].type == FormDataType.file
106+
bool isLast = idx + 1 == formRows.length;
107+
return formRows[idx].type == FormDataType.file
88108
? Align(
89109
alignment: Alignment.centerLeft,
90110
child: Row(
@@ -109,17 +129,17 @@ class _FormDataBodyState extends ConsumerState<FormDataWidget> {
109129
if (pickedResult != null &&
110130
pickedResult.files.isNotEmpty &&
111131
pickedResult.files.first.path != null) {
112-
rows[idx] = rows[idx].copyWith(
132+
formRows[idx] = formRows[idx].copyWith(
113133
value: pickedResult.files.first.path!,
114134
);
115135
setState(() {});
116136
_onFieldChange(selectedId!);
117137
}
118138
},
119139
label: Text(
120-
(rows[idx].type == FormDataType.file &&
121-
rows[idx].value.isNotEmpty)
122-
? rows[idx].value.toString()
140+
(formRows[idx].type == FormDataType.file &&
141+
formRows[idx].value.isNotEmpty)
142+
? formRows[idx].value.toString()
123143
: "Select File",
124144
textAlign: TextAlign.center,
125145
overflow: TextOverflow.ellipsis,
@@ -133,10 +153,11 @@ class _FormDataBodyState extends ConsumerState<FormDataWidget> {
133153
)
134154
: CellField(
135155
keyId: "$selectedId-$idx-form-v-$seed",
136-
initialValue: rows[idx].value,
156+
initialValue: formRows[idx].value,
137157
hintText: " Add Value",
138158
onChanged: (value) {
139-
rows[idx] = rows[idx].copyWith(value: value);
159+
formRows[idx] = formRows[idx].copyWith(value: value);
160+
if (isLast) formRows.add(kFormDataEmptyModel);
140161
_onFieldChange(selectedId!);
141162
},
142163
colorScheme: Theme.of(context).colorScheme,
@@ -148,22 +169,24 @@ class _FormDataBodyState extends ConsumerState<FormDataWidget> {
148169
pinStatus: PinStatus.none,
149170
width: 30,
150171
cellBuilder: (_, row) {
172+
bool isLast = row.index + 1 == formRows.length;
151173
return InkWell(
174+
onTap: isLast
175+
? null
176+
: () {
177+
seed = random.nextInt(kRandMax);
178+
if (formRows.length == 2) {
179+
setState(() {
180+
formRows = [kFormDataEmptyModel];
181+
});
182+
} else {
183+
formRows.removeAt(row.index);
184+
}
185+
_onFieldChange(selectedId!);
186+
},
152187
child: Theme.of(context).brightness == Brightness.dark
153188
? kIconRemoveDark
154189
: kIconRemoveLight,
155-
onTap: () {
156-
seed = random.nextInt(kRandMax);
157-
if (rows.length == 1) {
158-
setState(() {
159-
rows = [kFormDataEmptyModel];
160-
});
161-
} else {
162-
rows.removeAt(row.index);
163-
}
164-
_onFieldChange(selectedId!);
165-
setState(() {});
166-
},
167190
);
168191
},
169192
),
@@ -194,9 +217,7 @@ class _FormDataBodyState extends ConsumerState<FormDataWidget> {
194217
padding: const EdgeInsets.only(bottom: 30),
195218
child: ElevatedButton.icon(
196219
onPressed: () {
197-
setState(() {
198-
rows.add(kFormDataEmptyModel);
199-
});
220+
formRows.add(kFormDataEmptyModel);
200221
_onFieldChange(selectedId!);
201222
},
202223
icon: const Icon(Icons.add),
@@ -210,11 +231,4 @@ class _FormDataBodyState extends ConsumerState<FormDataWidget> {
210231
],
211232
);
212233
}
213-
214-
void _onFieldChange(String selectedId) {
215-
ref.read(collectionStateNotifierProvider.notifier).update(
216-
selectedId,
217-
requestFormDataList: rows,
218-
);
219-
}
220234
}

0 commit comments

Comments
 (0)