Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hurl parser rust #516

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/

# IntelliJ related
Expand Down
3 changes: 2 additions & 1 deletion lib/consts.dart
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,8 @@ enum CodegenLanguage {

enum ImportFormat {
curl("cURL"),
postman("Postman Collection v2.1");
postman("Postman Collection v2.1"),
hurl("hurl");

const ImportFormat(this.label);
final String label;
Expand Down
2 changes: 2 additions & 0 deletions lib/importer/importer.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:apidash/consts.dart';
import 'package:apidash_core/apidash_core.dart';
import 'package:apidash_core/import_export/hurl_io.dart';

class Importer {
Future<List<(String?, HttpRequestModel)>?> getHttpRequestModelList(
Expand All @@ -12,6 +13,7 @@ class Importer {
?.map((t) => (null, t))
.toList(),
ImportFormat.postman => PostmanIO().getHttpRequestModelList(content),
ImportFormat.hurl => await HurlIo().getHttpRequestModelList(content)
};
}
}
Expand Down
160 changes: 160 additions & 0 deletions packages/apidash_core/lib/import_export/hurl_io.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import 'dart:async';
import 'dart:convert';
import 'package:apidash_core/utils/utils.dart';
import 'package:hurl/hurl.dart';
import 'package:seed/seed.dart';
import '../consts.dart';
import '../models/http_request_model.dart';

class HurlIo {
static bool _initialized = false;
static final _initCompleter = Completer<void>();

HurlIo() {
print('HurlIo constructor called'); // Debug log
_initializeIfNeeded();
}

void _initializeIfNeeded() {
if (!_initialized) {
print('Starting initialization'); // Debug log
_initialized = true;
HurlParser.initialize().then((_) {
print('Initialization completed successfully'); // Debug log
_initCompleter.complete();
}).catchError((error) {
print('Initialization failed with error: $error'); // Debug log
_initialized = false;
_initCompleter.completeError(error);
});
} else {
print('Already initialized'); // Debug log
}
}

Future<HurlFile> getHurlFile(String content) async {
print(
'getHurlFile called with content: "$content"'); // Print actual content
try {
await _initCompleter.future;
print('Initialization confirmed, proceeding to parse');

// Get the JSON string first
final jsonString = HurlParser.parseToJson(content);
print('Parsed JSON: $jsonString'); // Print the intermediate JSON

// Try to decode the JSON string to see if it's valid JSON
final jsonMap = jsonDecode(jsonString);
print('Decoded JSON structure: $jsonMap'); // Print decoded structure

final result = HurlFile.fromJson(jsonMap);
print('Successfully created HurlFile object');
return result;
} catch (e, stackTrace) {
print('Error in getHurlFile: $e');
print('Stack trace: $stackTrace');
rethrow;
}
}

Future<String> getJson(String content) async {
print('getJson called'); // Debug log
await _initCompleter.future;
return HurlParser.parseToJson(content);
}

Future<List<(String?, HttpRequestModel)>?> getHttpRequestModelList(
String content) async {
print('getHttpRequestModelList called'); // Debug log
try {
final hurlFile = await getHurlFile(content);
print(
'HurlFile obtained, entries count: ${hurlFile.entries.length}'); // Debug log

return hurlFile.entries.map((entry) {
print(
'Processing entry: ${entry.request.method} ${entry.request.url}'); // Debug log
final HurlRequest request = entry.request;

// Convert HTTP method
HTTPVerb method;
try {
method = HTTPVerb.values.byName(request.method.toLowerCase());
} catch (e) {
print('Error converting HTTP method: $e'); // Debug log
method = kDefaultHttpMethod;
}

// Process URL
final url = stripUrlParams(request.url);

// Process headers
final headers = <NameValueModel>[];
for (Header header in request.headers ?? []) {
headers.add(NameValueModel(
name: header.name,
value: header.value,
));
}
print('Processed ${headers.length} headers'); // Debug log

// Process body and form data
ContentType bodyContentType = kDefaultContentType;
String? body;
List<FormDataModel>? formData;

// Check for multipart form data
if (request.multiPartFormData != null &&
request.multiPartFormData!.isNotEmpty) {
print('Processing multipart form data'); // Debug log
bodyContentType = ContentType.formdata;
formData = request.multiPartFormData!.map((item) {
return FormDataModel(
name: item.name,
value: item.filename ?? item.value ?? '',
type:
item.filename != null ? FormDataType.file : FormDataType.text,
);
}).toList();
}
// Handle regular body
else if (request.body != null) {
print('Processing regular body'); // Debug log
final contentTypeHeader = request.headers?.firstWhere(
(element) => element.name.toLowerCase().contains("content-type"),
orElse: () => Header(name: '', value: ''),
);

body = request.body?.value?.toString();
print(
'Body content type header: ${contentTypeHeader?.value}'); // Debug log

if (contentTypeHeader?.value.contains('application/json') == true) {
bodyContentType = ContentType.json;
} else if (contentTypeHeader?.value.contains('text/plain') == true) {
bodyContentType = ContentType.text;
}
}

// Create the HttpRequestModel
return (
null,
HttpRequestModel(
method: method,
url: url,
headers: headers,
params: [],
isParamEnabledList: [],
body: body,
bodyContentType: bodyContentType,
formData: formData,
)
);
}).toList();
} catch (e, stackTrace) {
print('Error in getHttpRequestModelList: $e'); // Debug log
print('Stack trace: $stackTrace'); // Debug log
return null;
}
}
}
44 changes: 23 additions & 21 deletions packages/apidash_core/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,30 @@ homepage:
publish_to: none

environment:
sdk: ^3.5.3
flutter: ">=1.17.0"
sdk: ^3.5.3
flutter: ">=1.17.0"

dependencies:
flutter:
sdk: flutter
collection: ^1.18.0
curl_parser:
path: ../curl_parser
freezed_annotation: ^2.4.1
http: ^1.2.1
http_parser: ^4.0.2
postman:
path: ../postman
seed: ^0.0.3
xml: ^6.3.0
flutter:
sdk: flutter
collection: ^1.18.0
curl_parser:
path: ../curl_parser
hurl:
path: ../hurl
freezed_annotation: ^2.4.1
http: ^1.2.1
http_parser: ^4.0.2
postman:
path: ../postman
seed: ^0.0.3
xml: ^6.3.0

dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^2.4.12
flutter_lints: ^4.0.0
freezed: ^2.5.7
json_serializable: ^6.7.1
test: ^1.25.2
flutter_test:
sdk: flutter
build_runner: ^2.4.12
flutter_lints: ^4.0.0
freezed: ^2.5.7
json_serializable: ^6.7.1
test: ^1.25.2
6 changes: 5 additions & 1 deletion packages/apidash_core/pubspec_overrides.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
# melos_managed_dependency_overrides: curl_parser,postman,seed
# melos_managed_dependency_overrides: curl_parser,postman,seed,hurl,rust_lib_hurl
dependency_overrides:
curl_parser:
path: ../curl_parser
hurl:
path: ../hurl
postman:
path: ../postman
rust_lib_hurl:
path: ../hurl/rust_builder
seed:
path: ../seed
33 changes: 33 additions & 0 deletions packages/hurl/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/

# IntelliJ related
*.iml
*.ipr
*.iws
.idea/

# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/

# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
build/
3 changes: 3 additions & 0 deletions packages/hurl/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 0.0.1

* TODO: Describe initial release.
1 change: 1 addition & 0 deletions packages/hurl/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
TODO: Add your license here.
92 changes: 92 additions & 0 deletions packages/hurl/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# hurl

A new Flutter FFI plugin project.

## Getting Started

This project is a starting point for a Flutter
[FFI plugin](https://flutter.dev/to/ffi-package),
a specialized package that includes native code directly invoked with Dart FFI.

## Project structure

This template uses the following structure:

* `src`: Contains the native source code, and a CmakeFile.txt file for building
that source code into a dynamic library.

* `lib`: Contains the Dart code that defines the API of the plugin, and which
calls into the native code using `dart:ffi`.

* platform folders (`android`, `ios`, `windows`, etc.): Contains the build files
for building and bundling the native code library with the platform application.

## Building and bundling native code

The `pubspec.yaml` specifies FFI plugins as follows:

```yaml
plugin:
platforms:
some_platform:
ffiPlugin: true
```

This configuration invokes the native build for the various target platforms
and bundles the binaries in Flutter applications using these FFI plugins.

This can be combined with dartPluginClass, such as when FFI is used for the
implementation of one platform in a federated plugin:

```yaml
plugin:
implements: some_other_plugin
platforms:
some_platform:
dartPluginClass: SomeClass
ffiPlugin: true
```

A plugin can have both FFI and method channels:

```yaml
plugin:
platforms:
some_platform:
pluginClass: SomeName
ffiPlugin: true
```

The native build systems that are invoked by FFI (and method channel) plugins are:

* For Android: Gradle, which invokes the Android NDK for native builds.
* See the documentation in android/build.gradle.
* For iOS and MacOS: Xcode, via CocoaPods.
* See the documentation in ios/hurl.podspec.
* See the documentation in macos/hurl.podspec.
* For Linux and Windows: CMake.
* See the documentation in linux/CMakeLists.txt.
* See the documentation in windows/CMakeLists.txt.

## Binding to native code

To use the native code, bindings in Dart are needed.
To avoid writing these by hand, they are generated from the header file
(`src/hurl.h`) by `package:ffigen`.
Regenerate the bindings by running `dart run ffigen --config ffigen.yaml`.

## Invoking native code

Very short-running native functions can be directly invoked from any isolate.
For example, see `sum` in `lib/hurl.dart`.

Longer-running functions should be invoked on a helper isolate to avoid
dropping frames in Flutter applications.
For example, see `sumAsync` in `lib/hurl.dart`.

## Flutter help

For help getting started with Flutter, view our
[online documentation](https://docs.flutter.dev), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

Loading