LCOV - code coverage report
Current view: top level - core\network\dts_api_call.dart - dts_api_call.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 59 59 100.0 %
Date: Wed Aug 6 23:43:55 2025 Functions: 0 0 -

          Line data    Source code
       1             : import 'dart:convert';
       2             : import 'dart:developer';
       3             : 
       4             : import 'package:http/http.dart';
       5             : import 'package:flutter/foundation.dart';
       6             : 
       7             : import 'aro_result.dart';
       8             : import 'mdl_api_success.dart';
       9             : 
      10             : import '../errors/dts_http_failure.dart';
      11             : 
      12             : import '../../app/config/constans/app_env.dart';
      13             : import '../../app/config/constans/app_enums.dart';
      14             : import '../../app/data/datasources/local/dts_user_pref.dart';
      15             : 
      16             : /// A service class for handling API requests with enhanced logging and error handling.
      17             : ///
      18             : /// The `ApiCall` class facilitates communication with external APIs by wrapping
      19             : /// HTTP requests with configurable parameters, timeout settings, and structured
      20             : /// logging. It supports both GET and POST methods and can attach authorization headers.
      21             : ///
      22             : /// ## Example
      23             : ///
      24             : /// ```dart
      25             : /// void main() async {
      26             : ///   final client = Client();
      27             : ///   final env = 'https://api.example.com';
      28             : ///   final userPrefs = UserPref();
      29             : ///   await userPrefs.initPrefs();
      30             : ///
      31             : ///   final apiService = ApiCall(client, env, userPrefs);
      32             : ///   final result = await apiService.request(
      33             : ///     endpoint: '/login',
      34             : ///     bearer: 'your-auth-token',
      35             : ///     body: jsonEncode({'username': 'user', 'password': 'pass'}),
      36             : ///   );
      37             : ///
      38             : ///   result.when(
      39             : ///     success: (data) => print('Success: ${data.data}'),
      40             : ///     failure: (error) => print('Error: ${error.message}'),
      41             : ///   );
      42             : /// }
      43             : /// ```
      44             : ///
      45             : /// ## Properties
      46             : ///
      47             : /// - `_client` (`Client`): The HTTP client used for making API requests.
      48             : /// - `_env` (`String`): The base URL or environment for API endpoints.
      49             : /// - `_userPrefs` (`UserPref`): Instance of `UserPref` for storing request logs.
      50             : ///
      51             : /// ### `Future<ApiWrapper<ApiFailure, ApiSuccess>> request({...})`
      52             : /// Makes an API request with the provided parameters.
      53             : ///
      54             : /// #### Parameters:
      55             : /// - `body` (`String`): The request body in JSON format.
      56             : /// - `bearer` (`String`): Bearer token for authorization (optional).
      57             : /// - `endpoint` (`String`): The endpoint to append to the base URL.
      58             : /// - `epName` (`String`): An optional identifier for the endpoint (default: `-|`).
      59             : /// - `method` (`HttpMethod`): The HTTP method, defaulting to POST.
      60             : /// - `headers` (`Map<String, String>`): Additional headers (default: content type JSON).
      61             : /// - `queryParameters` (`Map<String, String>`): Query parameters (default: empty).
      62             : /// - `tkMovil` (`String`): Mobile token (optional).
      63             : ///
      64             : /// #### Returns:
      65             : /// - `ApiWrapper.success`: Contains `ApiSuccess` with the status code and response data.
      66             : /// - `ApiWrapper.failure`: Contains `ApiFailure` with the error code and message.
      67             : ///
      68             : /// #### Example:
      69             : /// ```dart
      70             : /// final response = await apiService.request(
      71             : ///   endpoint: '/user/profile',
      72             : ///   bearer: 'auth-token',
      73             : ///   method: HttpMethod.get,
      74             : /// );
      75             : ///
      76             : /// response.when(
      77             : ///   success: (success) {
      78             : ///     print('Response data: ${success.data}');
      79             : ///   },
      80             : ///   failure: (failure) {
      81             : ///     print('Error: ${failure.message}');
      82             : ///   },
      83             : /// );
      84             : /// ```
      85             : class ApiCall {
      86           1 :   ApiCall(this._client, this._env, this._userPrefs);
      87             : 
      88             :   final Client _client;
      89             :   final String _env;
      90             :   final UserPref _userPrefs;
      91             : 
      92           1 :   Future<ARoResult<ApiFailure, ApiSuccess>> request({
      93             :     required String endpoint,
      94             :     required String body,
      95             :     String epName = '-|',
      96             :     HttpMethod method = HttpMethod.get,
      97             :     Map<String, String> headers = const {
      98             :       'Content-Type': 'application/json',
      99             :       'Accept': 'application/json',
     100             :       'charset': 'UTF-8',
     101             :     },
     102             :     Map<String, String> queryParameters = const {},
     103             :   }) async {
     104             :     /// Makes an HTTP request to the specified endpoint with configurable options.
     105             :     ///
     106             :     /// Handles logging, timeout, error handling, and response decoding. The result
     107             :     /// is returned as an `ApiWrapper`, which is a wrapper for success and failure states.
     108             : 
     109             :     const timeout = Duration(seconds: 15);
     110             : 
     111           2 :     String uri = getBaseUri(_env, endpoint);
     112           1 :     DateTime startTime = DateTime.now();
     113             :     String resApiWithChartSet = '';
     114           2 :     Map<String, dynamic> logs = {'startTime': startTime.toString()};
     115             :     StackTrace? stackTrace;
     116             : 
     117           1 :     Response petitionResponse = Response('', 200);
     118             : 
     119             :     try {
     120           1 :       Uri url = Uri.parse(uri);
     121           2 :       logs = {'url': '$url'};
     122             : 
     123           1 :       if (queryParameters.isNotEmpty) {
     124           1 :         url = url.replace(queryParameters: queryParameters);
     125             :       }
     126             : 
     127           2 :       logs = {...logs, 'queryParameters': queryParameters};
     128             : 
     129           2 :       headers = {...headers, 'Authorization': 'Bearer $apiKey'};
     130             : 
     131           2 :       logs = {...logs, 'headers': headers};
     132             : 
     133             :       switch (method) {
     134           1 :         case HttpMethod.get:
     135           1 :           petitionResponse = await _client
     136           1 :               .get(url, headers: headers)
     137           1 :               .timeout(timeout);
     138             :           break;
     139             : 
     140           1 :         case HttpMethod.post:
     141           1 :           petitionResponse = await _client
     142           1 :               .post(url, headers: headers, body: body)
     143           1 :               .timeout(timeout);
     144             :           break;
     145             : 
     146             :         default:
     147           1 :           petitionResponse = await _client
     148           1 :               .get(url, headers: headers)
     149           1 :               .timeout(timeout);
     150             :           break;
     151             :       }
     152             : 
     153           1 :       logs = {
     154             :         ...logs,
     155           3 :         'body': (body == 'mock') ? body : jsonDecode(body),
     156           1 :         'Response::':
     157             :             '**************************************************************',
     158           2 :         'Response code:': petitionResponse.statusCode,
     159             :       };
     160             : 
     161             :       /// Change charSet UTF-8
     162           2 :       resApiWithChartSet = utf8.decode(petitionResponse.bodyBytes);
     163             : 
     164           3 :       logs = {...logs, 'Response body': jsonDecode(resApiWithChartSet)};
     165             : 
     166           2 :       if (petitionResponse.statusCode == 200) {
     167             :         //. Success root request
     168           1 :         return ARoResult.success(
     169           1 :           ApiSuccess(
     170           1 :             code: petitionResponse.statusCode,
     171             :             data: resApiWithChartSet,
     172             :           ),
     173             :         );
     174             :       } else {
     175             :         //. Failure root request
     176           1 :         ApiFailure resFailure = ApiFailure.fromJson(resApiWithChartSet);
     177           1 :         return ARoResult.failure(
     178           1 :           ApiFailure(
     179           1 :             code: petitionResponse.statusCode,
     180           1 :             message: resFailure.message,
     181             :           ),
     182             :         );
     183             :       }
     184             :     } catch (e, s) {
     185             :       stackTrace = s;
     186           3 :       logs = {...logs, 'error': e.toString()};
     187             : 
     188           3 :       if (e is ClientException || e.toString().contains('SocketException')) {
     189           1 :         return ARoResult.failure(
     190           3 :           ApiFailure(code: petitionResponse.statusCode, message: e.toString()),
     191             :         );
     192             :       } else {
     193           1 :         return ARoResult.failure(
     194           3 :           ApiFailure(code: e.hashCode, message: e.toString()),
     195             :         );
     196             :       }
     197             : 
     198             :       ///
     199             :     } finally {
     200           1 :       DateTime endTime = DateTime.now();
     201           2 :       String timeElapsed = endTime.difference(startTime).toString();
     202           1 :       logs = {
     203             :         ...logs,
     204           3 :         'endTime': DateTime.now().toString(),
     205           1 :         'TimeElapsed': timeElapsed,
     206             :       };
     207             : 
     208             :       /// Only for purpose QA
     209             :       // coverage:ignore-start
     210             : 
     211             :       List<String> oldData = _userPrefs.resHttp;
     212             : 
     213             :       if (body != 'mock') {
     214             :         oldData.add(
     215             :           '$epName~$body~$resApiWithChartSet~${startTime.toString().substring(0, 22)} -> ${endTime.toString().substring(0, 22)}~$timeElapsed~$uri~$queryParameters~${jsonEncode(headers)}',
     216             :         );
     217             : 
     218             :         _userPrefs.resHttp = oldData;
     219             :       }
     220             :       // coverage:ignore-end
     221             : 
     222             :       ///  Delete this snippet when finish
     223             :       if (kDebugMode) {
     224           1 :         log('''
     225             : $epName
     226             : 
     227             : Endpoint ::-> $endpoint
     228             : Url :: -> $uri
     229             : --------------------------------------------------------------
     230           1 :   ${const JsonEncoder.withIndent(' ').convert(logs)}
     231             : --------------------------------------------------------------
     232           1 : ''', stackTrace: stackTrace);
     233             :       }
     234             :     }
     235             : 
     236             :     ///
     237             :   }
     238             : 
     239             :   /// Returns the base URL for the API environment based on the specified kind.
     240             :   ///
     241             :   /// This method provides the base URL for different environments (development, QA, production).
     242             :   /// It returns the corresponding API URL based on the provided environment kind.
     243             :   ///
     244             :   /// ***Parameters***:
     245             :   /// - `env` (String):
     246             :   ///    The environment kind, which determines the base URL to return. It can be one of the following:
     247             :   ///
     248             :   ///   - `'dev'` for the development environment.
     249             :   ///   - `'qa'` for the QA environment.
     250             :   ///   - `'prod'` for the production environment.
     251             :   ///
     252             :   /// ***Returns***:
     253             :   /// - [String]: The base URL corresponding to the given environment kind.
     254             :   ///   If the kind is not recognized, an empty string is returned.
     255             :   ///
     256             :   /// ***Notes***:
     257             :   /// - The method uses a map to store the base URLs for different environments.
     258             :   ///   Ensure that the URLs are up-to-date with the correct endpoints for each environment.
     259             :   ///
     260             :   /// **Example**:
     261             :   /// ```dart
     262             :   /// final uri = getBaseUri(_env);
     263             :   ///
     264             :   /// print('Base URL for dev environment: $uri');
     265             :   /// ```
     266           1 :   String getBaseUri(String env, String ep) {
     267           1 :     Map<String, String> map = {
     268           1 :       'dev': 'https://api.themoviedb.org$ep',
     269           1 :       'qa': 'https://api.themoviedb.org$ep',
     270           1 :       'prod': 'https://api.themoviedb.org$ep',
     271             :     };
     272             : 
     273           2 :     return map[env] ?? 'https://api.themoviedb.org$ep';
     274             :   }
     275             : }

Generated by: LCOV version 1.15.alpha0w