LCOV - code coverage report
Current view: top level - Users/duwen/Documents/code/dio/dio/lib/src - transformer.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 48 61 78.7 %
Date: 2021-11-28 14:37:50 Functions: 0 0 -

          Line data    Source code
       1             : import 'dart:async';
       2             : import 'dart:convert';
       3             : import 'dart:typed_data';
       4             : 
       5             : import 'package:http_parser/http_parser.dart';
       6             : 
       7             : import 'adapter.dart';
       8             : import 'dio_error.dart';
       9             : import 'headers.dart';
      10             : import 'options.dart';
      11             : import 'utils.dart';
      12             : 
      13             : /// [Transformer] allows changes to the request/response data before
      14             : /// it is sent/received to/from the server.
      15             : ///
      16             : /// Dio has already implemented a [DefaultTransformer], and as the default
      17             : /// [Transformer]. If you want to custom the transformation of
      18             : /// request/response data, you can provide a [Transformer] by your self, and
      19             : /// replace the [DefaultTransformer] by setting the [dio.Transformer].
      20             : 
      21             : abstract class Transformer {
      22             :   /// `transformRequest` allows changes to the request data before it is
      23             :   /// sent to the server, but **after** the [RequestInterceptor].
      24             :   ///
      25             :   /// This is only applicable for request methods 'PUT', 'POST', and 'PATCH'
      26             :   Future<String> transformRequest(RequestOptions options);
      27             : 
      28             :   /// `transformResponse` allows changes to the response data  before
      29             :   /// it is passed to [ResponseInterceptor].
      30             :   ///
      31             :   /// **Note**: As an agreement, you must return the [response]
      32             :   /// when the Options.responseType is [ResponseType.stream].
      33             :   Future transformResponse(RequestOptions options, ResponseBody response);
      34             : 
      35             :   /// Deep encode the [Map<String, dynamic>] to percent-encoding.
      36             :   /// It is mostly used with  the "application/x-www-form-urlencoded" content-type.
      37           9 :   static String urlEncodeMap(
      38             :     Map map, [
      39             :     ListFormat listFormat = ListFormat.multi,
      40             :   ]) {
      41           9 :     return encodeMap(
      42             :       map,
      43           2 :       (key, value) {
      44             :         if (value == null) return key;
      45           6 :         return '$key=${Uri.encodeQueryComponent(value.toString())}';
      46             :       },
      47             :       listFormat: listFormat,
      48             :     );
      49             :   }
      50             : }
      51             : 
      52             : /// The default [Transformer] for [Dio]. If you want to custom the transformation of
      53             : /// request/response data, you can provide a [Transformer] by your self, and
      54             : /// replace the [DefaultTransformer] by setting the [dio.Transformer].
      55             : 
      56             : typedef JsonDecodeCallback = dynamic Function(String);
      57             : 
      58             : class DefaultTransformer extends Transformer {
      59           8 :   DefaultTransformer({this.jsonDecodeCallback});
      60             : 
      61             :   JsonDecodeCallback? jsonDecodeCallback;
      62             : 
      63             :   @override
      64           3 :   Future<String> transformRequest(RequestOptions options) async {
      65           3 :     var data = options.data ?? '';
      66           3 :     if (data is! String) {
      67           2 :       if (_isJsonMime(options.contentType)) {
      68           2 :         return json.encode(options.data);
      69           0 :       } else if (data is Map) {
      70           0 :         options.contentType =
      71           0 :             options.contentType ?? Headers.formUrlEncodedContentType;
      72           0 :         return Transformer.urlEncodeMap(data);
      73             :       }
      74             :     }
      75           2 :     return data.toString();
      76             :   }
      77             : 
      78             :   /// As an agreement, we return the [response] when the
      79             :   /// Options.responseType is [ResponseType.stream].
      80             :   @override
      81           7 :   Future transformResponse(
      82             :       RequestOptions options, ResponseBody response) async {
      83          14 :     if (options.responseType == ResponseType.stream) {
      84             :       return response;
      85             :     }
      86             :     var length = 0;
      87             :     var received = 0;
      88           7 :     var showDownloadProgress = options.onReceiveProgress != null;
      89             :     if (showDownloadProgress) {
      90           1 :       length = int.parse(
      91           3 :           response.headers[Headers.contentLengthHeader]?.first ?? '-1');
      92             :     }
      93           7 :     var completer = Completer();
      94             :     var stream =
      95          21 :         response.stream.transform<Uint8List>(StreamTransformer.fromHandlers(
      96           6 :       handleData: (data, sink) {
      97           6 :         sink.add(data);
      98             :         if (showDownloadProgress) {
      99           2 :           received += data.length;
     100           1 :           options.onReceiveProgress?.call(received, length);
     101             :         }
     102             :       },
     103             :     ));
     104             :     // let's keep references to the data chunks and concatenate them later
     105           7 :     final chunks = <Uint8List>[];
     106             :     var finalSize = 0;
     107           7 :     StreamSubscription subscription = stream.listen(
     108           6 :       (chunk) {
     109          12 :         finalSize += chunk.length;
     110           6 :         chunks.add(chunk);
     111             :       },
     112           0 :       onError: (Object error, StackTrace stackTrace) {
     113           0 :         completer.completeError(error, stackTrace);
     114             :       },
     115          14 :       onDone: () => completer.complete(),
     116             :       cancelOnError: true,
     117             :     );
     118             :     // ignore: unawaited_futures
     119           9 :     options.cancelToken?.whenCancel.then((_) {
     120           0 :       return subscription.cancel();
     121             :     });
     122          14 :     if (options.receiveTimeout > 0) {
     123             :       try {
     124           2 :         await completer.future
     125           3 :             .timeout(Duration(milliseconds: options.receiveTimeout));
     126           0 :       } on TimeoutException {
     127           0 :         await subscription.cancel();
     128           0 :         throw DioError(
     129             :           requestOptions: options,
     130           0 :           error: 'Receiving data timeout[${options.receiveTimeout}ms]',
     131             :           type: DioErrorType.receiveTimeout,
     132             :         );
     133             :       }
     134             :     } else {
     135          12 :       await completer.future;
     136             :     }
     137             :     // we create a final Uint8List and copy all chunks into it
     138           7 :     final responseBytes = Uint8List(finalSize);
     139             :     var chunkOffset = 0;
     140          13 :     for (var chunk in chunks) {
     141           6 :       responseBytes.setAll(chunkOffset, chunk);
     142          12 :       chunkOffset += chunk.length;
     143             :     }
     144             : 
     145          14 :     if (options.responseType == ResponseType.bytes) return responseBytes;
     146             : 
     147             :     String? responseBody;
     148           7 :     if (options.responseDecoder != null) {
     149           0 :       responseBody = options.responseDecoder!(
     150             :         responseBytes,
     151             :         options,
     152           0 :         response..stream = Stream.empty(),
     153             :       );
     154             :     } else {
     155           7 :       responseBody = utf8.decode(responseBytes, allowMalformed: true);
     156             :     }
     157           7 :     if (responseBody.isNotEmpty &&
     158          10 :         options.responseType == ResponseType.json &&
     159          20 :         _isJsonMime(response.headers[Headers.contentTypeHeader]?.first)) {
     160           3 :       final callback = jsonDecodeCallback;
     161             :       if (callback != null) {
     162             :         return callback(responseBody);
     163             :       } else {
     164           3 :         return json.decode(responseBody);
     165             :       }
     166             :     }
     167             :     return responseBody;
     168             :   }
     169             : 
     170           5 :   bool _isJsonMime(String? contentType) {
     171             :     if (contentType == null) return false;
     172          15 :     return MediaType.parse(contentType).mimeType ==
     173          10 :         Headers.jsonMimeType.mimeType;
     174             :   }
     175             : }

Generated by: LCOV version 1.14