|
| 1 | +# Flutter Rust Bridge Experiment for Parsing Hurl |
| 2 | +*Author: [WrathOP](https://github.com/WrathOP)* |
| 3 | + |
| 4 | +## Background and Initial Approach |
| 5 | + |
| 6 | +When I first started adding hurl parser in API Dash, I wrote a custom parser using `petitparser` for the [hurl grammar](https://hurl.dev/docs/grammar.html). After spending about a week on this approach, I received some feedback from the maintainer to look into some approach of integrating the hurl Rust implementation directly rather than re-implementing the wheel. So that any future changes directly flow into the project. |
| 7 | + |
| 8 | +## Understanding Flutter Rust Bridge |
| 9 | + |
| 10 | +### What is Flutter Rust Bridge? |
| 11 | + |
| 12 | +Flutter Rust Bridge serves as a powerful tool for developing Flutter plugins using Rust. It automatically generates all the necessary code for both Flutter and Rust, creating a seamless bridge between the two languages. This approach is particularly valuable when you want to harness Rust's performance and safety features within Flutter projects. |
| 13 | + |
| 14 | +The [official documentation](https://cjycode.com/flutter_rust_bridge/) highlights three key features: |
| 15 | +1. Write standard Rust code, including complex features like arbitrary types, closures, mutable references, async functions, and traits |
| 16 | +2. Call Rust code from Flutter as if it were native Flutter code |
| 17 | +3. Automatic generation of all intermediate code required for communication |
| 18 | + |
| 19 | +### Why Choose Flutter Rust Bridge? |
| 20 | + |
| 21 | +Two main factors drove my decision to use Flutter Rust Bridge: |
| 22 | + |
| 23 | +1. **Automatic Updates with Upstream Changes**: By using the official Hurl Rust library, any changes made to the `hurl.dev` parser would automatically propagate to our plugin. This ensures long-term maintainability and compatibility. |
| 24 | + |
| 25 | +2. **Performance Benefits**: Rust's renowned performance characteristics and safety guarantees make it ideal for parsing large Hurl files efficiently. |
| 26 | + |
| 27 | +## Implementation Process |
| 28 | + |
| 29 | +### Setting Up the Flutter Rust Bridge Plugin |
| 30 | + |
| 31 | +While there are several examples of Flutter Rust Bridge usage available, such as [flutter smooth](https://github.com/fzyzcjy/flutter_smooth) and the [official documentation demo](https://cjycode.com/flutter_rust_bridge/demo/), I found that plugin-specific examples were scarce. Here's a detailed breakdown of my setup process: |
| 32 | + |
| 33 | +1. **Project Initialization** |
| 34 | +Instead of using the standard Flutter plugin template (`flutter create --template=plugin hurl`), Flutter Rust Bridge provides its own initialization command. |
| 35 | + |
| 36 | +Inside the `apidash/packages` directory, I executed: |
| 37 | + |
| 38 | +```bash |
| 39 | +flutter_rust_bridge_codegen create hurl --template plugin |
| 40 | +``` |
| 41 | + |
| 42 | +This command generated three main directories: |
| 43 | + |
| 44 | +1. `cargokit/`: Contains build infrastructure |
| 45 | + - build_tool/: Build system utilities |
| 46 | + - cmake/: CMake configuration files |
| 47 | + - Pod creation scripts for iOS |
| 48 | + - Build scripts for various platforms |
| 49 | + - Gradle configuration for Android |
| 50 | + |
| 51 | +2. `rust/`: The Rust project structure |
| 52 | + - src/ |
| 53 | + - api/: Contains the core Rust implementation |
| 54 | + - simple.rs: Main implementation file |
| 55 | + - mod.rs: Module definitions |
| 56 | + - frbgenerated.rs: Auto-generated bridge code |
| 57 | + - lib.rs: Library entry point |
| 58 | + - cargo.toml: Rust dependencies and configuration |
| 59 | + - .gitignore |
| 60 | + |
| 61 | +3. `lib/`: Flutter code |
| 62 | + - rust/: Generated Rust bindings |
| 63 | + - hurl.dart: Package export file |
| 64 | + |
| 65 | +### Implementation Details |
| 66 | + |
| 67 | +The core parsing functionality was implemented in [`rust/src/api/simple.rs`](https://github.com/foss42/apidash/blob/9a201dd10af6e6804c7fd8fab8352701ab722fb2/packages/hurl/rust/src/api/simple.rs): |
| 68 | + |
| 69 | +```rust |
| 70 | +/// Parses a Hurl file content and returns its JSON representation |
| 71 | +#[flutter_rust_bridge::frb(sync)] |
| 72 | +pub fn parse_hurl_to_json(content: String) -> Result<String, String> { |
| 73 | + match parse_hurl_file(&content) { |
| 74 | + Ok(hurl_file) => { |
| 75 | + // Convert to JSON using hurlfmt's format_json |
| 76 | + Ok(format_json(&hurl_file)) |
| 77 | + } |
| 78 | + Err(errors) => { |
| 79 | + let mut error_msg = String::new(); |
| 80 | + write!(error_msg, "Failed to parse Hurl file: {:?}", errors).unwrap(); |
| 81 | + Err(error_msg) |
| 82 | + } |
| 83 | + } |
| 84 | +} |
| 85 | +``` |
| 86 | + |
| 87 | +For dependencies, I added hurlfmt to `cargo.toml`: |
| 88 | + |
| 89 | +```toml |
| 90 | +[package] |
| 91 | +name = "hurl" |
| 92 | +version = "0.1.0" |
| 93 | +edition = "2021" |
| 94 | + |
| 95 | +[lib] |
| 96 | +crate-type = ["cdylib", "staticlib"] |
| 97 | + |
| 98 | +[dependencies] |
| 99 | +flutter_rust_bridge = "=2.7.0" |
| 100 | +hurlfmt = "6.0.0" |
| 101 | +hurl_core = "6.0.0" |
| 102 | +``` |
| 103 | + |
| 104 | +### Dart Model Implementation |
| 105 | + |
| 106 | +Based on the Hurl documentation, I identified the core data structures: |
| 107 | +- HurlFile: Contains a list of Entries |
| 108 | +- Entries: Composed of Request and Response objects |
| 109 | + |
| 110 | +I implemented these models using the `freezed` package in Dart, creating a clean mapping between the JSON output from the Hurl parser and our Dart data structures. This part of the implementation proved particularly successful and robust. |
| 111 | + |
| 112 | +## Compilation Challenges and Platform Support |
| 113 | + |
| 114 | +### Build Process Overview |
| 115 | + |
| 116 | +The compilation process varies by platform, but generally follows these steps: |
| 117 | + |
| 118 | +1. For local testing in the `hurl/tests` directory, building the Rust component requires: |
| 119 | +```bash |
| 120 | +cd apidash/packages/hurl/rust |
| 121 | +cargo build --release |
| 122 | +``` |
| 123 | + |
| 124 | +2. For actual deployment, cargokit manages the platform-specific build process during Flutter's standard build phase. The process involves: |
| 125 | + - On iOS: Generating and building Pod files |
| 126 | + - On Android: Creating platform-specific binaries |
| 127 | + - On macOS/Linux: Building dynamic libraries |
| 128 | + - On Windows: Generating appropriate DLLs |
| 129 | + |
| 130 | +### Understanding Flutter Rust Bridge on Mobile Platforms |
| 131 | + |
| 132 | +Flutter Rust Bridge generally works seamlessly across platforms as long as the Rust dependencies don't rely on platform-specific implementations. The bridge itself handles the complexity of cross-platform compilation through cargokit, which manages the build process for each target platform. However, when Rust crates depend on system libraries or have platform-specific requirements, complications can arise. |
| 133 | + |
| 134 | +For iOS and Android specifically, the build process involves: |
| 135 | +1. For iOS: Compiling Rust code to a static library that gets embedded in the iOS app bundle |
| 136 | +2. For Android: Creating platform-specific .so files for each supported architecture (arm64-v8a, armeabi-v7a, x86_64) |
| 137 | + |
| 138 | +The challenge comes when dependencies like `libxml2` expect certain system libraries to be available, which may not exist or behave differently on mobile platforms. |
| 139 | + |
| 140 | +### Platform-Specific Challenges |
| 141 | + |
| 142 | +#### macOS Implementation |
| 143 | + |
| 144 | +The main challenge on macOS involved the `libxml2` dependency. I reported this issue on the [Hurl repository](https://github.com/Orange-OpenSource/hurl/issues/3603). The solution required adding specific linker flags to the `apidash/packages/hurl/macos/hurl.podspec`: |
| 145 | + |
| 146 | +```ruby |
| 147 | +spec.pod_target_xcconfig = { |
| 148 | + 'OTHER_LDFLAGS' => '-force_load ${BUILT_PRODUCTS_DIR}/libhurl.a -lxml2' |
| 149 | +} |
| 150 | +``` |
| 151 | + |
| 152 | +#### iOS and Android Challenges |
| 153 | + |
| 154 | +The iOS and Android implementations faced more significant hurdles, primarily due to `libxml2` compatibility issues. Unlike macOS, these platforms don't include `libxml2` as a system library, and the Rust crate wrapper doesn't support mobile platforms. |
| 155 | + |
| 156 | +The following approaches were attempted for iOS: |
| 157 | +1. Modifying header search paths |
| 158 | +2. Adding framework linkage |
| 159 | +3. Adjusting Xcode build phases |
| 160 | +4. Experimenting with different linking configurations |
| 161 | + |
| 162 | +None of these attempts proved successful, leading to a deeper investigation of the `libxml2` dependency. |
| 163 | + |
| 164 | +#### Maintainer Feedback and Investigation |
| 165 | + |
| 166 | +After reaching out to both the Hurl and Flutter Rust Bridge maintainers, I received valuable feedback from the Hurl maintainer: |
| 167 | + |
| 168 | +> _"I'm not an expert in iOS/Android, but what I would do is try to build something smaller, maybe just calling libxml through a Flutter component, then trying to build with libxml but with the Rust crate wrapper, etc."_ ([GitHub comment](https://github.com/Orange-OpenSource/hurl/issues/3603#issuecomment-2611159759)) |
| 169 | +
|
| 170 | +This suggestion highlighted a crucial debugging approach: isolating the `libxml2` integration from the rest of the Hurl parsing functionality. The idea is to: |
| 171 | + |
| 172 | +1. First verify that we can call `libxml2` directly from Flutter |
| 173 | +2. Then attempt to use the Rust crate wrapper |
| 174 | +3. Finally integrate it with the full Hurl parser |
| 175 | + |
| 176 | +This systematic approach could help identify exactly where the build process breaks down on mobile platforms and potentially lead to a workable solution. |
| 177 | + |
| 178 | +### Current Status and Potential Solutions |
| 179 | + |
| 180 | +After extensive testing and consultation with both the Hurl and Flutter Rust Bridge maintainers, I've identified several potential paths forward: |
| 181 | + |
| 182 | +1. **Dart Native FFIs**: This approach is currently experimental but shows promise. The Flutter Rust Bridge maintainer suggested this option in this [Github comment](https://github.com/fzyzcjy/flutter_rust_bridge/issues/2510#issuecomment-2608469143). |
| 183 | + |
| 184 | +2. **Legacy Hurlfmt Version**: We could use a version of the library from before the `libxml` transition. This would avoid the platform compatibility issues but might miss out on recent performance improvements. |
| 185 | + |
| 186 | +3. **Custom Parser Completion**: Returning to and completing the `petitparser`-based implementation could provide a more controlled, albeit maintenance-heavy solution. |
| 187 | + |
| 188 | +## Code Availability |
| 189 | + |
| 190 | +The current implementation is available in two branches of my fork: |
| 191 | +- [Hurl Parser with flutter_rust_bridge](https://github.com/WrathOP/apidash/tree/hurl-parser-rust) |
| 192 | +- [Hurl Parser with petiteparser](https://github.com/WrathOP/apidash/tree/hurl-parser-added) |
| 193 | + |
| 194 | +Feel free to contribute ideas or suggestions for addressing these challenges! |
0 commit comments