LCOV - code coverage report
Current view: top level - src - transformer.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 47 60 78.3 %
Date: 2020-02-27 17:47: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             :   ///
      38           5 :   static String urlEncodeMap(Map map) {
      39           7 :     return encodeMap(map, (key, value) {
      40             :       if (value == null) return key;
      41           6 :       return '$key=${Uri.encodeQueryComponent(value.toString())}';
      42             :     });
      43             :   }
      44             : }
      45             : 
      46             : /// The default [Transformer] for [Dio]. If you want to custom the transformation of
      47             : /// request/response data, you can provide a [Transformer] by your self, and
      48             : /// replace the [DefaultTransformer] by setting the [dio.Transformer].
      49             : 
      50             : typedef JsonDecodeCallback = dynamic Function(String);
      51             : 
      52             : class DefaultTransformer extends Transformer {
      53           5 :   DefaultTransformer({this.jsonDecodeCallback});
      54             : 
      55             :   JsonDecodeCallback jsonDecodeCallback;
      56             : 
      57             :   @override
      58           1 :   Future<String> transformRequest(RequestOptions options) async {
      59           1 :     var data = options.data ?? '';
      60           1 :     if (data is! String) {
      61           2 :       if (_isJsonMime(options.contentType)) {
      62           2 :         return json.encode(options.data);
      63           0 :       } else if (data is Map) {
      64           0 :         return Transformer.urlEncodeMap(data);
      65             :       }
      66             :     }
      67           0 :     return data.toString();
      68             :   }
      69             : 
      70             :   /// As an agreement, you must return the [response]
      71             :   /// when the Options.responseType is [ResponseType.stream].
      72             :   @override
      73           3 :   Future transformResponse(
      74             :       RequestOptions options, ResponseBody response) async {
      75           6 :     if (options.responseType == ResponseType.stream) {
      76             :       return response;
      77             :     }
      78             :     var length = 0;
      79             :     var received = 0;
      80           3 :     var showDownloadProgress = options.onReceiveProgress != null;
      81             :     if (showDownloadProgress) {
      82           1 :       length = int.parse(
      83           3 :           response.headers[Headers.contentLengthHeader]?.first ?? '-1');
      84             :     }
      85           3 :     var completer = Completer();
      86             :     var stream =
      87           9 :     response.stream.transform<Uint8List>(StreamTransformer.fromHandlers(
      88           3 :       handleData: (data, sink) {
      89           3 :         sink.add(data);
      90             :         if (showDownloadProgress) {
      91           2 :           received += data.length;
      92           1 :           options.onReceiveProgress(received, length);
      93             :         }
      94             :       },
      95             :     ));
      96             :     // let's keep references to the data chunks and concatenate them later
      97           3 :     final  chunks = <Uint8List>[];
      98             :     var finalSize = 0;
      99           3 :     StreamSubscription subscription = stream.listen(
     100           3 :           (chunk) {
     101           6 :         finalSize += chunk.length;
     102           3 :         chunks.add(chunk);
     103             :       },
     104           0 :       onError: (e) {
     105           0 :         completer.completeError(e);
     106             :       },
     107           3 :       onDone: () {
     108           3 :         completer.complete();
     109             :       },
     110             :       cancelOnError: true,
     111             :     );
     112             :     // ignore: unawaited_futures
     113           5 :     options.cancelToken?.whenCancel?.then((_) {
     114           0 :       return subscription.cancel();
     115             :     });
     116           6 :     if (options.receiveTimeout > 0) {
     117             :       try {
     118           2 :         await completer.future
     119           3 :             .timeout(Duration(milliseconds: options.receiveTimeout));
     120           0 :       } on TimeoutException {
     121           0 :         await subscription.cancel();
     122           0 :         throw DioError(
     123             :           request: options,
     124           0 :           error: 'Receiving data timeout[${options.receiveTimeout}ms]',
     125             :           type: DioErrorType.RECEIVE_TIMEOUT,
     126             :         );
     127             :       }
     128             :     } else {
     129           4 :       await completer.future;
     130             :     }
     131             :     // we create a final Uint8List and copy all chunks into it
     132           3 :     final responseBytes = Uint8List(finalSize);
     133             :     var chunkOffset = 0;
     134           6 :     for (var chunk in chunks) {
     135           3 :       responseBytes.setAll(chunkOffset, chunk);
     136           6 :       chunkOffset += chunk.length;
     137             :     }
     138             : 
     139           6 :     if (options.responseType == ResponseType.bytes) return responseBytes;
     140             : 
     141             :     String responseBody;
     142           3 :     if (options.responseDecoder != null) {
     143           0 :       responseBody = options.responseDecoder(
     144           0 :           responseBytes, options, response..stream = null);
     145             :     } else {
     146           3 :       responseBody = utf8.decode(responseBytes, allowMalformed: true);
     147             :     }
     148             :     if (responseBody != null &&
     149           3 :         responseBody.isNotEmpty &&
     150           6 :         options.responseType == ResponseType.json &&
     151          12 :         _isJsonMime(response.headers[Headers.contentTypeHeader]?.first)) {
     152           2 :       if (jsonDecodeCallback != null) {
     153           0 :         return jsonDecodeCallback(responseBody);
     154             :       } else {
     155           2 :         return json.decode(responseBody);
     156             :       }
     157             :     }
     158             :     return responseBody;
     159             :   }
     160             : 
     161           3 :   bool _isJsonMime(String contentType) {
     162             :     if (contentType == null) return false;
     163          12 :     return MediaType.parse(contentType).mimeType.toLowerCase() ==
     164           6 :         Headers.jsonMimeType.mimeType;
     165             :   }
     166             : }

Generated by: LCOV version 1.14