Merge pull request #53 from mmjsmohit/add-feature-codegendropdown

Adding a dropdown for language(library) selection & Kotlin codegen
This commit is contained in:
Ankit Mahato
2023-06-10 08:55:19 +00:00
committed by GitHub
9 changed files with 357 additions and 6 deletions

View File

@ -1 +1,22 @@
export 'dart/pkg_http.dart'; import 'package:apidash/codegen/kotlin/pkg_okhttp.dart';
import 'package:apidash/consts.dart';
import 'package:apidash/models/models.dart' show RequestModel;
import 'dart/pkg_http.dart';
class Codegen {
String? getCode(
CodegenLanguage codegenLanguage,
RequestModel requestModel,
String defaultUriScheme,
) {
switch (codegenLanguage) {
case CodegenLanguage.dartHttp:
return DartHttpCodeGen().getCode(requestModel, defaultUriScheme);
case CodegenLanguage.kotlinOkHttp:
return KotlinOkHttpCodeGen().getCode(requestModel);
default:
throw ArgumentError('Invalid codegenLanguage');
}
}
}

View File

@ -0,0 +1,80 @@
import 'package:apidash/consts.dart';
import '../../models/request_model.dart';
class KotlinOkHttpCodeGen {
final String headerSnippet = """import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MultipartBody
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.RequestBody.Companion.asRequestBody
import java.io.File
import java.util.concurrent.TimeUnit
val client = OkHttpClient()
""";
final String footerSnippet = """ .build()
val response = client.newCall(request).execute()
println(response.body!!.string())
""";
String getCode(RequestModel requestModel) {
String result = "";
result = result + headerSnippet;
if (requestModel.method != HTTPVerb.get &&
requestModel.method != HTTPVerb.head) {
result =
"""${result}val mediaType = "${requestModel.requestBodyContentType == ContentType.json ? "application/json" : "text/plain"}".toMediaType()
val body = "${requestModel.requestBody}".toRequestBody(mediaType)\n""";
}
result = "${result}val request = Request.Builder()\n";
result = "$result .url(\"${requestModel.url}\")\n";
result = result + addQueryParams(requestModel);
result = result + addRequestMethod(requestModel);
result = result + addHeaders(requestModel);
result = result + footerSnippet;
return result;
}
String addQueryParams(RequestModel requestModel) {
String result = "";
if (requestModel.requestParams == null) {
return result;
}
for (final queryParam in requestModel.requestParams!) {
result =
"""$result .addQueryParameter("${queryParam.k}", "${queryParam.v}")\n""";
}
return result;
}
String addHeaders(RequestModel requestModel) {
String result = "";
if (requestModel.requestHeaders == null) {
return result;
}
for (final header in requestModel.requestHeaders!) {
result = """$result .addHeader("${header.k}", "${header.v}")\n""";
}
return result;
}
String addRequestMethod(RequestModel requestModel) {
String result = "";
if (requestModel.method != HTTPVerb.get &&
requestModel.method != HTTPVerb.head &&
requestModel.method != HTTPVerb.delete) {
result = """$result .${requestModel.method.name}(body)\n""";
} else if (requestModel.method == HTTPVerb.head) {
result = """$result .${requestModel.method.name}()\n""";
}
if (requestModel.method == HTTPVerb.delete) {
result = """$result .method("DELETE", body)\n""";
}
return result;
}
}

View File

@ -228,6 +228,14 @@ const kMethodsWithBody = [
const kDefaultHttpMethod = HTTPVerb.get; const kDefaultHttpMethod = HTTPVerb.get;
const kDefaultContentType = ContentType.json; const kDefaultContentType = ContentType.json;
enum CodegenLanguage {
dartHttp("Dart (http)"),
kotlinOkHttp("Kotlin (OkHttp)");
const CodegenLanguage(this.label);
final String label;
}
const JsonEncoder kEncoder = JsonEncoder.withIndent(' '); const JsonEncoder kEncoder = JsonEncoder.withIndent(' ');
const LineSplitter kSplitter = LineSplitter(); const LineSplitter kSplitter = LineSplitter();

View File

@ -1,3 +1,4 @@
import 'package:apidash/consts.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
final navRailIndexStateProvider = StateProvider<int?>((ref) => 0); final navRailIndexStateProvider = StateProvider<int?>((ref) => 0);
@ -7,3 +8,4 @@ final sentRequestIdStateProvider = StateProvider<String?>((ref) => null);
final codePaneVisibleStateProvider = StateProvider<bool>((ref) => false); final codePaneVisibleStateProvider = StateProvider<bool>((ref) => false);
final saveDataStateProvider = StateProvider<bool>((ref) => false); final saveDataStateProvider = StateProvider<bool>((ref) => false);
final clearDataStateProvider = StateProvider<bool>((ref) => false); final clearDataStateProvider = StateProvider<bool>((ref) => false);
final codegenLanguageStateProvider = StateProvider<CodegenLanguage>((ref) => CodegenLanguage.dartHttp);

View File

@ -13,7 +13,7 @@ class CodePane extends ConsumerStatefulWidget {
} }
class _CodePaneState extends ConsumerState<CodePane> { class _CodePaneState extends ConsumerState<CodePane> {
final DartHttpCodeGen dartHttpCodeGen = DartHttpCodeGen(); final Codegen codegen = Codegen();
@override @override
void initState() { void initState() {
@ -22,10 +22,15 @@ class _CodePaneState extends ConsumerState<CodePane> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final CodegenLanguage codegenLanguage =
ref.watch(codegenLanguageStateProvider);
final activeRequestModel = ref.watch(activeRequestModelProvider); final activeRequestModel = ref.watch(activeRequestModelProvider);
final defaultUriScheme = final defaultUriScheme =
ref.watch(settingsProvider.select((value) => value.defaultUriScheme)); ref.watch(settingsProvider.select((value) => value.defaultUriScheme));
final code = dartHttpCodeGen.getCode(activeRequestModel!, defaultUriScheme);
final code =
codegen.getCode(codegenLanguage, activeRequestModel!, defaultUriScheme);
if (code == null) { if (code == null) {
return const ErrorMessage( return const ErrorMessage(
message: "An error was encountered while generating code. $kRaiseIssue", message: "An error was encountered while generating code. $kRaiseIssue",
@ -33,6 +38,12 @@ class _CodePaneState extends ConsumerState<CodePane> {
} }
return ViewCodePane( return ViewCodePane(
code: code, code: code,
codegenLanguage: codegenLanguage,
onChangedCodegenLanguage: (CodegenLanguage? value) {
ref
.read(codegenLanguageStateProvider.notifier)
.update((state) => value!);
},
); );
} }
} }

View File

@ -2,8 +2,9 @@ import 'package:flutter/material.dart';
import 'package:highlighter/highlighter.dart' show highlight; import 'package:highlighter/highlighter.dart' show highlight;
import 'package:apidash/consts.dart'; import 'package:apidash/consts.dart';
import 'package:apidash/utils/utils.dart'; import 'package:apidash/utils/utils.dart';
import 'code_previewer.dart' show convert; import 'code_previewer.dart';
import 'buttons.dart'; import 'widgets.dart'
show CopyButton, DropdownButtonCodegenLanguage, SaveInDownloadsButton;
class CodeGenPreviewer extends StatefulWidget { class CodeGenPreviewer extends StatefulWidget {
const CodeGenPreviewer({ const CodeGenPreviewer({
@ -105,9 +106,13 @@ class ViewCodePane extends StatefulWidget {
const ViewCodePane({ const ViewCodePane({
super.key, super.key,
required this.code, required this.code,
required this.codegenLanguage,
required this.onChangedCodegenLanguage,
}); });
final String code; final String code;
final CodegenLanguage codegenLanguage;
final Function(CodegenLanguage?) onChangedCodegenLanguage;
@override @override
State<ViewCodePane> createState() => _ViewCodePaneState(); State<ViewCodePane> createState() => _ViewCodePaneState();
@ -144,6 +149,10 @@ class _ViewCodePaneState extends State<ViewCodePane> {
style: Theme.of(context).textTheme.titleMedium, style: Theme.of(context).textTheme.titleMedium,
), ),
), ),
DropdownButtonCodegenLanguage(
codegenLanguage: widget.codegenLanguage,
onChanged: widget.onChangedCodegenLanguage,
),
CopyButton(toCopy: widget.code), CopyButton(toCopy: widget.code),
SaveInDownloadsButton( SaveInDownloadsButton(
content: stringToBytes(widget.code), content: stringToBytes(widget.code),

View File

@ -109,3 +109,55 @@ class _DropdownButtonContentTypeState extends State<DropdownButtonContentType> {
); );
} }
} }
class DropdownButtonCodegenLanguage extends StatefulWidget {
const DropdownButtonCodegenLanguage({
Key? key,
this.codegenLanguage,
this.onChanged,
}) : super(key: key);
@override
State<DropdownButtonCodegenLanguage> createState() =>
_DropdownButtonCodegenLanguageState();
final CodegenLanguage? codegenLanguage;
final void Function(CodegenLanguage?)? onChanged;
}
class _DropdownButtonCodegenLanguageState
extends State<DropdownButtonCodegenLanguage> {
@override
Widget build(BuildContext context) {
final surfaceColor = Theme.of(context).colorScheme.surface;
return DropdownButton<CodegenLanguage>(
focusColor: surfaceColor,
value: widget.codegenLanguage,
icon: const Icon(
Icons.unfold_more_rounded,
size: 16,
),
elevation: 4,
style: kCodeStyle.copyWith(
color: Theme.of(context).colorScheme.primary,
),
underline: Container(
height: 0,
),
onChanged: widget.onChanged,
borderRadius: kBorderRadius12,
items: CodegenLanguage.values
.map<DropdownMenuItem<CodegenLanguage>>((CodegenLanguage value) {
return DropdownMenuItem<CodegenLanguage>(
value: value,
child: Padding(
padding: kPs8,
child: Text(
value.label,
style: kTextStyleButton,
),
),
);
}).toList(),
);
}
}

View File

@ -0,0 +1,163 @@
import 'package:apidash/codegen/kotlin/pkg_okhttp.dart';
import 'package:apidash/models/models.dart' show KVRow, RequestModel;
import 'package:test/test.dart';
import 'package:apidash/consts.dart';
void main() {
group('KotlinOkHttpCodeGen', () {
final kotlinOkHttpCodeGen = KotlinOkHttpCodeGen();
test('getCode returns valid code for GET request', () {
const requestModel = RequestModel(
url: 'https://jsonplaceholder.typicode.com/todos/1',
method: HTTPVerb.get,
id: '',
);
const expectedCode = """import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MultipartBody
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.RequestBody.Companion.asRequestBody
import java.io.File
import java.util.concurrent.TimeUnit
val client = OkHttpClient()
val request = Request.Builder()
.url("https://jsonplaceholder.typicode.com/todos/1")
.build()
val response = client.newCall(request).execute()
println(response.body!!.string())
""";
expect(kotlinOkHttpCodeGen.getCode(requestModel), expectedCode);
});
test('getCode returns valid code for POST request', () {
const requestModel = RequestModel(
url: 'https://jsonplaceholder.typicode.com/posts',
method: HTTPVerb.post,
requestBody: '{"title": "foo","body": "bar","userId": 1}',
requestBodyContentType: ContentType.json,
id: '1',
);
const expectedCode = """import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MultipartBody
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.RequestBody.Companion.asRequestBody
import java.io.File
import java.util.concurrent.TimeUnit
val client = OkHttpClient()
val mediaType = "application/json".toMediaType()
val body = "{\"title\": \"foo\",\"body\": \"bar\",\"userId\": 1}".toRequestBody(mediaType)
val request = Request.Builder()
.url("https://jsonplaceholder.typicode.com/posts")
.post(body)
.build()
val response = client.newCall(request).execute()
println(response.body!!.string())
""";
expect(kotlinOkHttpCodeGen.getCode(requestModel), expectedCode);
});
test('getCode returns valid code for DELETE request', () {
const requestModel = RequestModel(
url: 'https://jsonplaceholder.typicode.com/posts/1',
method: HTTPVerb.delete,
requestBody: '{"title": "foo","body": "bar","userId": 1}',
requestBodyContentType: ContentType.json,
id: '1',
);
const expectedCode = """import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MultipartBody
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.RequestBody.Companion.asRequestBody
import java.io.File
import java.util.concurrent.TimeUnit
val client = OkHttpClient()
val mediaType = "application/json".toMediaType()
val body = "{\"title\": \"foo\",\"body\": \"bar\",\"userId\": 1}".toRequestBody(mediaType)
val request = Request.Builder()
.url("https://jsonplaceholder.typicode.com/posts/1")
.method("DELETE", body)
.build()
val response = client.newCall(request).execute()
println(response.body!!.string())
""";
expect(kotlinOkHttpCodeGen.getCode(requestModel), expectedCode);
});
test('getCode returns valid code for HEAD request', () {
const requestModel = RequestModel(
url: 'https://jsonplaceholder.typicode.com/posts/1',
method: HTTPVerb.head,
id: '1',
);
const expectedCode = """import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MultipartBody
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.RequestBody.Companion.asRequestBody
import java.io.File
import java.util.concurrent.TimeUnit
val client = OkHttpClient()
val request = Request.Builder()
.url("https://jsonplaceholder.typicode.com/posts/1")
.head()
.build()
val response = client.newCall(request).execute()
println(response.body!!.string())
""";
expect(kotlinOkHttpCodeGen.getCode(requestModel), expectedCode);
});
test(
'getCode returns valid code for requests with headers and query parameters',
() {
const requestModel = RequestModel(
url: 'https://jsonplaceholder.typicode.com/posts',
method: HTTPVerb.get,
requestParams: [
KVRow('userId', 1),
],
requestHeaders: [
KVRow('Custom-Header-1', 'Value-1'),
KVRow('Custom-Header-2', 'Value-2')
],
id: '1',
);
const expectedCode = """import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MultipartBody
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.RequestBody.Companion.asRequestBody
import java.io.File
import java.util.concurrent.TimeUnit
val client = OkHttpClient()
val request = Request.Builder()
.url("https://jsonplaceholder.typicode.com/posts")
.addQueryParameter("userId", "1")
.addHeader("Custom-Header-1", "Value-1")
.addHeader("Custom-Header-2", "Value-2")
.build()
val response = client.newCall(request).execute()
println(response.body!!.string())
""";
expect(kotlinOkHttpCodeGen.getCode(requestModel), expectedCode);
});
});
}

View File

@ -1,6 +1,7 @@
import 'package:apidash/widgets/widgets.dart'
show ViewCodePane, CodeGenPreviewer;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:apidash/widgets/codegen_previewer.dart';
import 'package:apidash/consts.dart'; import 'package:apidash/consts.dart';
import '../test_consts.dart'; import '../test_consts.dart';
@ -58,6 +59,8 @@ void main() async {
Expanded( Expanded(
child: ViewCodePane( child: ViewCodePane(
code: code, code: code,
codegenLanguage: CodegenLanguage.dartHttp,
onChangedCodegenLanguage: (p0) {},
), ),
), ),
], ],
@ -84,6 +87,8 @@ void main() async {
Expanded( Expanded(
child: ViewCodePane( child: ViewCodePane(
code: code, code: code,
codegenLanguage: CodegenLanguage.dartHttp,
onChangedCodegenLanguage: (p0) {},
), ),
), ),
], ],