diff --git a/packages/curl_parser/.gitignore b/packages/curl_parser/.gitignore new file mode 100644 index 00000000..04a9b6ff --- /dev/null +++ b/packages/curl_parser/.gitignore @@ -0,0 +1,32 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +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/ +build/ + +.vscode/ +coverage/ diff --git a/packages/curl_parser/CHANGELOG.md b/packages/curl_parser/CHANGELOG.md new file mode 100644 index 00000000..26fd78f3 --- /dev/null +++ b/packages/curl_parser/CHANGELOG.md @@ -0,0 +1,5 @@ +## 0.1.0 + +- Rectified & revamped original code and added better cURL parsing support. +- Replace native shell command parser with shlex. +- Forked from [curl_converter](https://pub.dev/packages/curl_converter). diff --git a/packages/curl_parser/LICENSE b/packages/curl_parser/LICENSE new file mode 100644 index 00000000..d036f89e --- /dev/null +++ b/packages/curl_parser/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2023 Ankit Mahato, Ashita Prasad + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/curl_parser/README.md b/packages/curl_parser/README.md new file mode 100644 index 00000000..a98995bc --- /dev/null +++ b/packages/curl_parser/README.md @@ -0,0 +1,40 @@ +# curl_parser + +A Dart package that provides a `Curl` class for parsing and formatting cURL commands. + +## Usage + +1. Add the package to your `pubspec.yaml` file + +2. Use the package: + +```dart +import 'package:curl_converter/curl_converter.dart'; + +void main() { + // Parse a cURL command + final curlString = 'curl -X GET https://www.example.com/'; + final curl = Curl.parse(curlString); + + // Access parsed data + print(curl.method); // GET + print(curl.uri); // https://www.example.com/ + + // Format Curl object to a cURL command + final formattedCurlString = curl.toCurlString(); + print(formattedCurlString); // curl -X GET https://www.example.com/ +} +``` + +See [test](https://github.com/foss42/curl_converter/tree/main/test) folder for more example usages. + +## Features + +- Parse a cURL command into a `Curl` class instance. +- Format a `Curl` object back into a cURL command. +- Supports various options such as request method, headers, data, cookies, user-agent, and more. + +## License + +This project is licensed under the [Apache License 2.0](https://github.com/foss42/apidash/blob/main/packages/curl_parser/LICENSE). +Fork of [curl_converter](https://github.com/utopicnarwhal/curl_converter). diff --git a/packages/curl_parser/analysis_options.yaml b/packages/curl_parser/analysis_options.yaml new file mode 100644 index 00000000..572dd239 --- /dev/null +++ b/packages/curl_parser/analysis_options.yaml @@ -0,0 +1 @@ +include: package:lints/recommended.yaml diff --git a/packages/curl_parser/example/curl_parser_example.dart b/packages/curl_parser/example/curl_parser_example.dart new file mode 100644 index 00000000..7175ac6f --- /dev/null +++ b/packages/curl_parser/example/curl_parser_example.dart @@ -0,0 +1,15 @@ +import 'package:curl_parser/curl_parser.dart'; + +void main() { + // Parse a cURL command + final curlString = 'curl -X GET https://www.example.com/'; + final curl = Curl.parse(curlString); + + // Access parsed data + print(curl.method); // GET + print(curl.uri); // https://www.example.com/ + + // Format Curl object to a cURL command + final formattedCurlString = curl.toCurlString(); + print(formattedCurlString); // curl "https://www.example.com/"" +} diff --git a/packages/curl_parser/lib/curl_parser.dart b/packages/curl_parser/lib/curl_parser.dart new file mode 100644 index 00000000..beae2205 --- /dev/null +++ b/packages/curl_parser/lib/curl_parser.dart @@ -0,0 +1,3 @@ +library curl_parser; + +export 'src/models/curl.dart'; diff --git a/packages/curl_parser/lib/src/models/curl.dart b/packages/curl_parser/lib/src/models/curl.dart new file mode 100644 index 00000000..7b2a7485 --- /dev/null +++ b/packages/curl_parser/lib/src/models/curl.dart @@ -0,0 +1,232 @@ +import 'package:args/args.dart'; +import 'package:equatable/equatable.dart'; +import '../../utils/string.dart'; + +/// A representation of a cURL command in Dart. +/// +/// The Curl class provides methods for parsing a cURL command string +/// and formatting a Curl object back into a cURL command. +class Curl extends Equatable { + /// Specifies the HTTP request method (e.g., GET, POST, PUT, DELETE). + final String method; + + /// Specifies the HTTP request URL + final Uri uri; + + /// Adds custom HTTP headers to the request. + final Map? headers; + + /// Sends data as the request body (typically used with POST requests). + final String? data; + + /// Sends cookies with the request. + final String? cookie; + + /// Specifies the username and password for HTTP basic authentication. + final String? user; + + /// Sets the Referer header for the request. + final String? referer; + + /// Sets the User-Agent header for the request. + final String? userAgent; + + /// Sends data as a multipart/form-data request. + final bool form; + + /// Allows insecure SSL connections. + final bool insecure; + + /// Follows HTTP redirects. + final bool location; + + /// Constructs a new Curl object with the specified parameters. + /// + /// The uri parameter is required, while the remaining parameters are optional. + Curl({ + required this.uri, + this.method = 'GET', + this.headers, + this.data, + this.cookie, + this.user, + this.referer, + this.userAgent, + this.form = false, + this.insecure = false, + this.location = false, + }); + + /// Parse [curlString] as a [Curl] class instance. + /// + /// Like [parse] except that this function returns `null` where a + /// similar call to [parse] would throw a throwable. + /// + /// Example: + /// ```dart + /// print(Curl.tryParse('curl -X GET https://www.example.com/')); // Curl(method: 'GET', url: 'https://www.example.com/') + /// print(Curl.tryParse('1f')); // null + /// ``` + static Curl? tryParse(String curlString) { + try { + return Curl.parse(curlString); + } catch (_) {} + return null; + } + + /// Parse [curlString] as a [Curl] class instance. + /// + /// Example: + /// ```dart + /// print(Curl.parse('curl -X GET https://www.example.com/')); // Curl(method: 'GET', url: 'https://www.example.com/') + /// print(Curl.parse('1f')); // [Exception] is thrown + /// ``` + static Curl parse(String curlString) { + String? clean(String? url) { + return url?.replaceAll('"', '').replaceAll("'", ''); + } + + final parser = ArgParser(allowTrailingOptions: true); + + // Define the expected options + parser.addOption('url'); + parser.addOption('request', abbr: 'X'); + parser.addMultiOption('header', abbr: 'H', splitCommas: false); + parser.addOption('data', abbr: 'd'); + parser.addOption('cookie', abbr: 'b'); + parser.addOption('user', abbr: 'u'); + parser.addOption('referer', abbr: 'e'); + parser.addOption('user-agent', abbr: 'A'); + parser.addFlag('head', abbr: 'I'); + parser.addFlag('form', abbr: 'F'); + parser.addFlag('insecure', abbr: 'k'); + parser.addFlag('location', abbr: 'L'); + + if (!curlString.startsWith('curl ')) { + throw Exception("curlString doesn't start with 'curl '"); + } + + final splittedCurlString = + splitAsCommandLineArgs(curlString.replaceFirst('curl ', '')); + + final result = parser.parse(splittedCurlString); + + final method = (result['request'] as String?)?.toUpperCase(); + + // Extract the request headers + Map? headers; + if (result['header'] != null) { + final List headersList = result['header']; + if (headersList.isNotEmpty == true) { + headers = {}; + for (var headerString in headersList) { + final splittedHeaderString = headerString.split(RegExp(r':\s*')); + if (splittedHeaderString.length != 2) { + throw Exception('Failed to split the `$headerString` header'); + } + headers.addAll({splittedHeaderString[0]: splittedHeaderString[1]}); + } + } + } + + String? url = clean(result['url']); + final String? data = result['data']; + final String? cookie = result['cookie']; + final String? user = result['user']; + final String? referer = result['referer']; + final String? userAgent = result['user-agent']; + final bool form = result['form'] ?? false; + final bool head = result['head'] ?? false; + final bool insecure = result['insecure'] ?? false; + final bool location = result['location'] ?? false; + + // Extract the request URL + url ??= result.rest.isNotEmpty ? clean(result.rest.first) : null; + if (url == null) { + throw Exception('url is null'); + } + final uri = Uri.parse(url); + + return Curl( + method: head ? "HEAD" : (method ?? 'GET'), + uri: uri, + headers: headers, + data: data, + cookie: cookie, + user: user, + referer: referer, + userAgent: userAgent, + form: form, + insecure: insecure, + location: location, + ); + } + + // Formatted cURL command + String toCurlString() { + var cmd = 'curl '; + + // Add the request method + if (method != 'GET') { + cmd += '-X $method '; + } + + // Add the headers + headers?.forEach((key, value) { + cmd += '-H "$key: $value" '; + }); + + // Add the body + if (data?.isNotEmpty == true) { + cmd += '-d \'$data\' '; + } + // Add the cookie + if (cookie?.isNotEmpty == true) { + cmd += '-b \'$cookie\' '; + } + // Add the user + if (user?.isNotEmpty == true) { + cmd += '-u \'$user\' '; + } + // Add the referer + if (referer?.isNotEmpty == true) { + cmd += '-e \'$referer\' '; + } + // Add the user-agent + if (userAgent?.isNotEmpty == true) { + cmd += '-A \'$userAgent\' '; + } + // Add the form flag + if (form) { + cmd += '-F '; + } + // Add the insecure flag + if (insecure) { + cmd += '-k '; + } + // Add the location flag + if (location) { + cmd += '-L '; + } + + // Add the URL + cmd += '"${Uri.encodeFull(uri.toString())}"'; + + return cmd.trim(); + } + + @override + List get props => [ + method, + uri, + headers, + data, + cookie, + user, + referer, + userAgent, + form, + insecure, + location, + ]; +} diff --git a/packages/curl_parser/lib/utils/string.dart b/packages/curl_parser/lib/utils/string.dart new file mode 100644 index 00000000..075b0080 --- /dev/null +++ b/packages/curl_parser/lib/utils/string.dart @@ -0,0 +1,5 @@ +import 'package:shlex/shlex.dart' as shlex; + +List splitAsCommandLineArgs(String command) { + return shlex.split(command); +} diff --git a/packages/curl_parser/pubspec.yaml b/packages/curl_parser/pubspec.yaml new file mode 100644 index 00000000..f8b4cf7c --- /dev/null +++ b/packages/curl_parser/pubspec.yaml @@ -0,0 +1,16 @@ +name: curl_parser +description: A Dart package for effortless conversion between cURL commands and Dart class. +version: 0.1.0 +repository: https://github.com/foss42/apidash/tree/main/packages/curl_parser + +environment: + sdk: ">=2.17.0 <4.0.0" + +dependencies: + args: ">=2.3.1 <3.0.0" + equatable: ^2.0.5 + shlex: ^2.0.2 + +dev_dependencies: + lints: ^3.0.0 + test: ^1.21.0 diff --git a/packages/curl_parser/test/curl_parser_test.dart b/packages/curl_parser/test/curl_parser_test.dart new file mode 100644 index 00000000..18d28a26 --- /dev/null +++ b/packages/curl_parser/test/curl_parser_test.dart @@ -0,0 +1,258 @@ +import 'dart:io'; + +import 'package:curl_parser/src/models/curl.dart'; +import 'package:test/test.dart'; + +const defaultTimeout = Timeout(Duration(seconds: 3)); +final exampleDotComUri = Uri.parse('https://www.example.com/'); + +void main() { + test('parse an easy cURL', () async { + expect( + Curl.parse('curl -X GET https://www.example.com/'), + Curl( + method: 'GET', + uri: exampleDotComUri, + ), + ); + }, timeout: defaultTimeout); + + test('compose an easy cURL', () async { + expect( + Curl.parse('curl -X GET https://www.example.com/'), + Curl( + method: 'GET', + uri: exampleDotComUri, + ), + ); + }, timeout: defaultTimeout); + + test('Check quotes support for URL string', () async { + expect( + Curl.parse('curl -X GET "https://www.example.com/"'), + Curl( + method: 'GET', + uri: exampleDotComUri, + ), + ); + }, timeout: defaultTimeout); + + test('parses two headers', () async { + expect( + Curl.parse( + 'curl -X GET https://www.example.com/ -H "${HttpHeaders.contentTypeHeader}: ${ContentType.text}" -H "${HttpHeaders.authorizationHeader}: Bearer %token%"', + ), + Curl( + method: 'GET', + uri: exampleDotComUri, + headers: { + HttpHeaders.contentTypeHeader: ContentType.text.toString(), + HttpHeaders.authorizationHeader: 'Bearer %token%', + }, + ), + ); + }, timeout: defaultTimeout); + + test('parses a lot of headers', () async { + expect( + Curl.parse( + r'curl -H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36" -H "Upgrade-Insecure-Requests: 1" -H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7" -H "Accept-Encoding: gzip, deflate, br" -H "Accept-Language: en,en-GB;q=0.9,en-US;q=0.8,ru;q=0.7" -H "Cache-Control: max-age=0" -H "Dnt: 1" -H "Sec-Ch-Ua: \"Google Chrome\";v=\"117\", \"Not;A=Brand\";v=\"8\", \"Chromium\";v=\"117\"" -H "Sec-Ch-Ua-Mobile: ?0" -H "Sec-Ch-Ua-Platform: \"macOS\"" -H "Sec-Fetch-Dest: document" -H "Sec-Fetch-Mode: navigate" -H "Sec-Fetch-Site: same-origin" -H "Sec-Fetch-User: ?1" https://animego.org/anime/eta-farforovaya-kukla-vlyubilas-1937', + ), + Curl( + method: 'GET', + uri: Uri.parse( + 'https://animego.org/anime/eta-farforovaya-kukla-vlyubilas-1937'), + headers: { + 'User-Agent': + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36', + 'Upgrade-Insecure-Requests': '1', + 'Accept': + 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', + 'Accept-Encoding': 'gzip, deflate, br', + 'Accept-Language': 'en,en-GB;q=0.9,en-US;q=0.8,ru;q=0.7', + 'Cache-Control': 'max-age=0', + 'Dnt': '1', + 'Sec-Ch-Ua': + '"Google Chrome";v="117", "Not;A=Brand";v="8", "Chromium";v="117"', + 'Sec-Ch-Ua-Mobile': '?0', + 'Sec-Ch-Ua-Platform': '"macOS"', + 'Sec-Fetch-Dest': 'document', + 'Sec-Fetch-Mode': 'navigate', + 'Sec-Fetch-Site': 'same-origin', + 'Sec-Fetch-User': '?1' + }, + ), + ); + }, timeout: defaultTimeout); + + test('throw exception when parses wrong input', () async { + expect( + () => Curl.parse('1f'), + throwsException, + ); + }, timeout: defaultTimeout); + + test('tryParse return null when parses wrong input', () async { + expect( + Curl.tryParse('1f'), + null, + ); + }, timeout: defaultTimeout); + + test('tryParse success', () async { + expect( + Curl.tryParse( + 'curl -X GET https://www.example.com/ -H "${HttpHeaders.contentTypeHeader}: ${ContentType.text}" -H "${HttpHeaders.authorizationHeader}: Bearer %token%"', + ), + Curl( + method: 'GET', + uri: exampleDotComUri, + headers: { + HttpHeaders.contentTypeHeader: ContentType.text.toString(), + HttpHeaders.authorizationHeader: 'Bearer %token%', + }, + ), + ); + }, timeout: defaultTimeout); + + test('GET 1', () async { + expect( + Curl.parse( + r"""curl --url 'https://api.apidash.dev'""", + ), + Curl( + method: 'GET', + uri: Uri.parse('https://api.apidash.dev'), + ), + ); + }, timeout: defaultTimeout); + + test('GET 2', () async { + expect( + Curl.parse( + r"""curl --url 'https://api.apidash.dev/country/data?code=US'""", + ), + Curl( + method: 'GET', + uri: Uri.parse('https://api.apidash.dev/country/data?code=US'), + ), + ); + }, timeout: defaultTimeout); + + test('GET 3', () async { + expect( + Curl.parse( + r"""curl --url 'https://api.apidash.dev/country/data?code=IND'""", + ), + Curl( + method: 'GET', + uri: Uri.parse('https://api.apidash.dev/country/data?code=IND'), + ), + ); + }, timeout: defaultTimeout); + + test('GET 4', () async { + expect( + Curl.parse( + r"""curl --url 'https://api.apidash.dev/humanize/social?num=8700000&digits=3&system=SS&add_space=true&trailing_zeros=true'""", + ), + Curl( + method: 'GET', + uri: Uri.parse( + 'https://api.apidash.dev/humanize/social?num=8700000&digits=3&system=SS&add_space=true&trailing_zeros=true'), + ), + ); + }, timeout: defaultTimeout); + + test('GET 5', () async { + expect( + Curl.parse( + r"""curl --url 'https://api.github.com/repos/foss42/apidash' \ + --header 'User-Agent: Test Agent'""", + ), + Curl( + method: 'GET', + uri: Uri.parse('https://api.github.com/repos/foss42/apidash'), + headers: {"User-Agent": "Test Agent"}, + ), + ); + }, timeout: defaultTimeout); + + test('GET 6', () async { + expect( + Curl.parse( + r"""curl --url 'https://api.github.com/repos/foss42/apidash?raw=true' \ + --header 'User-Agent: Test Agent'""", + ), + Curl( + method: 'GET', + uri: Uri.parse('https://api.github.com/repos/foss42/apidash?raw=true'), + headers: {"User-Agent": "Test Agent"}, + ), + ); + }, timeout: defaultTimeout); + + test('HEAD 2', () async { + expect( + Curl.parse( + r"""curl --head --url 'http://api.apidash.dev'""", + ), + Curl( + method: 'HEAD', + uri: Uri.parse('http://api.apidash.dev'), + ), + ); + }, timeout: defaultTimeout); + + test('POST 1', () async { + expect( + Curl.parse( + r"""curl --request POST \ + --url 'https://api.apidash.dev/case/lower' \ + --header 'Content-Type: text/plain' \ + --data '{ +"text": "I LOVE Flutter" +}'""", + ), + Curl( + method: 'POST', + uri: Uri.parse('https://api.apidash.dev/case/lower'), + headers: {"Content-Type": "text/plain"}, + data: r"""{ +"text": "I LOVE Flutter" +}""", + ), + ); + }, timeout: defaultTimeout); + + test('POST 2', () async { + expect( + Curl.parse( + r"""curl --request POST \ + --url 'https://api.apidash.dev/case/lower' \ + --header 'Content-Type: application/json' \ + --data '{ +"text": "I LOVE Flutter", +"flag": null, +"male": true, +"female": false, +"no": 1.2, +"arr": ["null", "true", "false", null] +}'""", + ), + Curl( + method: 'POST', + uri: Uri.parse('https://api.apidash.dev/case/lower'), + headers: {"Content-Type": "application/json"}, + data: r"""{ +"text": "I LOVE Flutter", +"flag": null, +"male": true, +"female": false, +"no": 1.2, +"arr": ["null", "true", "false", null] +}""", + ), + ); + }, timeout: defaultTimeout); +} diff --git a/packages/curl_parser/test/utility_test.dart b/packages/curl_parser/test/utility_test.dart new file mode 100644 index 00000000..124ea150 --- /dev/null +++ b/packages/curl_parser/test/utility_test.dart @@ -0,0 +1,67 @@ +import 'package:curl_parser/utils/string.dart'; +import 'package:test/test.dart'; + +const defaultTimeout = Timeout(Duration(seconds: 3)); + +void main() { + test('parse cURL with single quotes', () async { + expect( + splitAsCommandLineArgs( + r"""--url 'https://api.github.com/repos/foss42/apidash?raw=true' \ + --header 'User-Agent: Test Agent'"""), + [ + "--url", + "https://api.github.com/repos/foss42/apidash?raw=true", + "--header", + "User-Agent: Test Agent", + ], + ); + }, timeout: defaultTimeout); + + test('parse cURL with double quotes', () async { + expect( + splitAsCommandLineArgs( + r'''--url "https://api.github.com/repos/foss42/apidash?raw=true" \ + --header "User-Agent: Test Agent"'''), + [ + "--url", + "https://api.github.com/repos/foss42/apidash?raw=true", + "--header", + "User-Agent: Test Agent", + ], + ); + }, timeout: defaultTimeout); + + test('parse cURL with body', () async { + expect( + splitAsCommandLineArgs(r"""--request POST \ + --url 'https://api.apidash.dev/case/lower' \ + --header 'Content-Type: application/json' \ + --data '{ +"text": "I LOVE Flutter", +"flag": null, +"male": true, +"female": false, +"no": 1.2, +"arr": ["null", "true", "false", null] +}'"""), + [ + "--request", + "POST", + "--url", + "https://api.apidash.dev/case/lower", + "--header", + "Content-Type: application/json", + "--data", + r'''{ +"text": "I LOVE Flutter", +"flag": null, +"male": true, +"female": false, +"no": 1.2, +"arr": ["null", "true", "false", null] +}''', + ], + ); + }, timeout: defaultTimeout); +}