From 8edf981628f3c95990a090ae38be15ede3c2c75f Mon Sep 17 00:00:00 2001 From: siddu015 <116783967+siddu015@users.noreply.github.com> Date: Wed, 12 Mar 2025 02:34:48 +0530 Subject: [PATCH 01/30] Feat: API Documentation & process general queries --- lib/dashbot/features/documentation.dart | 66 +++++++++++++++++++++++ lib/dashbot/features/general_query.dart | 45 ++++++++++++++++ lib/dashbot/services/dashbot_service.dart | 22 +++++--- lib/dashbot/widgets/dashbot_widget.dart | 8 +++ lib/screens/dashboard.dart | 22 ++++---- 5 files changed, 145 insertions(+), 18 deletions(-) create mode 100644 lib/dashbot/features/documentation.dart create mode 100644 lib/dashbot/features/general_query.dart diff --git a/lib/dashbot/features/documentation.dart b/lib/dashbot/features/documentation.dart new file mode 100644 index 00000000..da8f1949 --- /dev/null +++ b/lib/dashbot/features/documentation.dart @@ -0,0 +1,66 @@ +import 'dart:convert'; +import '../services/dashbot_service.dart'; +import 'package:apidash/models/request_model.dart'; + +class DocumentationFeature { + final DashBotService _service; + + DocumentationFeature(this._service); + + Future generateApiDocumentation({ + required RequestModel? requestModel, + required dynamic responseModel, + }) async { + if (requestModel == null || responseModel == null) { + return "No recent API requests found."; + } + + final method = requestModel.httpRequestModel?.method + .toString() + .split('.') + .last + .toUpperCase() ?? + "GET"; + final endpoint = requestModel.httpRequestModel?.url ?? "Unknown Endpoint"; + final headers = requestModel.httpRequestModel?.enabledHeadersMap ?? {}; + final parameters = requestModel.httpRequestModel?.enabledParamsMap ?? {}; + final body = requestModel.httpRequestModel?.body; + final rawResponse = responseModel.body; + final responseBody = + rawResponse is String ? rawResponse : jsonEncode(rawResponse); + final statusCode = responseModel.statusCode ?? 0; + + final prompt = """ +API DOCUMENTATION GENERATION + +**API Details:** +- Endpoint: $endpoint +- Method: $method +- Status Code: $statusCode + +**Request Components:** +- Headers: ${headers.isNotEmpty ? jsonEncode(headers) : "None"} +- Query Parameters: ${parameters.isNotEmpty ? jsonEncode(parameters) : "None"} +- Request Body: ${body != null && body.isNotEmpty ? body : "None"} + +**Response Example:** +``` +$responseBody +``` + +**Documentation Instructions:** +Create comprehensive API documentation that includes: + +1. **Overview**: A clear, concise description of what this API endpoint does +2. **Authentication**: Required authentication method based on headers +3. **Request Details**: All required and optional parameters with descriptions +4. **Response Structure**: Breakdown of response fields and their meanings +5. **Error Handling**: Possible error codes and troubleshooting +6. **Example Usage**: A complete code example showing how to call this API + +Format in clean markdown with proper sections and code blocks where appropriate. +"""; + + return _service.generateResponse(prompt); + } +} diff --git a/lib/dashbot/features/general_query.dart b/lib/dashbot/features/general_query.dart new file mode 100644 index 00000000..45f4d3b5 --- /dev/null +++ b/lib/dashbot/features/general_query.dart @@ -0,0 +1,45 @@ +import 'package:ollama_dart/ollama_dart.dart'; +import 'package:apidash/models/request_model.dart'; + +class GeneralQueryFeature { + final OllamaClient _client; + + GeneralQueryFeature(this._client); + + Future generateResponse(String prompt, {RequestModel? requestModel, dynamic responseModel}) async { + // Create a more focused prompt that incorporates request/response context if available + String enhancedPrompt = prompt; + + if (requestModel != null && responseModel != null) { + final method = requestModel.httpRequestModel?.method.toString().split('.').last.toUpperCase() ?? "GET"; + final endpoint = requestModel.httpRequestModel?.url ?? "Unknown Endpoint"; + final statusCode = responseModel.statusCode ?? 0; + + enhancedPrompt = ''' +CONTEXT-AWARE RESPONSE + +**User Question:** +$prompt + +**Related API Context:** +- Endpoint: $endpoint +- Method: $method +- Status Code: $statusCode + +**Instructions:** +1. Directly address the user's specific question +2. Provide relevant, concise information +3. Reference the API context when helpful +4. Focus on practical, actionable insights +5. Avoid generic explanations or documentation + +Respond in a helpful, direct manner that specifically answers what was asked. +'''; + } + + final response = await _client.generateCompletion( + request: GenerateCompletionRequest(model: 'llama3.2:3b', prompt: enhancedPrompt), + ); + return response.response.toString(); + } +} diff --git a/lib/dashbot/services/dashbot_service.dart b/lib/dashbot/services/dashbot_service.dart index 8eb0087c..8a1abd1e 100644 --- a/lib/dashbot/services/dashbot_service.dart +++ b/lib/dashbot/services/dashbot_service.dart @@ -1,24 +1,29 @@ import 'package:apidash/dashbot/features/debug.dart'; +import 'package:apidash/dashbot/features/documentation.dart'; import 'package:ollama_dart/ollama_dart.dart'; -import '../features/explain.dart'; +import 'package:apidash/dashbot/features/explain.dart'; import 'package:apidash/models/request_model.dart'; +import 'package:apidash/dashbot/features/general_query.dart'; class DashBotService { final OllamaClient _client; late final ExplainFeature _explainFeature; late final DebugFeature _debugFeature; + late final DocumentationFeature _documentationFeature; + final GeneralQueryFeature _generalQueryFeature; + DashBotService() - : _client = OllamaClient(baseUrl: 'http://127.0.0.1:11434/api') { + : _client = OllamaClient(baseUrl: 'http://127.0.0.1:11434/api'), + _generalQueryFeature = GeneralQueryFeature(OllamaClient(baseUrl: 'http://127.0.0.1:11434/api')) { + _explainFeature = ExplainFeature(this); _debugFeature = DebugFeature(this); + _documentationFeature = DocumentationFeature(this); } Future generateResponse(String prompt) async { - final response = await _client.generateCompletion( - request: GenerateCompletionRequest(model: 'llama3.2:3b', prompt: prompt), - ); - return response.response.toString(); + return _generalQueryFeature.generateResponse(prompt); } Future handleRequest( @@ -29,8 +34,11 @@ class DashBotService { } else if (input == "Debug API") { return _debugFeature.debugApi( requestModel: requestModel, responseModel: responseModel); + } else if (input == "Document API") { + return _documentationFeature.generateApiDocumentation( + requestModel: requestModel, responseModel: responseModel); } - return generateResponse(input); + return _generalQueryFeature.generateResponse(input, requestModel: requestModel, responseModel: responseModel); } } diff --git a/lib/dashbot/widgets/dashbot_widget.dart b/lib/dashbot/widgets/dashbot_widget.dart index 200d4c5f..7618fb98 100644 --- a/lib/dashbot/widgets/dashbot_widget.dart +++ b/lib/dashbot/widgets/dashbot_widget.dart @@ -143,6 +143,14 @@ class _DashBotWidgetState extends ConsumerState { padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), ), ), + ElevatedButton.icon( + onPressed: () => _sendMessage("Document API"), + icon: const Icon(Icons.description_outlined), + label: const Text("Document"), + style: ElevatedButton.styleFrom ( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + ), + ), ], ); } diff --git a/lib/screens/dashboard.dart b/lib/screens/dashboard.dart index 428ffaeb..cc9a6267 100644 --- a/lib/screens/dashboard.dart +++ b/lib/screens/dashboard.dart @@ -126,17 +126,17 @@ class Dashboard extends ConsumerWidget { ), ), // TODO: Release DashBot - // floatingActionButton: FloatingActionButton( - // onPressed: () => showModalBottomSheet( - // context: context, - // isScrollControlled: true, - // builder: (context) => const Padding( - // padding: EdgeInsets.all(16.0), - // child: DashBotWidget(), - // ), - // ), - // child: const Icon(Icons.help_outline), - // ), + floatingActionButton: FloatingActionButton( + onPressed: () => showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (context) => const Padding( + padding: EdgeInsets.all(16.0), + child: DashBotWidget(), + ), + ), + child: const Icon(Icons.help_outline), + ), ); } } From 6fa9d221117b7ae0f3833cd9a325ce51d113e5c0 Mon Sep 17 00:00:00 2001 From: siddu015 <116783967+siddu015@users.noreply.github.com> Date: Thu, 13 Mar 2025 02:10:19 +0530 Subject: [PATCH 02/30] Test cases generator --- lib/dashbot/features/test_generator.dart | 97 ++++++ lib/dashbot/services/dashbot_service.dart | 8 +- lib/dashbot/widgets/dashbot_widget.dart | 101 ++++-- lib/dashbot/widgets/test_runner_widget.dart | 352 ++++++++++++++++++++ 4 files changed, 527 insertions(+), 31 deletions(-) create mode 100644 lib/dashbot/features/test_generator.dart create mode 100644 lib/dashbot/widgets/test_runner_widget.dart diff --git a/lib/dashbot/features/test_generator.dart b/lib/dashbot/features/test_generator.dart new file mode 100644 index 00000000..c638964b --- /dev/null +++ b/lib/dashbot/features/test_generator.dart @@ -0,0 +1,97 @@ +import 'dart:convert'; +import '../services/dashbot_service.dart'; +import 'package:apidash/models/request_model.dart'; + +class TestGeneratorFeature { + final DashBotService _service; + + TestGeneratorFeature(this._service); + + Future generateApiTests({ + required RequestModel? requestModel, + required dynamic responseModel, + }) async { + if (requestModel == null || responseModel == null) { + return "No recent API requests found."; + } + + final method = requestModel.httpRequestModel?.method + .toString() + .split('.') + .last + .toUpperCase() ?? + "GET"; + final endpoint = requestModel.httpRequestModel?.url ?? "Unknown Endpoint"; + final headers = requestModel.httpRequestModel?.enabledHeadersMap ?? {}; + final parameters = requestModel.httpRequestModel?.enabledParamsMap ?? {}; + final body = requestModel.httpRequestModel?.body; + final rawResponse = responseModel.body; + final responseBody = + rawResponse is String ? rawResponse : jsonEncode(rawResponse); + final statusCode = responseModel.statusCode ?? 0; + + // Extract base URL and endpoint path + Uri uri = Uri.parse(endpoint); + final baseUrl = "${uri.scheme}://${uri.host}"; + final path = uri.path; + + // Analyze parameter types and values + final parameterAnalysis = _analyzeParameters(uri.queryParameters); + + final prompt = """ +EXECUTABLE API TEST CASES GENERATOR + +**API Analysis:** +- Base URL: $baseUrl +- Endpoint: $path +- Method: $method +- Current Parameters: ${uri.queryParameters} +- Current Response: $responseBody (Status: $statusCode) +- Parameter Types: $parameterAnalysis + +**Test Generation Task:** +Generate practical, ready-to-use test cases for this API in cURL format. Each test should be executable immediately. + +Include these test categories: +1. **Valid Cases**: Different valid parameter values (use real-world examples like other country codes if this is a country API) +2. **Invalid Parameter Tests**: Missing parameters, empty values, incorrect formats +3. **Edge Cases**: Special characters, long values, unexpected inputs +4. **Validation Tests**: Test input validation and error handling + +For each test case: +1. Provide a brief description of what the test verifies +2. Include a complete, executable cURL command +3. Show the expected outcome (status code and sample response) +4. Organize tests in a way that's easy to copy and run + +Focus on creating realistic test values based on the API context (e.g., for a country flag API, use real country codes, invalid codes, etc.) +"""; + + return _service.generateResponse(prompt); + } + + String _analyzeParameters(Map parameters) { + if (parameters.isEmpty) { + return "No parameters detected"; + } + + Map analysis = {}; + + parameters.forEach((key, value) { + // Try to determine parameter type and format + if (RegExp(r'^[A-Z]{3}$').hasMatch(value)) { + analysis[key] = "Appears to be a 3-letter country code (ISO 3166-1 alpha-3)"; + } else if (RegExp(r'^[A-Z]{2}$').hasMatch(value)) { + analysis[key] = "Appears to be a 2-letter country code (ISO 3166-1 alpha-2)"; + } else if (RegExp(r'^\d+$').hasMatch(value)) { + analysis[key] = "Numeric value"; + } else if (RegExp(r'^[a-zA-Z]+$').hasMatch(value)) { + analysis[key] = "Alphabetic string"; + } else { + analysis[key] = "Unknown format: $value"; + } + }); + + return jsonEncode(analysis); + } +} diff --git a/lib/dashbot/services/dashbot_service.dart b/lib/dashbot/services/dashbot_service.dart index 8a1abd1e..64b3ca86 100644 --- a/lib/dashbot/services/dashbot_service.dart +++ b/lib/dashbot/services/dashbot_service.dart @@ -2,6 +2,7 @@ import 'package:apidash/dashbot/features/debug.dart'; import 'package:apidash/dashbot/features/documentation.dart'; import 'package:ollama_dart/ollama_dart.dart'; import 'package:apidash/dashbot/features/explain.dart'; +import 'package:apidash/dashbot/features/test_generator.dart'; // New import import 'package:apidash/models/request_model.dart'; import 'package:apidash/dashbot/features/general_query.dart'; @@ -10,16 +11,16 @@ class DashBotService { late final ExplainFeature _explainFeature; late final DebugFeature _debugFeature; late final DocumentationFeature _documentationFeature; + late final TestGeneratorFeature _testGeneratorFeature; // New feature final GeneralQueryFeature _generalQueryFeature; - DashBotService() : _client = OllamaClient(baseUrl: 'http://127.0.0.1:11434/api'), _generalQueryFeature = GeneralQueryFeature(OllamaClient(baseUrl: 'http://127.0.0.1:11434/api')) { - _explainFeature = ExplainFeature(this); _debugFeature = DebugFeature(this); _documentationFeature = DocumentationFeature(this); + _testGeneratorFeature = TestGeneratorFeature(this); // Initialize new feature } Future generateResponse(String prompt) async { @@ -37,6 +38,9 @@ class DashBotService { } else if (input == "Document API") { return _documentationFeature.generateApiDocumentation( requestModel: requestModel, responseModel: responseModel); + } else if (input == "Test API") { // New condition + return _testGeneratorFeature.generateApiTests( + requestModel: requestModel, responseModel: responseModel); } return _generalQueryFeature.generateResponse(input, requestModel: requestModel, responseModel: responseModel); diff --git a/lib/dashbot/widgets/dashbot_widget.dart b/lib/dashbot/widgets/dashbot_widget.dart index 7618fb98..a113bb9d 100644 --- a/lib/dashbot/widgets/dashbot_widget.dart +++ b/lib/dashbot/widgets/dashbot_widget.dart @@ -1,8 +1,8 @@ -// lib/dashbot/widgets/dashbot_widget.dart import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:apidash/dashbot/providers/dashbot_providers.dart'; import 'package:apidash/providers/providers.dart'; +import 'test_runner_widget.dart'; import 'chat_bubble.dart'; class DashBotWidget extends ConsumerStatefulWidget { @@ -18,6 +18,8 @@ class _DashBotWidgetState extends ConsumerState { final TextEditingController _controller = TextEditingController(); late ScrollController _scrollController; bool _isLoading = false; + bool _showTestRunner = false; + String _testCases = ''; @override void initState() { @@ -34,6 +36,12 @@ class _DashBotWidgetState extends ConsumerState { Future _sendMessage(String message) async { if (message.trim().isEmpty) return; + + // Reset test runner state when sending a new message + setState(() { + _showTestRunner = false; + }); + final dashBotService = ref.read(dashBotServiceProvider); final requestModel = ref.read(selectedRequestModelProvider); final responseModel = requestModel?.httpResponseModel; @@ -48,10 +56,19 @@ class _DashBotWidgetState extends ConsumerState { try { final response = await dashBotService.handleRequest( message, requestModel, responseModel); + ref.read(chatMessagesProvider.notifier).addMessage({ 'role': 'bot', 'message': response, }); + + // If message was "Test API", show the test runner + if (message == "Test API") { + setState(() { + _showTestRunner = true; + _testCases = response; + }); + } } catch (error, stackTrace) { debugPrint('Error in _sendMessage: $error'); debugPrint('StackTrace: $stackTrace'); @@ -78,30 +95,38 @@ class _DashBotWidgetState extends ConsumerState { final statusCode = requestModel?.httpResponseModel?.statusCode; final showDebugButton = statusCode != null && statusCode >= 400; - return Container( - height: 450, - width: double.infinity, - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: BorderRadius.circular(12), - boxShadow: const [ - BoxShadow(color: Colors.black12, blurRadius: 8, offset: Offset(0, 4)) + return Column( + children: [ + Container( + height: 450, + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: BorderRadius.circular(12), + boxShadow: const [ + BoxShadow(color: Colors.black12, blurRadius: 8, offset: Offset(0, 4)) + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildHeader(context), + const SizedBox(height: 12), + _buildQuickActions(showDebugButton), + const SizedBox(height: 12), + Expanded(child: _buildChatArea(messages)), + if (_isLoading) _buildLoadingIndicator(), + const SizedBox(height: 10), + _buildInputArea(context), + ], + ), + ), + if (_showTestRunner) ...[ + const SizedBox(height: 20), + TestRunnerWidget(testCases: _testCases), ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildHeader(context), - const SizedBox(height: 12), - _buildQuickActions(showDebugButton), - const SizedBox(height: 12), - Expanded(child: _buildChatArea(messages)), - if (_isLoading) _buildLoadingIndicator(), - const SizedBox(height: 10), - _buildInputArea(context), - ], - ), + ], ); } @@ -114,8 +139,12 @@ class _DashBotWidgetState extends ConsumerState { IconButton( icon: const Icon(Icons.delete_sweep), tooltip: 'Clear Chat', - onPressed: () => - ref.read(chatMessagesProvider.notifier).clearMessages(), + onPressed: () { + ref.read(chatMessagesProvider.notifier).clearMessages(); + setState(() { + _showTestRunner = false; + }); + }, ), ], ); @@ -147,7 +176,15 @@ class _DashBotWidgetState extends ConsumerState { onPressed: () => _sendMessage("Document API"), icon: const Icon(Icons.description_outlined), label: const Text("Document"), - style: ElevatedButton.styleFrom ( + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + ), + ), + ElevatedButton.icon( + onPressed: () => _sendMessage("Test API"), + icon: const Icon(Icons.science_outlined), + label: const Text("Test"), + style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), ), ), @@ -193,13 +230,19 @@ class _DashBotWidgetState extends ConsumerState { hintText: 'Ask DashBot...', border: InputBorder.none, ), - onSubmitted: _sendMessage, + onSubmitted: (value) { + _sendMessage(value); + _controller.clear(); + }, maxLines: 1, ), ), IconButton( icon: const Icon(Icons.send), - onPressed: () => _sendMessage(_controller.text), + onPressed: () { + _sendMessage(_controller.text); + _controller.clear(); + }, ), ], ), diff --git a/lib/dashbot/widgets/test_runner_widget.dart b/lib/dashbot/widgets/test_runner_widget.dart new file mode 100644 index 00000000..e2a449a1 --- /dev/null +++ b/lib/dashbot/widgets/test_runner_widget.dart @@ -0,0 +1,352 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'content_renderer.dart'; + +class TestRunnerWidget extends ConsumerStatefulWidget { + final String testCases; + + const TestRunnerWidget({Key? key, required this.testCases}) : super(key: key); + + @override + ConsumerState createState() => _TestRunnerWidgetState(); +} + +class _TestRunnerWidgetState extends ConsumerState { + List> _parsedTests = []; + Map> _results = {}; + bool _isRunning = false; + int _currentTestIndex = -1; + + @override + void initState() { + super.initState(); + _parseTestCases(); + } + + void _parseTestCases() { + // Basic parsing of cURL commands from the text + final curlRegex = RegExp(r'```(.*?)curl\s+(.*?)```', dotAll: true); + final descriptionRegex = RegExp(r'###\s*(.*?)\n', dotAll: true); + + final curlMatches = curlRegex.allMatches(widget.testCases); + final descMatches = descriptionRegex.allMatches(widget.testCases); + + List> tests = []; + int index = 0; + + for (var match in curlMatches) { + String? description = "Test case ${index + 1}"; + if (index < descMatches.length) { + description = descMatches.elementAt(index).group(1)?.trim(); + } + + final curlCommand = match.group(2)?.trim() ?? ""; + + tests.add({ + 'description': description, + 'command': curlCommand, + 'index': index, + }); + + index++; + } + + setState(() { + _parsedTests = tests; + }); + } + + Future _runTest(int index) async { + if (_isRunning) return; + + setState(() { + _isRunning = true; + _currentTestIndex = index; + }); + + final test = _parsedTests[index]; + final command = test['command']; + + try { + // Parse curl command to make HTTP request + // This is a simplified version - a real implementation would need to handle all curl options + final urlMatch = RegExp(r'"([^"]*)"').firstMatch(command); + String url = urlMatch?.group(1) ?? ""; + + if (url.isEmpty) { + final urlMatch2 = RegExp(r"'([^']*)'").firstMatch(command); + url = urlMatch2?.group(1) ?? ""; + } + + if (url.isEmpty) { + throw Exception("Could not parse URL from curl command"); + } + + // Determine HTTP method (default to GET) + String method = "GET"; + if (command.contains("-X POST") || command.contains("--request POST")) { + method = "POST"; + } else if (command.contains("-X PUT") || command.contains("--request PUT")) { + method = "PUT"; + } // Add other methods as needed + + // Make the actual request + http.Response response; + if (method == "GET") { + response = await http.get(Uri.parse(url)); + } else if (method == "POST") { + // Extract body if present + final bodyMatch = RegExp(r'-d\s+"([^"]*)"').firstMatch(command); + final body = bodyMatch?.group(1) ?? ""; + response = await http.post(Uri.parse(url), body: body); + } else { + throw Exception("Unsupported HTTP method: $method"); + } + + setState(() { + _results[index] = { + 'status': response.statusCode, + 'body': response.body, + 'headers': response.headers, + 'isSuccess': response.statusCode >= 200 && response.statusCode < 300, + }; + }); + } catch (e) { + setState(() { + _results[index] = { + 'error': e.toString(), + 'isSuccess': false, + }; + }); + } finally { + setState(() { + _isRunning = false; + _currentTestIndex = -1; + }); + } + } + + Future _runAllTests() async { + for (int i = 0; i < _parsedTests.length; i++) { + await _runTest(i); + } + } + + @override + Widget build(BuildContext context) { + return Container( + height: 500, + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildHeader(context), + const SizedBox(height: 16), + Expanded( + child: _parsedTests.isEmpty + ? Center(child: Text("No test cases found")) + : _buildTestList(), + ), + const SizedBox(height: 16), + _buildActionButtons(), + ], + ), + ); + } + + Widget _buildHeader(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'API Test Runner', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + IconButton( + icon: const Icon(Icons.help_outline), + tooltip: 'How to use', + onPressed: () { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('API Test Runner'), + content: const Text( + 'This tool runs the API tests generated from your request.\n\n' + '• Click "Run All" to execute all tests\n' + '• Click individual "Run" buttons to execute specific tests\n' + '• Click "Copy" to copy a curl command to clipboard', + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Close'), + ), + ], + ), + ); + }, + ), + ], + ); + } + + Widget _buildTestList() { + return ListView.builder( + itemCount: _parsedTests.length, + itemBuilder: (context, index) { + final test = _parsedTests[index]; + final result = _results[index]; + final bool hasResult = result != null; + final bool isSuccess = hasResult ? result['isSuccess'] ?? false : false; + + return Card( + margin: const EdgeInsets.only(bottom: 12), + child: ExpansionTile( + title: Text( + test['description'] ?? "Test case ${index + 1}", + style: TextStyle( + fontWeight: FontWeight.bold, + color: hasResult + ? (isSuccess + ? Colors.green + : Colors.red) + : null, + ), + ), + subtitle: Text('Test ${index + 1} of ${_parsedTests.length}'), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon(Icons.copy), + tooltip: 'Copy command', + onPressed: () { + Clipboard.setData(ClipboardData(text: test['command'])); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Command copied to clipboard')), + ); + }, + ), + if (_currentTestIndex == index && _isRunning) + const SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator(strokeWidth: 2), + ) + else + IconButton( + icon: Icon(hasResult + ? (isSuccess ? Icons.check_circle : Icons.error) + : Icons.play_arrow), + color: hasResult + ? (isSuccess ? Colors.green : Colors.red) + : null, + tooltip: hasResult ? 'Run again' : 'Run test', + onPressed: () => _runTest(index), + ), + ], + ), + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Command:', + style: TextStyle(fontWeight: FontWeight.bold), + ), + Container( + padding: const EdgeInsets.all(8), + margin: const EdgeInsets.only(top: 4, bottom: 16), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceContainerLow, + borderRadius: BorderRadius.circular(4), + ), + width: double.infinity, + child: SelectableText( + test['command'], + style: const TextStyle(fontFamily: 'monospace'), + ), + ), + if (hasResult) ...[ + const Divider(), + Text( + 'Result:', + style: TextStyle( + fontWeight: FontWeight.bold, + color: isSuccess ? Colors.green : Colors.red, + ), + ), + const SizedBox(height: 8), + if (result.containsKey('error')) + Text( + 'Error: ${result['error']}', + style: const TextStyle(color: Colors.red), + ) + else ...[ + Text('Status: ${result['status']}'), + const SizedBox(height: 8), + const Text( + 'Response:', + style: TextStyle(fontWeight: FontWeight.bold), + ), + Container( + padding: const EdgeInsets.all(8), + margin: const EdgeInsets.only(top: 4), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceContainerLow, + borderRadius: BorderRadius.circular(4), + ), + width: double.infinity, + child: renderContent( + context, + _tryFormatJson(result['body']) + ), + ), + ], + ], + ], + ), + ), + ], + ), + ); + }, + ); + } + + Widget _buildActionButtons() { + return Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + ElevatedButton.icon( + onPressed: _isRunning ? null : _runAllTests, + icon: const Icon(Icons.play_circle_outline), + label: const Text("Run All Tests"), + ), + ], + ); + } + + String _tryFormatJson(dynamic input) { + if (input == null) return "null"; + + if (input is! String) return input.toString(); + + try { + final decoded = json.decode(input); + return JsonEncoder.withIndent(' ').convert(decoded); + } catch (e) { + return input; + } + } +} From 877ea6127c64fca881271cd2e49a7a82a02ca894 Mon Sep 17 00:00:00 2001 From: siddu015 <116783967+siddu015@users.noreply.github.com> Date: Fri, 14 Mar 2025 00:43:59 +0530 Subject: [PATCH 03/30] Test cases generator response with a button to run the test cases --- lib/dashbot/widgets/dashbot_widget.dart | 120 ++++++++-------- lib/dashbot/widgets/test_runner_widget.dart | 145 ++++++++------------ 2 files changed, 124 insertions(+), 141 deletions(-) diff --git a/lib/dashbot/widgets/dashbot_widget.dart b/lib/dashbot/widgets/dashbot_widget.dart index a113bb9d..846aca67 100644 --- a/lib/dashbot/widgets/dashbot_widget.dart +++ b/lib/dashbot/widgets/dashbot_widget.dart @@ -6,9 +6,7 @@ import 'test_runner_widget.dart'; import 'chat_bubble.dart'; class DashBotWidget extends ConsumerStatefulWidget { - const DashBotWidget({ - super.key, - }); + const DashBotWidget({super.key}); @override ConsumerState createState() => _DashBotWidgetState(); @@ -18,8 +16,6 @@ class _DashBotWidgetState extends ConsumerState { final TextEditingController _controller = TextEditingController(); late ScrollController _scrollController; bool _isLoading = false; - bool _showTestRunner = false; - String _testCases = ''; @override void initState() { @@ -37,11 +33,6 @@ class _DashBotWidgetState extends ConsumerState { Future _sendMessage(String message) async { if (message.trim().isEmpty) return; - // Reset test runner state when sending a new message - setState(() { - _showTestRunner = false; - }); - final dashBotService = ref.read(dashBotServiceProvider); final requestModel = ref.read(selectedRequestModelProvider); final responseModel = requestModel?.httpResponseModel; @@ -57,18 +48,15 @@ class _DashBotWidgetState extends ConsumerState { final response = await dashBotService.handleRequest( message, requestModel, responseModel); + // If "Test API" is requested, append a button to the response + final botMessage = message == "Test API" + ? "$response\n\n**[Run Test Cases]**" + : response; + ref.read(chatMessagesProvider.notifier).addMessage({ 'role': 'bot', - 'message': response, + 'message': botMessage, }); - - // If message was "Test API", show the test runner - if (message == "Test API") { - setState(() { - _showTestRunner = true; - _testCases = response; - }); - } } catch (error, stackTrace) { debugPrint('Error in _sendMessage: $error'); debugPrint('StackTrace: $stackTrace'); @@ -88,6 +76,19 @@ class _DashBotWidgetState extends ConsumerState { } } + void _showTestRunner(String testCases) { + showDialog( + context: context, + builder: (context) => Dialog( + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.8, + height: 500, + child: TestRunnerWidget(testCases: testCases), + ), + ), + ); + } + @override Widget build(BuildContext context) { final messages = ref.watch(chatMessagesProvider); @@ -95,38 +96,30 @@ class _DashBotWidgetState extends ConsumerState { final statusCode = requestModel?.httpResponseModel?.statusCode; final showDebugButton = statusCode != null && statusCode >= 400; - return Column( - children: [ - Container( - height: 450, - width: double.infinity, - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: BorderRadius.circular(12), - boxShadow: const [ - BoxShadow(color: Colors.black12, blurRadius: 8, offset: Offset(0, 4)) - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildHeader(context), - const SizedBox(height: 12), - _buildQuickActions(showDebugButton), - const SizedBox(height: 12), - Expanded(child: _buildChatArea(messages)), - if (_isLoading) _buildLoadingIndicator(), - const SizedBox(height: 10), - _buildInputArea(context), - ], - ), - ), - if (_showTestRunner) ...[ - const SizedBox(height: 20), - TestRunnerWidget(testCases: _testCases), + return Container( + height: 450, + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: BorderRadius.circular(12), + boxShadow: const [ + BoxShadow(color: Colors.black12, blurRadius: 8, offset: Offset(0, 4)) ], - ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildHeader(context), + const SizedBox(height: 12), + _buildQuickActions(showDebugButton), + const SizedBox(height: 12), + Expanded(child: _buildChatArea(messages)), + if (_isLoading) _buildLoadingIndicator(), + const SizedBox(height: 10), + _buildInputArea(context), + ], + ), ); } @@ -141,9 +134,6 @@ class _DashBotWidgetState extends ConsumerState { tooltip: 'Clear Chat', onPressed: () { ref.read(chatMessagesProvider.notifier).clearMessages(); - setState(() { - _showTestRunner = false; - }); }, ), ], @@ -199,8 +189,30 @@ class _DashBotWidgetState extends ConsumerState { itemCount: messages.length, itemBuilder: (context, index) { final message = messages.reversed.toList()[index]; + final isBot = message['role'] == 'bot'; + final text = message['message'] as String; + + // Check if the message contains the "Run Test Cases" button + if (isBot && text.contains("[Run Test Cases]")) { + final testCases = text.replaceAll("\n\n**[Run Test Cases]**", ""); + return Column( + crossAxisAlignment: + isBot ? CrossAxisAlignment.start : CrossAxisAlignment.end, + children: [ + ChatBubble(message: testCases, isUser: false), + Padding( + padding: const EdgeInsets.only(left: 12, top: 4, bottom: 4), + child: ElevatedButton( + onPressed: () => _showTestRunner(testCases), + child: const Text("Run Test Cases"), + ), + ), + ], + ); + } + return ChatBubble( - message: message['message'], + message: text, isUser: message['role'] == 'user', ); }, diff --git a/lib/dashbot/widgets/test_runner_widget.dart b/lib/dashbot/widgets/test_runner_widget.dart index e2a449a1..0057bba8 100644 --- a/lib/dashbot/widgets/test_runner_widget.dart +++ b/lib/dashbot/widgets/test_runner_widget.dart @@ -27,8 +27,7 @@ class _TestRunnerWidgetState extends ConsumerState { } void _parseTestCases() { - // Basic parsing of cURL commands from the text - final curlRegex = RegExp(r'```(.*?)curl\s+(.*?)```', dotAll: true); + final curlRegex = RegExp(r'```bash\ncurl\s+(.*?)\n```', dotAll: true); final descriptionRegex = RegExp(r'###\s*(.*?)\n', dotAll: true); final curlMatches = curlRegex.allMatches(widget.testCases); @@ -43,7 +42,7 @@ class _TestRunnerWidgetState extends ConsumerState { description = descMatches.elementAt(index).group(1)?.trim(); } - final curlCommand = match.group(2)?.trim() ?? ""; + final curlCommand = match.group(1)?.trim() ?? ""; tests.add({ 'description': description, @@ -71,34 +70,22 @@ class _TestRunnerWidgetState extends ConsumerState { final command = test['command']; try { - // Parse curl command to make HTTP request - // This is a simplified version - a real implementation would need to handle all curl options - final urlMatch = RegExp(r'"([^"]*)"').firstMatch(command); - String url = urlMatch?.group(1) ?? ""; + final urlMatch = RegExp(r'"([^"]*)"').firstMatch(command) ?? + RegExp(r"'([^']*)'").firstMatch(command); + final url = urlMatch?.group(1) ?? ""; + if (url.isEmpty) throw Exception("Could not parse URL from curl command"); - if (url.isEmpty) { - final urlMatch2 = RegExp(r"'([^']*)'").firstMatch(command); - url = urlMatch2?.group(1) ?? ""; - } - - if (url.isEmpty) { - throw Exception("Could not parse URL from curl command"); - } - - // Determine HTTP method (default to GET) String method = "GET"; if (command.contains("-X POST") || command.contains("--request POST")) { method = "POST"; } else if (command.contains("-X PUT") || command.contains("--request PUT")) { method = "PUT"; - } // Add other methods as needed + } - // Make the actual request http.Response response; if (method == "GET") { response = await http.get(Uri.parse(url)); } else if (method == "POST") { - // Extract body if present final bodyMatch = RegExp(r'-d\s+"([^"]*)"').firstMatch(command); final body = bodyMatch?.group(1) ?? ""; response = await http.post(Uri.parse(url), body: body); @@ -131,70 +118,58 @@ class _TestRunnerWidgetState extends ConsumerState { Future _runAllTests() async { for (int i = 0; i < _parsedTests.length; i++) { + if (!mounted) return; await _runTest(i); } } @override Widget build(BuildContext context) { - return Container( - height: 500, - width: double.infinity, - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: BorderRadius.circular(12), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildHeader(context), - const SizedBox(height: 16), - Expanded( - child: _parsedTests.isEmpty - ? Center(child: Text("No test cases found")) - : _buildTestList(), + return Scaffold( + appBar: AppBar( + title: const Text('API Test Runner'), + actions: [ + IconButton( + icon: const Icon(Icons.help_outline), + tooltip: 'How to use', + onPressed: () { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('API Test Runner'), + content: const Text( + 'Run generated API tests:\n\n' + '• "Run All" executes all tests\n' + '• "Run" executes a single test\n' + '• "Copy" copies the curl command', + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Close'), + ), + ], + ), + ); + }, ), - const SizedBox(height: 16), - _buildActionButtons(), ], ), - ); - } - - Widget _buildHeader(BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( - 'API Test Runner', - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: _parsedTests.isEmpty + ? const Center(child: Text("No test cases found")) + : _buildTestList(), + ), + const SizedBox(height: 16), + _buildActionButtons(), + ], ), - IconButton( - icon: const Icon(Icons.help_outline), - tooltip: 'How to use', - onPressed: () { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('API Test Runner'), - content: const Text( - 'This tool runs the API tests generated from your request.\n\n' - '• Click "Run All" to execute all tests\n' - '• Click individual "Run" buttons to execute specific tests\n' - '• Click "Copy" to copy a curl command to clipboard', - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text('Close'), - ), - ], - ), - ); - }, - ), - ], + ), ); } @@ -205,19 +180,17 @@ class _TestRunnerWidgetState extends ConsumerState { final test = _parsedTests[index]; final result = _results[index]; final bool hasResult = result != null; - final bool isSuccess = hasResult ? result['isSuccess'] ?? false : false; + final bool isSuccess = hasResult && (result['isSuccess'] ?? false); return Card( - margin: const EdgeInsets.only(bottom: 12), + margin: const EdgeInsets.symmetric(vertical: 6), child: ExpansionTile( title: Text( test['description'] ?? "Test case ${index + 1}", style: TextStyle( fontWeight: FontWeight.bold, color: hasResult - ? (isSuccess - ? Colors.green - : Colors.red) + ? (isSuccess ? Colors.green : Colors.red) : null, ), ), @@ -231,7 +204,7 @@ class _TestRunnerWidgetState extends ConsumerState { onPressed: () { Clipboard.setData(ClipboardData(text: test['command'])); ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Command copied to clipboard')), + const SnackBar(content: Text('Command copied')), ); }, ), @@ -303,14 +276,14 @@ class _TestRunnerWidgetState extends ConsumerState { padding: const EdgeInsets.all(8), margin: const EdgeInsets.only(top: 4), decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceContainerLow, + color: Theme.of(context) + .colorScheme + .surfaceContainerLow, borderRadius: BorderRadius.circular(4), ), width: double.infinity, child: renderContent( - context, - _tryFormatJson(result['body']) - ), + context, _tryFormatJson(result['body'])), ), ], ], @@ -339,13 +312,11 @@ class _TestRunnerWidgetState extends ConsumerState { String _tryFormatJson(dynamic input) { if (input == null) return "null"; - if (input is! String) return input.toString(); - try { final decoded = json.decode(input); return JsonEncoder.withIndent(' ').convert(decoded); - } catch (e) { + } catch (_) { return input; } } From 7582c7888089218c5c7052bd3a63d8926b38a60b Mon Sep 17 00:00:00 2001 From: siddu015 <116783967+siddu015@users.noreply.github.com> Date: Thu, 20 Mar 2025 16:57:21 +0530 Subject: [PATCH 04/30] Test cases generator modification to only Run test cases feature without response --- lib/dashbot/features/test_generator.dart | 9 +++-- lib/dashbot/widgets/dashbot_widget.dart | 42 +++++++++++++++--------- 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/lib/dashbot/features/test_generator.dart b/lib/dashbot/features/test_generator.dart index c638964b..301eb475 100644 --- a/lib/dashbot/features/test_generator.dart +++ b/lib/dashbot/features/test_generator.dart @@ -1,5 +1,5 @@ import 'dart:convert'; -import '../services/dashbot_service.dart'; +import 'package:apidash/dashbot/services/dashbot_service.dart'; import 'package:apidash/models/request_model.dart'; class TestGeneratorFeature { @@ -67,7 +67,12 @@ For each test case: Focus on creating realistic test values based on the API context (e.g., for a country flag API, use real country codes, invalid codes, etc.) """; - return _service.generateResponse(prompt); + // Generate the test cases + final testCases = await _service.generateResponse(prompt); + + // Return only a button trigger message with the test cases hidden + // This will be detected in DashBotWidget to show only a button instead of the full text + return "TEST_CASES_HIDDEN\n$testCases"; } String _analyzeParameters(Map parameters) { diff --git a/lib/dashbot/widgets/dashbot_widget.dart b/lib/dashbot/widgets/dashbot_widget.dart index 846aca67..65e706ea 100644 --- a/lib/dashbot/widgets/dashbot_widget.dart +++ b/lib/dashbot/widgets/dashbot_widget.dart @@ -48,15 +48,25 @@ class _DashBotWidgetState extends ConsumerState { final response = await dashBotService.handleRequest( message, requestModel, responseModel); - // If "Test API" is requested, append a button to the response - final botMessage = message == "Test API" - ? "$response\n\n**[Run Test Cases]**" - : response; + // Check if this is a test case response with hidden content + if (response.startsWith("TEST_CASES_HIDDEN\n")) { + // Extract the test cases but don't show them in the message + final testCases = response.replaceFirst("TEST_CASES_HIDDEN\n", ""); - ref.read(chatMessagesProvider.notifier).addMessage({ - 'role': 'bot', - 'message': botMessage, - }); + // Add a message with a marker that will trigger the button display + ref.read(chatMessagesProvider.notifier).addMessage({ + 'role': 'bot', + 'message': "Test cases generated successfully. Click the button below to run them.", + 'testCases': testCases, + 'showTestButton': true, + }); + } else { + // Normal message handling + ref.read(chatMessagesProvider.notifier).addMessage({ + 'role': 'bot', + 'message': response, + }); + } } catch (error, stackTrace) { debugPrint('Error in _sendMessage: $error'); debugPrint('StackTrace: $stackTrace'); @@ -191,20 +201,20 @@ class _DashBotWidgetState extends ConsumerState { final message = messages.reversed.toList()[index]; final isBot = message['role'] == 'bot'; final text = message['message'] as String; + final showTestButton = message['showTestButton'] == true; + final testCases = message['testCases'] as String?; - // Check if the message contains the "Run Test Cases" button - if (isBot && text.contains("[Run Test Cases]")) { - final testCases = text.replaceAll("\n\n**[Run Test Cases]**", ""); + if (isBot && showTestButton && testCases != null) { return Column( - crossAxisAlignment: - isBot ? CrossAxisAlignment.start : CrossAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - ChatBubble(message: testCases, isUser: false), + ChatBubble(message: text, isUser: false), Padding( padding: const EdgeInsets.only(left: 12, top: 4, bottom: 4), - child: ElevatedButton( + child: ElevatedButton.icon( onPressed: () => _showTestRunner(testCases), - child: const Text("Run Test Cases"), + icon: const Icon(Icons.play_arrow), + label: const Text("Run Test Cases"), ), ), ], From f44091a50c8464d8794d984acf9e7e0d6ede5274 Mon Sep 17 00:00:00 2001 From: siddu015 <116783967+siddu015@users.noreply.github.com> Date: Sat, 22 Mar 2025 15:18:25 +0530 Subject: [PATCH 05/30] Move DashBot to bottom-right, add close/minimize/maximize buttons and toggle FAB visibility --- lib/dashbot/dashbot.dart | 67 ++++++- lib/dashbot/widgets/dashbot_widget.dart | 237 ++++++++++++++++-------- lib/screens/dashboard.dart | 208 +++++++++++---------- 3 files changed, 328 insertions(+), 184 deletions(-) diff --git a/lib/dashbot/dashbot.dart b/lib/dashbot/dashbot.dart index 9454aa85..e2371acd 100644 --- a/lib/dashbot/dashbot.dart +++ b/lib/dashbot/dashbot.dart @@ -1 +1,66 @@ -export 'widgets/dashbot_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:apidash/dashbot/widgets/dashbot_widget.dart'; + +// Provider to manage DashBot visibility state +final dashBotVisibilityProvider = StateProvider((ref) => false); +final dashBotMinimizedProvider = StateProvider((ref) => false); + +// Function to show DashBot in a bottom sheet (old style) +void showDashBotBottomSheet(BuildContext context) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (context) => const Padding( + padding: EdgeInsets.all(16.0), + child: DashBotWidget(), + ), + ); +} + +// Function to toggle DashBot overlay (new style) +void toggleDashBotOverlay(WidgetRef ref) { + ref.read(dashBotVisibilityProvider.notifier).state = true; + ref.read(dashBotMinimizedProvider.notifier).state = false; +} + +// DashBot Overlay Widget +class DashBotOverlay extends ConsumerWidget { + const DashBotOverlay({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final isMinimized = ref.watch(dashBotMinimizedProvider); + + return Material( + elevation: 8, + borderRadius: BorderRadius.circular(12), + child: SizedBox( + width: 400, // Fixed width for the DashBot + height: isMinimized ? 120 : 450, + child: const DashBotWidget(), + ), + ); + } +} + +// FloatingActionButton for DashBot +class DashBotFAB extends ConsumerWidget { + final bool useOverlay; + + const DashBotFAB({this.useOverlay = true, super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return FloatingActionButton( + onPressed: () { + if (useOverlay) { + toggleDashBotOverlay(ref); + } else { + showDashBotBottomSheet(context); + } + }, + child: const Icon(Icons.help_outline), + ); + } +} diff --git a/lib/dashbot/widgets/dashbot_widget.dart b/lib/dashbot/widgets/dashbot_widget.dart index 65e706ea..2426fca8 100644 --- a/lib/dashbot/widgets/dashbot_widget.dart +++ b/lib/dashbot/widgets/dashbot_widget.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:apidash/dashbot/providers/dashbot_providers.dart'; import 'package:apidash/providers/providers.dart'; +import 'package:apidash/dashbot/dashbot.dart'; import 'test_runner_widget.dart'; import 'chat_bubble.dart'; @@ -78,7 +79,7 @@ class _DashBotWidgetState extends ConsumerState { setState(() => _isLoading = false); WidgetsBinding.instance.addPostFrameCallback((_) { _scrollController.animateTo( - 0, + _scrollController.position.minScrollExtent, duration: const Duration(milliseconds: 300), curve: Curves.easeOut, ); @@ -105,19 +106,18 @@ class _DashBotWidgetState extends ConsumerState { final requestModel = ref.read(selectedRequestModelProvider); final statusCode = requestModel?.httpResponseModel?.statusCode; final showDebugButton = statusCode != null && statusCode >= 400; + final isMinimized = ref.watch(dashBotMinimizedProvider); return Container( - height: 450, + height: double.infinity, width: double.infinity, - padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(12), - boxShadow: const [ - BoxShadow(color: Colors.black12, blurRadius: 8, offset: Offset(0, 4)) - ], ), - child: Column( + child: isMinimized + ? _buildMinimizedView(context) + : Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildHeader(context), @@ -134,115 +134,181 @@ class _DashBotWidgetState extends ConsumerState { } Widget _buildHeader(BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + final isMinimized = ref.watch(dashBotMinimizedProvider); + + return Padding( + padding: const EdgeInsets.fromLTRB(16, 16, 16, 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'DashBot', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + Row( + children: [ + // Minimize/Maximize button with proper alignment + IconButton( + padding: const EdgeInsets.all(8), + visualDensity: VisualDensity.compact, + icon: Icon( + isMinimized ? Icons.fullscreen : Icons.remove, + size: 20, + ), + tooltip: isMinimized ? 'Maximize' : 'Minimize', + onPressed: () { + ref.read(dashBotMinimizedProvider.notifier).state = !isMinimized; + }, + ), + // Close button + IconButton( + padding: const EdgeInsets.all(8), + visualDensity: VisualDensity.compact, + icon: const Icon(Icons.close, size: 20), + tooltip: 'Close', + onPressed: () { + ref.read(dashBotVisibilityProvider.notifier).state = false; + }, + ), + // Clear chat button + IconButton( + padding: const EdgeInsets.all(8), + visualDensity: VisualDensity.compact, + icon: const Icon(Icons.delete_sweep, size: 20), + tooltip: 'Clear Chat', + onPressed: () { + ref.read(chatMessagesProvider.notifier).clearMessages(); + }, + ), + ], + ), + ], + ), + ); + } + + Widget _buildMinimizedView(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('DashBot', - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), - IconButton( - icon: const Icon(Icons.delete_sweep), - tooltip: 'Clear Chat', - onPressed: () { - ref.read(chatMessagesProvider.notifier).clearMessages(); - }, + _buildHeader(context), + const SizedBox(height: 8), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: _buildInputArea(context), ), ], ); } Widget _buildQuickActions(bool showDebugButton) { - return Wrap( - spacing: 8, - runSpacing: 8, - children: [ - ElevatedButton.icon( - onPressed: () => _sendMessage("Explain API"), - icon: const Icon(Icons.info_outline), - label: const Text("Explain"), - style: ElevatedButton.styleFrom( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - ), - ), - if (showDebugButton) + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Wrap( + spacing: 8, + runSpacing: 8, + children: [ ElevatedButton.icon( - onPressed: () => _sendMessage("Debug API"), - icon: const Icon(Icons.bug_report_outlined), - label: const Text("Debug"), + onPressed: () => _sendMessage("Explain API"), + icon: const Icon(Icons.info_outline, size: 16), + label: const Text("Explain"), style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + visualDensity: VisualDensity.compact, ), ), - ElevatedButton.icon( - onPressed: () => _sendMessage("Document API"), - icon: const Icon(Icons.description_outlined), - label: const Text("Document"), - style: ElevatedButton.styleFrom( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + if (showDebugButton) + ElevatedButton.icon( + onPressed: () => _sendMessage("Debug API"), + icon: const Icon(Icons.bug_report_outlined, size: 16), + label: const Text("Debug"), + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + visualDensity: VisualDensity.compact, + ), + ), + ElevatedButton.icon( + onPressed: () => _sendMessage("Document API"), + icon: const Icon(Icons.description_outlined, size: 16), + label: const Text("Document"), + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + visualDensity: VisualDensity.compact, + ), ), - ), - ElevatedButton.icon( - onPressed: () => _sendMessage("Test API"), - icon: const Icon(Icons.science_outlined), - label: const Text("Test"), - style: ElevatedButton.styleFrom( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + ElevatedButton.icon( + onPressed: () => _sendMessage("Test API"), + icon: const Icon(Icons.science_outlined, size: 16), + label: const Text("Test"), + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + visualDensity: VisualDensity.compact, + ), ), - ), - ], + ], + ), ); } Widget _buildChatArea(List> messages) { - return ListView.builder( - controller: _scrollController, - reverse: true, - itemCount: messages.length, - itemBuilder: (context, index) { - final message = messages.reversed.toList()[index]; - final isBot = message['role'] == 'bot'; - final text = message['message'] as String; - final showTestButton = message['showTestButton'] == true; - final testCases = message['testCases'] as String?; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: ListView.builder( + controller: _scrollController, + reverse: true, + itemCount: messages.length, + itemBuilder: (context, index) { + final message = messages.reversed.toList()[index]; + final isBot = message['role'] == 'bot'; + final text = message['message'] as String; + final showTestButton = message['showTestButton'] == true; + final testCases = message['testCases'] as String?; - if (isBot && showTestButton && testCases != null) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ChatBubble(message: text, isUser: false), - Padding( - padding: const EdgeInsets.only(left: 12, top: 4, bottom: 4), - child: ElevatedButton.icon( - onPressed: () => _showTestRunner(testCases), - icon: const Icon(Icons.play_arrow), - label: const Text("Run Test Cases"), + if (isBot && showTestButton && testCases != null) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ChatBubble(message: text, isUser: false), + Padding( + padding: const EdgeInsets.only(left: 12, top: 4, bottom: 4), + child: ElevatedButton.icon( + onPressed: () => _showTestRunner(testCases), + icon: const Icon(Icons.play_arrow, size: 16), + label: const Text("Run Test Cases"), + style: ElevatedButton.styleFrom( + visualDensity: VisualDensity.compact, + ), + ), ), - ), - ], - ); - } + ], + ); + } - return ChatBubble( - message: text, - isUser: message['role'] == 'user', - ); - }, + return ChatBubble( + message: text, + isUser: message['role'] == 'user', + ); + }, + ), ); } Widget _buildLoadingIndicator() { return const Padding( - padding: EdgeInsets.all(8.0), + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: LinearProgressIndicator(), ); } Widget _buildInputArea(BuildContext context) { + final isMinimized = ref.watch(dashBotMinimizedProvider); + return Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), color: Theme.of(context).colorScheme.surfaceContainer, ), - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), child: Row( children: [ Expanded( @@ -251,19 +317,28 @@ class _DashBotWidgetState extends ConsumerState { decoration: const InputDecoration( hintText: 'Ask DashBot...', border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 8), ), onSubmitted: (value) { _sendMessage(value); _controller.clear(); + if (isMinimized) { + ref.read(dashBotMinimizedProvider.notifier).state = false; + } }, maxLines: 1, ), ), IconButton( - icon: const Icon(Icons.send), + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + icon: const Icon(Icons.send, size: 20), onPressed: () { _sendMessage(_controller.text); _controller.clear(); + if (isMinimized) { + ref.read(dashBotMinimizedProvider.notifier).state = false; + } }, ), ], diff --git a/lib/screens/dashboard.dart b/lib/screens/dashboard.dart index cc9a6267..148ff5c9 100644 --- a/lib/screens/dashboard.dart +++ b/lib/screens/dashboard.dart @@ -17,126 +17,130 @@ class Dashboard extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final railIdx = ref.watch(navRailIndexStateProvider); + final isDashBotVisible = ref.watch(dashBotVisibilityProvider); + return Scaffold( body: SafeArea( - child: Row( - children: [ - Column( - children: [ - SizedBox( - height: kIsMacOS ? 32.0 : 16.0, - width: 64, - ), + child: Stack( + children: [ + Row( + children: [ Column( - mainAxisSize: MainAxisSize.min, children: [ - IconButton( - isSelected: railIdx == 0, - onPressed: () { - ref.read(navRailIndexStateProvider.notifier).state = 0; - }, - icon: const Icon(Icons.auto_awesome_mosaic_outlined), - selectedIcon: const Icon(Icons.auto_awesome_mosaic), + SizedBox( + height: kIsMacOS ? 32.0 : 16.0, + width: 64, ), - Text( - 'Requests', - style: Theme.of(context).textTheme.labelSmall, + Column( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + isSelected: railIdx == 0, + onPressed: () { + ref.read(navRailIndexStateProvider.notifier).state = 0; + }, + icon: const Icon(Icons.auto_awesome_mosaic_outlined), + selectedIcon: const Icon(Icons.auto_awesome_mosaic), + ), + Text( + 'Requests', + style: Theme.of(context).textTheme.labelSmall, + ), + kVSpacer10, + IconButton( + isSelected: railIdx == 1, + onPressed: () { + ref.read(navRailIndexStateProvider.notifier).state = 1; + }, + icon: const Icon(Icons.laptop_windows_outlined), + selectedIcon: const Icon(Icons.laptop_windows), + ), + Text( + 'Variables', + style: Theme.of(context).textTheme.labelSmall, + ), + kVSpacer10, + IconButton( + isSelected: railIdx == 2, + onPressed: () { + ref.read(navRailIndexStateProvider.notifier).state = 2; + }, + icon: const Icon(Icons.history_outlined), + selectedIcon: const Icon(Icons.history_rounded), + ), + Text( + 'History', + style: Theme.of(context).textTheme.labelSmall, + ), + ], ), - kVSpacer10, - IconButton( - isSelected: railIdx == 1, - onPressed: () { - ref.read(navRailIndexStateProvider.notifier).state = 1; - }, - icon: const Icon(Icons.laptop_windows_outlined), - selectedIcon: const Icon(Icons.laptop_windows), - ), - Text( - 'Variables', - style: Theme.of(context).textTheme.labelSmall, - ), - kVSpacer10, - IconButton( - isSelected: railIdx == 2, - onPressed: () { - ref.read(navRailIndexStateProvider.notifier).state = 2; - }, - icon: const Icon(Icons.history_outlined), - selectedIcon: const Icon(Icons.history_rounded), - ), - Text( - 'History', - style: Theme.of(context).textTheme.labelSmall, + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 16.0), + child: NavbarButton( + railIdx: railIdx, + selectedIcon: Icons.help, + icon: Icons.help_outline, + label: 'About', + showLabel: false, + isCompact: true, + onTap: () { + showAboutAppDialog(context); + }, + ), + ), + Padding( + padding: const EdgeInsets.only(bottom: 16.0), + child: NavbarButton( + railIdx: railIdx, + buttonIdx: 3, + selectedIcon: Icons.settings, + icon: Icons.settings_outlined, + label: 'Settings', + showLabel: false, + isCompact: true, + ), + ), + ], + ), ), ], ), + VerticalDivider( + thickness: 1, + width: 1, + color: Theme.of(context).colorScheme.surfaceContainerHigh, + ), Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Padding( - padding: const EdgeInsets.only(bottom: 16.0), - child: NavbarButton( - railIdx: railIdx, - selectedIcon: Icons.help, - icon: Icons.help_outline, - label: 'About', - showLabel: false, - isCompact: true, - onTap: () { - showAboutAppDialog(context); - }, - ), - ), - Padding( - padding: const EdgeInsets.only(bottom: 16.0), - child: NavbarButton( - railIdx: railIdx, - buttonIdx: 3, - selectedIcon: Icons.settings, - icon: Icons.settings_outlined, - label: 'Settings', - showLabel: false, - isCompact: true, - ), - ), + child: IndexedStack( + alignment: AlignmentDirectional.topCenter, + index: railIdx, + children: const [ + HomePage(), + EnvironmentPage(), + HistoryPage(), + SettingsPage(), ], ), - ), + ) ], ), - VerticalDivider( - thickness: 1, - width: 1, - color: Theme.of(context).colorScheme.surfaceContainerHigh, - ), - Expanded( - child: IndexedStack( - alignment: AlignmentDirectional.topCenter, - index: railIdx, - children: const [ - HomePage(), - EnvironmentPage(), - HistoryPage(), - SettingsPage(), - ], + + // DashBot Overlay + if (isDashBotVisible) + Positioned( + bottom: 20, + right: 20, + child: const DashBotOverlay(), ), - ) ], ), ), - // TODO: Release DashBot - floatingActionButton: FloatingActionButton( - onPressed: () => showModalBottomSheet( - context: context, - isScrollControlled: true, - builder: (context) => const Padding( - padding: EdgeInsets.all(16.0), - child: DashBotWidget(), - ), - ), - child: const Icon(Icons.help_outline), - ), + // Conditionally show FAB only when DashBot is not visible + floatingActionButton: !isDashBotVisible ? const DashBotFAB() : null, ); } } From c3a6df9627bf5d5789e4d80195585ec718d3e250 Mon Sep 17 00:00:00 2001 From: sumaiya shazad Date: Sun, 23 Mar 2025 08:23:53 +0530 Subject: [PATCH 06/30] #122 - Package for har (import) --- packages/har_parser/.gitignore | 31 + packages/har_parser/CHANGELOG.md | 3 + packages/har_parser/LICENSE | 201 ++ packages/har_parser/README.md | 287 +++ packages/har_parser/analysis_options.yaml | 6 + packages/har_parser/example/har_example.dart | 254 +++ packages/har_parser/lib/har_parser.dart | 3 + packages/har_parser/lib/models/har_log.dart | 107 ++ .../lib/models/har_log.freezed.dart | 1683 +++++++++++++++++ packages/har_parser/lib/models/har_log.g.dart | 131 ++ packages/har_parser/lib/models/models.dart | 1 + packages/har_parser/pubspec.yaml | 25 + .../collection_apidash.dart | 253 +++ packages/har_parser/test/har_parser_test.dart | 25 + .../test/models/collection_apidash_model.dart | 120 ++ 15 files changed, 3130 insertions(+) create mode 100644 packages/har_parser/.gitignore create mode 100644 packages/har_parser/CHANGELOG.md create mode 100644 packages/har_parser/LICENSE create mode 100644 packages/har_parser/README.md create mode 100644 packages/har_parser/analysis_options.yaml create mode 100644 packages/har_parser/example/har_example.dart create mode 100644 packages/har_parser/lib/har_parser.dart create mode 100644 packages/har_parser/lib/models/har_log.dart create mode 100644 packages/har_parser/lib/models/har_log.freezed.dart create mode 100644 packages/har_parser/lib/models/har_log.g.dart create mode 100644 packages/har_parser/lib/models/models.dart create mode 100644 packages/har_parser/pubspec.yaml create mode 100644 packages/har_parser/test/collection_examples/collection_apidash.dart create mode 100644 packages/har_parser/test/har_parser_test.dart create mode 100644 packages/har_parser/test/models/collection_apidash_model.dart diff --git a/packages/har_parser/.gitignore b/packages/har_parser/.gitignore new file mode 100644 index 00000000..eb6c05cd --- /dev/null +++ b/packages/har_parser/.gitignore @@ -0,0 +1,31 @@ +# 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/ +.flutter-plugins +.flutter-plugins-dependencies +build/ diff --git a/packages/har_parser/CHANGELOG.md b/packages/har_parser/CHANGELOG.md new file mode 100644 index 00000000..41cc7d81 --- /dev/null +++ b/packages/har_parser/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* TODO: Describe initial release. diff --git a/packages/har_parser/LICENSE b/packages/har_parser/LICENSE new file mode 100644 index 00000000..e1622b53 --- /dev/null +++ b/packages/har_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 Ashita Prasad, Ankit Mahato + +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/har_parser/README.md b/packages/har_parser/README.md new file mode 100644 index 00000000..462f3c5c --- /dev/null +++ b/packages/har_parser/README.md @@ -0,0 +1,287 @@ +# insomnia + +Seamlessly convert Har Collection Format to Dart. + +Helps you bring your APIs stored in Har to Dart and work with them. + +Currently, this package is being used by [API Dash](https://github.com/foss42/apidash), a beautiful open-source cross-platform (macOS, Windows, Linux, Android & iOS) API Client built using Flutter which can help you easily create & customize your API requests, visually inspect responses and generate API integration code. A lightweight alternative to postman. + +## Usage + +### Example 1: Har collection JSON string to Har model + +```dart +import 'package:har_parser/har_parser.dart'; + +void main() { + // Example 1: Har collection JSON string to Har model + var collectionJsonStr = r''' +{ +"log": { + "version": "1.2", + "creator": {"name": "Sample HAR Creator", "version": "1.0"}, + "entries": [ + { + "startedDateTime": "2024-02-23T08:00:00.000Z", + "time": 50, + "request": { + "method": "GET", + "url": "https://api.example.com/users", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "queryString": [], + "postData": {}, + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "content": {"size": 100, "mimeType": "application/json"}, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1 + } + }, + { + "startedDateTime": "2024-02-23T08:05:00.000Z", + "time": 70, + "request": { + "method": "POST", + "url": "https://api.example.com/login", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "queryString": [], + "postData": { + "mimeType": "application/json", + "text": "{\"username\":\"user\",\"password\":\"pass\"}" + }, + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "content": {"size": 50, "mimeType": "application/json"}, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1 + } + }, + { + "startedDateTime": "2024-02-23T08:10:00.000Z", + "time": 60, + "request": { + "method": "GET", + "url": "https://api.example.com/products", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "queryString": [], + "postData": {}, + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "content": {"size": 200, "mimeType": "application/json"}, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1 + } + }, + { + "startedDateTime": "2024-02-23T08:15:00.000Z", + "time": 80, + "request": { + "method": "PUT", + "url": "https://api.example.com/products/123", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "queryString": [], + "postData": { + "mimeType": "application/json", + "text": "{\"name\":\"New Product\",\"price\":50}" + }, + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "content": {"size": 50, "mimeType": "application/json"}, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1 + } + } + ] + } + }'''; + + var collection = harLogFromJsonStr(collectionJsonStr); + + print(collection.log?.creator); + print(collection.log?.entries?[0].startedDateTime); + print(collection.log?.entries?[0].request?.url); +} +``` + +### Example 2: Har collection from JSON + +```dart +import 'package:har_parser/har_parser.dart'; + +void main() { + // Example 2: Har collection from JSON +var collectionJson = { + "log": { + "version": "1.2", + "creator": {"name": "Sample HAR Creator", "version": "1.0"}, + "entries": [ + { + "startedDateTime": "2024-02-23T08:00:00.000Z", + "time": 50, + "request": { + "method": "GET", + "url": "https://api.example.com/users", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "queryString": [], + "postData": {}, + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "content": {"size": 100, "mimeType": "application/json"}, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1 + } + }, + { + "startedDateTime": "2024-02-23T08:05:00.000Z", + "time": 70, + "request": { + "method": "POST", + "url": "https://api.example.com/login", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "queryString": [], + "postData": { + "mimeType": "application/json", + "text": "{\"username\":\"user\",\"password\":\"pass\"}" + }, + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "content": {"size": 50, "mimeType": "application/json"}, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1 + } + }, + { + "startedDateTime": "2024-02-23T08:10:00.000Z", + "time": 60, + "request": { + "method": "GET", + "url": "https://api.example.com/products", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "queryString": [], + "postData": {}, + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "content": {"size": 200, "mimeType": "application/json"}, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1 + } + }, + { + "startedDateTime": "2024-02-23T08:15:00.000Z", + "time": 80, + "request": { + "method": "PUT", + "url": "https://api.example.com/products/123", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "queryString": [], + "postData": { + "mimeType": "application/json", + "text": "{\"name\":\"New Product\",\"price\":50}" + }, + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "content": {"size": 50, "mimeType": "application/json"}, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1 + } + } + ] + } + }; + + var collection1 = HarLog.fromJson(collectionJson); + print(collection1.log?.creator?.name); + print(collection1.log?.entries?[0].startedDateTime); + print(collection1.log?.entries?[0].request?.url); + +} +``` + +## Maintainer + +- Ashita Prasad ([GitHub](https://github.com/ashitaprasad), [LinkedIn](https://www.linkedin.com/in/ashitaprasad/), [X](https://x.com/ashitaprasad)) +- Mohammed Ayaan (contributor) ([GitHub](https://github.com/ayaan-md-blr)) + +## License + +This project is licensed under the [Apache License 2.0](https://github.com/foss42/apidash/blob/main/packages/har_parser/LICENSE). diff --git a/packages/har_parser/analysis_options.yaml b/packages/har_parser/analysis_options.yaml new file mode 100644 index 00000000..1dea9522 --- /dev/null +++ b/packages/har_parser/analysis_options.yaml @@ -0,0 +1,6 @@ +analyzer: + exclude: + - "**/*.g.dart" + - "**/*.freezed.dart" + errors: + invalid_annotation_target: ignore diff --git a/packages/har_parser/example/har_example.dart b/packages/har_parser/example/har_example.dart new file mode 100644 index 00000000..6b1c560c --- /dev/null +++ b/packages/har_parser/example/har_example.dart @@ -0,0 +1,254 @@ +import 'package:har_parser/har_parser.dart'; + +void main() { + //Example 1 + var collectionJsonStr = r''' +{ +"log": { + "version": "1.2", + "creator": {"name": "Sample HAR Creator", "version": "1.0"}, + "entries": [ + { + "startedDateTime": "2024-02-23T08:00:00.000Z", + "time": 50, + "request": { + "method": "GET", + "url": "https://api.example.com/users", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "queryString": [], + "postData": {}, + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "content": {"size": 100, "mimeType": "application/json"}, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1 + } + }, + { + "startedDateTime": "2024-02-23T08:05:00.000Z", + "time": 70, + "request": { + "method": "POST", + "url": "https://api.example.com/login", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "queryString": [], + "postData": { + "mimeType": "application/json", + "text": "{\"username\":\"user\",\"password\":\"pass\"}" + }, + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "content": {"size": 50, "mimeType": "application/json"}, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1 + } + }, + { + "startedDateTime": "2024-02-23T08:10:00.000Z", + "time": 60, + "request": { + "method": "GET", + "url": "https://api.example.com/products", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "queryString": [], + "postData": {}, + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "content": {"size": 200, "mimeType": "application/json"}, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1 + } + }, + { + "startedDateTime": "2024-02-23T08:15:00.000Z", + "time": 80, + "request": { + "method": "PUT", + "url": "https://api.example.com/products/123", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "queryString": [], + "postData": { + "mimeType": "application/json", + "text": "{\"name\":\"New Product\",\"price\":50}" + }, + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "content": {"size": 50, "mimeType": "application/json"}, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1 + } + } + ] + } + }'''; + + var collection = harLogFromJsonStr(collectionJsonStr); + + print(collection.log?.creator); + print(collection.log?.entries?[0].startedDateTime); + print(collection.log?.entries?[0].request?.url); + + var collectionJson = { + "log": { + "version": "1.2", + "creator": {"name": "Sample HAR Creator", "version": "1.0"}, + "entries": [ + { + "startedDateTime": "2024-02-23T08:00:00.000Z", + "time": 50, + "request": { + "method": "GET", + "url": "https://api.example.com/users", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "queryString": [], + "postData": {}, + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "content": {"size": 100, "mimeType": "application/json"}, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1 + } + }, + { + "startedDateTime": "2024-02-23T08:05:00.000Z", + "time": 70, + "request": { + "method": "POST", + "url": "https://api.example.com/login", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "queryString": [], + "postData": { + "mimeType": "application/json", + "text": "{\"username\":\"user\",\"password\":\"pass\"}" + }, + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "content": {"size": 50, "mimeType": "application/json"}, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1 + } + }, + { + "startedDateTime": "2024-02-23T08:10:00.000Z", + "time": 60, + "request": { + "method": "GET", + "url": "https://api.example.com/products", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "queryString": [], + "postData": {}, + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "content": {"size": 200, "mimeType": "application/json"}, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1 + } + }, + { + "startedDateTime": "2024-02-23T08:15:00.000Z", + "time": 80, + "request": { + "method": "PUT", + "url": "https://api.example.com/products/123", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "queryString": [], + "postData": { + "mimeType": "application/json", + "text": "{\"name\":\"New Product\",\"price\":50}" + }, + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "content": {"size": 50, "mimeType": "application/json"}, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1 + } + } + ] + } + }; + + var collection1 = HarLog.fromJson(collectionJson); + print(collection1.log?.creator?.name); + print(collection1.log?.entries?[0].startedDateTime); + print(collection1.log?.entries?[0].request?.url); +} diff --git a/packages/har_parser/lib/har_parser.dart b/packages/har_parser/lib/har_parser.dart new file mode 100644 index 00000000..4300b9d6 --- /dev/null +++ b/packages/har_parser/lib/har_parser.dart @@ -0,0 +1,3 @@ +library har; + +export 'models/models.dart'; diff --git a/packages/har_parser/lib/models/har_log.dart b/packages/har_parser/lib/models/har_log.dart new file mode 100644 index 00000000..8887e315 --- /dev/null +++ b/packages/har_parser/lib/models/har_log.dart @@ -0,0 +1,107 @@ +import 'dart:convert'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'har_log.freezed.dart'; +part 'har_log.g.dart'; + +HarLog harLogFromJsonStr(String str) => HarLog.fromJson(json.decode(str)); + +String harLogToJsonStr(HarLog data) => + JsonEncoder.withIndent(' ').convert(data); + +@freezed +class HarLog with _$HarLog { + @JsonSerializable(explicitToJson: true, anyMap: true, includeIfNull: false) + const factory HarLog({ + Log? log, + }) = _HarLog; + + factory HarLog.fromJson(Map json) => _$HarLogFromJson(json); +} + +@freezed +class Log with _$Log { + @JsonSerializable(explicitToJson: true, anyMap: true, includeIfNull: false) + const factory Log({ + String? version, + Creator? creator, + List? entries, + }) = _Log; + + factory Log.fromJson(Map json) => _$LogFromJson(json); +} + +@freezed +class Creator with _$Creator { + @JsonSerializable(explicitToJson: true, anyMap: true, includeIfNull: false) + const factory Creator({ + String? name, + String? version, + }) = _Creator; + + factory Creator.fromJson(Map json) => + _$CreatorFromJson(json); +} + +@freezed +class Entry with _$Entry { + @JsonSerializable(explicitToJson: true, anyMap: true, includeIfNull: false) + const factory Entry({ + String? startedDateTime, + int? time, + Request? request, + Response? response, + }) = _Entry; + + factory Entry.fromJson(Map json) => _$EntryFromJson(json); +} + +@freezed +class Request with _$Request { + @JsonSerializable(explicitToJson: true, anyMap: true, includeIfNull: false) + const factory Request({ + String? method, + String? url, + String? httpVersion, + List? cookies, + List? headers, + List? queryString, + Map? postData, + int? headersSize, + int? bodySize, + }) = _Request; + + factory Request.fromJson(Map json) => + _$RequestFromJson(json); +} + +@freezed +class Response with _$Response { + @JsonSerializable(explicitToJson: true, anyMap: true, includeIfNull: false) + const factory Response({ + int? status, + String? statusText, + String? httpVersion, + List? cookies, + List? headers, + Content? content, + String? redirectURL, + int? headersSize, + int? bodySize, + }) = _Response; + + factory Response.fromJson(Map json) => + _$ResponseFromJson(json); +} + +@freezed +class Content with _$Content { + @JsonSerializable(explicitToJson: true, anyMap: true, includeIfNull: false) + const factory Content({ + int? size, + String? mimeType, + }) = _Content; + + factory Content.fromJson(Map json) => + _$ContentFromJson(json); +} diff --git a/packages/har_parser/lib/models/har_log.freezed.dart b/packages/har_parser/lib/models/har_log.freezed.dart new file mode 100644 index 00000000..4e8ffad6 --- /dev/null +++ b/packages/har_parser/lib/models/har_log.freezed.dart @@ -0,0 +1,1683 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'har_log.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +HarLog _$HarLogFromJson(Map json) { + return _HarLog.fromJson(json); +} + +/// @nodoc +mixin _$HarLog { + Log? get log => throw _privateConstructorUsedError; + + /// Serializes this HarLog to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of HarLog + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $HarLogCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $HarLogCopyWith<$Res> { + factory $HarLogCopyWith(HarLog value, $Res Function(HarLog) then) = + _$HarLogCopyWithImpl<$Res, HarLog>; + @useResult + $Res call({Log? log}); + + $LogCopyWith<$Res>? get log; +} + +/// @nodoc +class _$HarLogCopyWithImpl<$Res, $Val extends HarLog> + implements $HarLogCopyWith<$Res> { + _$HarLogCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of HarLog + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? log = freezed, + }) { + return _then(_value.copyWith( + log: freezed == log + ? _value.log + : log // ignore: cast_nullable_to_non_nullable + as Log?, + ) as $Val); + } + + /// Create a copy of HarLog + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $LogCopyWith<$Res>? get log { + if (_value.log == null) { + return null; + } + + return $LogCopyWith<$Res>(_value.log!, (value) { + return _then(_value.copyWith(log: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$HarLogImplCopyWith<$Res> implements $HarLogCopyWith<$Res> { + factory _$$HarLogImplCopyWith( + _$HarLogImpl value, $Res Function(_$HarLogImpl) then) = + __$$HarLogImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({Log? log}); + + @override + $LogCopyWith<$Res>? get log; +} + +/// @nodoc +class __$$HarLogImplCopyWithImpl<$Res> + extends _$HarLogCopyWithImpl<$Res, _$HarLogImpl> + implements _$$HarLogImplCopyWith<$Res> { + __$$HarLogImplCopyWithImpl( + _$HarLogImpl _value, $Res Function(_$HarLogImpl) _then) + : super(_value, _then); + + /// Create a copy of HarLog + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? log = freezed, + }) { + return _then(_$HarLogImpl( + log: freezed == log + ? _value.log + : log // ignore: cast_nullable_to_non_nullable + as Log?, + )); + } +} + +/// @nodoc + +@JsonSerializable(explicitToJson: true, anyMap: true, includeIfNull: false) +class _$HarLogImpl implements _HarLog { + const _$HarLogImpl({this.log}); + + factory _$HarLogImpl.fromJson(Map json) => + _$$HarLogImplFromJson(json); + + @override + final Log? log; + + @override + String toString() { + return 'HarLog(log: $log)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$HarLogImpl && + (identical(other.log, log) || other.log == log)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, log); + + /// Create a copy of HarLog + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$HarLogImplCopyWith<_$HarLogImpl> get copyWith => + __$$HarLogImplCopyWithImpl<_$HarLogImpl>(this, _$identity); + + @override + Map toJson() { + return _$$HarLogImplToJson( + this, + ); + } +} + +abstract class _HarLog implements HarLog { + const factory _HarLog({final Log? log}) = _$HarLogImpl; + + factory _HarLog.fromJson(Map json) = _$HarLogImpl.fromJson; + + @override + Log? get log; + + /// Create a copy of HarLog + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$HarLogImplCopyWith<_$HarLogImpl> get copyWith => + throw _privateConstructorUsedError; +} + +Log _$LogFromJson(Map json) { + return _Log.fromJson(json); +} + +/// @nodoc +mixin _$Log { + String? get version => throw _privateConstructorUsedError; + Creator? get creator => throw _privateConstructorUsedError; + List? get entries => throw _privateConstructorUsedError; + + /// Serializes this Log to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of Log + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $LogCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $LogCopyWith<$Res> { + factory $LogCopyWith(Log value, $Res Function(Log) then) = + _$LogCopyWithImpl<$Res, Log>; + @useResult + $Res call({String? version, Creator? creator, List? entries}); + + $CreatorCopyWith<$Res>? get creator; +} + +/// @nodoc +class _$LogCopyWithImpl<$Res, $Val extends Log> implements $LogCopyWith<$Res> { + _$LogCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of Log + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? version = freezed, + Object? creator = freezed, + Object? entries = freezed, + }) { + return _then(_value.copyWith( + version: freezed == version + ? _value.version + : version // ignore: cast_nullable_to_non_nullable + as String?, + creator: freezed == creator + ? _value.creator + : creator // ignore: cast_nullable_to_non_nullable + as Creator?, + entries: freezed == entries + ? _value.entries + : entries // ignore: cast_nullable_to_non_nullable + as List?, + ) as $Val); + } + + /// Create a copy of Log + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $CreatorCopyWith<$Res>? get creator { + if (_value.creator == null) { + return null; + } + + return $CreatorCopyWith<$Res>(_value.creator!, (value) { + return _then(_value.copyWith(creator: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$LogImplCopyWith<$Res> implements $LogCopyWith<$Res> { + factory _$$LogImplCopyWith(_$LogImpl value, $Res Function(_$LogImpl) then) = + __$$LogImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String? version, Creator? creator, List? entries}); + + @override + $CreatorCopyWith<$Res>? get creator; +} + +/// @nodoc +class __$$LogImplCopyWithImpl<$Res> extends _$LogCopyWithImpl<$Res, _$LogImpl> + implements _$$LogImplCopyWith<$Res> { + __$$LogImplCopyWithImpl(_$LogImpl _value, $Res Function(_$LogImpl) _then) + : super(_value, _then); + + /// Create a copy of Log + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? version = freezed, + Object? creator = freezed, + Object? entries = freezed, + }) { + return _then(_$LogImpl( + version: freezed == version + ? _value.version + : version // ignore: cast_nullable_to_non_nullable + as String?, + creator: freezed == creator + ? _value.creator + : creator // ignore: cast_nullable_to_non_nullable + as Creator?, + entries: freezed == entries + ? _value._entries + : entries // ignore: cast_nullable_to_non_nullable + as List?, + )); + } +} + +/// @nodoc + +@JsonSerializable(explicitToJson: true, anyMap: true, includeIfNull: false) +class _$LogImpl implements _Log { + const _$LogImpl({this.version, this.creator, final List? entries}) + : _entries = entries; + + factory _$LogImpl.fromJson(Map json) => + _$$LogImplFromJson(json); + + @override + final String? version; + @override + final Creator? creator; + final List? _entries; + @override + List? get entries { + final value = _entries; + if (value == null) return null; + if (_entries is EqualUnmodifiableListView) return _entries; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + @override + String toString() { + return 'Log(version: $version, creator: $creator, entries: $entries)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LogImpl && + (identical(other.version, version) || other.version == version) && + (identical(other.creator, creator) || other.creator == creator) && + const DeepCollectionEquality().equals(other._entries, _entries)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, version, creator, + const DeepCollectionEquality().hash(_entries)); + + /// Create a copy of Log + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$LogImplCopyWith<_$LogImpl> get copyWith => + __$$LogImplCopyWithImpl<_$LogImpl>(this, _$identity); + + @override + Map toJson() { + return _$$LogImplToJson( + this, + ); + } +} + +abstract class _Log implements Log { + const factory _Log( + {final String? version, + final Creator? creator, + final List? entries}) = _$LogImpl; + + factory _Log.fromJson(Map json) = _$LogImpl.fromJson; + + @override + String? get version; + @override + Creator? get creator; + @override + List? get entries; + + /// Create a copy of Log + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$LogImplCopyWith<_$LogImpl> get copyWith => + throw _privateConstructorUsedError; +} + +Creator _$CreatorFromJson(Map json) { + return _Creator.fromJson(json); +} + +/// @nodoc +mixin _$Creator { + String? get name => throw _privateConstructorUsedError; + String? get version => throw _privateConstructorUsedError; + + /// Serializes this Creator to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of Creator + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $CreatorCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CreatorCopyWith<$Res> { + factory $CreatorCopyWith(Creator value, $Res Function(Creator) then) = + _$CreatorCopyWithImpl<$Res, Creator>; + @useResult + $Res call({String? name, String? version}); +} + +/// @nodoc +class _$CreatorCopyWithImpl<$Res, $Val extends Creator> + implements $CreatorCopyWith<$Res> { + _$CreatorCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of Creator + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = freezed, + Object? version = freezed, + }) { + return _then(_value.copyWith( + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + version: freezed == version + ? _value.version + : version // ignore: cast_nullable_to_non_nullable + as String?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$CreatorImplCopyWith<$Res> implements $CreatorCopyWith<$Res> { + factory _$$CreatorImplCopyWith( + _$CreatorImpl value, $Res Function(_$CreatorImpl) then) = + __$$CreatorImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String? name, String? version}); +} + +/// @nodoc +class __$$CreatorImplCopyWithImpl<$Res> + extends _$CreatorCopyWithImpl<$Res, _$CreatorImpl> + implements _$$CreatorImplCopyWith<$Res> { + __$$CreatorImplCopyWithImpl( + _$CreatorImpl _value, $Res Function(_$CreatorImpl) _then) + : super(_value, _then); + + /// Create a copy of Creator + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = freezed, + Object? version = freezed, + }) { + return _then(_$CreatorImpl( + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + version: freezed == version + ? _value.version + : version // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc + +@JsonSerializable(explicitToJson: true, anyMap: true, includeIfNull: false) +class _$CreatorImpl implements _Creator { + const _$CreatorImpl({this.name, this.version}); + + factory _$CreatorImpl.fromJson(Map json) => + _$$CreatorImplFromJson(json); + + @override + final String? name; + @override + final String? version; + + @override + String toString() { + return 'Creator(name: $name, version: $version)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$CreatorImpl && + (identical(other.name, name) || other.name == name) && + (identical(other.version, version) || other.version == version)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, name, version); + + /// Create a copy of Creator + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$CreatorImplCopyWith<_$CreatorImpl> get copyWith => + __$$CreatorImplCopyWithImpl<_$CreatorImpl>(this, _$identity); + + @override + Map toJson() { + return _$$CreatorImplToJson( + this, + ); + } +} + +abstract class _Creator implements Creator { + const factory _Creator({final String? name, final String? version}) = + _$CreatorImpl; + + factory _Creator.fromJson(Map json) = _$CreatorImpl.fromJson; + + @override + String? get name; + @override + String? get version; + + /// Create a copy of Creator + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$CreatorImplCopyWith<_$CreatorImpl> get copyWith => + throw _privateConstructorUsedError; +} + +Entry _$EntryFromJson(Map json) { + return _Entry.fromJson(json); +} + +/// @nodoc +mixin _$Entry { + String? get startedDateTime => throw _privateConstructorUsedError; + int? get time => throw _privateConstructorUsedError; + Request? get request => throw _privateConstructorUsedError; + Response? get response => throw _privateConstructorUsedError; + + /// Serializes this Entry to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of Entry + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $EntryCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $EntryCopyWith<$Res> { + factory $EntryCopyWith(Entry value, $Res Function(Entry) then) = + _$EntryCopyWithImpl<$Res, Entry>; + @useResult + $Res call( + {String? startedDateTime, + int? time, + Request? request, + Response? response}); + + $RequestCopyWith<$Res>? get request; + $ResponseCopyWith<$Res>? get response; +} + +/// @nodoc +class _$EntryCopyWithImpl<$Res, $Val extends Entry> + implements $EntryCopyWith<$Res> { + _$EntryCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of Entry + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? startedDateTime = freezed, + Object? time = freezed, + Object? request = freezed, + Object? response = freezed, + }) { + return _then(_value.copyWith( + startedDateTime: freezed == startedDateTime + ? _value.startedDateTime + : startedDateTime // ignore: cast_nullable_to_non_nullable + as String?, + time: freezed == time + ? _value.time + : time // ignore: cast_nullable_to_non_nullable + as int?, + request: freezed == request + ? _value.request + : request // ignore: cast_nullable_to_non_nullable + as Request?, + response: freezed == response + ? _value.response + : response // ignore: cast_nullable_to_non_nullable + as Response?, + ) as $Val); + } + + /// Create a copy of Entry + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $RequestCopyWith<$Res>? get request { + if (_value.request == null) { + return null; + } + + return $RequestCopyWith<$Res>(_value.request!, (value) { + return _then(_value.copyWith(request: value) as $Val); + }); + } + + /// Create a copy of Entry + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $ResponseCopyWith<$Res>? get response { + if (_value.response == null) { + return null; + } + + return $ResponseCopyWith<$Res>(_value.response!, (value) { + return _then(_value.copyWith(response: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$EntryImplCopyWith<$Res> implements $EntryCopyWith<$Res> { + factory _$$EntryImplCopyWith( + _$EntryImpl value, $Res Function(_$EntryImpl) then) = + __$$EntryImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String? startedDateTime, + int? time, + Request? request, + Response? response}); + + @override + $RequestCopyWith<$Res>? get request; + @override + $ResponseCopyWith<$Res>? get response; +} + +/// @nodoc +class __$$EntryImplCopyWithImpl<$Res> + extends _$EntryCopyWithImpl<$Res, _$EntryImpl> + implements _$$EntryImplCopyWith<$Res> { + __$$EntryImplCopyWithImpl( + _$EntryImpl _value, $Res Function(_$EntryImpl) _then) + : super(_value, _then); + + /// Create a copy of Entry + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? startedDateTime = freezed, + Object? time = freezed, + Object? request = freezed, + Object? response = freezed, + }) { + return _then(_$EntryImpl( + startedDateTime: freezed == startedDateTime + ? _value.startedDateTime + : startedDateTime // ignore: cast_nullable_to_non_nullable + as String?, + time: freezed == time + ? _value.time + : time // ignore: cast_nullable_to_non_nullable + as int?, + request: freezed == request + ? _value.request + : request // ignore: cast_nullable_to_non_nullable + as Request?, + response: freezed == response + ? _value.response + : response // ignore: cast_nullable_to_non_nullable + as Response?, + )); + } +} + +/// @nodoc + +@JsonSerializable(explicitToJson: true, anyMap: true, includeIfNull: false) +class _$EntryImpl implements _Entry { + const _$EntryImpl( + {this.startedDateTime, this.time, this.request, this.response}); + + factory _$EntryImpl.fromJson(Map json) => + _$$EntryImplFromJson(json); + + @override + final String? startedDateTime; + @override + final int? time; + @override + final Request? request; + @override + final Response? response; + + @override + String toString() { + return 'Entry(startedDateTime: $startedDateTime, time: $time, request: $request, response: $response)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$EntryImpl && + (identical(other.startedDateTime, startedDateTime) || + other.startedDateTime == startedDateTime) && + (identical(other.time, time) || other.time == time) && + (identical(other.request, request) || other.request == request) && + (identical(other.response, response) || + other.response == response)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => + Object.hash(runtimeType, startedDateTime, time, request, response); + + /// Create a copy of Entry + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$EntryImplCopyWith<_$EntryImpl> get copyWith => + __$$EntryImplCopyWithImpl<_$EntryImpl>(this, _$identity); + + @override + Map toJson() { + return _$$EntryImplToJson( + this, + ); + } +} + +abstract class _Entry implements Entry { + const factory _Entry( + {final String? startedDateTime, + final int? time, + final Request? request, + final Response? response}) = _$EntryImpl; + + factory _Entry.fromJson(Map json) = _$EntryImpl.fromJson; + + @override + String? get startedDateTime; + @override + int? get time; + @override + Request? get request; + @override + Response? get response; + + /// Create a copy of Entry + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$EntryImplCopyWith<_$EntryImpl> get copyWith => + throw _privateConstructorUsedError; +} + +Request _$RequestFromJson(Map json) { + return _Request.fromJson(json); +} + +/// @nodoc +mixin _$Request { + String? get method => throw _privateConstructorUsedError; + String? get url => throw _privateConstructorUsedError; + String? get httpVersion => throw _privateConstructorUsedError; + List? get cookies => throw _privateConstructorUsedError; + List? get headers => throw _privateConstructorUsedError; + List? get queryString => throw _privateConstructorUsedError; + Map? get postData => throw _privateConstructorUsedError; + int? get headersSize => throw _privateConstructorUsedError; + int? get bodySize => throw _privateConstructorUsedError; + + /// Serializes this Request to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of Request + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $RequestCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $RequestCopyWith<$Res> { + factory $RequestCopyWith(Request value, $Res Function(Request) then) = + _$RequestCopyWithImpl<$Res, Request>; + @useResult + $Res call( + {String? method, + String? url, + String? httpVersion, + List? cookies, + List? headers, + List? queryString, + Map? postData, + int? headersSize, + int? bodySize}); +} + +/// @nodoc +class _$RequestCopyWithImpl<$Res, $Val extends Request> + implements $RequestCopyWith<$Res> { + _$RequestCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of Request + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? method = freezed, + Object? url = freezed, + Object? httpVersion = freezed, + Object? cookies = freezed, + Object? headers = freezed, + Object? queryString = freezed, + Object? postData = freezed, + Object? headersSize = freezed, + Object? bodySize = freezed, + }) { + return _then(_value.copyWith( + method: freezed == method + ? _value.method + : method // ignore: cast_nullable_to_non_nullable + as String?, + url: freezed == url + ? _value.url + : url // ignore: cast_nullable_to_non_nullable + as String?, + httpVersion: freezed == httpVersion + ? _value.httpVersion + : httpVersion // ignore: cast_nullable_to_non_nullable + as String?, + cookies: freezed == cookies + ? _value.cookies + : cookies // ignore: cast_nullable_to_non_nullable + as List?, + headers: freezed == headers + ? _value.headers + : headers // ignore: cast_nullable_to_non_nullable + as List?, + queryString: freezed == queryString + ? _value.queryString + : queryString // ignore: cast_nullable_to_non_nullable + as List?, + postData: freezed == postData + ? _value.postData + : postData // ignore: cast_nullable_to_non_nullable + as Map?, + headersSize: freezed == headersSize + ? _value.headersSize + : headersSize // ignore: cast_nullable_to_non_nullable + as int?, + bodySize: freezed == bodySize + ? _value.bodySize + : bodySize // ignore: cast_nullable_to_non_nullable + as int?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$RequestImplCopyWith<$Res> implements $RequestCopyWith<$Res> { + factory _$$RequestImplCopyWith( + _$RequestImpl value, $Res Function(_$RequestImpl) then) = + __$$RequestImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String? method, + String? url, + String? httpVersion, + List? cookies, + List? headers, + List? queryString, + Map? postData, + int? headersSize, + int? bodySize}); +} + +/// @nodoc +class __$$RequestImplCopyWithImpl<$Res> + extends _$RequestCopyWithImpl<$Res, _$RequestImpl> + implements _$$RequestImplCopyWith<$Res> { + __$$RequestImplCopyWithImpl( + _$RequestImpl _value, $Res Function(_$RequestImpl) _then) + : super(_value, _then); + + /// Create a copy of Request + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? method = freezed, + Object? url = freezed, + Object? httpVersion = freezed, + Object? cookies = freezed, + Object? headers = freezed, + Object? queryString = freezed, + Object? postData = freezed, + Object? headersSize = freezed, + Object? bodySize = freezed, + }) { + return _then(_$RequestImpl( + method: freezed == method + ? _value.method + : method // ignore: cast_nullable_to_non_nullable + as String?, + url: freezed == url + ? _value.url + : url // ignore: cast_nullable_to_non_nullable + as String?, + httpVersion: freezed == httpVersion + ? _value.httpVersion + : httpVersion // ignore: cast_nullable_to_non_nullable + as String?, + cookies: freezed == cookies + ? _value._cookies + : cookies // ignore: cast_nullable_to_non_nullable + as List?, + headers: freezed == headers + ? _value._headers + : headers // ignore: cast_nullable_to_non_nullable + as List?, + queryString: freezed == queryString + ? _value._queryString + : queryString // ignore: cast_nullable_to_non_nullable + as List?, + postData: freezed == postData + ? _value._postData + : postData // ignore: cast_nullable_to_non_nullable + as Map?, + headersSize: freezed == headersSize + ? _value.headersSize + : headersSize // ignore: cast_nullable_to_non_nullable + as int?, + bodySize: freezed == bodySize + ? _value.bodySize + : bodySize // ignore: cast_nullable_to_non_nullable + as int?, + )); + } +} + +/// @nodoc + +@JsonSerializable(explicitToJson: true, anyMap: true, includeIfNull: false) +class _$RequestImpl implements _Request { + const _$RequestImpl( + {this.method, + this.url, + this.httpVersion, + final List? cookies, + final List? headers, + final List? queryString, + final Map? postData, + this.headersSize, + this.bodySize}) + : _cookies = cookies, + _headers = headers, + _queryString = queryString, + _postData = postData; + + factory _$RequestImpl.fromJson(Map json) => + _$$RequestImplFromJson(json); + + @override + final String? method; + @override + final String? url; + @override + final String? httpVersion; + final List? _cookies; + @override + List? get cookies { + final value = _cookies; + if (value == null) return null; + if (_cookies is EqualUnmodifiableListView) return _cookies; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + final List? _headers; + @override + List? get headers { + final value = _headers; + if (value == null) return null; + if (_headers is EqualUnmodifiableListView) return _headers; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + final List? _queryString; + @override + List? get queryString { + final value = _queryString; + if (value == null) return null; + if (_queryString is EqualUnmodifiableListView) return _queryString; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + final Map? _postData; + @override + Map? get postData { + final value = _postData; + if (value == null) return null; + if (_postData is EqualUnmodifiableMapView) return _postData; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(value); + } + + @override + final int? headersSize; + @override + final int? bodySize; + + @override + String toString() { + return 'Request(method: $method, url: $url, httpVersion: $httpVersion, cookies: $cookies, headers: $headers, queryString: $queryString, postData: $postData, headersSize: $headersSize, bodySize: $bodySize)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$RequestImpl && + (identical(other.method, method) || other.method == method) && + (identical(other.url, url) || other.url == url) && + (identical(other.httpVersion, httpVersion) || + other.httpVersion == httpVersion) && + const DeepCollectionEquality().equals(other._cookies, _cookies) && + const DeepCollectionEquality().equals(other._headers, _headers) && + const DeepCollectionEquality() + .equals(other._queryString, _queryString) && + const DeepCollectionEquality().equals(other._postData, _postData) && + (identical(other.headersSize, headersSize) || + other.headersSize == headersSize) && + (identical(other.bodySize, bodySize) || + other.bodySize == bodySize)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + method, + url, + httpVersion, + const DeepCollectionEquality().hash(_cookies), + const DeepCollectionEquality().hash(_headers), + const DeepCollectionEquality().hash(_queryString), + const DeepCollectionEquality().hash(_postData), + headersSize, + bodySize); + + /// Create a copy of Request + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$RequestImplCopyWith<_$RequestImpl> get copyWith => + __$$RequestImplCopyWithImpl<_$RequestImpl>(this, _$identity); + + @override + Map toJson() { + return _$$RequestImplToJson( + this, + ); + } +} + +abstract class _Request implements Request { + const factory _Request( + {final String? method, + final String? url, + final String? httpVersion, + final List? cookies, + final List? headers, + final List? queryString, + final Map? postData, + final int? headersSize, + final int? bodySize}) = _$RequestImpl; + + factory _Request.fromJson(Map json) = _$RequestImpl.fromJson; + + @override + String? get method; + @override + String? get url; + @override + String? get httpVersion; + @override + List? get cookies; + @override + List? get headers; + @override + List? get queryString; + @override + Map? get postData; + @override + int? get headersSize; + @override + int? get bodySize; + + /// Create a copy of Request + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$RequestImplCopyWith<_$RequestImpl> get copyWith => + throw _privateConstructorUsedError; +} + +Response _$ResponseFromJson(Map json) { + return _Response.fromJson(json); +} + +/// @nodoc +mixin _$Response { + int? get status => throw _privateConstructorUsedError; + String? get statusText => throw _privateConstructorUsedError; + String? get httpVersion => throw _privateConstructorUsedError; + List? get cookies => throw _privateConstructorUsedError; + List? get headers => throw _privateConstructorUsedError; + Content? get content => throw _privateConstructorUsedError; + String? get redirectURL => throw _privateConstructorUsedError; + int? get headersSize => throw _privateConstructorUsedError; + int? get bodySize => throw _privateConstructorUsedError; + + /// Serializes this Response to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of Response + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ResponseCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ResponseCopyWith<$Res> { + factory $ResponseCopyWith(Response value, $Res Function(Response) then) = + _$ResponseCopyWithImpl<$Res, Response>; + @useResult + $Res call( + {int? status, + String? statusText, + String? httpVersion, + List? cookies, + List? headers, + Content? content, + String? redirectURL, + int? headersSize, + int? bodySize}); + + $ContentCopyWith<$Res>? get content; +} + +/// @nodoc +class _$ResponseCopyWithImpl<$Res, $Val extends Response> + implements $ResponseCopyWith<$Res> { + _$ResponseCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of Response + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = freezed, + Object? statusText = freezed, + Object? httpVersion = freezed, + Object? cookies = freezed, + Object? headers = freezed, + Object? content = freezed, + Object? redirectURL = freezed, + Object? headersSize = freezed, + Object? bodySize = freezed, + }) { + return _then(_value.copyWith( + status: freezed == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as int?, + statusText: freezed == statusText + ? _value.statusText + : statusText // ignore: cast_nullable_to_non_nullable + as String?, + httpVersion: freezed == httpVersion + ? _value.httpVersion + : httpVersion // ignore: cast_nullable_to_non_nullable + as String?, + cookies: freezed == cookies + ? _value.cookies + : cookies // ignore: cast_nullable_to_non_nullable + as List?, + headers: freezed == headers + ? _value.headers + : headers // ignore: cast_nullable_to_non_nullable + as List?, + content: freezed == content + ? _value.content + : content // ignore: cast_nullable_to_non_nullable + as Content?, + redirectURL: freezed == redirectURL + ? _value.redirectURL + : redirectURL // ignore: cast_nullable_to_non_nullable + as String?, + headersSize: freezed == headersSize + ? _value.headersSize + : headersSize // ignore: cast_nullable_to_non_nullable + as int?, + bodySize: freezed == bodySize + ? _value.bodySize + : bodySize // ignore: cast_nullable_to_non_nullable + as int?, + ) as $Val); + } + + /// Create a copy of Response + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $ContentCopyWith<$Res>? get content { + if (_value.content == null) { + return null; + } + + return $ContentCopyWith<$Res>(_value.content!, (value) { + return _then(_value.copyWith(content: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$ResponseImplCopyWith<$Res> + implements $ResponseCopyWith<$Res> { + factory _$$ResponseImplCopyWith( + _$ResponseImpl value, $Res Function(_$ResponseImpl) then) = + __$$ResponseImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {int? status, + String? statusText, + String? httpVersion, + List? cookies, + List? headers, + Content? content, + String? redirectURL, + int? headersSize, + int? bodySize}); + + @override + $ContentCopyWith<$Res>? get content; +} + +/// @nodoc +class __$$ResponseImplCopyWithImpl<$Res> + extends _$ResponseCopyWithImpl<$Res, _$ResponseImpl> + implements _$$ResponseImplCopyWith<$Res> { + __$$ResponseImplCopyWithImpl( + _$ResponseImpl _value, $Res Function(_$ResponseImpl) _then) + : super(_value, _then); + + /// Create a copy of Response + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = freezed, + Object? statusText = freezed, + Object? httpVersion = freezed, + Object? cookies = freezed, + Object? headers = freezed, + Object? content = freezed, + Object? redirectURL = freezed, + Object? headersSize = freezed, + Object? bodySize = freezed, + }) { + return _then(_$ResponseImpl( + status: freezed == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as int?, + statusText: freezed == statusText + ? _value.statusText + : statusText // ignore: cast_nullable_to_non_nullable + as String?, + httpVersion: freezed == httpVersion + ? _value.httpVersion + : httpVersion // ignore: cast_nullable_to_non_nullable + as String?, + cookies: freezed == cookies + ? _value._cookies + : cookies // ignore: cast_nullable_to_non_nullable + as List?, + headers: freezed == headers + ? _value._headers + : headers // ignore: cast_nullable_to_non_nullable + as List?, + content: freezed == content + ? _value.content + : content // ignore: cast_nullable_to_non_nullable + as Content?, + redirectURL: freezed == redirectURL + ? _value.redirectURL + : redirectURL // ignore: cast_nullable_to_non_nullable + as String?, + headersSize: freezed == headersSize + ? _value.headersSize + : headersSize // ignore: cast_nullable_to_non_nullable + as int?, + bodySize: freezed == bodySize + ? _value.bodySize + : bodySize // ignore: cast_nullable_to_non_nullable + as int?, + )); + } +} + +/// @nodoc + +@JsonSerializable(explicitToJson: true, anyMap: true, includeIfNull: false) +class _$ResponseImpl implements _Response { + const _$ResponseImpl( + {this.status, + this.statusText, + this.httpVersion, + final List? cookies, + final List? headers, + this.content, + this.redirectURL, + this.headersSize, + this.bodySize}) + : _cookies = cookies, + _headers = headers; + + factory _$ResponseImpl.fromJson(Map json) => + _$$ResponseImplFromJson(json); + + @override + final int? status; + @override + final String? statusText; + @override + final String? httpVersion; + final List? _cookies; + @override + List? get cookies { + final value = _cookies; + if (value == null) return null; + if (_cookies is EqualUnmodifiableListView) return _cookies; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + final List? _headers; + @override + List? get headers { + final value = _headers; + if (value == null) return null; + if (_headers is EqualUnmodifiableListView) return _headers; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + @override + final Content? content; + @override + final String? redirectURL; + @override + final int? headersSize; + @override + final int? bodySize; + + @override + String toString() { + return 'Response(status: $status, statusText: $statusText, httpVersion: $httpVersion, cookies: $cookies, headers: $headers, content: $content, redirectURL: $redirectURL, headersSize: $headersSize, bodySize: $bodySize)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ResponseImpl && + (identical(other.status, status) || other.status == status) && + (identical(other.statusText, statusText) || + other.statusText == statusText) && + (identical(other.httpVersion, httpVersion) || + other.httpVersion == httpVersion) && + const DeepCollectionEquality().equals(other._cookies, _cookies) && + const DeepCollectionEquality().equals(other._headers, _headers) && + (identical(other.content, content) || other.content == content) && + (identical(other.redirectURL, redirectURL) || + other.redirectURL == redirectURL) && + (identical(other.headersSize, headersSize) || + other.headersSize == headersSize) && + (identical(other.bodySize, bodySize) || + other.bodySize == bodySize)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + status, + statusText, + httpVersion, + const DeepCollectionEquality().hash(_cookies), + const DeepCollectionEquality().hash(_headers), + content, + redirectURL, + headersSize, + bodySize); + + /// Create a copy of Response + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ResponseImplCopyWith<_$ResponseImpl> get copyWith => + __$$ResponseImplCopyWithImpl<_$ResponseImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ResponseImplToJson( + this, + ); + } +} + +abstract class _Response implements Response { + const factory _Response( + {final int? status, + final String? statusText, + final String? httpVersion, + final List? cookies, + final List? headers, + final Content? content, + final String? redirectURL, + final int? headersSize, + final int? bodySize}) = _$ResponseImpl; + + factory _Response.fromJson(Map json) = + _$ResponseImpl.fromJson; + + @override + int? get status; + @override + String? get statusText; + @override + String? get httpVersion; + @override + List? get cookies; + @override + List? get headers; + @override + Content? get content; + @override + String? get redirectURL; + @override + int? get headersSize; + @override + int? get bodySize; + + /// Create a copy of Response + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ResponseImplCopyWith<_$ResponseImpl> get copyWith => + throw _privateConstructorUsedError; +} + +Content _$ContentFromJson(Map json) { + return _Content.fromJson(json); +} + +/// @nodoc +mixin _$Content { + int? get size => throw _privateConstructorUsedError; + String? get mimeType => throw _privateConstructorUsedError; + + /// Serializes this Content to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of Content + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ContentCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ContentCopyWith<$Res> { + factory $ContentCopyWith(Content value, $Res Function(Content) then) = + _$ContentCopyWithImpl<$Res, Content>; + @useResult + $Res call({int? size, String? mimeType}); +} + +/// @nodoc +class _$ContentCopyWithImpl<$Res, $Val extends Content> + implements $ContentCopyWith<$Res> { + _$ContentCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of Content + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? size = freezed, + Object? mimeType = freezed, + }) { + return _then(_value.copyWith( + size: freezed == size + ? _value.size + : size // ignore: cast_nullable_to_non_nullable + as int?, + mimeType: freezed == mimeType + ? _value.mimeType + : mimeType // ignore: cast_nullable_to_non_nullable + as String?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ContentImplCopyWith<$Res> implements $ContentCopyWith<$Res> { + factory _$$ContentImplCopyWith( + _$ContentImpl value, $Res Function(_$ContentImpl) then) = + __$$ContentImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({int? size, String? mimeType}); +} + +/// @nodoc +class __$$ContentImplCopyWithImpl<$Res> + extends _$ContentCopyWithImpl<$Res, _$ContentImpl> + implements _$$ContentImplCopyWith<$Res> { + __$$ContentImplCopyWithImpl( + _$ContentImpl _value, $Res Function(_$ContentImpl) _then) + : super(_value, _then); + + /// Create a copy of Content + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? size = freezed, + Object? mimeType = freezed, + }) { + return _then(_$ContentImpl( + size: freezed == size + ? _value.size + : size // ignore: cast_nullable_to_non_nullable + as int?, + mimeType: freezed == mimeType + ? _value.mimeType + : mimeType // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc + +@JsonSerializable(explicitToJson: true, anyMap: true, includeIfNull: false) +class _$ContentImpl implements _Content { + const _$ContentImpl({this.size, this.mimeType}); + + factory _$ContentImpl.fromJson(Map json) => + _$$ContentImplFromJson(json); + + @override + final int? size; + @override + final String? mimeType; + + @override + String toString() { + return 'Content(size: $size, mimeType: $mimeType)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ContentImpl && + (identical(other.size, size) || other.size == size) && + (identical(other.mimeType, mimeType) || + other.mimeType == mimeType)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, size, mimeType); + + /// Create a copy of Content + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ContentImplCopyWith<_$ContentImpl> get copyWith => + __$$ContentImplCopyWithImpl<_$ContentImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ContentImplToJson( + this, + ); + } +} + +abstract class _Content implements Content { + const factory _Content({final int? size, final String? mimeType}) = + _$ContentImpl; + + factory _Content.fromJson(Map json) = _$ContentImpl.fromJson; + + @override + int? get size; + @override + String? get mimeType; + + /// Create a copy of Content + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ContentImplCopyWith<_$ContentImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/har_parser/lib/models/har_log.g.dart b/packages/har_parser/lib/models/har_log.g.dart new file mode 100644 index 00000000..7bec7f01 --- /dev/null +++ b/packages/har_parser/lib/models/har_log.g.dart @@ -0,0 +1,131 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'har_log.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$HarLogImpl _$$HarLogImplFromJson(Map json) => _$HarLogImpl( + log: json['log'] == null + ? null + : Log.fromJson(Map.from(json['log'] as Map)), + ); + +Map _$$HarLogImplToJson(_$HarLogImpl instance) => + { + if (instance.log?.toJson() case final value?) 'log': value, + }; + +_$LogImpl _$$LogImplFromJson(Map json) => _$LogImpl( + version: json['version'] as String?, + creator: json['creator'] == null + ? null + : Creator.fromJson(Map.from(json['creator'] as Map)), + entries: (json['entries'] as List?) + ?.map((e) => Entry.fromJson(Map.from(e as Map))) + .toList(), + ); + +Map _$$LogImplToJson(_$LogImpl instance) => { + if (instance.version case final value?) 'version': value, + if (instance.creator?.toJson() case final value?) 'creator': value, + if (instance.entries?.map((e) => e.toJson()).toList() case final value?) + 'entries': value, + }; + +_$CreatorImpl _$$CreatorImplFromJson(Map json) => _$CreatorImpl( + name: json['name'] as String?, + version: json['version'] as String?, + ); + +Map _$$CreatorImplToJson(_$CreatorImpl instance) => + { + if (instance.name case final value?) 'name': value, + if (instance.version case final value?) 'version': value, + }; + +_$EntryImpl _$$EntryImplFromJson(Map json) => _$EntryImpl( + startedDateTime: json['startedDateTime'] as String?, + time: (json['time'] as num?)?.toInt(), + request: json['request'] == null + ? null + : Request.fromJson(Map.from(json['request'] as Map)), + response: json['response'] == null + ? null + : Response.fromJson( + Map.from(json['response'] as Map)), + ); + +Map _$$EntryImplToJson(_$EntryImpl instance) => + { + if (instance.startedDateTime case final value?) 'startedDateTime': value, + if (instance.time case final value?) 'time': value, + if (instance.request?.toJson() case final value?) 'request': value, + if (instance.response?.toJson() case final value?) 'response': value, + }; + +_$RequestImpl _$$RequestImplFromJson(Map json) => _$RequestImpl( + method: json['method'] as String?, + url: json['url'] as String?, + httpVersion: json['httpVersion'] as String?, + cookies: json['cookies'] as List?, + headers: json['headers'] as List?, + queryString: json['queryString'] as List?, + postData: (json['postData'] as Map?)?.map( + (k, e) => MapEntry(k as String, e), + ), + headersSize: (json['headersSize'] as num?)?.toInt(), + bodySize: (json['bodySize'] as num?)?.toInt(), + ); + +Map _$$RequestImplToJson(_$RequestImpl instance) => + { + if (instance.method case final value?) 'method': value, + if (instance.url case final value?) 'url': value, + if (instance.httpVersion case final value?) 'httpVersion': value, + if (instance.cookies case final value?) 'cookies': value, + if (instance.headers case final value?) 'headers': value, + if (instance.queryString case final value?) 'queryString': value, + if (instance.postData case final value?) 'postData': value, + if (instance.headersSize case final value?) 'headersSize': value, + if (instance.bodySize case final value?) 'bodySize': value, + }; + +_$ResponseImpl _$$ResponseImplFromJson(Map json) => _$ResponseImpl( + status: (json['status'] as num?)?.toInt(), + statusText: json['statusText'] as String?, + httpVersion: json['httpVersion'] as String?, + cookies: json['cookies'] as List?, + headers: json['headers'] as List?, + content: json['content'] == null + ? null + : Content.fromJson(Map.from(json['content'] as Map)), + redirectURL: json['redirectURL'] as String?, + headersSize: (json['headersSize'] as num?)?.toInt(), + bodySize: (json['bodySize'] as num?)?.toInt(), + ); + +Map _$$ResponseImplToJson(_$ResponseImpl instance) => + { + if (instance.status case final value?) 'status': value, + if (instance.statusText case final value?) 'statusText': value, + if (instance.httpVersion case final value?) 'httpVersion': value, + if (instance.cookies case final value?) 'cookies': value, + if (instance.headers case final value?) 'headers': value, + if (instance.content?.toJson() case final value?) 'content': value, + if (instance.redirectURL case final value?) 'redirectURL': value, + if (instance.headersSize case final value?) 'headersSize': value, + if (instance.bodySize case final value?) 'bodySize': value, + }; + +_$ContentImpl _$$ContentImplFromJson(Map json) => _$ContentImpl( + size: (json['size'] as num?)?.toInt(), + mimeType: json['mimeType'] as String?, + ); + +Map _$$ContentImplToJson(_$ContentImpl instance) => + { + if (instance.size case final value?) 'size': value, + if (instance.mimeType case final value?) 'mimeType': value, + }; diff --git a/packages/har_parser/lib/models/models.dart b/packages/har_parser/lib/models/models.dart new file mode 100644 index 00000000..f3ea5369 --- /dev/null +++ b/packages/har_parser/lib/models/models.dart @@ -0,0 +1 @@ +export 'har_log.dart'; diff --git a/packages/har_parser/pubspec.yaml b/packages/har_parser/pubspec.yaml new file mode 100644 index 00000000..14b9c10a --- /dev/null +++ b/packages/har_parser/pubspec.yaml @@ -0,0 +1,25 @@ +name: har_parser +description: "Seamlessly convert har Format to Dart and vice versa." +version: 0.0.1 +homepage: https://github.com/foss42/apidash + +topics: + - har + - api + - rest + - http + - network + +environment: + sdk: ">=3.0.0 <4.0.0" + +dependencies: + freezed_annotation: ^2.4.4 + json_annotation: ^4.9.0 + +dev_dependencies: + build_runner: ^2.4.12 + freezed: ^2.5.7 + json_serializable: ^6.7.1 + lints: ^4.0.0 + test: ^1.24.0 diff --git a/packages/har_parser/test/collection_examples/collection_apidash.dart b/packages/har_parser/test/collection_examples/collection_apidash.dart new file mode 100644 index 00000000..f759eb5d --- /dev/null +++ b/packages/har_parser/test/collection_examples/collection_apidash.dart @@ -0,0 +1,253 @@ +var collectionJsonStr = r''' +{ + "log": { + "version": "1.2", + "creator": { + "name": "Sample HAR Creator", + "version": "1.0" + }, + "entries": [ + { + "startedDateTime": "2024-02-23T08:00:00.000Z", + "time": 50, + "request": { + "method": "GET", + "url": "https://api.example.com/users", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "queryString": [], + "postData": {}, + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "content": { + "size": 100, + "mimeType": "application/json" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1 + } + }, + { + "startedDateTime": "2024-02-23T08:05:00.000Z", + "time": 70, + "request": { + "method": "POST", + "url": "https://api.example.com/login", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "queryString": [], + "postData": { + "mimeType": "application/json", + "text": "{\"username\":\"user\",\"password\":\"pass\"}" + }, + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "content": { + "size": 50, + "mimeType": "application/json" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1 + } + }, + { + "startedDateTime": "2024-02-23T08:10:00.000Z", + "time": 60, + "request": { + "method": "GET", + "url": "https://api.example.com/products", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "queryString": [], + "postData": {}, + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "content": { + "size": 200, + "mimeType": "application/json" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1 + } + }, + { + "startedDateTime": "2024-02-23T08:15:00.000Z", + "time": 80, + "request": { + "method": "PUT", + "url": "https://api.example.com/products/123", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "queryString": [], + "postData": { + "mimeType": "application/json", + "text": "{\"name\":\"New Product\",\"price\":50}" + }, + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "content": { + "size": 50, + "mimeType": "application/json" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1 + } + } + ] + } +}'''; + +var collectionJson = { + "log": { + "version": "1.2", + "creator": {"name": "Sample HAR Creator", "version": "1.0"}, + "entries": [ + { + "startedDateTime": "2024-02-23T08:00:00.000Z", + "time": 50, + "request": { + "method": "GET", + "url": "https://api.example.com/users", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "queryString": [], + "postData": {}, + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "content": {"size": 100, "mimeType": "application/json"}, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1 + } + }, + { + "startedDateTime": "2024-02-23T08:05:00.000Z", + "time": 70, + "request": { + "method": "POST", + "url": "https://api.example.com/login", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "queryString": [], + "postData": { + "mimeType": "application/json", + "text": "{\"username\":\"user\",\"password\":\"pass\"}" + }, + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "content": {"size": 50, "mimeType": "application/json"}, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1 + } + }, + { + "startedDateTime": "2024-02-23T08:10:00.000Z", + "time": 60, + "request": { + "method": "GET", + "url": "https://api.example.com/products", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "queryString": [], + "postData": {}, + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "content": {"size": 200, "mimeType": "application/json"}, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1 + } + }, + { + "startedDateTime": "2024-02-23T08:15:00.000Z", + "time": 80, + "request": { + "method": "PUT", + "url": "https://api.example.com/products/123", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "queryString": [], + "postData": { + "mimeType": "application/json", + "text": "{\"name\":\"New Product\",\"price\":50}" + }, + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [], + "content": {"size": 50, "mimeType": "application/json"}, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1 + } + } + ] + } +}; diff --git a/packages/har_parser/test/har_parser_test.dart b/packages/har_parser/test/har_parser_test.dart new file mode 100644 index 00000000..2dee014e --- /dev/null +++ b/packages/har_parser/test/har_parser_test.dart @@ -0,0 +1,25 @@ +import 'package:har_parser/har_parser.dart'; +import 'package:test/test.dart'; + +import 'collection_examples/collection_apidash.dart'; +import 'models/collection_apidash_model.dart'; + +void main() { + group('Postman tests', () { + test('API Dash Postman collection from Json String', () { + expect(harLogFromJsonStr(collectionJsonStr), collectionApiDashModel); + }); + + test('API Dash Postman collection from Json', () { + expect(HarLog.fromJson(collectionJson), collectionApiDashModel); + }); + + test('API Dash Postman collection to Json String', () { + expect(harLogToJsonStr(collectionApiDashModel), collectionJsonStr); + }); + + test('API Dash Postman collection to Json', () { + expect(collectionApiDashModel.toJson(), collectionJson); + }); + }); +} diff --git a/packages/har_parser/test/models/collection_apidash_model.dart b/packages/har_parser/test/models/collection_apidash_model.dart new file mode 100644 index 00000000..a92278c5 --- /dev/null +++ b/packages/har_parser/test/models/collection_apidash_model.dart @@ -0,0 +1,120 @@ +import 'package:har_parser/models/models.dart'; + +var collectionApiDashModel = HarLog( + log: Log( + version: "1.2", + creator: Creator(name: "Sample HAR Creator", version: "1.0"), + entries: [ + Entry( + startedDateTime: "2024-02-23T08:00:00.000Z", + time: 50, + request: Request( + method: "GET", + url: "https://api.example.com/users", + httpVersion: "HTTP/1.1", + cookies: [], + headers: [], + queryString: [], + postData: {}, + headersSize: -1, + bodySize: -1, + ), + response: Response( + status: 200, + statusText: "OK", + httpVersion: "HTTP/1.1", + cookies: [], + headers: [], + content: Content(size: 100, mimeType: "application/json"), + redirectURL: "", + headersSize: -1, + bodySize: -1, + ), + ), + Entry( + startedDateTime: "2024-02-23T08:05:00.000Z", + time: 70, + request: Request( + method: "POST", + url: "https://api.example.com/login", + httpVersion: "HTTP/1.1", + cookies: [], + headers: [], + queryString: [], + postData: { + "mimeType": "application/json", + "text": '{"username":"user","password":"pass"}', + }, + headersSize: -1, + bodySize: -1, + ), + response: Response( + status: 200, + statusText: "OK", + httpVersion: "HTTP/1.1", + cookies: [], + headers: [], + content: Content(size: 50, mimeType: "application/json"), + redirectURL: "", + headersSize: -1, + bodySize: -1, + ), + ), + Entry( + startedDateTime: "2024-02-23T08:10:00.000Z", + time: 60, + request: Request( + method: "GET", + url: "https://api.example.com/products", + httpVersion: "HTTP/1.1", + cookies: [], + headers: [], + queryString: [], + postData: {}, + headersSize: -1, + bodySize: -1, + ), + response: Response( + status: 200, + statusText: "OK", + httpVersion: "HTTP/1.1", + cookies: [], + headers: [], + content: Content(size: 200, mimeType: "application/json"), + redirectURL: "", + headersSize: -1, + bodySize: -1, + ), + ), + Entry( + startedDateTime: "2024-02-23T08:15:00.000Z", + time: 80, + request: Request( + method: "PUT", + url: "https://api.example.com/products/123", + httpVersion: "HTTP/1.1", + cookies: [], + headers: [], + queryString: [], + postData: { + "mimeType": "application/json", + "text": '{"name":"New Product","price":50}', + }, + headersSize: -1, + bodySize: -1, + ), + response: Response( + status: 200, + statusText: "OK", + httpVersion: "HTTP/1.1", + cookies: [], + headers: [], + content: Content(size: 50, mimeType: "application/json"), + redirectURL: "", + headersSize: -1, + bodySize: -1, + ), + ), + ], + ), +); From 1c0af6ebdab96e3f464fb771881e9a6bb4a4874f Mon Sep 17 00:00:00 2001 From: siddu015 <116783967+siddu015@users.noreply.github.com> Date: Sun, 23 Mar 2025 22:34:15 +0530 Subject: [PATCH 07/30] Update Dashboard widget to fix DashBot overlay positioning with Stack --- lib/screens/dashboard.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/screens/dashboard.dart b/lib/screens/dashboard.dart index 148ff5c9..5931c7fe 100644 --- a/lib/screens/dashboard.dart +++ b/lib/screens/dashboard.dart @@ -18,7 +18,6 @@ class Dashboard extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final railIdx = ref.watch(navRailIndexStateProvider); final isDashBotVisible = ref.watch(dashBotVisibilityProvider); - return Scaffold( body: SafeArea( child: Stack( @@ -139,7 +138,7 @@ class Dashboard extends ConsumerWidget { ], ), ), - // Conditionally show FAB only when DashBot is not visible + // TODO: Release DashBot floatingActionButton: !isDashBotVisible ? const DashBotFAB() : null, ); } From 7c5a841fb71ae463edf6ccdc410dddb89323ea4a Mon Sep 17 00:00:00 2001 From: siddu015 <116783967+siddu015@users.noreply.github.com> Date: Sun, 23 Mar 2025 23:09:27 +0530 Subject: [PATCH 08/30] Update Dashbot --- lib/dashbot/dashbot.dart | 7 +------ lib/dashbot/features/documentation.dart | 2 +- lib/dashbot/features/general_query.dart | 1 - lib/dashbot/features/test_generator.dart | 10 ---------- lib/dashbot/services/dashbot_service.dart | 8 ++++---- lib/dashbot/widgets/dashbot_widget.dart | 16 +++------------- lib/dashbot/widgets/test_runner_widget.dart | 4 ++-- 7 files changed, 11 insertions(+), 37 deletions(-) diff --git a/lib/dashbot/dashbot.dart b/lib/dashbot/dashbot.dart index e2371acd..07bc6b7f 100644 --- a/lib/dashbot/dashbot.dart +++ b/lib/dashbot/dashbot.dart @@ -2,11 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:apidash/dashbot/widgets/dashbot_widget.dart'; -// Provider to manage DashBot visibility state final dashBotVisibilityProvider = StateProvider((ref) => false); final dashBotMinimizedProvider = StateProvider((ref) => false); -// Function to show DashBot in a bottom sheet (old style) void showDashBotBottomSheet(BuildContext context) { showModalBottomSheet( context: context, @@ -18,13 +16,11 @@ void showDashBotBottomSheet(BuildContext context) { ); } -// Function to toggle DashBot overlay (new style) void toggleDashBotOverlay(WidgetRef ref) { ref.read(dashBotVisibilityProvider.notifier).state = true; ref.read(dashBotMinimizedProvider.notifier).state = false; } -// DashBot Overlay Widget class DashBotOverlay extends ConsumerWidget { const DashBotOverlay({super.key}); @@ -36,7 +32,7 @@ class DashBotOverlay extends ConsumerWidget { elevation: 8, borderRadius: BorderRadius.circular(12), child: SizedBox( - width: 400, // Fixed width for the DashBot + width: 400, height: isMinimized ? 120 : 450, child: const DashBotWidget(), ), @@ -44,7 +40,6 @@ class DashBotOverlay extends ConsumerWidget { } } -// FloatingActionButton for DashBot class DashBotFAB extends ConsumerWidget { final bool useOverlay; diff --git a/lib/dashbot/features/documentation.dart b/lib/dashbot/features/documentation.dart index da8f1949..9f6464de 100644 --- a/lib/dashbot/features/documentation.dart +++ b/lib/dashbot/features/documentation.dart @@ -1,5 +1,5 @@ import 'dart:convert'; -import '../services/dashbot_service.dart'; +import 'package:apidash/dashbot/services/dashbot_service.dart'; import 'package:apidash/models/request_model.dart'; class DocumentationFeature { diff --git a/lib/dashbot/features/general_query.dart b/lib/dashbot/features/general_query.dart index 45f4d3b5..f5ce53ee 100644 --- a/lib/dashbot/features/general_query.dart +++ b/lib/dashbot/features/general_query.dart @@ -7,7 +7,6 @@ class GeneralQueryFeature { GeneralQueryFeature(this._client); Future generateResponse(String prompt, {RequestModel? requestModel, dynamic responseModel}) async { - // Create a more focused prompt that incorporates request/response context if available String enhancedPrompt = prompt; if (requestModel != null && responseModel != null) { diff --git a/lib/dashbot/features/test_generator.dart b/lib/dashbot/features/test_generator.dart index 301eb475..85442632 100644 --- a/lib/dashbot/features/test_generator.dart +++ b/lib/dashbot/features/test_generator.dart @@ -22,20 +22,15 @@ class TestGeneratorFeature { .toUpperCase() ?? "GET"; final endpoint = requestModel.httpRequestModel?.url ?? "Unknown Endpoint"; - final headers = requestModel.httpRequestModel?.enabledHeadersMap ?? {}; - final parameters = requestModel.httpRequestModel?.enabledParamsMap ?? {}; - final body = requestModel.httpRequestModel?.body; final rawResponse = responseModel.body; final responseBody = rawResponse is String ? rawResponse : jsonEncode(rawResponse); final statusCode = responseModel.statusCode ?? 0; - // Extract base URL and endpoint path Uri uri = Uri.parse(endpoint); final baseUrl = "${uri.scheme}://${uri.host}"; final path = uri.path; - // Analyze parameter types and values final parameterAnalysis = _analyzeParameters(uri.queryParameters); final prompt = """ @@ -67,11 +62,7 @@ For each test case: Focus on creating realistic test values based on the API context (e.g., for a country flag API, use real country codes, invalid codes, etc.) """; - // Generate the test cases final testCases = await _service.generateResponse(prompt); - - // Return only a button trigger message with the test cases hidden - // This will be detected in DashBotWidget to show only a button instead of the full text return "TEST_CASES_HIDDEN\n$testCases"; } @@ -83,7 +74,6 @@ Focus on creating realistic test values based on the API context (e.g., for a co Map analysis = {}; parameters.forEach((key, value) { - // Try to determine parameter type and format if (RegExp(r'^[A-Z]{3}$').hasMatch(value)) { analysis[key] = "Appears to be a 3-letter country code (ISO 3166-1 alpha-3)"; } else if (RegExp(r'^[A-Z]{2}$').hasMatch(value)) { diff --git a/lib/dashbot/services/dashbot_service.dart b/lib/dashbot/services/dashbot_service.dart index 64b3ca86..4d39f826 100644 --- a/lib/dashbot/services/dashbot_service.dart +++ b/lib/dashbot/services/dashbot_service.dart @@ -2,7 +2,7 @@ import 'package:apidash/dashbot/features/debug.dart'; import 'package:apidash/dashbot/features/documentation.dart'; import 'package:ollama_dart/ollama_dart.dart'; import 'package:apidash/dashbot/features/explain.dart'; -import 'package:apidash/dashbot/features/test_generator.dart'; // New import +import 'package:apidash/dashbot/features/test_generator.dart'; import 'package:apidash/models/request_model.dart'; import 'package:apidash/dashbot/features/general_query.dart'; @@ -11,7 +11,7 @@ class DashBotService { late final ExplainFeature _explainFeature; late final DebugFeature _debugFeature; late final DocumentationFeature _documentationFeature; - late final TestGeneratorFeature _testGeneratorFeature; // New feature + late final TestGeneratorFeature _testGeneratorFeature; final GeneralQueryFeature _generalQueryFeature; DashBotService() @@ -20,7 +20,7 @@ class DashBotService { _explainFeature = ExplainFeature(this); _debugFeature = DebugFeature(this); _documentationFeature = DocumentationFeature(this); - _testGeneratorFeature = TestGeneratorFeature(this); // Initialize new feature + _testGeneratorFeature = TestGeneratorFeature(this); } Future generateResponse(String prompt) async { @@ -38,7 +38,7 @@ class DashBotService { } else if (input == "Document API") { return _documentationFeature.generateApiDocumentation( requestModel: requestModel, responseModel: responseModel); - } else if (input == "Test API") { // New condition + } else if (input == "Test API") { return _testGeneratorFeature.generateApiTests( requestModel: requestModel, responseModel: responseModel); } diff --git a/lib/dashbot/widgets/dashbot_widget.dart b/lib/dashbot/widgets/dashbot_widget.dart index 2426fca8..98defd77 100644 --- a/lib/dashbot/widgets/dashbot_widget.dart +++ b/lib/dashbot/widgets/dashbot_widget.dart @@ -7,7 +7,9 @@ import 'test_runner_widget.dart'; import 'chat_bubble.dart'; class DashBotWidget extends ConsumerStatefulWidget { - const DashBotWidget({super.key}); + const DashBotWidget({ + super.key, + }); @override ConsumerState createState() => _DashBotWidgetState(); @@ -33,7 +35,6 @@ class _DashBotWidgetState extends ConsumerState { Future _sendMessage(String message) async { if (message.trim().isEmpty) return; - final dashBotService = ref.read(dashBotServiceProvider); final requestModel = ref.read(selectedRequestModelProvider); final responseModel = requestModel?.httpResponseModel; @@ -48,13 +49,8 @@ class _DashBotWidgetState extends ConsumerState { try { final response = await dashBotService.handleRequest( message, requestModel, responseModel); - - // Check if this is a test case response with hidden content if (response.startsWith("TEST_CASES_HIDDEN\n")) { - // Extract the test cases but don't show them in the message final testCases = response.replaceFirst("TEST_CASES_HIDDEN\n", ""); - - // Add a message with a marker that will trigger the button display ref.read(chatMessagesProvider.notifier).addMessage({ 'role': 'bot', 'message': "Test cases generated successfully. Click the button below to run them.", @@ -62,7 +58,6 @@ class _DashBotWidgetState extends ConsumerState { 'showTestButton': true, }); } else { - // Normal message handling ref.read(chatMessagesProvider.notifier).addMessage({ 'role': 'bot', 'message': response, @@ -135,7 +130,6 @@ class _DashBotWidgetState extends ConsumerState { Widget _buildHeader(BuildContext context) { final isMinimized = ref.watch(dashBotMinimizedProvider); - return Padding( padding: const EdgeInsets.fromLTRB(16, 16, 16, 0), child: Row( @@ -147,7 +141,6 @@ class _DashBotWidgetState extends ConsumerState { ), Row( children: [ - // Minimize/Maximize button with proper alignment IconButton( padding: const EdgeInsets.all(8), visualDensity: VisualDensity.compact, @@ -160,7 +153,6 @@ class _DashBotWidgetState extends ConsumerState { ref.read(dashBotMinimizedProvider.notifier).state = !isMinimized; }, ), - // Close button IconButton( padding: const EdgeInsets.all(8), visualDensity: VisualDensity.compact, @@ -170,7 +162,6 @@ class _DashBotWidgetState extends ConsumerState { ref.read(dashBotVisibilityProvider.notifier).state = false; }, ), - // Clear chat button IconButton( padding: const EdgeInsets.all(8), visualDensity: VisualDensity.compact, @@ -302,7 +293,6 @@ class _DashBotWidgetState extends ConsumerState { Widget _buildInputArea(BuildContext context) { final isMinimized = ref.watch(dashBotMinimizedProvider); - return Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), diff --git a/lib/dashbot/widgets/test_runner_widget.dart b/lib/dashbot/widgets/test_runner_widget.dart index 0057bba8..e93f384d 100644 --- a/lib/dashbot/widgets/test_runner_widget.dart +++ b/lib/dashbot/widgets/test_runner_widget.dart @@ -1,8 +1,8 @@ +import 'dart:convert'; +import 'package:apidash_core/apidash_core.dart' as http; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'dart:convert'; -import 'package:http/http.dart' as http; import 'content_renderer.dart'; class TestRunnerWidget extends ConsumerStatefulWidget { From 40322ad909b0e126537d5e800b5a4913396ed4c7 Mon Sep 17 00:00:00 2001 From: sumaiya shazad Date: Wed, 26 Mar 2025 05:54:05 +0530 Subject: [PATCH 09/30] complete changes for har importer #122 --- lib/consts.dart | 3 +- lib/importer/importer.dart | 1 + .../lib/import_export/har_io.dart | 135 +++ .../lib/import_export/import_export.dart | 1 + packages/apidash_core/pubspec.yaml | 2 + packages/apidash_core/pubspec_overrides.yaml | 3 + packages/har_parser/README.md | 311 +++--- packages/har_parser/example/har_example.dart | 414 ++++---- packages/har_parser/lib/har_parser.dart | 3 +- packages/har_parser/lib/models/har_log.dart | 73 +- .../lib/models/har_log.freezed.dart | 883 +++++++++++++++++- packages/har_parser/lib/models/har_log.g.dart | 83 +- .../lib/utils/har_parser_utils.dart | 24 + .../collection_apidash.dart | 449 +++++---- packages/har_parser/test/har_parser_test.dart | 10 +- .../test/models/collection_apidash_model.dart | 196 ++-- pubspec.lock | 7 + 17 files changed, 1980 insertions(+), 618 deletions(-) create mode 100644 packages/apidash_core/lib/import_export/har_io.dart create mode 100644 packages/har_parser/lib/utils/har_parser_utils.dart diff --git a/lib/consts.dart b/lib/consts.dart index 595aa69e..d6110f85 100644 --- a/lib/consts.dart +++ b/lib/consts.dart @@ -140,7 +140,8 @@ enum CodegenLanguage { enum ImportFormat { curl("cURL"), postman("Postman Collection v2.1"), - insomnia("Insomnia v4"); + insomnia("Insomnia v4"), + har("Har v1.2"); const ImportFormat(this.label); final String label; diff --git a/lib/importer/importer.dart b/lib/importer/importer.dart index 4d93d9be..da9bdeae 100644 --- a/lib/importer/importer.dart +++ b/lib/importer/importer.dart @@ -13,6 +13,7 @@ class Importer { .toList(), ImportFormat.postman => PostmanIO().getHttpRequestModelList(content), ImportFormat.insomnia => InsomniaIO().getHttpRequestModelList(content), + ImportFormat.har => HarParserIO().getHttpRequestModelList(content), }; } } diff --git a/packages/apidash_core/lib/import_export/har_io.dart b/packages/apidash_core/lib/import_export/har_io.dart new file mode 100644 index 00000000..93e04835 --- /dev/null +++ b/packages/apidash_core/lib/import_export/har_io.dart @@ -0,0 +1,135 @@ +import 'package:har_parser/har_parser.dart' as hp; +import 'package:seed/seed.dart'; +import '../consts.dart'; +import '../models/models.dart'; +import '../utils/utils.dart'; + +class HarParserIO { + List<(String?, HttpRequestModel)>? getHttpRequestModelList(String content) { + content = content.trim(); + try { + final hl = hp.harLogFromJsonStr(content); + final requests = hp.getRequestsFromHarLog(hl); + return requests + .map((req) => (req.$2.url, harRequestToHttpRequestModel(req.$2))) + .toList(); + } catch (e) { + return null; + } + } + + HttpRequestModel harRequestToHttpRequestModel(hp.Request request) { + HTTPVerb method; + + try { + method = HTTPVerb.values.byName((request.method ?? "").toLowerCase()); + } catch (e) { + method = kDefaultHttpMethod; + } + String url = stripUrlParams(request.url ?? ""); + List headers = []; + List isHeaderEnabledList = []; + + List params = []; + List isParamEnabledList = []; + + for (var header in request.headers ?? []) { + var name = header.name ?? ""; + var value = header.value; + var activeHeader = header.disabled ?? false; + headers.add(NameValueModel(name: name, value: value)); + isHeaderEnabledList.add(!activeHeader); + } + + for (var query in request.queryString ?? []) { + var name = query.name ?? ""; + var value = query.value; + var activeQuery = query.disabled ?? false; + params.add(NameValueModel(name: name, value: value)); + isParamEnabledList.add(!activeQuery); + } + + ContentType bodyContentType = kDefaultContentType; + String? body; + List? formData = []; + + if (request.postData?.mimeType == "application/json") { + bodyContentType = ContentType.json; + body = request.postData?.text; + } + FormDataType formDataType = FormDataType.text; + if (request.postData?.mimeType == "application/x-www-form-urlencoded") { + bodyContentType = ContentType.formdata; + var formDataStr = request.postData?.text; + Map parsedData = parseFormData(formDataStr); + parsedData.forEach((key, value) { + formDataType = FormDataType.text; + var name = key ?? ""; + var val = value ?? ""; + formData.add(FormDataModel( + name: name, + value: val, + type: formDataType, + )); + }); + } + + if (request.postData?.mimeType == "multipart/form-data") { + bodyContentType = ContentType.formdata; + var name, val; + for (var fd in request.postData?.params ?? []) { + name = fd.name; + if (fd.contentType == "text/plain") { + formDataType = FormDataType.text; + val = fd.value; + } else { + formDataType = FormDataType.file; + val = fd.fileName; + } + formData.add(FormDataModel( + name: name, + value: val, + type: formDataType, + )); + } + } + + return HttpRequestModel( + method: method, + url: url, + headers: headers, + params: params, + isHeaderEnabledList: isHeaderEnabledList, + isParamEnabledList: isParamEnabledList, + body: body, + bodyContentType: bodyContentType, + formData: formData); + } + + Map parseFormData(String? data) { + // Return an empty map if the input is null or empty + if (data == null || data.isEmpty) { + return {}; + } + // Split the input string into individual key-value pairs + var pairs = data.split('&'); + + // Create a Map to store key-value pairs + Map result = {}; + + // Loop through the pairs and split them into keys and values + for (var pair in pairs) { + var keyValue = pair.split('='); + + // Ensure the pair contains both key and value + if (keyValue.length == 2) { + var key = Uri.decodeComponent(keyValue[0]); + var value = Uri.decodeComponent(keyValue[1]); + + result[key] = value; + } + } + + return result; + } +} diff --git a/packages/apidash_core/lib/import_export/import_export.dart b/packages/apidash_core/lib/import_export/import_export.dart index 33762e50..bf195455 100644 --- a/packages/apidash_core/lib/import_export/import_export.dart +++ b/packages/apidash_core/lib/import_export/import_export.dart @@ -1,3 +1,4 @@ export 'curl_io.dart'; export 'postman_io.dart'; export 'insomnia_io.dart'; +export 'har_io.dart'; diff --git a/packages/apidash_core/pubspec.yaml b/packages/apidash_core/pubspec.yaml index 13aaea07..935052ae 100644 --- a/packages/apidash_core/pubspec.yaml +++ b/packages/apidash_core/pubspec.yaml @@ -21,6 +21,8 @@ dependencies: path: ../insomnia_collection postman: path: ../postman + har_parser: + path: ../har_parser seed: ^0.0.3 xml: ^6.3.0 diff --git a/packages/apidash_core/pubspec_overrides.yaml b/packages/apidash_core/pubspec_overrides.yaml index 61081fc7..f3858f35 100644 --- a/packages/apidash_core/pubspec_overrides.yaml +++ b/packages/apidash_core/pubspec_overrides.yaml @@ -1,7 +1,10 @@ +# melos_managed_dependency_overrides: har_parser # melos_managed_dependency_overrides: curl_parser,insomnia_collection,postman,seed dependency_overrides: curl_parser: path: ../curl_parser + har_parser: + path: ..\\har_parser insomnia_collection: path: ../insomnia_collection postman: diff --git a/packages/har_parser/README.md b/packages/har_parser/README.md index 462f3c5c..a9dfc45f 100644 --- a/packages/har_parser/README.md +++ b/packages/har_parser/README.md @@ -1,6 +1,6 @@ # insomnia -Seamlessly convert Har Collection Format to Dart. +Seamlessly convert Har Collection Format v1.2 to Dart. Helps you bring your APIs stored in Har to Dart and work with them. @@ -17,123 +17,150 @@ void main() { // Example 1: Har collection JSON string to Har model var collectionJsonStr = r''' { -"log": { + "log": { "version": "1.2", - "creator": {"name": "Sample HAR Creator", "version": "1.0"}, + "creator": {"name": "Postman", "version": "v8.x.x"}, "entries": [ { - "startedDateTime": "2024-02-23T08:00:00.000Z", - "time": 50, + "startedDateTime": "2025-03-25T12:00:00.000Z", + "time": 100, "request": { "method": "GET", - "url": "https://api.example.com/users", - "httpVersion": "HTTP/1.1", - "cookies": [], + "url": "https://api.apidash.dev", "headers": [], "queryString": [], - "postData": {}, - "headersSize": -1, - "bodySize": -1 + "bodySize": 0 }, "response": { "status": 200, "statusText": "OK", - "httpVersion": "HTTP/1.1", - "cookies": [], "headers": [], - "content": {"size": 100, "mimeType": "application/json"}, - "redirectURL": "", - "headersSize": -1, - "bodySize": -1 + "bodySize": 0 } }, { - "startedDateTime": "2024-02-23T08:05:00.000Z", - "time": 70, + "startedDateTime": "2025-03-25T12:01:00.000Z", + "time": 150, + "request": { + "method": "GET", + "url": "https://api.apidash.dev/country/data?code=US", + "headers": [], + "queryString": [ + {"name": "code", "value": "US"} + ], + "bodySize": 0 + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": [], + "bodySize": 0 + } + }, + { + "startedDateTime": "2025-03-25T12:02:00.000Z", + "time": 200, + "request": { + "method": "GET", + "url": + "https://api.apidash.dev/humanize/social?num=8700000&digits=3&system=SS&add_space=true&trailing_zeros=true", + "headers": [], + "queryString": [ + {"name": "num", "value": "8700000"}, + {"name": "digits", "value": "3"}, + {"name": "system", "value": "SS"}, + {"name": "add_space", "value": "true"}, + {"name": "trailing_zeros", "value": "true"} + ], + "bodySize": 0 + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": [], + "bodySize": 0 + } + }, + { + "startedDateTime": "2025-03-25T12:03:00.000Z", + "time": 300, "request": { "method": "POST", - "url": "https://api.example.com/login", - "httpVersion": "HTTP/1.1", - "cookies": [], + "url": "https://api.apidash.dev/case/lower", "headers": [], "queryString": [], + "bodySize": 50, "postData": { "mimeType": "application/json", - "text": "{\"username\":\"user\",\"password\":\"pass\"}" - }, - "headersSize": -1, - "bodySize": -1 + "text": "{ \"text\": \"I LOVE Flutter\" }" + } }, "response": { "status": 200, "statusText": "OK", - "httpVersion": "HTTP/1.1", - "cookies": [], "headers": [], - "content": {"size": 50, "mimeType": "application/json"}, - "redirectURL": "", - "headersSize": -1, - "bodySize": -1 + "bodySize": 0 } }, { - "startedDateTime": "2024-02-23T08:10:00.000Z", - "time": 60, + "startedDateTime": "2025-03-25T12:04:00.000Z", + "time": 350, "request": { - "method": "GET", - "url": "https://api.example.com/products", - "httpVersion": "HTTP/1.1", - "cookies": [], - "headers": [], + "method": "POST", + "url": "https://api.apidash.dev/io/form", + "headers": [ + {"name": "User-Agent", "value": "Test Agent"} + ], "queryString": [], - "postData": {}, - "headersSize": -1, - "bodySize": -1 + "bodySize": 100, + "postData": { + "mimeType": "multipart/form-data", + "params": [ + {"name": "text", "value": "API", "contentType": "text/plain"}, + {"name": "sep", "value": "|", "contentType": "text/plain"}, + {"name": "times", "value": "3", "contentType": "text/plain"} + ] + } }, "response": { "status": 200, "statusText": "OK", - "httpVersion": "HTTP/1.1", - "cookies": [], "headers": [], - "content": {"size": 200, "mimeType": "application/json"}, - "redirectURL": "", - "headersSize": -1, - "bodySize": -1 + "bodySize": 0 } }, { - "startedDateTime": "2024-02-23T08:15:00.000Z", - "time": 80, + "startedDateTime": "2025-03-25T12:05:00.000Z", + "time": 400, "request": { - "method": "PUT", - "url": "https://api.example.com/products/123", - "httpVersion": "HTTP/1.1", - "cookies": [], + "method": "POST", + "url": "https://api.apidash.dev/io/img", "headers": [], "queryString": [], + "bodySize": 150, "postData": { - "mimeType": "application/json", - "text": "{\"name\":\"New Product\",\"price\":50}" - }, - "headersSize": -1, - "bodySize": -1 + "mimeType": "multipart/form-data", + "params": [ + {"name": "token", "value": "xyz", "contentType": "text/plain"}, + { + "name": "imfile", + "fileName": "hire AI.jpeg", + "contentType": "image/jpeg" + } + ] + } }, "response": { "status": 200, "statusText": "OK", - "httpVersion": "HTTP/1.1", - "cookies": [], "headers": [], - "content": {"size": 50, "mimeType": "application/json"}, - "redirectURL": "", - "headersSize": -1, - "bodySize": -1 + "bodySize": 0 } } ] } - }'''; + } +'''; var collection = harLogFromJsonStr(collectionJsonStr); @@ -153,116 +180,142 @@ void main() { var collectionJson = { "log": { "version": "1.2", - "creator": {"name": "Sample HAR Creator", "version": "1.0"}, + "creator": {"name": "Postman", "version": "v8.x.x"}, "entries": [ { - "startedDateTime": "2024-02-23T08:00:00.000Z", - "time": 50, + "startedDateTime": "2025-03-25T12:00:00.000Z", + "time": 100, "request": { "method": "GET", - "url": "https://api.example.com/users", - "httpVersion": "HTTP/1.1", - "cookies": [], + "url": "https://api.apidash.dev", "headers": [], "queryString": [], - "postData": {}, - "headersSize": -1, - "bodySize": -1 + "bodySize": 0 }, "response": { "status": 200, "statusText": "OK", - "httpVersion": "HTTP/1.1", - "cookies": [], "headers": [], - "content": {"size": 100, "mimeType": "application/json"}, - "redirectURL": "", - "headersSize": -1, - "bodySize": -1 + "bodySize": 0 } }, { - "startedDateTime": "2024-02-23T08:05:00.000Z", - "time": 70, + "startedDateTime": "2025-03-25T12:01:00.000Z", + "time": 150, + "request": { + "method": "GET", + "url": "https://api.apidash.dev/country/data?code=US", + "headers": [], + "queryString": [ + {"name": "code", "value": "US"} + ], + "bodySize": 0 + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": [], + "bodySize": 0 + } + }, + { + "startedDateTime": "2025-03-25T12:02:00.000Z", + "time": 200, + "request": { + "method": "GET", + "url": + "https://api.apidash.dev/humanize/social?num=8700000&digits=3&system=SS&add_space=true&trailing_zeros=true", + "headers": [], + "queryString": [ + {"name": "num", "value": "8700000"}, + {"name": "digits", "value": "3"}, + {"name": "system", "value": "SS"}, + {"name": "add_space", "value": "true"}, + {"name": "trailing_zeros", "value": "true"} + ], + "bodySize": 0 + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": [], + "bodySize": 0 + } + }, + { + "startedDateTime": "2025-03-25T12:03:00.000Z", + "time": 300, "request": { "method": "POST", - "url": "https://api.example.com/login", - "httpVersion": "HTTP/1.1", - "cookies": [], + "url": "https://api.apidash.dev/case/lower", "headers": [], "queryString": [], + "bodySize": 50, "postData": { "mimeType": "application/json", - "text": "{\"username\":\"user\",\"password\":\"pass\"}" - }, - "headersSize": -1, - "bodySize": -1 + "text": "{ \"text\": \"I LOVE Flutter\" }" + } }, "response": { "status": 200, "statusText": "OK", - "httpVersion": "HTTP/1.1", - "cookies": [], "headers": [], - "content": {"size": 50, "mimeType": "application/json"}, - "redirectURL": "", - "headersSize": -1, - "bodySize": -1 + "bodySize": 0 } }, { - "startedDateTime": "2024-02-23T08:10:00.000Z", - "time": 60, + "startedDateTime": "2025-03-25T12:04:00.000Z", + "time": 350, "request": { - "method": "GET", - "url": "https://api.example.com/products", - "httpVersion": "HTTP/1.1", - "cookies": [], - "headers": [], + "method": "POST", + "url": "https://api.apidash.dev/io/form", + "headers": [ + {"name": "User-Agent", "value": "Test Agent"} + ], "queryString": [], - "postData": {}, - "headersSize": -1, - "bodySize": -1 + "bodySize": 100, + "postData": { + "mimeType": "multipart/form-data", + "params": [ + {"name": "text", "value": "API", "contentType": "text/plain"}, + {"name": "sep", "value": "|", "contentType": "text/plain"}, + {"name": "times", "value": "3", "contentType": "text/plain"} + ] + } }, "response": { "status": 200, "statusText": "OK", - "httpVersion": "HTTP/1.1", - "cookies": [], "headers": [], - "content": {"size": 200, "mimeType": "application/json"}, - "redirectURL": "", - "headersSize": -1, - "bodySize": -1 + "bodySize": 0 } }, { - "startedDateTime": "2024-02-23T08:15:00.000Z", - "time": 80, + "startedDateTime": "2025-03-25T12:05:00.000Z", + "time": 400, "request": { - "method": "PUT", - "url": "https://api.example.com/products/123", - "httpVersion": "HTTP/1.1", - "cookies": [], + "method": "POST", + "url": "https://api.apidash.dev/io/img", "headers": [], "queryString": [], + "bodySize": 150, "postData": { - "mimeType": "application/json", - "text": "{\"name\":\"New Product\",\"price\":50}" - }, - "headersSize": -1, - "bodySize": -1 + "mimeType": "multipart/form-data", + "params": [ + {"name": "token", "value": "xyz", "contentType": "text/plain"}, + { + "name": "imfile", + "fileName": "hire AI.jpeg", + "contentType": "image/jpeg" + } + ] + } }, "response": { "status": 200, "statusText": "OK", - "httpVersion": "HTTP/1.1", - "cookies": [], "headers": [], - "content": {"size": 50, "mimeType": "application/json"}, - "redirectURL": "", - "headersSize": -1, - "bodySize": -1 + "bodySize": 0 } } ] diff --git a/packages/har_parser/example/har_example.dart b/packages/har_parser/example/har_example.dart index 6b1c560c..5d231702 100644 --- a/packages/har_parser/example/har_example.dart +++ b/packages/har_parser/example/har_example.dart @@ -4,123 +4,155 @@ void main() { //Example 1 var collectionJsonStr = r''' { -"log": { - "version": "1.2", - "creator": {"name": "Sample HAR Creator", "version": "1.0"}, - "entries": [ - { - "startedDateTime": "2024-02-23T08:00:00.000Z", - "time": 50, - "request": { - "method": "GET", - "url": "https://api.example.com/users", - "httpVersion": "HTTP/1.1", - "cookies": [], - "headers": [], - "queryString": [], - "postData": {}, - "headersSize": -1, - "bodySize": -1 - }, - "response": { - "status": 200, - "statusText": "OK", - "httpVersion": "HTTP/1.1", - "cookies": [], - "headers": [], - "content": {"size": 100, "mimeType": "application/json"}, - "redirectURL": "", - "headersSize": -1, - "bodySize": -1 - } + "log": { + "version": "1.2", + "creator": { + "name": "Postman", + "version": "v8.x.x" + }, + "entries": [ + { + "startedDateTime": "2025-03-25T12:00:00.000Z", + "time": 100, + "request": { + "method": "GET", + "url": "https://api.apidash.dev", + "headers": [], + "queryString": [], + "bodySize": 0 }, - { - "startedDateTime": "2024-02-23T08:05:00.000Z", - "time": 70, - "request": { - "method": "POST", - "url": "https://api.example.com/login", - "httpVersion": "HTTP/1.1", - "cookies": [], - "headers": [], - "queryString": [], - "postData": { - "mimeType": "application/json", - "text": "{\"username\":\"user\",\"password\":\"pass\"}" - }, - "headersSize": -1, - "bodySize": -1 - }, - "response": { - "status": 200, - "statusText": "OK", - "httpVersion": "HTTP/1.1", - "cookies": [], - "headers": [], - "content": {"size": 50, "mimeType": "application/json"}, - "redirectURL": "", - "headersSize": -1, - "bodySize": -1 - } - }, - { - "startedDateTime": "2024-02-23T08:10:00.000Z", - "time": 60, - "request": { - "method": "GET", - "url": "https://api.example.com/products", - "httpVersion": "HTTP/1.1", - "cookies": [], - "headers": [], - "queryString": [], - "postData": {}, - "headersSize": -1, - "bodySize": -1 - }, - "response": { - "status": 200, - "statusText": "OK", - "httpVersion": "HTTP/1.1", - "cookies": [], - "headers": [], - "content": {"size": 200, "mimeType": "application/json"}, - "redirectURL": "", - "headersSize": -1, - "bodySize": -1 - } - }, - { - "startedDateTime": "2024-02-23T08:15:00.000Z", - "time": 80, - "request": { - "method": "PUT", - "url": "https://api.example.com/products/123", - "httpVersion": "HTTP/1.1", - "cookies": [], - "headers": [], - "queryString": [], - "postData": { - "mimeType": "application/json", - "text": "{\"name\":\"New Product\",\"price\":50}" - }, - "headersSize": -1, - "bodySize": -1 - }, - "response": { - "status": 200, - "statusText": "OK", - "httpVersion": "HTTP/1.1", - "cookies": [], - "headers": [], - "content": {"size": 50, "mimeType": "application/json"}, - "redirectURL": "", - "headersSize": -1, - "bodySize": -1 - } + "response": { + "status": 200, + "statusText": "OK", + "headers": [], + "bodySize": 0 } - ] - } - }'''; + }, + { + "startedDateTime": "2025-03-25T12:01:00.000Z", + "time": 150, + "request": { + "method": "GET", + "url": "https://api.apidash.dev/country/data?code=US", + "headers": [], + "queryString": [ + { + "name": "code", + "value": "US" + } + ], + "bodySize": 0 + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": [], + "bodySize": 0 + } + }, + { + "startedDateTime": "2025-03-25T12:02:00.000Z", + "time": 200, + "request": { + "method": "GET", + "url": "https://api.apidash.dev/humanize/social?num=8700000&digits=3&system=SS&add_space=true&trailing_zeros=true", + "headers": [], + "queryString": [ + { "name": "num", "value": "8700000" }, + { "name": "digits", "value": "3" }, + { "name": "system", "value": "SS" }, + { "name": "add_space", "value": "true" }, + { "name": "trailing_zeros", "value": "true" } + ], + "bodySize": 0 + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": [], + "bodySize": 0 + } + }, + { + "startedDateTime": "2025-03-25T12:03:00.000Z", + "time": 300, + "request": { + "method": "POST", + "url": "https://api.apidash.dev/case/lower", + "headers": [], + "queryString": [], + "bodySize": 50, + "postData": { + "mimeType": "application/json", + "text": "{ \"text\": \"I LOVE Flutter\" }" + } + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": [], + "bodySize": 0 + } + }, + { + "startedDateTime": "2025-03-25T12:04:00.000Z", + "time": 350, + "request": { + "method": "POST", + "url": "https://api.apidash.dev/io/form", + "headers": [ + { + "name": "User-Agent", + "value": "Test Agent" + } + ], + "queryString": [], + "bodySize": 100, + "postData": { + "mimeType": "multipart/form-data", + "params": [ + { "name": "text", "value": "API", "contentType":"text/plain" }, + { "name": "sep", "value": "|", "contentType":"text/plain" }, + { "name": "times", "value": "3", "contentType":"text/plain" } + ] + } + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": [], + "bodySize": 0 + } + } + , + { + "startedDateTime": "2025-03-25T12:05:00.000Z", + "time": 400, + "request": { + "method": "POST", + "url": "https://api.apidash.dev/io/img", + "headers": [], + "queryString": [], + "bodySize": 150, + "postData": { + "mimeType": "multipart/form-data", + "params": [ + { "name": "token", "value": "xyz", "contentType":"text/plain" }, + { "name": "imfile", "fileName": "hire AI.jpeg", "contentType":"image/jpeg" } + ] + } + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": [], + "bodySize": 0 + } + } + ] + } +} +'''; var collection = harLogFromJsonStr(collectionJsonStr); @@ -131,116 +163,142 @@ void main() { var collectionJson = { "log": { "version": "1.2", - "creator": {"name": "Sample HAR Creator", "version": "1.0"}, + "creator": {"name": "Postman", "version": "v8.x.x"}, "entries": [ { - "startedDateTime": "2024-02-23T08:00:00.000Z", - "time": 50, + "startedDateTime": "2025-03-25T12:00:00.000Z", + "time": 100, "request": { "method": "GET", - "url": "https://api.example.com/users", - "httpVersion": "HTTP/1.1", - "cookies": [], + "url": "https://api.apidash.dev", "headers": [], "queryString": [], - "postData": {}, - "headersSize": -1, - "bodySize": -1 + "bodySize": 0 }, "response": { "status": 200, "statusText": "OK", - "httpVersion": "HTTP/1.1", - "cookies": [], "headers": [], - "content": {"size": 100, "mimeType": "application/json"}, - "redirectURL": "", - "headersSize": -1, - "bodySize": -1 + "bodySize": 0 } }, { - "startedDateTime": "2024-02-23T08:05:00.000Z", - "time": 70, + "startedDateTime": "2025-03-25T12:01:00.000Z", + "time": 150, + "request": { + "method": "GET", + "url": "https://api.apidash.dev/country/data?code=US", + "headers": [], + "queryString": [ + {"name": "code", "value": "US"} + ], + "bodySize": 0 + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": [], + "bodySize": 0 + } + }, + { + "startedDateTime": "2025-03-25T12:02:00.000Z", + "time": 200, + "request": { + "method": "GET", + "url": + "https://api.apidash.dev/humanize/social?num=8700000&digits=3&system=SS&add_space=true&trailing_zeros=true", + "headers": [], + "queryString": [ + {"name": "num", "value": "8700000"}, + {"name": "digits", "value": "3"}, + {"name": "system", "value": "SS"}, + {"name": "add_space", "value": "true"}, + {"name": "trailing_zeros", "value": "true"} + ], + "bodySize": 0 + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": [], + "bodySize": 0 + } + }, + { + "startedDateTime": "2025-03-25T12:03:00.000Z", + "time": 300, "request": { "method": "POST", - "url": "https://api.example.com/login", - "httpVersion": "HTTP/1.1", - "cookies": [], + "url": "https://api.apidash.dev/case/lower", "headers": [], "queryString": [], + "bodySize": 50, "postData": { "mimeType": "application/json", - "text": "{\"username\":\"user\",\"password\":\"pass\"}" - }, - "headersSize": -1, - "bodySize": -1 + "text": "{ \"text\": \"I LOVE Flutter\" }" + } }, "response": { "status": 200, "statusText": "OK", - "httpVersion": "HTTP/1.1", - "cookies": [], "headers": [], - "content": {"size": 50, "mimeType": "application/json"}, - "redirectURL": "", - "headersSize": -1, - "bodySize": -1 + "bodySize": 0 } }, { - "startedDateTime": "2024-02-23T08:10:00.000Z", - "time": 60, + "startedDateTime": "2025-03-25T12:04:00.000Z", + "time": 350, "request": { - "method": "GET", - "url": "https://api.example.com/products", - "httpVersion": "HTTP/1.1", - "cookies": [], - "headers": [], + "method": "POST", + "url": "https://api.apidash.dev/io/form", + "headers": [ + {"name": "User-Agent", "value": "Test Agent"} + ], "queryString": [], - "postData": {}, - "headersSize": -1, - "bodySize": -1 + "bodySize": 100, + "postData": { + "mimeType": "multipart/form-data", + "params": [ + {"name": "text", "value": "API", "contentType": "text/plain"}, + {"name": "sep", "value": "|", "contentType": "text/plain"}, + {"name": "times", "value": "3", "contentType": "text/plain"} + ] + } }, "response": { "status": 200, "statusText": "OK", - "httpVersion": "HTTP/1.1", - "cookies": [], "headers": [], - "content": {"size": 200, "mimeType": "application/json"}, - "redirectURL": "", - "headersSize": -1, - "bodySize": -1 + "bodySize": 0 } }, { - "startedDateTime": "2024-02-23T08:15:00.000Z", - "time": 80, + "startedDateTime": "2025-03-25T12:05:00.000Z", + "time": 400, "request": { - "method": "PUT", - "url": "https://api.example.com/products/123", - "httpVersion": "HTTP/1.1", - "cookies": [], + "method": "POST", + "url": "https://api.apidash.dev/io/img", "headers": [], "queryString": [], + "bodySize": 150, "postData": { - "mimeType": "application/json", - "text": "{\"name\":\"New Product\",\"price\":50}" - }, - "headersSize": -1, - "bodySize": -1 + "mimeType": "multipart/form-data", + "params": [ + {"name": "token", "value": "xyz", "contentType": "text/plain"}, + { + "name": "imfile", + "fileName": "hire AI.jpeg", + "contentType": "image/jpeg" + } + ] + } }, "response": { "status": 200, "statusText": "OK", - "httpVersion": "HTTP/1.1", - "cookies": [], "headers": [], - "content": {"size": 50, "mimeType": "application/json"}, - "redirectURL": "", - "headersSize": -1, - "bodySize": -1 + "bodySize": 0 } } ] diff --git a/packages/har_parser/lib/har_parser.dart b/packages/har_parser/lib/har_parser.dart index 4300b9d6..cd7cfb95 100644 --- a/packages/har_parser/lib/har_parser.dart +++ b/packages/har_parser/lib/har_parser.dart @@ -1,3 +1,4 @@ -library har; +library har_parser; export 'models/models.dart'; +export 'utils/har_parser_utils.dart'; diff --git a/packages/har_parser/lib/models/har_log.dart b/packages/har_parser/lib/models/har_log.dart index 8887e315..1c33db30 100644 --- a/packages/har_parser/lib/models/har_log.dart +++ b/packages/har_parser/lib/models/har_log.dart @@ -64,9 +64,9 @@ class Request with _$Request { String? url, String? httpVersion, List? cookies, - List? headers, - List? queryString, - Map? postData, + List
? headers, + List? queryString, + PostData? postData, int? headersSize, int? bodySize, }) = _Request; @@ -75,6 +75,73 @@ class Request with _$Request { _$RequestFromJson(json); } +@freezed +class PostData with _$PostData { + @JsonSerializable( + explicitToJson: true, + anyMap: true, + includeIfNull: false, + ) + const factory PostData({ + String? mimeType, + String? text, + List? params, // for multipart/form-data params + }) = _PostData; + + factory PostData.fromJson(Map json) => + _$PostDataFromJson(json); +} + +@freezed +class Param with _$Param { + @JsonSerializable( + explicitToJson: true, + anyMap: true, + includeIfNull: false, + ) + const factory Param({ + String? name, + String? value, + String? fileName, + String? contentType, + bool? disabled, + }) = _Param; + + factory Param.fromJson(Map json) => _$ParamFromJson(json); +} + +@freezed +class Query with _$Query { + @JsonSerializable( + explicitToJson: true, + anyMap: true, + includeIfNull: false, + ) + const factory Query({ + String? name, + String? value, + bool? disabled, + }) = _Query; + + factory Query.fromJson(Map json) => _$QueryFromJson(json); +} + +@freezed +class Header with _$Header { + @JsonSerializable( + explicitToJson: true, + anyMap: true, + includeIfNull: false, + ) + const factory Header({ + String? name, + String? value, + bool? disabled, + }) = _Header; + + factory Header.fromJson(Map json) => _$HeaderFromJson(json); +} + @freezed class Response with _$Response { @JsonSerializable(explicitToJson: true, anyMap: true, includeIfNull: false) diff --git a/packages/har_parser/lib/models/har_log.freezed.dart b/packages/har_parser/lib/models/har_log.freezed.dart index 4e8ffad6..eb5b7f5a 100644 --- a/packages/har_parser/lib/models/har_log.freezed.dart +++ b/packages/har_parser/lib/models/har_log.freezed.dart @@ -803,9 +803,9 @@ mixin _$Request { String? get url => throw _privateConstructorUsedError; String? get httpVersion => throw _privateConstructorUsedError; List? get cookies => throw _privateConstructorUsedError; - List? get headers => throw _privateConstructorUsedError; - List? get queryString => throw _privateConstructorUsedError; - Map? get postData => throw _privateConstructorUsedError; + List
? get headers => throw _privateConstructorUsedError; + List? get queryString => throw _privateConstructorUsedError; + PostData? get postData => throw _privateConstructorUsedError; int? get headersSize => throw _privateConstructorUsedError; int? get bodySize => throw _privateConstructorUsedError; @@ -828,11 +828,13 @@ abstract class $RequestCopyWith<$Res> { String? url, String? httpVersion, List? cookies, - List? headers, - List? queryString, - Map? postData, + List
? headers, + List? queryString, + PostData? postData, int? headersSize, int? bodySize}); + + $PostDataCopyWith<$Res>? get postData; } /// @nodoc @@ -880,15 +882,15 @@ class _$RequestCopyWithImpl<$Res, $Val extends Request> headers: freezed == headers ? _value.headers : headers // ignore: cast_nullable_to_non_nullable - as List?, + as List
?, queryString: freezed == queryString ? _value.queryString : queryString // ignore: cast_nullable_to_non_nullable - as List?, + as List?, postData: freezed == postData ? _value.postData : postData // ignore: cast_nullable_to_non_nullable - as Map?, + as PostData?, headersSize: freezed == headersSize ? _value.headersSize : headersSize // ignore: cast_nullable_to_non_nullable @@ -899,6 +901,20 @@ class _$RequestCopyWithImpl<$Res, $Val extends Request> as int?, ) as $Val); } + + /// Create a copy of Request + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $PostDataCopyWith<$Res>? get postData { + if (_value.postData == null) { + return null; + } + + return $PostDataCopyWith<$Res>(_value.postData!, (value) { + return _then(_value.copyWith(postData: value) as $Val); + }); + } } /// @nodoc @@ -913,11 +929,14 @@ abstract class _$$RequestImplCopyWith<$Res> implements $RequestCopyWith<$Res> { String? url, String? httpVersion, List? cookies, - List? headers, - List? queryString, - Map? postData, + List
? headers, + List? queryString, + PostData? postData, int? headersSize, int? bodySize}); + + @override + $PostDataCopyWith<$Res>? get postData; } /// @nodoc @@ -963,15 +982,15 @@ class __$$RequestImplCopyWithImpl<$Res> headers: freezed == headers ? _value._headers : headers // ignore: cast_nullable_to_non_nullable - as List?, + as List
?, queryString: freezed == queryString ? _value._queryString : queryString // ignore: cast_nullable_to_non_nullable - as List?, + as List?, postData: freezed == postData - ? _value._postData + ? _value.postData : postData // ignore: cast_nullable_to_non_nullable - as Map?, + as PostData?, headersSize: freezed == headersSize ? _value.headersSize : headersSize // ignore: cast_nullable_to_non_nullable @@ -993,15 +1012,14 @@ class _$RequestImpl implements _Request { this.url, this.httpVersion, final List? cookies, - final List? headers, - final List? queryString, - final Map? postData, + final List
? headers, + final List? queryString, + this.postData, this.headersSize, this.bodySize}) : _cookies = cookies, _headers = headers, - _queryString = queryString, - _postData = postData; + _queryString = queryString; factory _$RequestImpl.fromJson(Map json) => _$$RequestImplFromJson(json); @@ -1022,9 +1040,9 @@ class _$RequestImpl implements _Request { return EqualUnmodifiableListView(value); } - final List? _headers; + final List
? _headers; @override - List? get headers { + List
? get headers { final value = _headers; if (value == null) return null; if (_headers is EqualUnmodifiableListView) return _headers; @@ -1032,9 +1050,9 @@ class _$RequestImpl implements _Request { return EqualUnmodifiableListView(value); } - final List? _queryString; + final List? _queryString; @override - List? get queryString { + List? get queryString { final value = _queryString; if (value == null) return null; if (_queryString is EqualUnmodifiableListView) return _queryString; @@ -1042,16 +1060,8 @@ class _$RequestImpl implements _Request { return EqualUnmodifiableListView(value); } - final Map? _postData; @override - Map? get postData { - final value = _postData; - if (value == null) return null; - if (_postData is EqualUnmodifiableMapView) return _postData; - // ignore: implicit_dynamic_type - return EqualUnmodifiableMapView(value); - } - + final PostData? postData; @override final int? headersSize; @override @@ -1075,7 +1085,8 @@ class _$RequestImpl implements _Request { const DeepCollectionEquality().equals(other._headers, _headers) && const DeepCollectionEquality() .equals(other._queryString, _queryString) && - const DeepCollectionEquality().equals(other._postData, _postData) && + (identical(other.postData, postData) || + other.postData == postData) && (identical(other.headersSize, headersSize) || other.headersSize == headersSize) && (identical(other.bodySize, bodySize) || @@ -1092,7 +1103,7 @@ class _$RequestImpl implements _Request { const DeepCollectionEquality().hash(_cookies), const DeepCollectionEquality().hash(_headers), const DeepCollectionEquality().hash(_queryString), - const DeepCollectionEquality().hash(_postData), + postData, headersSize, bodySize); @@ -1118,9 +1129,9 @@ abstract class _Request implements Request { final String? url, final String? httpVersion, final List? cookies, - final List? headers, - final List? queryString, - final Map? postData, + final List
? headers, + final List? queryString, + final PostData? postData, final int? headersSize, final int? bodySize}) = _$RequestImpl; @@ -1135,11 +1146,11 @@ abstract class _Request implements Request { @override List? get cookies; @override - List? get headers; + List
? get headers; @override - List? get queryString; + List? get queryString; @override - Map? get postData; + PostData? get postData; @override int? get headersSize; @override @@ -1153,6 +1164,794 @@ abstract class _Request implements Request { throw _privateConstructorUsedError; } +PostData _$PostDataFromJson(Map json) { + return _PostData.fromJson(json); +} + +/// @nodoc +mixin _$PostData { + String? get mimeType => throw _privateConstructorUsedError; + String? get text => throw _privateConstructorUsedError; + List? get params => throw _privateConstructorUsedError; + + /// Serializes this PostData to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of PostData + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $PostDataCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $PostDataCopyWith<$Res> { + factory $PostDataCopyWith(PostData value, $Res Function(PostData) then) = + _$PostDataCopyWithImpl<$Res, PostData>; + @useResult + $Res call({String? mimeType, String? text, List? params}); +} + +/// @nodoc +class _$PostDataCopyWithImpl<$Res, $Val extends PostData> + implements $PostDataCopyWith<$Res> { + _$PostDataCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of PostData + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? mimeType = freezed, + Object? text = freezed, + Object? params = freezed, + }) { + return _then(_value.copyWith( + mimeType: freezed == mimeType + ? _value.mimeType + : mimeType // ignore: cast_nullable_to_non_nullable + as String?, + text: freezed == text + ? _value.text + : text // ignore: cast_nullable_to_non_nullable + as String?, + params: freezed == params + ? _value.params + : params // ignore: cast_nullable_to_non_nullable + as List?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$PostDataImplCopyWith<$Res> + implements $PostDataCopyWith<$Res> { + factory _$$PostDataImplCopyWith( + _$PostDataImpl value, $Res Function(_$PostDataImpl) then) = + __$$PostDataImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String? mimeType, String? text, List? params}); +} + +/// @nodoc +class __$$PostDataImplCopyWithImpl<$Res> + extends _$PostDataCopyWithImpl<$Res, _$PostDataImpl> + implements _$$PostDataImplCopyWith<$Res> { + __$$PostDataImplCopyWithImpl( + _$PostDataImpl _value, $Res Function(_$PostDataImpl) _then) + : super(_value, _then); + + /// Create a copy of PostData + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? mimeType = freezed, + Object? text = freezed, + Object? params = freezed, + }) { + return _then(_$PostDataImpl( + mimeType: freezed == mimeType + ? _value.mimeType + : mimeType // ignore: cast_nullable_to_non_nullable + as String?, + text: freezed == text + ? _value.text + : text // ignore: cast_nullable_to_non_nullable + as String?, + params: freezed == params + ? _value._params + : params // ignore: cast_nullable_to_non_nullable + as List?, + )); + } +} + +/// @nodoc + +@JsonSerializable(explicitToJson: true, anyMap: true, includeIfNull: false) +class _$PostDataImpl implements _PostData { + const _$PostDataImpl({this.mimeType, this.text, final List? params}) + : _params = params; + + factory _$PostDataImpl.fromJson(Map json) => + _$$PostDataImplFromJson(json); + + @override + final String? mimeType; + @override + final String? text; + final List? _params; + @override + List? get params { + final value = _params; + if (value == null) return null; + if (_params is EqualUnmodifiableListView) return _params; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + @override + String toString() { + return 'PostData(mimeType: $mimeType, text: $text, params: $params)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$PostDataImpl && + (identical(other.mimeType, mimeType) || + other.mimeType == mimeType) && + (identical(other.text, text) || other.text == text) && + const DeepCollectionEquality().equals(other._params, _params)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, mimeType, text, + const DeepCollectionEquality().hash(_params)); + + /// Create a copy of PostData + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$PostDataImplCopyWith<_$PostDataImpl> get copyWith => + __$$PostDataImplCopyWithImpl<_$PostDataImpl>(this, _$identity); + + @override + Map toJson() { + return _$$PostDataImplToJson( + this, + ); + } +} + +abstract class _PostData implements PostData { + const factory _PostData( + {final String? mimeType, + final String? text, + final List? params}) = _$PostDataImpl; + + factory _PostData.fromJson(Map json) = + _$PostDataImpl.fromJson; + + @override + String? get mimeType; + @override + String? get text; + @override + List? get params; + + /// Create a copy of PostData + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$PostDataImplCopyWith<_$PostDataImpl> get copyWith => + throw _privateConstructorUsedError; +} + +Param _$ParamFromJson(Map json) { + return _Param.fromJson(json); +} + +/// @nodoc +mixin _$Param { + String? get name => throw _privateConstructorUsedError; + String? get value => throw _privateConstructorUsedError; + String? get fileName => throw _privateConstructorUsedError; + String? get contentType => throw _privateConstructorUsedError; + bool? get disabled => throw _privateConstructorUsedError; + + /// Serializes this Param to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of Param + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ParamCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ParamCopyWith<$Res> { + factory $ParamCopyWith(Param value, $Res Function(Param) then) = + _$ParamCopyWithImpl<$Res, Param>; + @useResult + $Res call( + {String? name, + String? value, + String? fileName, + String? contentType, + bool? disabled}); +} + +/// @nodoc +class _$ParamCopyWithImpl<$Res, $Val extends Param> + implements $ParamCopyWith<$Res> { + _$ParamCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of Param + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = freezed, + Object? value = freezed, + Object? fileName = freezed, + Object? contentType = freezed, + Object? disabled = freezed, + }) { + return _then(_value.copyWith( + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + value: freezed == value + ? _value.value + : value // ignore: cast_nullable_to_non_nullable + as String?, + fileName: freezed == fileName + ? _value.fileName + : fileName // ignore: cast_nullable_to_non_nullable + as String?, + contentType: freezed == contentType + ? _value.contentType + : contentType // ignore: cast_nullable_to_non_nullable + as String?, + disabled: freezed == disabled + ? _value.disabled + : disabled // ignore: cast_nullable_to_non_nullable + as bool?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ParamImplCopyWith<$Res> implements $ParamCopyWith<$Res> { + factory _$$ParamImplCopyWith( + _$ParamImpl value, $Res Function(_$ParamImpl) then) = + __$$ParamImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String? name, + String? value, + String? fileName, + String? contentType, + bool? disabled}); +} + +/// @nodoc +class __$$ParamImplCopyWithImpl<$Res> + extends _$ParamCopyWithImpl<$Res, _$ParamImpl> + implements _$$ParamImplCopyWith<$Res> { + __$$ParamImplCopyWithImpl( + _$ParamImpl _value, $Res Function(_$ParamImpl) _then) + : super(_value, _then); + + /// Create a copy of Param + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = freezed, + Object? value = freezed, + Object? fileName = freezed, + Object? contentType = freezed, + Object? disabled = freezed, + }) { + return _then(_$ParamImpl( + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + value: freezed == value + ? _value.value + : value // ignore: cast_nullable_to_non_nullable + as String?, + fileName: freezed == fileName + ? _value.fileName + : fileName // ignore: cast_nullable_to_non_nullable + as String?, + contentType: freezed == contentType + ? _value.contentType + : contentType // ignore: cast_nullable_to_non_nullable + as String?, + disabled: freezed == disabled + ? _value.disabled + : disabled // ignore: cast_nullable_to_non_nullable + as bool?, + )); + } +} + +/// @nodoc + +@JsonSerializable(explicitToJson: true, anyMap: true, includeIfNull: false) +class _$ParamImpl implements _Param { + const _$ParamImpl( + {this.name, this.value, this.fileName, this.contentType, this.disabled}); + + factory _$ParamImpl.fromJson(Map json) => + _$$ParamImplFromJson(json); + + @override + final String? name; + @override + final String? value; + @override + final String? fileName; + @override + final String? contentType; + @override + final bool? disabled; + + @override + String toString() { + return 'Param(name: $name, value: $value, fileName: $fileName, contentType: $contentType, disabled: $disabled)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ParamImpl && + (identical(other.name, name) || other.name == name) && + (identical(other.value, value) || other.value == value) && + (identical(other.fileName, fileName) || + other.fileName == fileName) && + (identical(other.contentType, contentType) || + other.contentType == contentType) && + (identical(other.disabled, disabled) || + other.disabled == disabled)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => + Object.hash(runtimeType, name, value, fileName, contentType, disabled); + + /// Create a copy of Param + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ParamImplCopyWith<_$ParamImpl> get copyWith => + __$$ParamImplCopyWithImpl<_$ParamImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ParamImplToJson( + this, + ); + } +} + +abstract class _Param implements Param { + const factory _Param( + {final String? name, + final String? value, + final String? fileName, + final String? contentType, + final bool? disabled}) = _$ParamImpl; + + factory _Param.fromJson(Map json) = _$ParamImpl.fromJson; + + @override + String? get name; + @override + String? get value; + @override + String? get fileName; + @override + String? get contentType; + @override + bool? get disabled; + + /// Create a copy of Param + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ParamImplCopyWith<_$ParamImpl> get copyWith => + throw _privateConstructorUsedError; +} + +Query _$QueryFromJson(Map json) { + return _Query.fromJson(json); +} + +/// @nodoc +mixin _$Query { + String? get name => throw _privateConstructorUsedError; + String? get value => throw _privateConstructorUsedError; + bool? get disabled => throw _privateConstructorUsedError; + + /// Serializes this Query to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of Query + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $QueryCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $QueryCopyWith<$Res> { + factory $QueryCopyWith(Query value, $Res Function(Query) then) = + _$QueryCopyWithImpl<$Res, Query>; + @useResult + $Res call({String? name, String? value, bool? disabled}); +} + +/// @nodoc +class _$QueryCopyWithImpl<$Res, $Val extends Query> + implements $QueryCopyWith<$Res> { + _$QueryCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of Query + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = freezed, + Object? value = freezed, + Object? disabled = freezed, + }) { + return _then(_value.copyWith( + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + value: freezed == value + ? _value.value + : value // ignore: cast_nullable_to_non_nullable + as String?, + disabled: freezed == disabled + ? _value.disabled + : disabled // ignore: cast_nullable_to_non_nullable + as bool?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$QueryImplCopyWith<$Res> implements $QueryCopyWith<$Res> { + factory _$$QueryImplCopyWith( + _$QueryImpl value, $Res Function(_$QueryImpl) then) = + __$$QueryImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String? name, String? value, bool? disabled}); +} + +/// @nodoc +class __$$QueryImplCopyWithImpl<$Res> + extends _$QueryCopyWithImpl<$Res, _$QueryImpl> + implements _$$QueryImplCopyWith<$Res> { + __$$QueryImplCopyWithImpl( + _$QueryImpl _value, $Res Function(_$QueryImpl) _then) + : super(_value, _then); + + /// Create a copy of Query + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = freezed, + Object? value = freezed, + Object? disabled = freezed, + }) { + return _then(_$QueryImpl( + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + value: freezed == value + ? _value.value + : value // ignore: cast_nullable_to_non_nullable + as String?, + disabled: freezed == disabled + ? _value.disabled + : disabled // ignore: cast_nullable_to_non_nullable + as bool?, + )); + } +} + +/// @nodoc + +@JsonSerializable(explicitToJson: true, anyMap: true, includeIfNull: false) +class _$QueryImpl implements _Query { + const _$QueryImpl({this.name, this.value, this.disabled}); + + factory _$QueryImpl.fromJson(Map json) => + _$$QueryImplFromJson(json); + + @override + final String? name; + @override + final String? value; + @override + final bool? disabled; + + @override + String toString() { + return 'Query(name: $name, value: $value, disabled: $disabled)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$QueryImpl && + (identical(other.name, name) || other.name == name) && + (identical(other.value, value) || other.value == value) && + (identical(other.disabled, disabled) || + other.disabled == disabled)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, name, value, disabled); + + /// Create a copy of Query + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$QueryImplCopyWith<_$QueryImpl> get copyWith => + __$$QueryImplCopyWithImpl<_$QueryImpl>(this, _$identity); + + @override + Map toJson() { + return _$$QueryImplToJson( + this, + ); + } +} + +abstract class _Query implements Query { + const factory _Query( + {final String? name, + final String? value, + final bool? disabled}) = _$QueryImpl; + + factory _Query.fromJson(Map json) = _$QueryImpl.fromJson; + + @override + String? get name; + @override + String? get value; + @override + bool? get disabled; + + /// Create a copy of Query + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$QueryImplCopyWith<_$QueryImpl> get copyWith => + throw _privateConstructorUsedError; +} + +Header _$HeaderFromJson(Map json) { + return _Header.fromJson(json); +} + +/// @nodoc +mixin _$Header { + String? get name => throw _privateConstructorUsedError; + String? get value => throw _privateConstructorUsedError; + bool? get disabled => throw _privateConstructorUsedError; + + /// Serializes this Header to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of Header + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $HeaderCopyWith
get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $HeaderCopyWith<$Res> { + factory $HeaderCopyWith(Header value, $Res Function(Header) then) = + _$HeaderCopyWithImpl<$Res, Header>; + @useResult + $Res call({String? name, String? value, bool? disabled}); +} + +/// @nodoc +class _$HeaderCopyWithImpl<$Res, $Val extends Header> + implements $HeaderCopyWith<$Res> { + _$HeaderCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of Header + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = freezed, + Object? value = freezed, + Object? disabled = freezed, + }) { + return _then(_value.copyWith( + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + value: freezed == value + ? _value.value + : value // ignore: cast_nullable_to_non_nullable + as String?, + disabled: freezed == disabled + ? _value.disabled + : disabled // ignore: cast_nullable_to_non_nullable + as bool?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$HeaderImplCopyWith<$Res> implements $HeaderCopyWith<$Res> { + factory _$$HeaderImplCopyWith( + _$HeaderImpl value, $Res Function(_$HeaderImpl) then) = + __$$HeaderImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String? name, String? value, bool? disabled}); +} + +/// @nodoc +class __$$HeaderImplCopyWithImpl<$Res> + extends _$HeaderCopyWithImpl<$Res, _$HeaderImpl> + implements _$$HeaderImplCopyWith<$Res> { + __$$HeaderImplCopyWithImpl( + _$HeaderImpl _value, $Res Function(_$HeaderImpl) _then) + : super(_value, _then); + + /// Create a copy of Header + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = freezed, + Object? value = freezed, + Object? disabled = freezed, + }) { + return _then(_$HeaderImpl( + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + value: freezed == value + ? _value.value + : value // ignore: cast_nullable_to_non_nullable + as String?, + disabled: freezed == disabled + ? _value.disabled + : disabled // ignore: cast_nullable_to_non_nullable + as bool?, + )); + } +} + +/// @nodoc + +@JsonSerializable(explicitToJson: true, anyMap: true, includeIfNull: false) +class _$HeaderImpl implements _Header { + const _$HeaderImpl({this.name, this.value, this.disabled}); + + factory _$HeaderImpl.fromJson(Map json) => + _$$HeaderImplFromJson(json); + + @override + final String? name; + @override + final String? value; + @override + final bool? disabled; + + @override + String toString() { + return 'Header(name: $name, value: $value, disabled: $disabled)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$HeaderImpl && + (identical(other.name, name) || other.name == name) && + (identical(other.value, value) || other.value == value) && + (identical(other.disabled, disabled) || + other.disabled == disabled)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, name, value, disabled); + + /// Create a copy of Header + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$HeaderImplCopyWith<_$HeaderImpl> get copyWith => + __$$HeaderImplCopyWithImpl<_$HeaderImpl>(this, _$identity); + + @override + Map toJson() { + return _$$HeaderImplToJson( + this, + ); + } +} + +abstract class _Header implements Header { + const factory _Header( + {final String? name, + final String? value, + final bool? disabled}) = _$HeaderImpl; + + factory _Header.fromJson(Map json) = _$HeaderImpl.fromJson; + + @override + String? get name; + @override + String? get value; + @override + bool? get disabled; + + /// Create a copy of Header + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$HeaderImplCopyWith<_$HeaderImpl> get copyWith => + throw _privateConstructorUsedError; +} + Response _$ResponseFromJson(Map json) { return _Response.fromJson(json); } diff --git a/packages/har_parser/lib/models/har_log.g.dart b/packages/har_parser/lib/models/har_log.g.dart index 7bec7f01..e383f6a8 100644 --- a/packages/har_parser/lib/models/har_log.g.dart +++ b/packages/har_parser/lib/models/har_log.g.dart @@ -70,11 +70,16 @@ _$RequestImpl _$$RequestImplFromJson(Map json) => _$RequestImpl( url: json['url'] as String?, httpVersion: json['httpVersion'] as String?, cookies: json['cookies'] as List?, - headers: json['headers'] as List?, - queryString: json['queryString'] as List?, - postData: (json['postData'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - ), + headers: (json['headers'] as List?) + ?.map((e) => Header.fromJson(Map.from(e as Map))) + .toList(), + queryString: (json['queryString'] as List?) + ?.map((e) => Query.fromJson(Map.from(e as Map))) + .toList(), + postData: json['postData'] == null + ? null + : PostData.fromJson( + Map.from(json['postData'] as Map)), headersSize: (json['headersSize'] as num?)?.toInt(), bodySize: (json['bodySize'] as num?)?.toInt(), ); @@ -85,13 +90,75 @@ Map _$$RequestImplToJson(_$RequestImpl instance) => if (instance.url case final value?) 'url': value, if (instance.httpVersion case final value?) 'httpVersion': value, if (instance.cookies case final value?) 'cookies': value, - if (instance.headers case final value?) 'headers': value, - if (instance.queryString case final value?) 'queryString': value, - if (instance.postData case final value?) 'postData': value, + if (instance.headers?.map((e) => e.toJson()).toList() case final value?) + 'headers': value, + if (instance.queryString?.map((e) => e.toJson()).toList() + case final value?) + 'queryString': value, + if (instance.postData?.toJson() case final value?) 'postData': value, if (instance.headersSize case final value?) 'headersSize': value, if (instance.bodySize case final value?) 'bodySize': value, }; +_$PostDataImpl _$$PostDataImplFromJson(Map json) => _$PostDataImpl( + mimeType: json['mimeType'] as String?, + text: json['text'] as String?, + params: (json['params'] as List?) + ?.map((e) => Param.fromJson(Map.from(e as Map))) + .toList(), + ); + +Map _$$PostDataImplToJson(_$PostDataImpl instance) => + { + if (instance.mimeType case final value?) 'mimeType': value, + if (instance.text case final value?) 'text': value, + if (instance.params?.map((e) => e.toJson()).toList() case final value?) + 'params': value, + }; + +_$ParamImpl _$$ParamImplFromJson(Map json) => _$ParamImpl( + name: json['name'] as String?, + value: json['value'] as String?, + fileName: json['fileName'] as String?, + contentType: json['contentType'] as String?, + disabled: json['disabled'] as bool?, + ); + +Map _$$ParamImplToJson(_$ParamImpl instance) => + { + if (instance.name case final value?) 'name': value, + if (instance.value case final value?) 'value': value, + if (instance.fileName case final value?) 'fileName': value, + if (instance.contentType case final value?) 'contentType': value, + if (instance.disabled case final value?) 'disabled': value, + }; + +_$QueryImpl _$$QueryImplFromJson(Map json) => _$QueryImpl( + name: json['name'] as String?, + value: json['value'] as String?, + disabled: json['disabled'] as bool?, + ); + +Map _$$QueryImplToJson(_$QueryImpl instance) => + { + if (instance.name case final value?) 'name': value, + if (instance.value case final value?) 'value': value, + if (instance.disabled case final value?) 'disabled': value, + }; + +_$HeaderImpl _$$HeaderImplFromJson(Map json) => _$HeaderImpl( + name: json['name'] as String?, + value: json['value'] as String?, + disabled: json['disabled'] as bool?, + ); + +Map _$$HeaderImplToJson(_$HeaderImpl instance) => + { + if (instance.name case final value?) 'name': value, + if (instance.value case final value?) 'value': value, + if (instance.disabled case final value?) 'disabled': value, + }; + _$ResponseImpl _$$ResponseImplFromJson(Map json) => _$ResponseImpl( status: (json['status'] as num?)?.toInt(), statusText: json['statusText'] as String?, diff --git a/packages/har_parser/lib/utils/har_parser_utils.dart b/packages/har_parser/lib/utils/har_parser_utils.dart new file mode 100644 index 00000000..aec7bf71 --- /dev/null +++ b/packages/har_parser/lib/utils/har_parser_utils.dart @@ -0,0 +1,24 @@ +import '../models/har_log.dart'; + +List<(String?, Request)> getRequestsFromHarLog(HarLog? hl) { + if (hl == null || hl.log == null || hl.log?.entries == null) { + return []; + } + List<(String?, Request)> requests = []; + if (hl.log?.entries?.isNotEmpty ?? false) + for (var entry in hl.log!.entries!) { + requests.addAll(getRequestsFromHarLogEntry(entry)); + } + return requests; +} + +List<(String?, Request)> getRequestsFromHarLogEntry(Entry? entry) { + if (entry == null) { + return []; + } + List<(String?, Request)> requests = []; + if (entry.request != null) { + requests.add((entry.startedDateTime, entry.request!)); + } + return requests; +} diff --git a/packages/har_parser/test/collection_examples/collection_apidash.dart b/packages/har_parser/test/collection_examples/collection_apidash.dart index f759eb5d..47a1a0a0 100644 --- a/packages/har_parser/test/collection_examples/collection_apidash.dart +++ b/packages/har_parser/test/collection_examples/collection_apidash.dart @@ -3,130 +3,180 @@ var collectionJsonStr = r''' "log": { "version": "1.2", "creator": { - "name": "Sample HAR Creator", - "version": "1.0" + "name": "Postman", + "version": "v8.x.x" }, "entries": [ { - "startedDateTime": "2024-02-23T08:00:00.000Z", - "time": 50, + "startedDateTime": "2025-03-25T12:00:00.000Z", + "time": 100, "request": { "method": "GET", - "url": "https://api.example.com/users", - "httpVersion": "HTTP/1.1", - "cookies": [], + "url": "https://api.apidash.dev", "headers": [], "queryString": [], - "postData": {}, - "headersSize": -1, - "bodySize": -1 + "bodySize": 0 }, "response": { "status": 200, "statusText": "OK", - "httpVersion": "HTTP/1.1", - "cookies": [], "headers": [], - "content": { - "size": 100, - "mimeType": "application/json" - }, - "redirectURL": "", - "headersSize": -1, - "bodySize": -1 + "bodySize": 0 } }, { - "startedDateTime": "2024-02-23T08:05:00.000Z", - "time": 70, + "startedDateTime": "2025-03-25T12:01:00.000Z", + "time": 150, + "request": { + "method": "GET", + "url": "https://api.apidash.dev/country/data?code=US", + "headers": [], + "queryString": [ + { + "name": "code", + "value": "US" + } + ], + "bodySize": 0 + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": [], + "bodySize": 0 + } + }, + { + "startedDateTime": "2025-03-25T12:02:00.000Z", + "time": 200, + "request": { + "method": "GET", + "url": "https://api.apidash.dev/humanize/social?num=8700000&digits=3&system=SS&add_space=true&trailing_zeros=true", + "headers": [], + "queryString": [ + { + "name": "num", + "value": "8700000" + }, + { + "name": "digits", + "value": "3" + }, + { + "name": "system", + "value": "SS" + }, + { + "name": "add_space", + "value": "true" + }, + { + "name": "trailing_zeros", + "value": "true" + } + ], + "bodySize": 0 + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": [], + "bodySize": 0 + } + }, + { + "startedDateTime": "2025-03-25T12:03:00.000Z", + "time": 300, "request": { "method": "POST", - "url": "https://api.example.com/login", - "httpVersion": "HTTP/1.1", - "cookies": [], + "url": "https://api.apidash.dev/case/lower", "headers": [], "queryString": [], "postData": { "mimeType": "application/json", - "text": "{\"username\":\"user\",\"password\":\"pass\"}" + "text": "{ \"text\": \"I LOVE Flutter\" }" }, - "headersSize": -1, - "bodySize": -1 + "bodySize": 50 }, "response": { "status": 200, "statusText": "OK", - "httpVersion": "HTTP/1.1", - "cookies": [], "headers": [], - "content": { - "size": 50, - "mimeType": "application/json" - }, - "redirectURL": "", - "headersSize": -1, - "bodySize": -1 + "bodySize": 0 } }, { - "startedDateTime": "2024-02-23T08:10:00.000Z", - "time": 60, + "startedDateTime": "2025-03-25T12:04:00.000Z", + "time": 350, "request": { - "method": "GET", - "url": "https://api.example.com/products", - "httpVersion": "HTTP/1.1", - "cookies": [], - "headers": [], + "method": "POST", + "url": "https://api.apidash.dev/io/form", + "headers": [ + { + "name": "User-Agent", + "value": "Test Agent" + } + ], "queryString": [], - "postData": {}, - "headersSize": -1, - "bodySize": -1 + "postData": { + "mimeType": "multipart/form-data", + "params": [ + { + "name": "text", + "value": "API", + "contentType": "text/plain" + }, + { + "name": "sep", + "value": "|", + "contentType": "text/plain" + }, + { + "name": "times", + "value": "3", + "contentType": "text/plain" + } + ] + }, + "bodySize": 100 }, "response": { "status": 200, "statusText": "OK", - "httpVersion": "HTTP/1.1", - "cookies": [], "headers": [], - "content": { - "size": 200, - "mimeType": "application/json" - }, - "redirectURL": "", - "headersSize": -1, - "bodySize": -1 + "bodySize": 0 } }, { - "startedDateTime": "2024-02-23T08:15:00.000Z", - "time": 80, + "startedDateTime": "2025-03-25T12:05:00.000Z", + "time": 400, "request": { - "method": "PUT", - "url": "https://api.example.com/products/123", - "httpVersion": "HTTP/1.1", - "cookies": [], + "method": "POST", + "url": "https://api.apidash.dev/io/img", "headers": [], "queryString": [], "postData": { - "mimeType": "application/json", - "text": "{\"name\":\"New Product\",\"price\":50}" + "mimeType": "multipart/form-data", + "params": [ + { + "name": "token", + "value": "xyz", + "contentType": "text/plain" + }, + { + "name": "imfile", + "fileName": "hire AI.jpeg", + "contentType": "image/jpeg" + } + ] }, - "headersSize": -1, - "bodySize": -1 + "bodySize": 150 }, "response": { "status": 200, "statusText": "OK", - "httpVersion": "HTTP/1.1", - "cookies": [], "headers": [], - "content": { - "size": 50, - "mimeType": "application/json" - }, - "redirectURL": "", - "headersSize": -1, - "bodySize": -1 + "bodySize": 0 } } ] @@ -134,120 +184,149 @@ var collectionJsonStr = r''' }'''; var collectionJson = { - "log": { - "version": "1.2", - "creator": {"name": "Sample HAR Creator", "version": "1.0"}, - "entries": [ - { - "startedDateTime": "2024-02-23T08:00:00.000Z", - "time": 50, - "request": { - "method": "GET", - "url": "https://api.example.com/users", - "httpVersion": "HTTP/1.1", - "cookies": [], - "headers": [], - "queryString": [], - "postData": {}, - "headersSize": -1, - "bodySize": -1 + "log": { + "version": "1.2", + "creator": { + "name": "Postman", + "version": "v8.x.x" }, - "response": { - "status": 200, - "statusText": "OK", - "httpVersion": "HTTP/1.1", - "cookies": [], - "headers": [], - "content": {"size": 100, "mimeType": "application/json"}, - "redirectURL": "", - "headersSize": -1, - "bodySize": -1 - } - }, - { - "startedDateTime": "2024-02-23T08:05:00.000Z", - "time": 70, - "request": { - "method": "POST", - "url": "https://api.example.com/login", - "httpVersion": "HTTP/1.1", - "cookies": [], - "headers": [], - "queryString": [], - "postData": { - "mimeType": "application/json", - "text": "{\"username\":\"user\",\"password\":\"pass\"}" + "entries": [ + { + "startedDateTime": "2025-03-25T12:00:00.000Z", + "time": 100, + "request": { + "method": "GET", + "url": "https://api.apidash.dev", + "headers": [], + "queryString": [], + "bodySize": 0 }, - "headersSize": -1, - "bodySize": -1 + "response": { + "status": 200, + "statusText": "OK", + "headers": [], + "bodySize": 0 + } }, - "response": { - "status": 200, - "statusText": "OK", - "httpVersion": "HTTP/1.1", - "cookies": [], - "headers": [], - "content": {"size": 50, "mimeType": "application/json"}, - "redirectURL": "", - "headersSize": -1, - "bodySize": -1 - } - }, - { - "startedDateTime": "2024-02-23T08:10:00.000Z", - "time": 60, - "request": { - "method": "GET", - "url": "https://api.example.com/products", - "httpVersion": "HTTP/1.1", - "cookies": [], - "headers": [], - "queryString": [], - "postData": {}, - "headersSize": -1, - "bodySize": -1 - }, - "response": { - "status": 200, - "statusText": "OK", - "httpVersion": "HTTP/1.1", - "cookies": [], - "headers": [], - "content": {"size": 200, "mimeType": "application/json"}, - "redirectURL": "", - "headersSize": -1, - "bodySize": -1 - } - }, - { - "startedDateTime": "2024-02-23T08:15:00.000Z", - "time": 80, - "request": { - "method": "PUT", - "url": "https://api.example.com/products/123", - "httpVersion": "HTTP/1.1", - "cookies": [], - "headers": [], - "queryString": [], - "postData": { - "mimeType": "application/json", - "text": "{\"name\":\"New Product\",\"price\":50}" + { + "startedDateTime": "2025-03-25T12:01:00.000Z", + "time": 150, + "request": { + "method": "GET", + "url": "https://api.apidash.dev/country/data?code=US", + "headers": [], + "queryString": [ + {"name": "code", "value": "US"} + ], + "bodySize": 0 }, - "headersSize": -1, - "bodySize": -1 + "response": { + "status": 200, + "statusText": "OK", + "headers": [], + "bodySize": 0 + } }, - "response": { - "status": 200, - "statusText": "OK", - "httpVersion": "HTTP/1.1", - "cookies": [], - "headers": [], - "content": {"size": 50, "mimeType": "application/json"}, - "redirectURL": "", - "headersSize": -1, - "bodySize": -1 + { + "startedDateTime": "2025-03-25T12:02:00.000Z", + "time": 200, + "request": { + "method": "GET", + "url": + "https://api.apidash.dev/humanize/social?num=8700000&digits=3&system=SS&add_space=true&trailing_zeros=true", + "headers": [], + "queryString": [ + {"name": "num", "value": "8700000"}, + {"name": "digits", "value": "3"}, + {"name": "system", "value": "SS"}, + {"name": "add_space", "value": "true"}, + {"name": "trailing_zeros", "value": "true"} + ], + "bodySize": 0 + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": [], + "bodySize": 0 + } + }, + { + "startedDateTime": "2025-03-25T12:03:00.000Z", + "time": 300, + "request": { + "method": "POST", + "url": "https://api.apidash.dev/case/lower", + "headers": [], + "queryString": [], + "bodySize": 50, + "postData": { + "mimeType": "application/json", + "text": "{ \"text\": \"I LOVE Flutter\" }" + } + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": [], + "bodySize": 0 + } + }, + { + "startedDateTime": "2025-03-25T12:04:00.000Z", + "time": 350, + "request": { + "method": "POST", + "url": "https://api.apidash.dev/io/form", + "headers": [ + {"name": "User-Agent", "value": "Test Agent"} + ], + "queryString": [], + "bodySize": 100, + "postData": { + "mimeType": "multipart/form-data", + "params": [ + {"name": "text", "value": "API", "contentType": "text/plain"}, + {"name": "sep", "value": "|", "contentType": "text/plain"}, + {"name": "times", "value": "3", "contentType": "text/plain"} + ] + } + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": [], + "bodySize": 0 + } + }, + { + "startedDateTime": "2025-03-25T12:05:00.000Z", + "time": 400, + "request": { + "method": "POST", + "url": "https://api.apidash.dev/io/img", + "headers": [], + "queryString": [], + "bodySize": 150, + "postData": { + "mimeType": "multipart/form-data", + "params": [ + {"name": "token", "value": "xyz", "contentType": "text/plain"}, + { + "name": "imfile", + "fileName": "hire AI.jpeg", + "contentType": "image/jpeg" + } + ] + } + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": [], + "bodySize": 0 + } } - } - ] - } -}; + ] + } + }; diff --git a/packages/har_parser/test/har_parser_test.dart b/packages/har_parser/test/har_parser_test.dart index 2dee014e..52d29bd0 100644 --- a/packages/har_parser/test/har_parser_test.dart +++ b/packages/har_parser/test/har_parser_test.dart @@ -5,20 +5,20 @@ import 'collection_examples/collection_apidash.dart'; import 'models/collection_apidash_model.dart'; void main() { - group('Postman tests', () { - test('API Dash Postman collection from Json String', () { + group('Har tests', () { + test('API Dash Har Requests from Json String', () { expect(harLogFromJsonStr(collectionJsonStr), collectionApiDashModel); }); - test('API Dash Postman collection from Json', () { + test('API Dash Har Requests from Json', () { expect(HarLog.fromJson(collectionJson), collectionApiDashModel); }); - test('API Dash Postman collection to Json String', () { + test('API Dash Har Requests to Json String', () { expect(harLogToJsonStr(collectionApiDashModel), collectionJsonStr); }); - test('API Dash Postman collection to Json', () { + test('API Dash Har Requests to Json', () { expect(collectionApiDashModel.toJson(), collectionJson); }); }); diff --git a/packages/har_parser/test/models/collection_apidash_model.dart b/packages/har_parser/test/models/collection_apidash_model.dart index a92278c5..2705fcc5 100644 --- a/packages/har_parser/test/models/collection_apidash_model.dart +++ b/packages/har_parser/test/models/collection_apidash_model.dart @@ -3,116 +3,180 @@ import 'package:har_parser/models/models.dart'; var collectionApiDashModel = HarLog( log: Log( version: "1.2", - creator: Creator(name: "Sample HAR Creator", version: "1.0"), + creator: Creator(name: "Postman", version: "v8.x.x"), entries: [ Entry( - startedDateTime: "2024-02-23T08:00:00.000Z", - time: 50, + startedDateTime: "2025-03-25T12:00:00.000Z", + time: 100, request: Request( method: "GET", - url: "https://api.example.com/users", - httpVersion: "HTTP/1.1", - cookies: [], + url: "https://api.apidash.dev", + httpVersion: null, + cookies: null, headers: [], queryString: [], - postData: {}, - headersSize: -1, - bodySize: -1, + postData: null, + headersSize: null, + bodySize: 0, ), response: Response( status: 200, statusText: "OK", - httpVersion: "HTTP/1.1", - cookies: [], + httpVersion: null, + cookies: null, headers: [], - content: Content(size: 100, mimeType: "application/json"), - redirectURL: "", - headersSize: -1, - bodySize: -1, + content: null, + redirectURL: null, + headersSize: null, + bodySize: 0, ), ), Entry( - startedDateTime: "2024-02-23T08:05:00.000Z", - time: 70, + startedDateTime: "2025-03-25T12:01:00.000Z", + time: 150, + request: Request( + method: "GET", + url: "https://api.apidash.dev/country/data?code=US", + httpVersion: null, + cookies: null, + headers: [], + queryString: [Query(name: "code", value: "US", disabled: null)], + postData: null, + headersSize: null, + bodySize: 0, + ), + response: Response( + status: 200, + statusText: "OK", + httpVersion: null, + cookies: null, + headers: [], + content: null, + redirectURL: null, + headersSize: null, + bodySize: 0, + ), + ), + Entry( + startedDateTime: "2025-03-25T12:02:00.000Z", + time: 200, + request: Request( + method: "GET", + url: "https://api.apidash.dev/humanize/social?num=8700000&digits=3&system=SS&add_space=true&trailing_zeros=true", + httpVersion: null, + cookies: null, + headers: [], + queryString: [ + Query(name: "num", value: "8700000", disabled: null), + Query(name: "digits", value: "3", disabled: null), + Query(name: "system", value: "SS", disabled: null), + Query(name: "add_space", value: "true", disabled: null), + Query(name: "trailing_zeros", value: "true", disabled: null) + ], + postData: null, + headersSize: null, + bodySize: 0, + ), + response: Response( + status: 200, + statusText: "OK", + httpVersion: null, + cookies: null, + headers: [], + content: null, + redirectURL: null, + headersSize: null, + bodySize: 0, + ), + ), + Entry( + startedDateTime: "2025-03-25T12:03:00.000Z", + time: 300, request: Request( method: "POST", - url: "https://api.example.com/login", - httpVersion: "HTTP/1.1", - cookies: [], + url: "https://api.apidash.dev/case/lower", headers: [], queryString: [], - postData: { - "mimeType": "application/json", - "text": '{"username":"user","password":"pass"}', - }, - headersSize: -1, - bodySize: -1, + postData: PostData( + mimeType: "application/json", + text: '{ "text": "I LOVE Flutter" }', + params: null, + ), + bodySize: 50, ), response: Response( status: 200, statusText: "OK", - httpVersion: "HTTP/1.1", - cookies: [], + httpVersion: null, + cookies: null, headers: [], - content: Content(size: 50, mimeType: "application/json"), - redirectURL: "", - headersSize: -1, - bodySize: -1, + content: null, + redirectURL: null, + headersSize: null, + bodySize: 0, ), ), Entry( - startedDateTime: "2024-02-23T08:10:00.000Z", - time: 60, + startedDateTime: "2025-03-25T12:04:00.000Z", + time: 350, request: Request( - method: "GET", - url: "https://api.example.com/products", - httpVersion: "HTTP/1.1", - cookies: [], - headers: [], + method: "POST", + url: "https://api.apidash.dev/io/form", + headers: [Header(name: "User-Agent", value: "Test Agent", disabled: null)], queryString: [], - postData: {}, - headersSize: -1, - bodySize: -1, + bodySize: 100, + postData: PostData( + mimeType: "multipart/form-data", + params: [ + Param(name: "text", value: "API", fileName: null, contentType: "text/plain", disabled: null), + Param(name: "sep", value: "|", fileName: null, contentType: "text/plain", disabled: null), + Param(name: "times", value: "3", fileName: null, contentType: "text/plain", disabled: null) + ], + ), ), response: Response( status: 200, statusText: "OK", - httpVersion: "HTTP/1.1", - cookies: [], + httpVersion: null, + cookies: null, headers: [], - content: Content(size: 200, mimeType: "application/json"), - redirectURL: "", - headersSize: -1, - bodySize: -1, + content: null, + redirectURL: null, + headersSize: null, + bodySize: 0, ), ), Entry( - startedDateTime: "2024-02-23T08:15:00.000Z", - time: 80, + startedDateTime: "2025-03-25T12:05:00.000Z", + time: 400, request: Request( - method: "PUT", - url: "https://api.example.com/products/123", - httpVersion: "HTTP/1.1", - cookies: [], + method: "POST", + url: "https://api.apidash.dev/io/img", + httpVersion: null, + cookies: null, headers: [], queryString: [], - postData: { - "mimeType": "application/json", - "text": '{"name":"New Product","price":50}', - }, - headersSize: -1, - bodySize: -1, + postData: PostData( + mimeType: "multipart/form-data", + text: null, + params: [ + Param(name: "token", value: "xyz", fileName: null, contentType: "text/plain", disabled: null), + Param(name: "imfile", value: null, fileName: "hire AI.jpeg", contentType: "image/jpeg", disabled: null) + ], + ), + headersSize: null, + bodySize: 150, ), response: Response( status: 200, statusText: "OK", - httpVersion: "HTTP/1.1", - cookies: [], + httpVersion: null, + cookies: null, headers: [], - content: Content(size: 50, mimeType: "application/json"), - redirectURL: "", - headersSize: -1, - bodySize: -1, + content: null, + redirectURL: null, + headersSize: null, + bodySize: 0, ), ), ], diff --git a/pubspec.lock b/pubspec.lock index b16f69dd..c072a0c7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -719,6 +719,13 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.2" + har_parser: + dependency: transitive + description: + path: "packages/har_parser" + relative: true + source: path + version: "0.0.1" highlighter: dependency: "direct main" description: From 23fd475ef8bc2385d974eab1f0b16dd5a721c0a8 Mon Sep 17 00:00:00 2001 From: sumaiya shazad Date: Wed, 26 Mar 2025 07:55:57 +0530 Subject: [PATCH 10/30] gsoc application --- .../gsoc/application_ayaan_ai_ui_designer.md | 127 ++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 doc/proposals/2025/gsoc/application_ayaan_ai_ui_designer.md diff --git a/doc/proposals/2025/gsoc/application_ayaan_ai_ui_designer.md b/doc/proposals/2025/gsoc/application_ayaan_ai_ui_designer.md new file mode 100644 index 00000000..c3de33e1 --- /dev/null +++ b/doc/proposals/2025/gsoc/application_ayaan_ai_ui_designer.md @@ -0,0 +1,127 @@ +### About + +1. Full Name - Mohammed Ayaan +2. Contact info (email, phone, etc.) - ayaan.md.blr@gmail.com, 99025 87579 +3. Discord handle +4. Home page (if any) +5. Blog (if any) +6. GitHub profile link - https://github.com/ayaan-md-blr +7. Twitter, LinkedIn, other socials - https://www.linkedin.com/in/md-ayaan-blr/ +8. Time zone - UTC+05:30 +9. Link to a resume - https://drive.google.com/file/d/1kICrybHZfWLkmSFGOIfv9nFpnef14DPG/view?usp=sharing + +### University Info + +1. University name - PES University Bangalore +2. Program you are enrolled in (Degree & Major/Minor) - BTech (AI/ML) +3. Year - 2023 +4. Expected graduation date - 2027 + +### Motivation & Past Experience + +Short answers to the following questions (Add relevant links wherever you can): + +1. Have you worked on or contributed to a FOSS project before? Can you attach repo links or relevant PRs? + + No. My first experience is with apidash. I have raised a PR for issue #122(https://github.com/foss42/apidash/pull/713) and + had a good learning. Fairly comfortable with the process now + and looking forward to contribute and work towards merging the PR in the apidash repo. + +2. What is your one project/achievement that you are most proud of? Why? + + I am proud of my self-learning journey in the AI area so far. I am equipped with considerable predictive and generative AI concepts and related tools/apis. + I started with the perception that AI is new, exciting but extremely difficult. I overcame this challenge using multiple learning resources and balancing with + my college academics and have been able to achieve much more than my peer group in terms of learning. + Looking forward to learning and contributing to the open source space and add a new level to my learning journey. + +3. What kind of problems or challenges motivate you the most to solve them? + + DSA related problems challenged me the most which also pushed me to solve them. I was able to solve complex problems in trees, graphs, + recursion which I found very interesting. + I am also part of the avions (college club related to aviation and aerospace) where we are building working models of airplanes. It is very challenging and at the + same time motivating to make those models from scratch and fly them. + +4. Will you be working on GSoC full-time? In case not, what will you be studying or working on while working on the project? + + Yes I can contribute full time. I dont have any other engagements since it will be my summer break. + +5. Do you mind regularly syncing up with the project mentors? + + Definitely not. This is the opportunity I am looking forward to where I can work with the bright minds and gain guidance and knowledge. I would be available for + any form of communication as required by the assignment. + +6. What interests you the most about API Dash? + + The simplicity of the gitrepo attracted me to this project. It is very easy to understand and very well written. + +7. Can you mention some areas where the project can be improved? + + Developer documentation w.r.t to the components, system design, best practices, coding standards, testing standards will increase the productivity of contributors. + Also I feel there can be improvement in the look and feel of the user interface in terms of making it appear attractive and also enhance usability. + +### Project Proposal Information + +1. Proposal Title - AI UI Designer for APIs (#617) +2. Abstract: + Develop an AI Agent which transforms API responses into dynamic, user-friendly UI components, enabling developers to visualize and interact with data effortlessly. + I plan to address this by building a new component ai_ui_agent which uses ollama models suitable for codegen (codellama or deepseek probably) to generate the flutter + widgets which can be plugged into apidash ui. We can use third party component fl_chart for the charts generation. +3. Detailed Description + + ``` + To implement this we need to carry out the below tasks in order - + + Task1: + + Evaluate the Ollama supported LLMs with good code generation capability. + + We need to attempt several prompts which give us the output as required. + We need the prompt to + - List the suitable widgets (data table/ chart/ card/ form) for the given json data. + - The prompts should be fine tuned to generate different types of widgets as chosen by user. + - The prompts should also have placeholders for customizations (Searching, sorting, custom labels in charts) + - The prompts should be fine tuned to provide the look and feel of the apidash ui. + - The prompts should give good performance as well as provide accuracy of output. + At the end of this task we should have working prompts as per the requirement. + + Task2: Build the ai_ui_agent component in the lib folder of the repo which encapsulates both the back end logic and ui widgets. + At the end of this task we expect a working component with the below structure : + ai_ui_agent + - features + ai_ui_agent_codegen.dart (This will contain the fine tuned prompts for code generation) + exporter.dart (This will contain the logic to export the generated flutter widget) + - providers + ai_ui_agent_providers.dart (Will hold the generated flutter code as state/ available for download) + - services + ai_ui_agent_service.dart (Will invoke the ollama service using ollama_dart package) + - widgets + ai_ui_widget.dart (container widget for the generated code) + (any other widgets required for customizations/styles) + - utils + validate_widget.dart (This should perform some basic validation/compilation to ensure the generated component can get rendered/exported successfully) + ai_ui_agent.dart + + Task3: Integrating this component with the response_pane widget + screens/home_page/editor_pane/details_card/response_pane.dart (Add a new button on click - render the ai_ui_widget in a pop up.) + + Task4: Writing unit and integration tests + + Task5: Perform functional testing with different apis and response formats. + This will be crucial to ensure it works with different apis with different json structures. + This task may involve fine tuning/fixing the prompts as well. + + Taks6: Updating the dev guide and user guide + + ``` + +4. Weekly Timeline: + +| Week | Focus | Key Deliverables & Achievements | +| -------------- | --------------------------------------------------------- | -------------------------------------------------------------------------------------------- | +| **Week 1** | Community Bonding and dev env setup | Connect with mentors. Understand project expectations. Install and configure dev env. | +| **Week 2-3** | Task1: Evaluate Ollama codegen model and prompts creation | Working prompts and finalized Ollama ai model | +| **Week 4-5** | Task2: Build ai_ui_agent | Features and Services | +| **Week 6-7** | Task2,3: Build ai_ui_agent | widgets, providers and utils | +| **Week 8-9** | Task4,5: unit, integration and functional testing | Unit, integration tests, meet code coverage | +| **Week 9-10** | Task6: Documentation | Update Dev guide, User Guide, Readme, Changelog | +| **Week 10-12** | Feedback and wrapup | Implement any final feedback from mentors. Open to pick up other issue related to importers. | From ca356f1a15b7dcc1653f5c2193f54bf57fde56e2 Mon Sep 17 00:00:00 2001 From: sumaiya shazad Date: Fri, 28 Mar 2025 07:02:23 +0530 Subject: [PATCH 11/30] deleted proposal doc --- .../gsoc/application_ayaan_ai_ui_designer.md | 127 ------------------ 1 file changed, 127 deletions(-) delete mode 100644 doc/proposals/2025/gsoc/application_ayaan_ai_ui_designer.md diff --git a/doc/proposals/2025/gsoc/application_ayaan_ai_ui_designer.md b/doc/proposals/2025/gsoc/application_ayaan_ai_ui_designer.md deleted file mode 100644 index c3de33e1..00000000 --- a/doc/proposals/2025/gsoc/application_ayaan_ai_ui_designer.md +++ /dev/null @@ -1,127 +0,0 @@ -### About - -1. Full Name - Mohammed Ayaan -2. Contact info (email, phone, etc.) - ayaan.md.blr@gmail.com, 99025 87579 -3. Discord handle -4. Home page (if any) -5. Blog (if any) -6. GitHub profile link - https://github.com/ayaan-md-blr -7. Twitter, LinkedIn, other socials - https://www.linkedin.com/in/md-ayaan-blr/ -8. Time zone - UTC+05:30 -9. Link to a resume - https://drive.google.com/file/d/1kICrybHZfWLkmSFGOIfv9nFpnef14DPG/view?usp=sharing - -### University Info - -1. University name - PES University Bangalore -2. Program you are enrolled in (Degree & Major/Minor) - BTech (AI/ML) -3. Year - 2023 -4. Expected graduation date - 2027 - -### Motivation & Past Experience - -Short answers to the following questions (Add relevant links wherever you can): - -1. Have you worked on or contributed to a FOSS project before? Can you attach repo links or relevant PRs? - - No. My first experience is with apidash. I have raised a PR for issue #122(https://github.com/foss42/apidash/pull/713) and - had a good learning. Fairly comfortable with the process now - and looking forward to contribute and work towards merging the PR in the apidash repo. - -2. What is your one project/achievement that you are most proud of? Why? - - I am proud of my self-learning journey in the AI area so far. I am equipped with considerable predictive and generative AI concepts and related tools/apis. - I started with the perception that AI is new, exciting but extremely difficult. I overcame this challenge using multiple learning resources and balancing with - my college academics and have been able to achieve much more than my peer group in terms of learning. - Looking forward to learning and contributing to the open source space and add a new level to my learning journey. - -3. What kind of problems or challenges motivate you the most to solve them? - - DSA related problems challenged me the most which also pushed me to solve them. I was able to solve complex problems in trees, graphs, - recursion which I found very interesting. - I am also part of the avions (college club related to aviation and aerospace) where we are building working models of airplanes. It is very challenging and at the - same time motivating to make those models from scratch and fly them. - -4. Will you be working on GSoC full-time? In case not, what will you be studying or working on while working on the project? - - Yes I can contribute full time. I dont have any other engagements since it will be my summer break. - -5. Do you mind regularly syncing up with the project mentors? - - Definitely not. This is the opportunity I am looking forward to where I can work with the bright minds and gain guidance and knowledge. I would be available for - any form of communication as required by the assignment. - -6. What interests you the most about API Dash? - - The simplicity of the gitrepo attracted me to this project. It is very easy to understand and very well written. - -7. Can you mention some areas where the project can be improved? - - Developer documentation w.r.t to the components, system design, best practices, coding standards, testing standards will increase the productivity of contributors. - Also I feel there can be improvement in the look and feel of the user interface in terms of making it appear attractive and also enhance usability. - -### Project Proposal Information - -1. Proposal Title - AI UI Designer for APIs (#617) -2. Abstract: - Develop an AI Agent which transforms API responses into dynamic, user-friendly UI components, enabling developers to visualize and interact with data effortlessly. - I plan to address this by building a new component ai_ui_agent which uses ollama models suitable for codegen (codellama or deepseek probably) to generate the flutter - widgets which can be plugged into apidash ui. We can use third party component fl_chart for the charts generation. -3. Detailed Description - - ``` - To implement this we need to carry out the below tasks in order - - - Task1: - - Evaluate the Ollama supported LLMs with good code generation capability. - - We need to attempt several prompts which give us the output as required. - We need the prompt to - - List the suitable widgets (data table/ chart/ card/ form) for the given json data. - - The prompts should be fine tuned to generate different types of widgets as chosen by user. - - The prompts should also have placeholders for customizations (Searching, sorting, custom labels in charts) - - The prompts should be fine tuned to provide the look and feel of the apidash ui. - - The prompts should give good performance as well as provide accuracy of output. - At the end of this task we should have working prompts as per the requirement. - - Task2: Build the ai_ui_agent component in the lib folder of the repo which encapsulates both the back end logic and ui widgets. - At the end of this task we expect a working component with the below structure : - ai_ui_agent - - features - ai_ui_agent_codegen.dart (This will contain the fine tuned prompts for code generation) - exporter.dart (This will contain the logic to export the generated flutter widget) - - providers - ai_ui_agent_providers.dart (Will hold the generated flutter code as state/ available for download) - - services - ai_ui_agent_service.dart (Will invoke the ollama service using ollama_dart package) - - widgets - ai_ui_widget.dart (container widget for the generated code) - (any other widgets required for customizations/styles) - - utils - validate_widget.dart (This should perform some basic validation/compilation to ensure the generated component can get rendered/exported successfully) - ai_ui_agent.dart - - Task3: Integrating this component with the response_pane widget - screens/home_page/editor_pane/details_card/response_pane.dart (Add a new button on click - render the ai_ui_widget in a pop up.) - - Task4: Writing unit and integration tests - - Task5: Perform functional testing with different apis and response formats. - This will be crucial to ensure it works with different apis with different json structures. - This task may involve fine tuning/fixing the prompts as well. - - Taks6: Updating the dev guide and user guide - - ``` - -4. Weekly Timeline: - -| Week | Focus | Key Deliverables & Achievements | -| -------------- | --------------------------------------------------------- | -------------------------------------------------------------------------------------------- | -| **Week 1** | Community Bonding and dev env setup | Connect with mentors. Understand project expectations. Install and configure dev env. | -| **Week 2-3** | Task1: Evaluate Ollama codegen model and prompts creation | Working prompts and finalized Ollama ai model | -| **Week 4-5** | Task2: Build ai_ui_agent | Features and Services | -| **Week 6-7** | Task2,3: Build ai_ui_agent | widgets, providers and utils | -| **Week 8-9** | Task4,5: unit, integration and functional testing | Unit, integration tests, meet code coverage | -| **Week 9-10** | Task6: Documentation | Update Dev guide, User Guide, Readme, Changelog | -| **Week 10-12** | Feedback and wrapup | Implement any final feedback from mentors. Open to pick up other issue related to importers. | From 1ba6a3fa67afe5554b4b03b26db266862ffaa49d Mon Sep 17 00:00:00 2001 From: Ashita Prasad Date: Sat, 17 May 2025 22:49:25 +0530 Subject: [PATCH 12/30] Update dashbot.dart --- lib/dashbot/dashbot.dart | 62 +--------------------------------------- 1 file changed, 1 insertion(+), 61 deletions(-) diff --git a/lib/dashbot/dashbot.dart b/lib/dashbot/dashbot.dart index 07bc6b7f..9454aa85 100644 --- a/lib/dashbot/dashbot.dart +++ b/lib/dashbot/dashbot.dart @@ -1,61 +1 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:apidash/dashbot/widgets/dashbot_widget.dart'; - -final dashBotVisibilityProvider = StateProvider((ref) => false); -final dashBotMinimizedProvider = StateProvider((ref) => false); - -void showDashBotBottomSheet(BuildContext context) { - showModalBottomSheet( - context: context, - isScrollControlled: true, - builder: (context) => const Padding( - padding: EdgeInsets.all(16.0), - child: DashBotWidget(), - ), - ); -} - -void toggleDashBotOverlay(WidgetRef ref) { - ref.read(dashBotVisibilityProvider.notifier).state = true; - ref.read(dashBotMinimizedProvider.notifier).state = false; -} - -class DashBotOverlay extends ConsumerWidget { - const DashBotOverlay({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final isMinimized = ref.watch(dashBotMinimizedProvider); - - return Material( - elevation: 8, - borderRadius: BorderRadius.circular(12), - child: SizedBox( - width: 400, - height: isMinimized ? 120 : 450, - child: const DashBotWidget(), - ), - ); - } -} - -class DashBotFAB extends ConsumerWidget { - final bool useOverlay; - - const DashBotFAB({this.useOverlay = true, super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - return FloatingActionButton( - onPressed: () { - if (useOverlay) { - toggleDashBotOverlay(ref); - } else { - showDashBotBottomSheet(context); - } - }, - child: const Icon(Icons.help_outline), - ); - } -} +export 'widgets/dashbot_widget.dart'; From 962bf0f230144885504c042bd4d131f5c7a890d0 Mon Sep 17 00:00:00 2001 From: Ashita Prasad Date: Sat, 17 May 2025 22:51:32 +0530 Subject: [PATCH 13/30] Update dashboard.dart --- lib/screens/dashboard.dart | 204 ++++++++++++++++++------------------- 1 file changed, 101 insertions(+), 103 deletions(-) diff --git a/lib/screens/dashboard.dart b/lib/screens/dashboard.dart index 8955dd3e..29bb7907 100644 --- a/lib/screens/dashboard.dart +++ b/lib/screens/dashboard.dart @@ -20,126 +20,124 @@ class Dashboard extends ConsumerWidget { final settings = ref.watch(settingsProvider); return Scaffold( body: SafeArea( - child: Stack( - children: [ - Row( - children: [ + child: Row( + children: [ + Column( + children: [ + SizedBox( + height: kIsMacOS ? 32.0 : 16.0, + width: 64, + ), Column( + mainAxisSize: MainAxisSize.min, children: [ - SizedBox( - height: kIsMacOS ? 32.0 : 16.0, - width: 64, + IconButton( + isSelected: railIdx == 0, + onPressed: () { + ref.read(navRailIndexStateProvider.notifier).state = 0; + }, + icon: const Icon(Icons.auto_awesome_mosaic_outlined), + selectedIcon: const Icon(Icons.auto_awesome_mosaic), ), - Column( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton( - isSelected: railIdx == 0, - onPressed: () { - ref.read(navRailIndexStateProvider.notifier).state = 0; - }, - icon: const Icon(Icons.auto_awesome_mosaic_outlined), - selectedIcon: const Icon(Icons.auto_awesome_mosaic), - ), - Text( - 'Requests', - style: Theme.of(context).textTheme.labelSmall, - ), - kVSpacer10, - IconButton( - isSelected: railIdx == 1, - onPressed: () { - ref.read(navRailIndexStateProvider.notifier).state = 1; - }, - icon: const Icon(Icons.laptop_windows_outlined), - selectedIcon: const Icon(Icons.laptop_windows), - ), - Text( - 'Variables', - style: Theme.of(context).textTheme.labelSmall, - ), - kVSpacer10, - IconButton( - isSelected: railIdx == 2, - onPressed: () { - ref.read(navRailIndexStateProvider.notifier).state = 2; - }, - icon: const Icon(Icons.history_outlined), - selectedIcon: const Icon(Icons.history_rounded), - ), - Text( - 'History', - style: Theme.of(context).textTheme.labelSmall, - ), - ], + Text( + 'Requests', + style: Theme.of(context).textTheme.labelSmall, ), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Padding( - padding: const EdgeInsets.only(bottom: 16.0), - child: NavbarButton( - railIdx: railIdx, - selectedIcon: Icons.help, - icon: Icons.help_outline, - label: 'About', - showLabel: false, - isCompact: true, - onTap: () { - showAboutAppDialog(context); - }, - ), - ), - Padding( - padding: const EdgeInsets.only(bottom: 16.0), - child: NavbarButton( - railIdx: railIdx, - buttonIdx: 3, - selectedIcon: Icons.settings, - icon: Icons.settings_outlined, - label: 'Settings', - showLabel: false, - isCompact: true, - ), - ), - ], - ), + kVSpacer10, + IconButton( + isSelected: railIdx == 1, + onPressed: () { + ref.read(navRailIndexStateProvider.notifier).state = 1; + }, + icon: const Icon(Icons.laptop_windows_outlined), + selectedIcon: const Icon(Icons.laptop_windows), + ), + Text( + 'Variables', + style: Theme.of(context).textTheme.labelSmall, + ), + kVSpacer10, + IconButton( + isSelected: railIdx == 2, + onPressed: () { + ref.read(navRailIndexStateProvider.notifier).state = 2; + }, + icon: const Icon(Icons.history_outlined), + selectedIcon: const Icon(Icons.history_rounded), + ), + Text( + 'History', + style: Theme.of(context).textTheme.labelSmall, ), ], ), - VerticalDivider( - thickness: 1, - width: 1, - color: Theme.of(context).colorScheme.surfaceContainerHigh, - ), Expanded( - child: IndexedStack( - alignment: AlignmentDirectional.topCenter, - index: railIdx, - children: const [ - HomePage(), - EnvironmentPage(), - HistoryPage(), - SettingsPage(), + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 16.0), + child: NavbarButton( + railIdx: railIdx, + selectedIcon: Icons.help, + icon: Icons.help_outline, + label: 'About', + showLabel: false, + isCompact: true, + onTap: () { + showAboutAppDialog(context); + }, + ), + ), + Padding( + padding: const EdgeInsets.only(bottom: 16.0), + child: NavbarButton( + railIdx: railIdx, + buttonIdx: 3, + selectedIcon: Icons.settings, + icon: Icons.settings_outlined, + label: 'Settings', + showLabel: false, + isCompact: true, + ), + ), ], ), - ) + ), ], ), - - // DashBot Overlay - if (isDashBotVisible) - Positioned( - bottom: 20, - right: 20, - child: const DashBotOverlay(), + VerticalDivider( + thickness: 1, + width: 1, + color: Theme.of(context).colorScheme.surfaceContainerHigh, + ), + Expanded( + child: IndexedStack( + alignment: AlignmentDirectional.topCenter, + index: railIdx, + children: const [ + HomePage(), + EnvironmentPage(), + HistoryPage(), + SettingsPage(), + ], ), + ) ], ), ), floatingActionButton: settings.isDashBotEnabled - ? const DashBotFAB() + ? FloatingActionButton( + onPressed: () => showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (context) => const Padding( + padding: EdgeInsets.all(16.0), + child: DashBotWidget(), + ), + ), + child: const Icon(Icons.help_outline), + ) : null, ); } From 2863bdd951b440cd6cf37ddca1bf634181ef3bfd Mon Sep 17 00:00:00 2001 From: Ashita Prasad Date: Sat, 17 May 2025 23:03:43 +0530 Subject: [PATCH 14/30] fix formatting and imports --- lib/dashbot/features/documentation.dart | 10 ++--- lib/dashbot/features/features.dart | 5 +++ lib/dashbot/features/general_query.dart | 15 +++++-- lib/dashbot/features/test_generator.dart | 16 ++++--- lib/dashbot/services/dashbot_service.dart | 36 ++++++++++------ lib/dashbot/widgets/chat_bubble.dart | 6 ++- lib/dashbot/widgets/content_renderer.dart | 47 ++++++++++++++++----- lib/dashbot/widgets/dashbot_widget.dart | 33 ++++++++------- lib/dashbot/widgets/test_runner_widget.dart | 22 ++++++---- 9 files changed, 128 insertions(+), 62 deletions(-) create mode 100644 lib/dashbot/features/features.dart diff --git a/lib/dashbot/features/documentation.dart b/lib/dashbot/features/documentation.dart index 9f6464de..f509f054 100644 --- a/lib/dashbot/features/documentation.dart +++ b/lib/dashbot/features/documentation.dart @@ -16,10 +16,10 @@ class DocumentationFeature { } final method = requestModel.httpRequestModel?.method - .toString() - .split('.') - .last - .toUpperCase() ?? + .toString() + .split('.') + .last + .toUpperCase() ?? "GET"; final endpoint = requestModel.httpRequestModel?.url ?? "Unknown Endpoint"; final headers = requestModel.httpRequestModel?.enabledHeadersMap ?? {}; @@ -27,7 +27,7 @@ class DocumentationFeature { final body = requestModel.httpRequestModel?.body; final rawResponse = responseModel.body; final responseBody = - rawResponse is String ? rawResponse : jsonEncode(rawResponse); + rawResponse is String ? rawResponse : jsonEncode(rawResponse); final statusCode = responseModel.statusCode ?? 0; final prompt = """ diff --git a/lib/dashbot/features/features.dart b/lib/dashbot/features/features.dart new file mode 100644 index 00000000..829bd2ab --- /dev/null +++ b/lib/dashbot/features/features.dart @@ -0,0 +1,5 @@ +export 'debug.dart'; +export 'documentation.dart'; +export 'explain.dart'; +export 'general_query.dart'; +export 'test_generator.dart'; diff --git a/lib/dashbot/features/general_query.dart b/lib/dashbot/features/general_query.dart index f5ce53ee..28245704 100644 --- a/lib/dashbot/features/general_query.dart +++ b/lib/dashbot/features/general_query.dart @@ -6,11 +6,17 @@ class GeneralQueryFeature { GeneralQueryFeature(this._client); - Future generateResponse(String prompt, {RequestModel? requestModel, dynamic responseModel}) async { + Future generateResponse(String prompt, + {RequestModel? requestModel, dynamic responseModel}) async { String enhancedPrompt = prompt; if (requestModel != null && responseModel != null) { - final method = requestModel.httpRequestModel?.method.toString().split('.').last.toUpperCase() ?? "GET"; + final method = requestModel.httpRequestModel?.method + .toString() + .split('.') + .last + .toUpperCase() ?? + "GET"; final endpoint = requestModel.httpRequestModel?.url ?? "Unknown Endpoint"; final statusCode = responseModel.statusCode ?? 0; @@ -37,7 +43,10 @@ Respond in a helpful, direct manner that specifically answers what was asked. } final response = await _client.generateCompletion( - request: GenerateCompletionRequest(model: 'llama3.2:3b', prompt: enhancedPrompt), + request: GenerateCompletionRequest( + model: 'llama3.2:3b', + prompt: enhancedPrompt, + ), ); return response.response.toString(); } diff --git a/lib/dashbot/features/test_generator.dart b/lib/dashbot/features/test_generator.dart index 85442632..ffeeb89a 100644 --- a/lib/dashbot/features/test_generator.dart +++ b/lib/dashbot/features/test_generator.dart @@ -16,15 +16,15 @@ class TestGeneratorFeature { } final method = requestModel.httpRequestModel?.method - .toString() - .split('.') - .last - .toUpperCase() ?? + .toString() + .split('.') + .last + .toUpperCase() ?? "GET"; final endpoint = requestModel.httpRequestModel?.url ?? "Unknown Endpoint"; final rawResponse = responseModel.body; final responseBody = - rawResponse is String ? rawResponse : jsonEncode(rawResponse); + rawResponse is String ? rawResponse : jsonEncode(rawResponse); final statusCode = responseModel.statusCode ?? 0; Uri uri = Uri.parse(endpoint); @@ -75,9 +75,11 @@ Focus on creating realistic test values based on the API context (e.g., for a co parameters.forEach((key, value) { if (RegExp(r'^[A-Z]{3}$').hasMatch(value)) { - analysis[key] = "Appears to be a 3-letter country code (ISO 3166-1 alpha-3)"; + analysis[key] = + "Appears to be a 3-letter country code (ISO 3166-1 alpha-3)"; } else if (RegExp(r'^[A-Z]{2}$').hasMatch(value)) { - analysis[key] = "Appears to be a 2-letter country code (ISO 3166-1 alpha-2)"; + analysis[key] = + "Appears to be a 2-letter country code (ISO 3166-1 alpha-2)"; } else if (RegExp(r'^\d+$').hasMatch(value)) { analysis[key] = "Numeric value"; } else if (RegExp(r'^[a-zA-Z]+$').hasMatch(value)) { diff --git a/lib/dashbot/services/dashbot_service.dart b/lib/dashbot/services/dashbot_service.dart index 4d39f826..72d38f03 100644 --- a/lib/dashbot/services/dashbot_service.dart +++ b/lib/dashbot/services/dashbot_service.dart @@ -1,10 +1,6 @@ -import 'package:apidash/dashbot/features/debug.dart'; -import 'package:apidash/dashbot/features/documentation.dart'; import 'package:ollama_dart/ollama_dart.dart'; -import 'package:apidash/dashbot/features/explain.dart'; -import 'package:apidash/dashbot/features/test_generator.dart'; import 'package:apidash/models/request_model.dart'; -import 'package:apidash/dashbot/features/general_query.dart'; +import '../features/features.dart'; class DashBotService { final OllamaClient _client; @@ -16,7 +12,8 @@ class DashBotService { DashBotService() : _client = OllamaClient(baseUrl: 'http://127.0.0.1:11434/api'), - _generalQueryFeature = GeneralQueryFeature(OllamaClient(baseUrl: 'http://127.0.0.1:11434/api')) { + _generalQueryFeature = GeneralQueryFeature( + OllamaClient(baseUrl: 'http://127.0.0.1:11434/api')) { _explainFeature = ExplainFeature(this); _debugFeature = DebugFeature(this); _documentationFeature = DocumentationFeature(this); @@ -28,21 +25,36 @@ class DashBotService { } Future handleRequest( - String input, RequestModel? requestModel, dynamic responseModel) async { + String input, + RequestModel? requestModel, + dynamic responseModel, + ) async { if (input == "Explain API") { return _explainFeature.explainLatestApi( - requestModel: requestModel, responseModel: responseModel); + requestModel: requestModel, + responseModel: responseModel, + ); } else if (input == "Debug API") { return _debugFeature.debugApi( - requestModel: requestModel, responseModel: responseModel); + requestModel: requestModel, + responseModel: responseModel, + ); } else if (input == "Document API") { return _documentationFeature.generateApiDocumentation( - requestModel: requestModel, responseModel: responseModel); + requestModel: requestModel, + responseModel: responseModel, + ); } else if (input == "Test API") { return _testGeneratorFeature.generateApiTests( - requestModel: requestModel, responseModel: responseModel); + requestModel: requestModel, + responseModel: responseModel, + ); } - return _generalQueryFeature.generateResponse(input, requestModel: requestModel, responseModel: responseModel); + return _generalQueryFeature.generateResponse( + input, + requestModel: requestModel, + responseModel: responseModel, + ); } } diff --git a/lib/dashbot/widgets/chat_bubble.dart b/lib/dashbot/widgets/chat_bubble.dart index a99a76d2..f00c6d5d 100644 --- a/lib/dashbot/widgets/chat_bubble.dart +++ b/lib/dashbot/widgets/chat_bubble.dart @@ -7,7 +7,11 @@ class ChatBubble extends StatelessWidget { final String message; final bool isUser; - const ChatBubble({super.key, required this.message, this.isUser = false}); + const ChatBubble({ + super.key, + required this.message, + this.isUser = false, + }); @override Widget build(BuildContext context) { diff --git a/lib/dashbot/widgets/content_renderer.dart b/lib/dashbot/widgets/content_renderer.dart index 1e8699a6..500ebd74 100644 --- a/lib/dashbot/widgets/content_renderer.dart +++ b/lib/dashbot/widgets/content_renderer.dart @@ -5,12 +5,18 @@ import 'package:flutter_highlighter/flutter_highlighter.dart'; import 'package:flutter_highlighter/themes/monokai-sublime.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; -Widget renderContent(BuildContext context, String text) { +Widget renderContent( + BuildContext context, + String text, +) { if (text.isEmpty) { return const Text("No content to display."); } - final codeBlockPattern = RegExp(r'```(\w+)?\n([\s\S]*?)```', multiLine: true); + final codeBlockPattern = RegExp( + r'```(\w+)?\n([\s\S]*?)```', + multiLine: true, + ); final matches = codeBlockPattern.allMatches(text); if (matches.isEmpty) { @@ -22,8 +28,10 @@ Widget renderContent(BuildContext context, String text) { for (var match in matches) { if (match.start > lastEnd) { - children - .add(_renderMarkdown(context, text.substring(lastEnd, match.start))); + children.add(_renderMarkdown( + context, + text.substring(lastEnd, match.start), + )); } final language = match.group(1) ?? 'text'; @@ -43,7 +51,10 @@ Widget renderContent(BuildContext context, String text) { ); } -Widget _renderMarkdown(BuildContext context, String markdown) { +Widget _renderMarkdown( + BuildContext context, + String markdown, +) { return MarkdownBody( data: markdown, selectable: true, @@ -53,7 +64,11 @@ Widget _renderMarkdown(BuildContext context, String markdown) { ); } -Widget _renderCodeBlock(BuildContext context, String language, String code) { +Widget _renderCodeBlock( + BuildContext context, + String language, + String code, +) { if (language == 'json') { try { final prettyJson = @@ -63,7 +78,10 @@ Widget _renderCodeBlock(BuildContext context, String language, String code) { color: Theme.of(context).colorScheme.surfaceContainerLow, child: SelectableText( prettyJson, - style: const TextStyle(fontFamily: 'monospace', fontSize: 12), + style: const TextStyle( + fontFamily: 'monospace', + fontSize: 12, + ), ), ); } catch (e) { @@ -78,7 +96,10 @@ Widget _renderCodeBlock(BuildContext context, String language, String code) { code, language: language, theme: monokaiSublimeTheme, - textStyle: const TextStyle(fontFamily: 'monospace', fontSize: 12), + textStyle: const TextStyle( + fontFamily: 'monospace', + fontSize: 12, + ), ), ); } catch (e) { @@ -87,14 +108,20 @@ Widget _renderCodeBlock(BuildContext context, String language, String code) { } } -Widget _renderFallbackCode(BuildContext context, String code) { +Widget _renderFallbackCode( + BuildContext context, + String code, +) { return Container( padding: const EdgeInsets.all(8), color: Theme.of(context).colorScheme.surfaceContainerLow, child: SelectableText( code, style: const TextStyle( - fontFamily: 'monospace', fontSize: 12, color: Colors.red), + fontFamily: 'monospace', + fontSize: 12, + color: Colors.red, + ), ), ); } diff --git a/lib/dashbot/widgets/dashbot_widget.dart b/lib/dashbot/widgets/dashbot_widget.dart index 98defd77..fb90ffdb 100644 --- a/lib/dashbot/widgets/dashbot_widget.dart +++ b/lib/dashbot/widgets/dashbot_widget.dart @@ -53,7 +53,8 @@ class _DashBotWidgetState extends ConsumerState { final testCases = response.replaceFirst("TEST_CASES_HIDDEN\n", ""); ref.read(chatMessagesProvider.notifier).addMessage({ 'role': 'bot', - 'message': "Test cases generated successfully. Click the button below to run them.", + 'message': + "Test cases generated successfully. Click the button below to run them.", 'testCases': testCases, 'showTestButton': true, }); @@ -113,18 +114,18 @@ class _DashBotWidgetState extends ConsumerState { child: isMinimized ? _buildMinimizedView(context) : Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildHeader(context), - const SizedBox(height: 12), - _buildQuickActions(showDebugButton), - const SizedBox(height: 12), - Expanded(child: _buildChatArea(messages)), - if (_isLoading) _buildLoadingIndicator(), - const SizedBox(height: 10), - _buildInputArea(context), - ], - ), + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildHeader(context), + const SizedBox(height: 12), + _buildQuickActions(showDebugButton), + const SizedBox(height: 12), + Expanded(child: _buildChatArea(messages)), + if (_isLoading) _buildLoadingIndicator(), + const SizedBox(height: 10), + _buildInputArea(context), + ], + ), ); } @@ -150,7 +151,8 @@ class _DashBotWidgetState extends ConsumerState { ), tooltip: isMinimized ? 'Maximize' : 'Minimize', onPressed: () { - ref.read(dashBotMinimizedProvider.notifier).state = !isMinimized; + ref.read(dashBotMinimizedProvider.notifier).state = + !isMinimized; }, ), IconButton( @@ -214,7 +216,8 @@ class _DashBotWidgetState extends ConsumerState { icon: const Icon(Icons.bug_report_outlined, size: 16), label: const Text("Debug"), style: ElevatedButton.styleFrom( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + padding: + const EdgeInsets.symmetric(horizontal: 12, vertical: 8), visualDensity: VisualDensity.compact, ), ), diff --git a/lib/dashbot/widgets/test_runner_widget.dart b/lib/dashbot/widgets/test_runner_widget.dart index e93f384d..39ed1065 100644 --- a/lib/dashbot/widgets/test_runner_widget.dart +++ b/lib/dashbot/widgets/test_runner_widget.dart @@ -8,7 +8,10 @@ import 'content_renderer.dart'; class TestRunnerWidget extends ConsumerStatefulWidget { final String testCases; - const TestRunnerWidget({Key? key, required this.testCases}) : super(key: key); + const TestRunnerWidget({ + Key? key, + required this.testCases, + }) : super(key: key); @override ConsumerState createState() => _TestRunnerWidgetState(); @@ -78,7 +81,8 @@ class _TestRunnerWidgetState extends ConsumerState { String method = "GET"; if (command.contains("-X POST") || command.contains("--request POST")) { method = "POST"; - } else if (command.contains("-X PUT") || command.contains("--request PUT")) { + } else if (command.contains("-X PUT") || + command.contains("--request PUT")) { method = "PUT"; } @@ -139,9 +143,9 @@ class _TestRunnerWidgetState extends ConsumerState { title: const Text('API Test Runner'), content: const Text( 'Run generated API tests:\n\n' - '• "Run All" executes all tests\n' - '• "Run" executes a single test\n' - '• "Copy" copies the curl command', + '• "Run All" executes all tests\n' + '• "Run" executes a single test\n' + '• "Copy" copies the curl command', ), actions: [ TextButton( @@ -189,9 +193,8 @@ class _TestRunnerWidgetState extends ConsumerState { test['description'] ?? "Test case ${index + 1}", style: TextStyle( fontWeight: FontWeight.bold, - color: hasResult - ? (isSuccess ? Colors.green : Colors.red) - : null, + color: + hasResult ? (isSuccess ? Colors.green : Colors.red) : null, ), ), subtitle: Text('Test ${index + 1} of ${_parsedTests.length}'), @@ -241,7 +244,8 @@ class _TestRunnerWidgetState extends ConsumerState { padding: const EdgeInsets.all(8), margin: const EdgeInsets.only(top: 4, bottom: 16), decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceContainerLow, + color: + Theme.of(context).colorScheme.surfaceContainerLow, borderRadius: BorderRadius.circular(4), ), width: double.infinity, From b2ea4a4c16799500f8bb127015429c586f519841 Mon Sep 17 00:00:00 2001 From: Ashita Prasad Date: Sat, 17 May 2025 23:13:07 +0530 Subject: [PATCH 15/30] format fix --- lib/dashbot/features/debug.dart | 4 ++-- lib/dashbot/features/documentation.dart | 4 ++-- lib/dashbot/features/explain.dart | 4 ++-- lib/dashbot/features/general_query.dart | 2 +- lib/dashbot/features/test_generator.dart | 4 ++-- lib/dashbot/providers/dashbot_providers.dart | 4 ++-- lib/dashbot/services/services.dart | 1 + lib/dashbot/widgets/test_runner_widget.dart | 4 ++-- 8 files changed, 14 insertions(+), 13 deletions(-) create mode 100644 lib/dashbot/services/services.dart diff --git a/lib/dashbot/features/debug.dart b/lib/dashbot/features/debug.dart index c7d86ef9..32f83c17 100644 --- a/lib/dashbot/features/debug.dart +++ b/lib/dashbot/features/debug.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import '../services/dashbot_service.dart'; -import 'package:apidash/models/request_model.dart'; +import '../services/services.dart'; +import 'package:apidash/models/models.dart'; class DebugFeature { final DashBotService _service; diff --git a/lib/dashbot/features/documentation.dart b/lib/dashbot/features/documentation.dart index f509f054..73e9bad6 100644 --- a/lib/dashbot/features/documentation.dart +++ b/lib/dashbot/features/documentation.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'package:apidash/dashbot/services/dashbot_service.dart'; -import 'package:apidash/models/request_model.dart'; +import '../services/services.dart'; +import 'package:apidash/models/models.dart'; class DocumentationFeature { final DashBotService _service; diff --git a/lib/dashbot/features/explain.dart b/lib/dashbot/features/explain.dart index 74e30248..8a6fafdc 100644 --- a/lib/dashbot/features/explain.dart +++ b/lib/dashbot/features/explain.dart @@ -1,5 +1,5 @@ -import '../services/dashbot_service.dart'; -import 'package:apidash/models/request_model.dart'; +import '../services/services.dart'; +import 'package:apidash/models/models.dart'; class ExplainFeature { final DashBotService _service; diff --git a/lib/dashbot/features/general_query.dart b/lib/dashbot/features/general_query.dart index 28245704..602376bb 100644 --- a/lib/dashbot/features/general_query.dart +++ b/lib/dashbot/features/general_query.dart @@ -1,5 +1,5 @@ import 'package:ollama_dart/ollama_dart.dart'; -import 'package:apidash/models/request_model.dart'; +import 'package:apidash/models/models.dart'; class GeneralQueryFeature { final OllamaClient _client; diff --git a/lib/dashbot/features/test_generator.dart b/lib/dashbot/features/test_generator.dart index ffeeb89a..e74b401b 100644 --- a/lib/dashbot/features/test_generator.dart +++ b/lib/dashbot/features/test_generator.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'package:apidash/dashbot/services/dashbot_service.dart'; -import 'package:apidash/models/request_model.dart'; +import '../services/services.dart'; +import 'package:apidash/models/models.dart'; class TestGeneratorFeature { final DashBotService _service; diff --git a/lib/dashbot/providers/dashbot_providers.dart b/lib/dashbot/providers/dashbot_providers.dart index 2015d6a8..16616bfd 100644 --- a/lib/dashbot/providers/dashbot_providers.dart +++ b/lib/dashbot/providers/dashbot_providers.dart @@ -1,7 +1,7 @@ import 'dart:convert'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import '../services/dashbot_service.dart'; + +import '../services/services.dart'; final chatMessagesProvider = StateNotifierProvider>>( diff --git a/lib/dashbot/services/services.dart b/lib/dashbot/services/services.dart new file mode 100644 index 00000000..89a090d9 --- /dev/null +++ b/lib/dashbot/services/services.dart @@ -0,0 +1 @@ +export 'dashbot_service.dart'; diff --git a/lib/dashbot/widgets/test_runner_widget.dart b/lib/dashbot/widgets/test_runner_widget.dart index 39ed1065..59aacbdf 100644 --- a/lib/dashbot/widgets/test_runner_widget.dart +++ b/lib/dashbot/widgets/test_runner_widget.dart @@ -9,9 +9,9 @@ class TestRunnerWidget extends ConsumerStatefulWidget { final String testCases; const TestRunnerWidget({ - Key? key, + super.key, required this.testCases, - }) : super(key: key); + }); @override ConsumerState createState() => _TestRunnerWidgetState(); From ede412f11b9b25f0332b1727ccaf2b5112b49568 Mon Sep 17 00:00:00 2001 From: Ashita Prasad Date: Sat, 17 May 2025 23:42:10 +0530 Subject: [PATCH 16/30] Update hive_services.dart --- lib/services/hive_services.dart | 65 ++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/lib/services/hive_services.dart b/lib/services/hive_services.dart index 5ca47607..c2554722 100644 --- a/lib/services/hive_services.dart +++ b/lib/services/hive_services.dart @@ -1,6 +1,8 @@ import 'package:flutter/foundation.dart'; import 'package:hive_flutter/hive_flutter.dart'; +enum HiveBoxType { normal, lazy } + const String kDataBox = "apidash-data"; const String kKeyDataBoxIds = "ids"; @@ -11,6 +13,17 @@ const String kHistoryMetaBox = "apidash-history-meta"; const String kHistoryBoxIds = "historyIds"; const String kHistoryLazyBox = "apidash-history-lazy"; +const String kDashBotBox = "apidash-dashbot-data"; +const String kKeyDashBotBoxIds = 'messages'; + +const kHiveBoxes = [ + (kDataBox, HiveBoxType.normal), + (kEnvironmentBox, HiveBoxType.normal), + (kHistoryMetaBox, HiveBoxType.normal), + (kHistoryLazyBox, HiveBoxType.lazy), + (kDashBotBox, HiveBoxType.lazy), +]; + Future initHiveBoxes( bool initializeUsingPath, String? workspaceFolderPath, @@ -34,10 +47,13 @@ Future initHiveBoxes( Future openHiveBoxes() async { try { - await Hive.openBox(kDataBox); - await Hive.openBox(kEnvironmentBox); - await Hive.openBox(kHistoryMetaBox); - await Hive.openLazyBox(kHistoryLazyBox); + for (var box in kHiveBoxes) { + if (box.$2 == HiveBoxType.normal) { + await Hive.openBox(box.$1); + } else if (box.$2 == HiveBoxType.lazy) { + await Hive.openLazyBox(box.$1); + } + } return true; } catch (e) { debugPrint("ERROR OPEN HIVE BOXES: $e"); @@ -47,17 +63,14 @@ Future openHiveBoxes() async { Future clearHiveBoxes() async { try { - if (Hive.isBoxOpen(kDataBox)) { - await Hive.box(kDataBox).clear(); - } - if (Hive.isBoxOpen(kEnvironmentBox)) { - await Hive.box(kEnvironmentBox).clear(); - } - if (Hive.isBoxOpen(kHistoryMetaBox)) { - await Hive.box(kHistoryMetaBox).clear(); - } - if (Hive.isBoxOpen(kHistoryLazyBox)) { - await Hive.lazyBox(kHistoryLazyBox).clear(); + for (var box in kHiveBoxes) { + if (Hive.isBoxOpen(box.$1)) { + if (box.$2 == HiveBoxType.normal) { + await Hive.box(box.$1).clear(); + } else if (box.$2 == HiveBoxType.lazy) { + await Hive.lazyBox(box.$1).clear(); + } + } } } catch (e) { debugPrint("ERROR CLEAR HIVE BOXES: $e"); @@ -66,17 +79,14 @@ Future clearHiveBoxes() async { Future deleteHiveBoxes() async { try { - if (Hive.isBoxOpen(kDataBox)) { - await Hive.box(kDataBox).deleteFromDisk(); - } - if (Hive.isBoxOpen(kEnvironmentBox)) { - await Hive.box(kEnvironmentBox).deleteFromDisk(); - } - if (Hive.isBoxOpen(kHistoryMetaBox)) { - await Hive.box(kHistoryMetaBox).deleteFromDisk(); - } - if (Hive.isBoxOpen(kHistoryLazyBox)) { - await Hive.lazyBox(kHistoryLazyBox).deleteFromDisk(); + for (var box in kHiveBoxes) { + if (Hive.isBoxOpen(box.$1)) { + if (box.$2 == HiveBoxType.normal) { + await Hive.box(box.$1).deleteFromDisk(); + } else if (box.$2 == HiveBoxType.lazy) { + await Hive.lazyBox(box.$1).deleteFromDisk(); + } + } } await Hive.close(); } catch (e) { @@ -91,6 +101,7 @@ class HiveHandler { late final Box environmentBox; late final Box historyMetaBox; late final LazyBox historyLazyBox; + late final LazyBox dashBotBox; HiveHandler() { debugPrint("Trying to open Hive boxes"); @@ -98,6 +109,7 @@ class HiveHandler { environmentBox = Hive.box(kEnvironmentBox); historyMetaBox = Hive.box(kHistoryMetaBox); historyLazyBox = Hive.lazyBox(kHistoryLazyBox); + dashBotBox = Hive.lazyBox(kDashBotBox); } dynamic getIds() => dataBox.get(kKeyDataBoxIds); @@ -150,6 +162,7 @@ class HiveHandler { await environmentBox.clear(); await historyMetaBox.clear(); await historyLazyBox.clear(); + await dashBotBox.clear(); } Future removeUnused() async { From a4f0bb76d448f1cf2f52b09a333df4e9fa349e42 Mon Sep 17 00:00:00 2001 From: Ashita Prasad Date: Mon, 23 Jun 2025 02:10:25 +0530 Subject: [PATCH 17/30] Update debug.dart --- lib/dashbot/features/debug.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dashbot/features/debug.dart b/lib/dashbot/features/debug.dart index 32f83c17..ddd49513 100644 --- a/lib/dashbot/features/debug.dart +++ b/lib/dashbot/features/debug.dart @@ -1,6 +1,6 @@ import 'dart:convert'; import '../services/services.dart'; -import 'package:apidash/models/models.dart'; +import '../../models/models.dart'; class DebugFeature { final DashBotService _service; From 65ba7ab93e3b617eacba3ff175f60893b48c444a Mon Sep 17 00:00:00 2001 From: Ashita Prasad Date: Mon, 23 Jun 2025 02:11:21 +0530 Subject: [PATCH 18/30] import correction --- lib/dashbot/features/documentation.dart | 2 +- lib/dashbot/features/explain.dart | 2 +- lib/dashbot/features/general_query.dart | 2 +- lib/dashbot/features/test_generator.dart | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/dashbot/features/documentation.dart b/lib/dashbot/features/documentation.dart index 73e9bad6..05e41a83 100644 --- a/lib/dashbot/features/documentation.dart +++ b/lib/dashbot/features/documentation.dart @@ -1,6 +1,6 @@ import 'dart:convert'; import '../services/services.dart'; -import 'package:apidash/models/models.dart'; +import '../../models/models.dart'; class DocumentationFeature { final DashBotService _service; diff --git a/lib/dashbot/features/explain.dart b/lib/dashbot/features/explain.dart index 8a6fafdc..929576ff 100644 --- a/lib/dashbot/features/explain.dart +++ b/lib/dashbot/features/explain.dart @@ -1,5 +1,5 @@ import '../services/services.dart'; -import 'package:apidash/models/models.dart'; +import '../../models/models.dart'; class ExplainFeature { final DashBotService _service; diff --git a/lib/dashbot/features/general_query.dart b/lib/dashbot/features/general_query.dart index 602376bb..fa2264f4 100644 --- a/lib/dashbot/features/general_query.dart +++ b/lib/dashbot/features/general_query.dart @@ -1,5 +1,5 @@ import 'package:ollama_dart/ollama_dart.dart'; -import 'package:apidash/models/models.dart'; +import '../../models/models.dart'; class GeneralQueryFeature { final OllamaClient _client; diff --git a/lib/dashbot/features/test_generator.dart b/lib/dashbot/features/test_generator.dart index e74b401b..7dd26a20 100644 --- a/lib/dashbot/features/test_generator.dart +++ b/lib/dashbot/features/test_generator.dart @@ -1,6 +1,6 @@ import 'dart:convert'; import '../services/services.dart'; -import 'package:apidash/models/models.dart'; +import '../../models/models.dart'; class TestGeneratorFeature { final DashBotService _service; From 9089179a1aa1fce39c2e260009fd1221ae4914f5 Mon Sep 17 00:00:00 2001 From: Ashita Prasad Date: Mon, 23 Jun 2025 02:15:22 +0530 Subject: [PATCH 19/30] Add consts.dart --- lib/dashbot/consts.dart | 2 ++ lib/dashbot/features/general_query.dart | 3 ++- lib/dashbot/services/dashbot_service.dart | 7 ++++--- 3 files changed, 8 insertions(+), 4 deletions(-) create mode 100644 lib/dashbot/consts.dart diff --git a/lib/dashbot/consts.dart b/lib/dashbot/consts.dart new file mode 100644 index 00000000..07e83976 --- /dev/null +++ b/lib/dashbot/consts.dart @@ -0,0 +1,2 @@ +const kModel = 'llama3.2:3b'; +const kOllamaEndpoint = 'http://127.0.0.1:11434/api'; diff --git a/lib/dashbot/features/general_query.dart b/lib/dashbot/features/general_query.dart index fa2264f4..ad398205 100644 --- a/lib/dashbot/features/general_query.dart +++ b/lib/dashbot/features/general_query.dart @@ -1,5 +1,6 @@ import 'package:ollama_dart/ollama_dart.dart'; import '../../models/models.dart'; +import '../consts.dart'; class GeneralQueryFeature { final OllamaClient _client; @@ -44,7 +45,7 @@ Respond in a helpful, direct manner that specifically answers what was asked. final response = await _client.generateCompletion( request: GenerateCompletionRequest( - model: 'llama3.2:3b', + model: kModel, prompt: enhancedPrompt, ), ); diff --git a/lib/dashbot/services/dashbot_service.dart b/lib/dashbot/services/dashbot_service.dart index 72d38f03..94e9ce52 100644 --- a/lib/dashbot/services/dashbot_service.dart +++ b/lib/dashbot/services/dashbot_service.dart @@ -1,5 +1,6 @@ import 'package:ollama_dart/ollama_dart.dart'; import 'package:apidash/models/request_model.dart'; +import '../consts.dart'; import '../features/features.dart'; class DashBotService { @@ -11,9 +12,9 @@ class DashBotService { final GeneralQueryFeature _generalQueryFeature; DashBotService() - : _client = OllamaClient(baseUrl: 'http://127.0.0.1:11434/api'), - _generalQueryFeature = GeneralQueryFeature( - OllamaClient(baseUrl: 'http://127.0.0.1:11434/api')) { + : _client = OllamaClient(baseUrl: kOllamaEndpoint), + _generalQueryFeature = + GeneralQueryFeature(OllamaClient(baseUrl: kOllamaEndpoint)) { _explainFeature = ExplainFeature(this); _debugFeature = DebugFeature(this); _documentationFeature = DocumentationFeature(this); From 702b0d119cb923c80317116b6e6a356ff1a802d6 Mon Sep 17 00:00:00 2001 From: Ashita Prasad Date: Mon, 23 Jun 2025 03:04:24 +0530 Subject: [PATCH 20/30] sharedprefs to hive --- lib/dashbot/providers/dashbot_providers.dart | 11 ++++------- lib/services/hive_services.dart | 9 +++++++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/dashbot/providers/dashbot_providers.dart b/lib/dashbot/providers/dashbot_providers.dart index 16616bfd..e49c1c1e 100644 --- a/lib/dashbot/providers/dashbot_providers.dart +++ b/lib/dashbot/providers/dashbot_providers.dart @@ -1,6 +1,6 @@ import 'dart:convert'; +import 'package:apidash/services/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; - import '../services/services.dart'; final chatMessagesProvider = @@ -17,19 +17,16 @@ class ChatMessagesNotifier extends StateNotifier>> { _loadMessages(); } - static const _storageKey = 'chatMessages'; - Future _loadMessages() async { - final prefs = await SharedPreferences.getInstance(); - final messages = prefs.getString(_storageKey); + final messages = await hiveHandler.getDashbotMessages(); if (messages != null) { state = List>.from(json.decode(messages)); } } Future _saveMessages() async { - final prefs = await SharedPreferences.getInstance(); - await prefs.setString(_storageKey, json.encode(state)); + final messages = json.encode(state); + await hiveHandler.saveDashbotMessages(messages); } void addMessage(Map message) { diff --git a/lib/services/hive_services.dart b/lib/services/hive_services.dart index c2554722..dbfc66aa 100644 --- a/lib/services/hive_services.dart +++ b/lib/services/hive_services.dart @@ -147,11 +147,16 @@ class HiveHandler { Future getHistoryRequest(String id) async => await historyLazyBox.get(id); Future setHistoryRequest( - String id, Map? historyRequestJsoon) => - historyLazyBox.put(id, historyRequestJsoon); + String id, Map? historyRequestJson) => + historyLazyBox.put(id, historyRequestJson); Future deleteHistoryRequest(String id) => historyLazyBox.delete(id); + Future getDashbotMessages() async => + await dashBotBox.get(kKeyDashBotBoxIds); + Future saveDashbotMessages(String messages) => + dashBotBox.put(kKeyDashBotBoxIds, messages); + Future clearAllHistory() async { await historyMetaBox.clear(); await historyLazyBox.clear(); From 194f76f48ad6087f738954ca7c63f550ba496ca2 Mon Sep 17 00:00:00 2001 From: Ashita Prasad Date: Mon, 23 Jun 2025 03:25:50 +0530 Subject: [PATCH 21/30] DashBot providers --- lib/dashbot/providers/dashbot_providers.dart | 4 ++++ lib/dashbot/widgets/dashbot_widget.dart | 4 +++- lib/providers/settings_providers.dart | 3 +++ lib/screens/dashboard.dart | 4 ++-- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/dashbot/providers/dashbot_providers.dart b/lib/dashbot/providers/dashbot_providers.dart index e49c1c1e..1fa78970 100644 --- a/lib/dashbot/providers/dashbot_providers.dart +++ b/lib/dashbot/providers/dashbot_providers.dart @@ -3,6 +3,10 @@ import 'package:apidash/services/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../services/services.dart'; +final dashBotMinimizedProvider = StateProvider((ref) { + return true; +}); + final chatMessagesProvider = StateNotifierProvider>>( (ref) => ChatMessagesNotifier(), diff --git a/lib/dashbot/widgets/dashbot_widget.dart b/lib/dashbot/widgets/dashbot_widget.dart index fb90ffdb..720ed312 100644 --- a/lib/dashbot/widgets/dashbot_widget.dart +++ b/lib/dashbot/widgets/dashbot_widget.dart @@ -131,6 +131,7 @@ class _DashBotWidgetState extends ConsumerState { Widget _buildHeader(BuildContext context) { final isMinimized = ref.watch(dashBotMinimizedProvider); + return Padding( padding: const EdgeInsets.fromLTRB(16, 16, 16, 0), child: Row( @@ -161,7 +162,8 @@ class _DashBotWidgetState extends ConsumerState { icon: const Icon(Icons.close, size: 20), tooltip: 'Close', onPressed: () { - ref.read(dashBotVisibilityProvider.notifier).state = false; + ref.read(isDashBotEnabledStateProvider.notifier).state = + false; }, ), IconButton( diff --git a/lib/providers/settings_providers.dart b/lib/providers/settings_providers.dart index d3cb9f2f..82f3fd24 100644 --- a/lib/providers/settings_providers.dart +++ b/lib/providers/settings_providers.dart @@ -11,6 +11,9 @@ final codegenLanguageStateProvider = StateProvider((ref) => final activeEnvironmentIdStateProvider = StateProvider((ref) => ref.watch(settingsProvider.select((value) => value.activeEnvironmentId))); +final isDashBotEnabledStateProvider = StateProvider((ref) => + ref.watch(settingsProvider.select((value) => value.isDashBotEnabled))); + final StateNotifierProvider settingsProvider = StateNotifierProvider((ref) => ThemeStateNotifier()); diff --git a/lib/screens/dashboard.dart b/lib/screens/dashboard.dart index 29bb7907..8ca89bdd 100644 --- a/lib/screens/dashboard.dart +++ b/lib/screens/dashboard.dart @@ -17,7 +17,7 @@ class Dashboard extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final railIdx = ref.watch(navRailIndexStateProvider); - final settings = ref.watch(settingsProvider); + final isDashBotEnabled = ref.watch(isDashBotEnabledStateProvider); return Scaffold( body: SafeArea( child: Row( @@ -126,7 +126,7 @@ class Dashboard extends ConsumerWidget { ], ), ), - floatingActionButton: settings.isDashBotEnabled + floatingActionButton: isDashBotEnabled ? FloatingActionButton( onPressed: () => showModalBottomSheet( context: context, From a05894d97d405d531a8d5b6921d12d1abdacda6c Mon Sep 17 00:00:00 2001 From: Ashita Prasad Date: Mon, 23 Jun 2025 03:42:21 +0530 Subject: [PATCH 22/30] Update dashbot --- lib/dashbot/widgets/dashbot_widget.dart | 7 ++----- lib/providers/settings_providers.dart | 3 --- lib/screens/dashboard.dart | 3 ++- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/lib/dashbot/widgets/dashbot_widget.dart b/lib/dashbot/widgets/dashbot_widget.dart index 720ed312..ca65e4af 100644 --- a/lib/dashbot/widgets/dashbot_widget.dart +++ b/lib/dashbot/widgets/dashbot_widget.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:apidash/dashbot/providers/dashbot_providers.dart'; import 'package:apidash/providers/providers.dart'; -import 'package:apidash/dashbot/dashbot.dart'; import 'test_runner_widget.dart'; import 'chat_bubble.dart'; @@ -105,8 +104,6 @@ class _DashBotWidgetState extends ConsumerState { final isMinimized = ref.watch(dashBotMinimizedProvider); return Container( - height: double.infinity, - width: double.infinity, decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(12), @@ -162,8 +159,7 @@ class _DashBotWidgetState extends ConsumerState { icon: const Icon(Icons.close, size: 20), tooltip: 'Close', onPressed: () { - ref.read(isDashBotEnabledStateProvider.notifier).state = - false; + Navigator.pop(context); }, ), IconButton( @@ -185,6 +181,7 @@ class _DashBotWidgetState extends ConsumerState { Widget _buildMinimizedView(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, children: [ _buildHeader(context), const SizedBox(height: 8), diff --git a/lib/providers/settings_providers.dart b/lib/providers/settings_providers.dart index 82f3fd24..d3cb9f2f 100644 --- a/lib/providers/settings_providers.dart +++ b/lib/providers/settings_providers.dart @@ -11,9 +11,6 @@ final codegenLanguageStateProvider = StateProvider((ref) => final activeEnvironmentIdStateProvider = StateProvider((ref) => ref.watch(settingsProvider.select((value) => value.activeEnvironmentId))); -final isDashBotEnabledStateProvider = StateProvider((ref) => - ref.watch(settingsProvider.select((value) => value.isDashBotEnabled))); - final StateNotifierProvider settingsProvider = StateNotifierProvider((ref) => ThemeStateNotifier()); diff --git a/lib/screens/dashboard.dart b/lib/screens/dashboard.dart index 8ca89bdd..6e711c15 100644 --- a/lib/screens/dashboard.dart +++ b/lib/screens/dashboard.dart @@ -17,7 +17,8 @@ class Dashboard extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final railIdx = ref.watch(navRailIndexStateProvider); - final isDashBotEnabled = ref.watch(isDashBotEnabledStateProvider); + final isDashBotEnabled = + ref.watch(settingsProvider.select((value) => value.isDashBotEnabled)); return Scaffold( body: SafeArea( child: Row( From bfd85c87bd636f2129db8c3cdbbc39348e95d4e5 Mon Sep 17 00:00:00 2001 From: Ashita Prasad Date: Mon, 23 Jun 2025 03:56:13 +0530 Subject: [PATCH 23/30] Update settings_model_test.dart --- test/models/settings_model_test.dart | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/models/settings_model_test.dart b/test/models/settings_model_test.dart index 1928fd0e..76f65ce2 100644 --- a/test/models/settings_model_test.dart +++ b/test/models/settings_model_test.dart @@ -36,6 +36,7 @@ void main() { "historyRetentionPeriod": "oneWeek", "workspaceFolderPath": null, "isSSLDisabled": true, + "isDashBotEnabled": true, }; expect(sm.toJson(), expectedResult); }); @@ -56,6 +57,7 @@ void main() { "historyRetentionPeriod": "oneWeek", "workspaceFolderPath": null, "isSSLDisabled": true, + "isDashBotEnabled": true, }; expect(SettingsModel.fromJson(input), sm); }); @@ -73,12 +75,14 @@ void main() { activeEnvironmentId: null, historyRetentionPeriod: HistoryRetentionPeriod.oneWeek, isSSLDisabled: false, + isDashBotEnabled: false, ); expect( sm.copyWith( isDark: true, saveResponses: false, isSSLDisabled: false, + isDashBotEnabled: false, ), expectedResult); }); @@ -98,7 +102,8 @@ void main() { "activeEnvironmentId": null, "historyRetentionPeriod": "oneWeek", "workspaceFolderPath": null, - "isSSLDisabled": true + "isSSLDisabled": true, + "isDashBotEnabled": true }'''; expect(sm.toString(), expectedResult); }); From 583a11367f95dcba93f97836829e83c4f93d2dac Mon Sep 17 00:00:00 2001 From: Ashita Prasad Date: Mon, 23 Jun 2025 04:11:23 +0530 Subject: [PATCH 24/30] Update pubspec.lock --- pubspec.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.lock b/pubspec.lock index dcb7cd26..5e707e9d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -325,7 +325,7 @@ packages: path: "packages/curl_parser" relative: true source: path - version: "0.1.2" + version: "0.1.3" dart_style: dependency: "direct main" description: From a1eac15f0623035c2df1c1f352c8693bdf891e34 Mon Sep 17 00:00:00 2001 From: Ashita Prasad Date: Mon, 23 Jun 2025 04:37:56 +0530 Subject: [PATCH 25/30] Update har package name --- .../lib/import_export/har_io.dart | 14 +++--- packages/apidash_core/pubspec.yaml | 4 +- packages/apidash_core/pubspec_overrides.yaml | 7 ++- packages/{har_parser => har}/.gitignore | 0 packages/{har_parser => har}/CHANGELOG.md | 0 packages/{har_parser => har}/LICENSE | 2 +- packages/{har_parser => har}/README.md | 8 ++-- .../{har_parser => har}/analysis_options.yaml | 0 .../example/har_example.dart | 2 +- packages/har/lib/har.dart | 4 ++ .../lib/models/har_log.dart | 0 .../lib/models/har_log.freezed.dart | 0 .../lib/models/har_log.g.dart | 0 .../lib/models/models.dart | 0 .../lib/utils/har_utils.dart} | 0 packages/{har_parser => har}/pubspec.yaml | 2 +- .../collection_apidash.dart | 0 .../test/har_test.dart} | 2 +- .../test/models/collection_apidash_model.dart | 44 +++++++++++++++---- packages/har_parser/lib/har_parser.dart | 4 -- 20 files changed, 60 insertions(+), 33 deletions(-) rename packages/{har_parser => har}/.gitignore (100%) rename packages/{har_parser => har}/CHANGELOG.md (100%) rename packages/{har_parser => har}/LICENSE (99%) rename packages/{har_parser => har}/README.md (98%) rename packages/{har_parser => har}/analysis_options.yaml (100%) rename packages/{har_parser => har}/example/har_example.dart (99%) create mode 100644 packages/har/lib/har.dart rename packages/{har_parser => har}/lib/models/har_log.dart (100%) rename packages/{har_parser => har}/lib/models/har_log.freezed.dart (100%) rename packages/{har_parser => har}/lib/models/har_log.g.dart (100%) rename packages/{har_parser => har}/lib/models/models.dart (100%) rename packages/{har_parser/lib/utils/har_parser_utils.dart => har/lib/utils/har_utils.dart} (100%) rename packages/{har_parser => har}/pubspec.yaml (96%) rename packages/{har_parser => har}/test/collection_examples/collection_apidash.dart (100%) rename packages/{har_parser/test/har_parser_test.dart => har/test/har_test.dart} (94%) rename packages/{har_parser => har}/test/models/collection_apidash_model.dart (78%) delete mode 100644 packages/har_parser/lib/har_parser.dart diff --git a/packages/apidash_core/lib/import_export/har_io.dart b/packages/apidash_core/lib/import_export/har_io.dart index 93e04835..ea5b6772 100644 --- a/packages/apidash_core/lib/import_export/har_io.dart +++ b/packages/apidash_core/lib/import_export/har_io.dart @@ -1,4 +1,4 @@ -import 'package:har_parser/har_parser.dart' as hp; +import 'package:har/har.dart' as har; import 'package:seed/seed.dart'; import '../consts.dart'; import '../models/models.dart'; @@ -8,8 +8,8 @@ class HarParserIO { List<(String?, HttpRequestModel)>? getHttpRequestModelList(String content) { content = content.trim(); try { - final hl = hp.harLogFromJsonStr(content); - final requests = hp.getRequestsFromHarLog(hl); + final hl = har.harLogFromJsonStr(content); + final requests = har.getRequestsFromHarLog(hl); return requests .map((req) => (req.$2.url, harRequestToHttpRequestModel(req.$2))) .toList(); @@ -18,7 +18,7 @@ class HarParserIO { } } - HttpRequestModel harRequestToHttpRequestModel(hp.Request request) { + HttpRequestModel harRequestToHttpRequestModel(har.Request request) { HTTPVerb method; try { @@ -33,7 +33,7 @@ class HarParserIO { List params = []; List isParamEnabledList = []; - for (var header in request.headers ?? []) { + for (var header in request.headers ?? []) { var name = header.name ?? ""; var value = header.value; var activeHeader = header.disabled ?? false; @@ -41,7 +41,7 @@ class HarParserIO { isHeaderEnabledList.add(!activeHeader); } - for (var query in request.queryString ?? []) { + for (var query in request.queryString ?? []) { var name = query.name ?? ""; var value = query.value; var activeQuery = query.disabled ?? false; @@ -77,7 +77,7 @@ class HarParserIO { if (request.postData?.mimeType == "multipart/form-data") { bodyContentType = ContentType.formdata; var name, val; - for (var fd in request.postData?.params ?? []) { + for (var fd in request.postData?.params ?? []) { name = fd.name; if (fd.contentType == "text/plain") { formDataType = FormDataType.text; diff --git a/packages/apidash_core/pubspec.yaml b/packages/apidash_core/pubspec.yaml index 935052ae..bf0625ab 100644 --- a/packages/apidash_core/pubspec.yaml +++ b/packages/apidash_core/pubspec.yaml @@ -21,8 +21,8 @@ dependencies: path: ../insomnia_collection postman: path: ../postman - har_parser: - path: ../har_parser + har: + path: ../har seed: ^0.0.3 xml: ^6.3.0 diff --git a/packages/apidash_core/pubspec_overrides.yaml b/packages/apidash_core/pubspec_overrides.yaml index f3858f35..c3860574 100644 --- a/packages/apidash_core/pubspec_overrides.yaml +++ b/packages/apidash_core/pubspec_overrides.yaml @@ -1,10 +1,9 @@ -# melos_managed_dependency_overrides: har_parser -# melos_managed_dependency_overrides: curl_parser,insomnia_collection,postman,seed +# melos_managed_dependency_overrides: curl_parser,insomnia_collection,postman,seed,har dependency_overrides: curl_parser: path: ../curl_parser - har_parser: - path: ..\\har_parser + har: + path: ../har insomnia_collection: path: ../insomnia_collection postman: diff --git a/packages/har_parser/.gitignore b/packages/har/.gitignore similarity index 100% rename from packages/har_parser/.gitignore rename to packages/har/.gitignore diff --git a/packages/har_parser/CHANGELOG.md b/packages/har/CHANGELOG.md similarity index 100% rename from packages/har_parser/CHANGELOG.md rename to packages/har/CHANGELOG.md diff --git a/packages/har_parser/LICENSE b/packages/har/LICENSE similarity index 99% rename from packages/har_parser/LICENSE rename to packages/har/LICENSE index e1622b53..65d4cee9 100644 --- a/packages/har_parser/LICENSE +++ b/packages/har/LICENSE @@ -186,7 +186,7 @@ APPENDIX: How to apply the Apache License to your work. same "printed page" as the copyright notice for easier identification within third-party archives. -Copyright 2023 Ashita Prasad, Ankit Mahato +Copyright 2025 Ashita Prasad, Ankit Mahato Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/packages/har_parser/README.md b/packages/har/README.md similarity index 98% rename from packages/har_parser/README.md rename to packages/har/README.md index a9dfc45f..d2249c28 100644 --- a/packages/har_parser/README.md +++ b/packages/har/README.md @@ -1,4 +1,4 @@ -# insomnia +# har Seamlessly convert Har Collection Format v1.2 to Dart. @@ -11,7 +11,7 @@ Currently, this package is being used by [API Dash](https://github.com/foss42/ap ### Example 1: Har collection JSON string to Har model ```dart -import 'package:har_parser/har_parser.dart'; +import 'package:har/har.dart'; void main() { // Example 1: Har collection JSON string to Har model @@ -173,7 +173,7 @@ void main() { ### Example 2: Har collection from JSON ```dart -import 'package:har_parser/har_parser.dart'; +import 'package:har/har.dart'; void main() { // Example 2: Har collection from JSON @@ -337,4 +337,4 @@ var collectionJson = { ## License -This project is licensed under the [Apache License 2.0](https://github.com/foss42/apidash/blob/main/packages/har_parser/LICENSE). +This project is licensed under the [Apache License 2.0](https://github.com/foss42/apidash/blob/main/packages/har/LICENSE). diff --git a/packages/har_parser/analysis_options.yaml b/packages/har/analysis_options.yaml similarity index 100% rename from packages/har_parser/analysis_options.yaml rename to packages/har/analysis_options.yaml diff --git a/packages/har_parser/example/har_example.dart b/packages/har/example/har_example.dart similarity index 99% rename from packages/har_parser/example/har_example.dart rename to packages/har/example/har_example.dart index 5d231702..c0c2a9c6 100644 --- a/packages/har_parser/example/har_example.dart +++ b/packages/har/example/har_example.dart @@ -1,4 +1,4 @@ -import 'package:har_parser/har_parser.dart'; +import 'package:har/har.dart'; void main() { //Example 1 diff --git a/packages/har/lib/har.dart b/packages/har/lib/har.dart new file mode 100644 index 00000000..09b48e3c --- /dev/null +++ b/packages/har/lib/har.dart @@ -0,0 +1,4 @@ +library har; + +export 'models/models.dart'; +export 'utils/har_utils.dart'; diff --git a/packages/har_parser/lib/models/har_log.dart b/packages/har/lib/models/har_log.dart similarity index 100% rename from packages/har_parser/lib/models/har_log.dart rename to packages/har/lib/models/har_log.dart diff --git a/packages/har_parser/lib/models/har_log.freezed.dart b/packages/har/lib/models/har_log.freezed.dart similarity index 100% rename from packages/har_parser/lib/models/har_log.freezed.dart rename to packages/har/lib/models/har_log.freezed.dart diff --git a/packages/har_parser/lib/models/har_log.g.dart b/packages/har/lib/models/har_log.g.dart similarity index 100% rename from packages/har_parser/lib/models/har_log.g.dart rename to packages/har/lib/models/har_log.g.dart diff --git a/packages/har_parser/lib/models/models.dart b/packages/har/lib/models/models.dart similarity index 100% rename from packages/har_parser/lib/models/models.dart rename to packages/har/lib/models/models.dart diff --git a/packages/har_parser/lib/utils/har_parser_utils.dart b/packages/har/lib/utils/har_utils.dart similarity index 100% rename from packages/har_parser/lib/utils/har_parser_utils.dart rename to packages/har/lib/utils/har_utils.dart diff --git a/packages/har_parser/pubspec.yaml b/packages/har/pubspec.yaml similarity index 96% rename from packages/har_parser/pubspec.yaml rename to packages/har/pubspec.yaml index 14b9c10a..354da92b 100644 --- a/packages/har_parser/pubspec.yaml +++ b/packages/har/pubspec.yaml @@ -1,4 +1,4 @@ -name: har_parser +name: har description: "Seamlessly convert har Format to Dart and vice versa." version: 0.0.1 homepage: https://github.com/foss42/apidash diff --git a/packages/har_parser/test/collection_examples/collection_apidash.dart b/packages/har/test/collection_examples/collection_apidash.dart similarity index 100% rename from packages/har_parser/test/collection_examples/collection_apidash.dart rename to packages/har/test/collection_examples/collection_apidash.dart diff --git a/packages/har_parser/test/har_parser_test.dart b/packages/har/test/har_test.dart similarity index 94% rename from packages/har_parser/test/har_parser_test.dart rename to packages/har/test/har_test.dart index 52d29bd0..386bd924 100644 --- a/packages/har_parser/test/har_parser_test.dart +++ b/packages/har/test/har_test.dart @@ -1,4 +1,4 @@ -import 'package:har_parser/har_parser.dart'; +import 'package:har/har.dart'; import 'package:test/test.dart'; import 'collection_examples/collection_apidash.dart'; diff --git a/packages/har_parser/test/models/collection_apidash_model.dart b/packages/har/test/models/collection_apidash_model.dart similarity index 78% rename from packages/har_parser/test/models/collection_apidash_model.dart rename to packages/har/test/models/collection_apidash_model.dart index 2705fcc5..25c1ac3c 100644 --- a/packages/har_parser/test/models/collection_apidash_model.dart +++ b/packages/har/test/models/collection_apidash_model.dart @@ -1,4 +1,4 @@ -import 'package:har_parser/models/models.dart'; +import 'package:har/models/models.dart'; var collectionApiDashModel = HarLog( log: Log( @@ -62,7 +62,8 @@ var collectionApiDashModel = HarLog( time: 200, request: Request( method: "GET", - url: "https://api.apidash.dev/humanize/social?num=8700000&digits=3&system=SS&add_space=true&trailing_zeros=true", + url: + "https://api.apidash.dev/humanize/social?num=8700000&digits=3&system=SS&add_space=true&trailing_zeros=true", httpVersion: null, cookies: null, headers: [], @@ -122,15 +123,32 @@ var collectionApiDashModel = HarLog( request: Request( method: "POST", url: "https://api.apidash.dev/io/form", - headers: [Header(name: "User-Agent", value: "Test Agent", disabled: null)], + headers: [ + Header(name: "User-Agent", value: "Test Agent", disabled: null) + ], queryString: [], bodySize: 100, postData: PostData( mimeType: "multipart/form-data", params: [ - Param(name: "text", value: "API", fileName: null, contentType: "text/plain", disabled: null), - Param(name: "sep", value: "|", fileName: null, contentType: "text/plain", disabled: null), - Param(name: "times", value: "3", fileName: null, contentType: "text/plain", disabled: null) + Param( + name: "text", + value: "API", + fileName: null, + contentType: "text/plain", + disabled: null), + Param( + name: "sep", + value: "|", + fileName: null, + contentType: "text/plain", + disabled: null), + Param( + name: "times", + value: "3", + fileName: null, + contentType: "text/plain", + disabled: null) ], ), ), @@ -160,8 +178,18 @@ var collectionApiDashModel = HarLog( mimeType: "multipart/form-data", text: null, params: [ - Param(name: "token", value: "xyz", fileName: null, contentType: "text/plain", disabled: null), - Param(name: "imfile", value: null, fileName: "hire AI.jpeg", contentType: "image/jpeg", disabled: null) + Param( + name: "token", + value: "xyz", + fileName: null, + contentType: "text/plain", + disabled: null), + Param( + name: "imfile", + value: null, + fileName: "hire AI.jpeg", + contentType: "image/jpeg", + disabled: null) ], ), headersSize: null, diff --git a/packages/har_parser/lib/har_parser.dart b/packages/har_parser/lib/har_parser.dart deleted file mode 100644 index cd7cfb95..00000000 --- a/packages/har_parser/lib/har_parser.dart +++ /dev/null @@ -1,4 +0,0 @@ -library har_parser; - -export 'models/models.dart'; -export 'utils/har_parser_utils.dart'; From 9b0e22932579f16e3e6aa56d0ab2f5665764cfa1 Mon Sep 17 00:00:00 2001 From: Ashita Prasad Date: Mon, 23 Jun 2025 04:42:17 +0530 Subject: [PATCH 26/30] Update har_io.dart --- packages/apidash_core/lib/import_export/har_io.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/apidash_core/lib/import_export/har_io.dart b/packages/apidash_core/lib/import_export/har_io.dart index ea5b6772..b52e3bec 100644 --- a/packages/apidash_core/lib/import_export/har_io.dart +++ b/packages/apidash_core/lib/import_export/har_io.dart @@ -64,8 +64,8 @@ class HarParserIO { Map parsedData = parseFormData(formDataStr); parsedData.forEach((key, value) { formDataType = FormDataType.text; - var name = key ?? ""; - var val = value ?? ""; + var name = key; + var val = value; formData.add(FormDataModel( name: name, value: val, @@ -76,7 +76,7 @@ class HarParserIO { if (request.postData?.mimeType == "multipart/form-data") { bodyContentType = ContentType.formdata; - var name, val; + String? name, val; for (var fd in request.postData?.params ?? []) { name = fd.name; if (fd.contentType == "text/plain") { @@ -87,8 +87,8 @@ class HarParserIO { val = fd.fileName; } formData.add(FormDataModel( - name: name, - value: val, + name: name ?? "", + value: val ?? "", type: formDataType, )); } From cbe8dc3e1f97340f0e886f1a15790586e2ad46fe Mon Sep 17 00:00:00 2001 From: Ashita Prasad Date: Mon, 23 Jun 2025 04:47:14 +0530 Subject: [PATCH 27/30] Update pubspec.lock --- pubspec.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 9e5528c4..e70e6865 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -67,10 +67,10 @@ packages: dependency: transitive description: name: async - sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.12.0" + version: "2.13.0" audio_session: dependency: transitive description: @@ -386,10 +386,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.3.3" fetch_api: dependency: transitive description: @@ -663,10 +663,10 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.2" - har_parser: + har: dependency: transitive description: - path: "packages/har_parser" + path: "packages/har" relative: true source: path version: "0.0.1" @@ -885,10 +885,10 @@ packages: dependency: transitive description: name: leak_tracker - sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" url: "https://pub.dev" source: hosted - version: "10.0.8" + version: "10.0.9" leak_tracker_flutter_testing: dependency: transitive description: @@ -1792,10 +1792,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 url: "https://pub.dev" source: hosted - version: "14.3.1" + version: "15.0.0" watcher: dependency: transitive description: @@ -1832,10 +1832,10 @@ packages: dependency: transitive description: name: webdriver - sha256: "3d773670966f02a646319410766d3b5e1037efb7f07cc68f844d5e06cd4d61c8" + sha256: "2f3a14ca026957870cfd9c635b83507e0e51d8091568e90129fbf805aba7cade" url: "https://pub.dev" source: hosted - version: "3.0.4" + version: "3.1.0" webkit_inspection_protocol: dependency: transitive description: From 4be8f6ad9cb97da62610545a1528276b91545c23 Mon Sep 17 00:00:00 2001 From: Ashita Prasad Date: Mon, 23 Jun 2025 04:54:04 +0530 Subject: [PATCH 28/30] Update --- packages/har/example/har_example.dart | 4 +- .../collection_apidash.dart | 287 +++++++++--------- .../test/models/collection_apidash_model.dart | 2 +- 3 files changed, 145 insertions(+), 148 deletions(-) diff --git a/packages/har/example/har_example.dart b/packages/har/example/har_example.dart index c0c2a9c6..a074e5f3 100644 --- a/packages/har/example/har_example.dart +++ b/packages/har/example/har_example.dart @@ -7,7 +7,7 @@ void main() { "log": { "version": "1.2", "creator": { - "name": "Postman", + "name": "Client Name", "version": "v8.x.x" }, "entries": [ @@ -163,7 +163,7 @@ void main() { var collectionJson = { "log": { "version": "1.2", - "creator": {"name": "Postman", "version": "v8.x.x"}, + "creator": {"name": "Client Name", "version": "v8.x.x"}, "entries": [ { "startedDateTime": "2025-03-25T12:00:00.000Z", diff --git a/packages/har/test/collection_examples/collection_apidash.dart b/packages/har/test/collection_examples/collection_apidash.dart index 47a1a0a0..1fa2f94e 100644 --- a/packages/har/test/collection_examples/collection_apidash.dart +++ b/packages/har/test/collection_examples/collection_apidash.dart @@ -3,7 +3,7 @@ var collectionJsonStr = r''' "log": { "version": "1.2", "creator": { - "name": "Postman", + "name": "Client Name", "version": "v8.x.x" }, "entries": [ @@ -184,149 +184,146 @@ var collectionJsonStr = r''' }'''; var collectionJson = { - "log": { - "version": "1.2", - "creator": { - "name": "Postman", - "version": "v8.x.x" + "log": { + "version": "1.2", + "creator": {"name": "Client Name", "version": "v8.x.x"}, + "entries": [ + { + "startedDateTime": "2025-03-25T12:00:00.000Z", + "time": 100, + "request": { + "method": "GET", + "url": "https://api.apidash.dev", + "headers": [], + "queryString": [], + "bodySize": 0 }, - "entries": [ - { - "startedDateTime": "2025-03-25T12:00:00.000Z", - "time": 100, - "request": { - "method": "GET", - "url": "https://api.apidash.dev", - "headers": [], - "queryString": [], - "bodySize": 0 - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": [], - "bodySize": 0 - } - }, - { - "startedDateTime": "2025-03-25T12:01:00.000Z", - "time": 150, - "request": { - "method": "GET", - "url": "https://api.apidash.dev/country/data?code=US", - "headers": [], - "queryString": [ - {"name": "code", "value": "US"} - ], - "bodySize": 0 - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": [], - "bodySize": 0 - } - }, - { - "startedDateTime": "2025-03-25T12:02:00.000Z", - "time": 200, - "request": { - "method": "GET", - "url": - "https://api.apidash.dev/humanize/social?num=8700000&digits=3&system=SS&add_space=true&trailing_zeros=true", - "headers": [], - "queryString": [ - {"name": "num", "value": "8700000"}, - {"name": "digits", "value": "3"}, - {"name": "system", "value": "SS"}, - {"name": "add_space", "value": "true"}, - {"name": "trailing_zeros", "value": "true"} - ], - "bodySize": 0 - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": [], - "bodySize": 0 - } - }, - { - "startedDateTime": "2025-03-25T12:03:00.000Z", - "time": 300, - "request": { - "method": "POST", - "url": "https://api.apidash.dev/case/lower", - "headers": [], - "queryString": [], - "bodySize": 50, - "postData": { - "mimeType": "application/json", - "text": "{ \"text\": \"I LOVE Flutter\" }" - } - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": [], - "bodySize": 0 - } - }, - { - "startedDateTime": "2025-03-25T12:04:00.000Z", - "time": 350, - "request": { - "method": "POST", - "url": "https://api.apidash.dev/io/form", - "headers": [ - {"name": "User-Agent", "value": "Test Agent"} - ], - "queryString": [], - "bodySize": 100, - "postData": { - "mimeType": "multipart/form-data", - "params": [ - {"name": "text", "value": "API", "contentType": "text/plain"}, - {"name": "sep", "value": "|", "contentType": "text/plain"}, - {"name": "times", "value": "3", "contentType": "text/plain"} - ] - } - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": [], - "bodySize": 0 - } - }, - { - "startedDateTime": "2025-03-25T12:05:00.000Z", - "time": 400, - "request": { - "method": "POST", - "url": "https://api.apidash.dev/io/img", - "headers": [], - "queryString": [], - "bodySize": 150, - "postData": { - "mimeType": "multipart/form-data", - "params": [ - {"name": "token", "value": "xyz", "contentType": "text/plain"}, - { - "name": "imfile", - "fileName": "hire AI.jpeg", - "contentType": "image/jpeg" - } - ] - } - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": [], - "bodySize": 0 - } + "response": { + "status": 200, + "statusText": "OK", + "headers": [], + "bodySize": 0 } - ] - } - }; + }, + { + "startedDateTime": "2025-03-25T12:01:00.000Z", + "time": 150, + "request": { + "method": "GET", + "url": "https://api.apidash.dev/country/data?code=US", + "headers": [], + "queryString": [ + {"name": "code", "value": "US"} + ], + "bodySize": 0 + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": [], + "bodySize": 0 + } + }, + { + "startedDateTime": "2025-03-25T12:02:00.000Z", + "time": 200, + "request": { + "method": "GET", + "url": + "https://api.apidash.dev/humanize/social?num=8700000&digits=3&system=SS&add_space=true&trailing_zeros=true", + "headers": [], + "queryString": [ + {"name": "num", "value": "8700000"}, + {"name": "digits", "value": "3"}, + {"name": "system", "value": "SS"}, + {"name": "add_space", "value": "true"}, + {"name": "trailing_zeros", "value": "true"} + ], + "bodySize": 0 + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": [], + "bodySize": 0 + } + }, + { + "startedDateTime": "2025-03-25T12:03:00.000Z", + "time": 300, + "request": { + "method": "POST", + "url": "https://api.apidash.dev/case/lower", + "headers": [], + "queryString": [], + "bodySize": 50, + "postData": { + "mimeType": "application/json", + "text": "{ \"text\": \"I LOVE Flutter\" }" + } + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": [], + "bodySize": 0 + } + }, + { + "startedDateTime": "2025-03-25T12:04:00.000Z", + "time": 350, + "request": { + "method": "POST", + "url": "https://api.apidash.dev/io/form", + "headers": [ + {"name": "User-Agent", "value": "Test Agent"} + ], + "queryString": [], + "bodySize": 100, + "postData": { + "mimeType": "multipart/form-data", + "params": [ + {"name": "text", "value": "API", "contentType": "text/plain"}, + {"name": "sep", "value": "|", "contentType": "text/plain"}, + {"name": "times", "value": "3", "contentType": "text/plain"} + ] + } + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": [], + "bodySize": 0 + } + }, + { + "startedDateTime": "2025-03-25T12:05:00.000Z", + "time": 400, + "request": { + "method": "POST", + "url": "https://api.apidash.dev/io/img", + "headers": [], + "queryString": [], + "bodySize": 150, + "postData": { + "mimeType": "multipart/form-data", + "params": [ + {"name": "token", "value": "xyz", "contentType": "text/plain"}, + { + "name": "imfile", + "fileName": "hire AI.jpeg", + "contentType": "image/jpeg" + } + ] + } + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": [], + "bodySize": 0 + } + } + ] + } +}; diff --git a/packages/har/test/models/collection_apidash_model.dart b/packages/har/test/models/collection_apidash_model.dart index 25c1ac3c..93616810 100644 --- a/packages/har/test/models/collection_apidash_model.dart +++ b/packages/har/test/models/collection_apidash_model.dart @@ -3,7 +3,7 @@ import 'package:har/models/models.dart'; var collectionApiDashModel = HarLog( log: Log( version: "1.2", - creator: Creator(name: "Postman", version: "v8.x.x"), + creator: Creator(name: "Client Name", version: "v8.x.x"), entries: [ Entry( startedDateTime: "2025-03-25T12:00:00.000Z", From 386b363561b605f67d7b8dabeafbcd3f47040468 Mon Sep 17 00:00:00 2001 From: Ashita Prasad Date: Mon, 23 Jun 2025 05:05:43 +0530 Subject: [PATCH 29/30] Update README --- README.md | 2 +- packages/har/README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b47acb31..bd713667 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ API Dash can be downloaded from the links below: | Insomnia | ✅ | | OpenAPI | https://github.com/foss42/apidash/issues/121 | | hurl | https://github.com/foss42/apidash/issues/123 | -| HAR | https://github.com/foss42/apidash/issues/122 | +| HAR | ✅ | **↗️ Create & Customize API Requests** diff --git a/packages/har/README.md b/packages/har/README.md index d2249c28..2f522dd4 100644 --- a/packages/har/README.md +++ b/packages/har/README.md @@ -19,7 +19,7 @@ void main() { { "log": { "version": "1.2", - "creator": {"name": "Postman", "version": "v8.x.x"}, + "creator": {"name": "Client Name", "version": "v8.x.x"}, "entries": [ { "startedDateTime": "2025-03-25T12:00:00.000Z", @@ -180,7 +180,7 @@ void main() { var collectionJson = { "log": { "version": "1.2", - "creator": {"name": "Postman", "version": "v8.x.x"}, + "creator": {"name": "Client Name", "version": "v8.x.x"}, "entries": [ { "startedDateTime": "2025-03-25T12:00:00.000Z", From dc7e648f3e5a3b3815af01286ca60d1117d11657 Mon Sep 17 00:00:00 2001 From: Ashita Prasad Date: Mon, 23 Jun 2025 05:09:58 +0530 Subject: [PATCH 30/30] Update .gitignore --- packages/har/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/har/.gitignore b/packages/har/.gitignore index eb6c05cd..5d28e5d6 100644 --- a/packages/har/.gitignore +++ b/packages/har/.gitignore @@ -29,3 +29,4 @@ migrate_working_dir/ .flutter-plugins .flutter-plugins-dependencies build/ +coverage/