diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..7a5fd3e86821a4adefa86484cda2ff9d5eee0ce6
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2020 xuty
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
index bce04add25360f754742d39413e3665bb64a536e..0154671f0bfbaf93c46d70851e720bac2472aafb 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,4 @@
-A library for Dart developers.
-
-Created from templates made available by Stagehand under a BSD-style
-[license](https://github.com/dart-lang/stagehand/blob/master/LICENSE).
+This is the _unofficial_ MinIO Dart Client SDK that provides simple APIs to access any Amazon S3 compatible object storage server.
 
 ## API
 
@@ -21,20 +18,44 @@ Created from templates made available by Stagehand under a BSD-style
 
 ## Usage
 
-A simple usage example:
+### Initialize MinIO Client
+
+**MinIO**
 
 ```dart
 import 'package:minio/minio.dart';
 
-main() {
-  var awesome = new Awesome();
-}
+final minio = Minio(
+  endPoint: 'play.min.io',
+  accessKey: 'Q3AM3UQ867SPQQA43P2F',
+  secretKey: 'zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG',
+);
 ```
 
+**AWS S3**
+
+```dart
+import 'package:minio/minio.dart';
+
+final minio = Minio(
+  endPoint: 's3.amazonaws.com',
+  accessKey: 'YOUR-ACCESSKEYID',
+  secretKey: 'YOUR-SECRETACCESSKEY',
+);
+```
+
+For complete example, see: [example]
+
 ## Features and bugs
 
 Please file feature requests and bugs at the [issue tracker][tracker].
 
-[tracker]: http://example.com/issues/replaceme
+Contributions to this repository are welcomed.
+
+## Lisence
+
+MIT
 
+[tracker]: https://github.com/xtyxtyx/minio-dart/issues
+[example]: https://example.com
 [link text itself]: http://www.reddit.com
\ No newline at end of file
diff --git a/example/minio_example.dart b/example/minio_example.dart
index dcf4563d4938c1e7e746b082903aa6ce58b0a2de..345e8ae0481255205c2d1e08d373b6c9866bf809 100644
--- a/example/minio_example.dart
+++ b/example/minio_example.dart
@@ -6,7 +6,7 @@ void main() async {
     endPoint: 'play.min.io',
     accessKey: 'Q3AM3UQ867SPQQA43P2F',
     secretKey: 'zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG',
-    useSSL: false,
+    useSSL: true,
     // enableTrace: true,
   );
 
diff --git a/lib/io.dart b/lib/io.dart
index 26f5ebf3fa63fa71c1c7111dff8c43a2657a1cb5..6b20b1985155aa2f5dc1848506e4e38d77dca192 100644
--- a/lib/io.dart
+++ b/lib/io.dart
@@ -6,6 +6,7 @@ import 'package:minio/src/minio_helpers.dart';
 import 'package:path/path.dart' show dirname;
 
 extension MinioX on Minio {
+  // Uploads the object using contents from a file
   Future<String> fPutObject(
     String bucket,
     String object,
@@ -30,6 +31,7 @@ extension MinioX on Minio {
     return putObject(bucket, object, file.openRead(), stat.size);
   }
 
+  /// Downloads and saves the object as a file in the local filesystem.
   Future<void> fGetObject(
     String bucket,
     String object,
diff --git a/lib/src/minio.dart b/lib/src/minio.dart
index 787e13d85789786596e50720b838701035269627..6726870ed1b9cb63d1c5007bb6eacf403035e3ca 100644
--- a/lib/src/minio.dart
+++ b/lib/src/minio.dart
@@ -1,212 +1,14 @@
-import 'dart:convert';
-
 import 'package:http/http.dart';
 import 'package:minio/models.dart';
+import 'package:minio/src/minio_client.dart';
 import 'package:minio/src/minio_errors.dart';
 import 'package:minio/src/minio_helpers.dart';
-import 'package:minio/src/minio_s3.dart';
-import 'package:minio/src/minio_sign.dart';
 import 'package:minio/src/minio_uploader.dart';
 import 'package:minio/src/utils.dart';
 import 'package:xml/xml.dart' as xml;
 
-class MinioRequest extends BaseRequest {
-  MinioRequest(String method, Uri url) : super(method, url);
-
-  dynamic body;
-
-  @override
-  ByteStream finalize() {
-    super.finalize();
-    if (body is String) {
-      return ByteStream.fromBytes(utf8.encode(body));
-    }
-    if (body is List<int>) {
-      return ByteStream.fromBytes(body);
-    }
-    if (body is Stream<List<int>>) {
-      return ByteStream(body);
-    }
-    throw UnsupportedError('unsupported body type: ${body.runtimeType}');
-  }
-}
-
-class MinioClient {
-  MinioClient(this.minio) {
-    anonymous = minio.accessKey.isEmpty && minio.secretKey.isEmpty;
-    enableSHA256 = !anonymous && !minio.useSSL;
-    port = minio.port ?? implyPort(minio.useSSL);
-  }
-
-  final Minio minio;
-  final String userAgent = 'MinIO (Unknown; Unknown) minio-js/0.0.1';
-
-  bool enableSHA256;
-  bool anonymous;
-  int port;
-
-  Future<StreamedResponse> _request({
-    String method,
-    String bucket,
-    String object,
-    String region,
-    String resource,
-    dynamic payload = '',
-    Map<String, String> queries,
-    Map<String, String> headers,
-  }) async {
-    final url = getRequestUrl(bucket, object, resource, queries);
-    final request = MinioRequest(method, url);
-    final date = DateTime.now().toUtc();
-    final sha256sum = enableSHA256 ? sha256Hex(payload) : 'UNSIGNED-PAYLOAD';
-
-    region ??= await minio.getBucketRegion(bucket);
-
-    request.body = payload;
-
-    request.headers.addAll({
-      'host': url.host,
-      'user-agent': userAgent,
-      'x-amz-date': makeDateLong(date),
-      'x-amz-content-sha256': sha256sum,
-    });
-
-    if (headers != null) {
-      request.headers.addAll(headers);
-    }
-
-    final authorization = signV4(minio, request, date, 'us-east-1');
-    request.headers['authorization'] = authorization;
-
-    logRequest(request);
-    final response = await request.send();
-    return response;
-  }
-
-  Future<Response> request({
-    String method,
-    String bucket,
-    String object,
-    String region,
-    String resource,
-    dynamic payload = '',
-    Map<String, String> queries,
-    Map<String, String> headers,
-  }) async {
-    final stream = _request(
-      method: method,
-      bucket: bucket,
-      object: object,
-      region: region,
-      payload: payload,
-      resource: resource,
-      queries: queries,
-      headers: headers,
-    );
-
-    final response = await Response.fromStream(await stream);
-    logResponse(response);
-
-    return response;
-  }
-
-  Future<StreamedResponse> requestStream({
-    String method,
-    String bucket,
-    String object,
-    String region,
-    String resource,
-    dynamic payload = '',
-    Map<String, String> queries,
-    Map<String, String> headers,
-  }) async {
-    final response = await _request(
-      method: method,
-      bucket: bucket,
-      object: object,
-      region: region,
-      payload: payload,
-      resource: resource,
-      queries: queries,
-      headers: headers,
-    );
-
-    logResponse(response);
-    return response;
-  }
-
-  Uri getRequestUrl(
-    String bucket,
-    String object,
-    String resource,
-    Map<String, String> queries,
-  ) {
-    var host = minio.endPoint.toLowerCase();
-    var path = '/';
-
-    if (isAmazonEndpoint(host)) {
-      host = getS3Endpoint(minio.region);
-    }
-
-    if (isVirtualHostStyle(host, minio.useSSL, bucket)) {
-      if (bucket != null) host = '${bucket}.${host}';
-      if (object != null) path = '/${object}';
-    } else {
-      if (bucket != null) path = '/${bucket}';
-      if (object != null) path = '/${bucket}/${object}';
-    }
-
-    final resourcePart = resource == null ? '' : '$resource';
-    final queryPart = queries == null ? '' : '&${encodeQueries(queries)}';
-    final query = resourcePart + queryPart;
-
-    return Uri(
-      scheme: minio.useSSL ? 'https' : 'http',
-      host: host,
-      port: minio.port,
-      pathSegments: path.split('/'),
-      query: query,
-    );
-  }
-
-  void logRequest(MinioRequest request) {
-    if (!minio.enableTrace) return;
-
-    final buffer = StringBuffer();
-    buffer.writeln('REQUEST: ${request.method} ${request.url}');
-    for (var header in request.headers.entries) {
-      buffer.writeln('${header.key}: ${header.value}');
-    }
-
-    if (request.body is List<int>) {
-      buffer.writeln('List<int> of size ${request.body.length}');
-    } else {
-      buffer.writeln(request.body);
-    }
-
-    print(buffer.toString());
-  }
-
-  void logResponse(BaseResponse response) {
-    if (!minio.enableTrace) return;
-
-    final buffer = StringBuffer();
-    buffer.writeln('RESPONSE: ${response.statusCode} ${response.reasonPhrase}');
-    for (var header in response.headers.entries) {
-      buffer.writeln('${header.key}: ${header.value}');
-    }
-
-    if (response is Response) {
-      buffer.writeln(response.body);
-    } else if (response is StreamedResponse) {
-      buffer.writeln('STREAMED BODY');
-    }
-
-    print(buffer.toString());
-  }
-}
-
 class Minio {
+  /// Initializes a new client object.
   Minio({
     this.endPoint,
     this.port,
@@ -224,22 +26,43 @@ class Minio {
     _client = MinioClient(this);
   }
 
+  /// default part size for multipart uploads.
   final partSize = 64 * 1024 * 1024;
+
+  /// maximum part size for multipart uploads.
   final maximumPartSize = 5 * 1024 * 1024 * 1024;
+
+  /// maximum object size (5TB)
   final maxObjectSize = 5 * 1024 * 1024 * 1024 * 1024;
 
+  /// endPoint is a host name or an IP address.
   final String endPoint;
+
+  /// TCP/IP port number. This input is optional. Default value set to 80 for HTTP and 443 for HTTPs.
   final int port;
+
+  /// If set to true, https is used instead of http. Default is true.
   final bool useSSL;
+
+  /// accessKey is like user-id that uniquely identifies your account.
   final String accessKey;
+
+  /// secretKey is the password to your account.
   final String secretKey;
+
+  /// Set this value to provide x-amz-security-token (AWS S3 specific). (Optional)
   final String sessionToken;
+
+  /// Set this value to override region cache. (Optional)
   final String region;
+
+  /// Set this value to enable tracing. (Optional)
   final bool enableTrace;
 
   MinioClient _client;
   final _regionMap = <String, String>{};
 
+  /// Checks if a bucket exists.
   Future<bool> bucketExists(String bucket) async {
     MinioInvalidBucketNameError.check(bucket);
     try {
@@ -252,7 +75,7 @@ class Minio {
     return true;
   }
 
-  int calculatePartSize(int size) {
+  int _calculatePartSize(int size) {
     assert(size != null && size >= 0);
 
     if (size > maxObjectSize) {
@@ -272,6 +95,8 @@ class Minio {
     }
   }
 
+  /// Complete the multipart upload. After all the parts are uploaded issuing
+  /// this call will aggregate the parts on the server into a single object.
   Future<String> completeMultipartUpload(
     String bucket,
     String object,
@@ -307,6 +132,7 @@ class Minio {
     return etag;
   }
 
+  /// Copy the object.
   Future<CopyObjectResult> copyObject(
     String bucket,
     String object,
@@ -351,6 +177,7 @@ class Minio {
     return result;
   }
 
+  /// Find uploadId of an incomplete upload.
   Future<String> findUploadId(String bucket, String object) async {
     MinioInvalidBucketNameError.check(bucket);
     MinioInvalidObjectNameError.check(object);
@@ -382,7 +209,8 @@ class Minio {
 
     return latestUpload?.uploadId;
   }
-  
+
+  /// gets the region of the bucket
   Future<String> getBucketRegion(String bucket) async {
     MinioInvalidBucketNameError.check(bucket);
 
@@ -409,12 +237,14 @@ class Minio {
     return location;
   }
 
+  /// get a readable stream of the object content.
   Future<ByteStream> getObject(String bucket, String object) {
     MinioInvalidBucketNameError.check(bucket);
     MinioInvalidObjectNameError.check(object);
     return getPartialObject(bucket, object, null, null);
   }
 
+  /// get a readable stream of the partial object content.
   Future<ByteStream> getPartialObject(
     String bucket,
     String object, [
@@ -454,6 +284,7 @@ class Minio {
     return resp.stream;
   }
 
+  /// Initiate a new multipart upload.
   Future<String> initiateNewMultipartUpload(
     String bucket,
     String object,
@@ -475,6 +306,7 @@ class Minio {
     return node.findAllElements('UploadId').first.text;
   }
 
+  /// Returns a stream that emits objects that are partially uploaded.
   Stream<IncompleteUpload> listIncompleteUploads(
     String bucket,
     String prefix, [
@@ -508,6 +340,7 @@ class Minio {
     } while (isTruncated);
   }
 
+  /// Called by listIncompleteUploads to fetch a batch of incomplete uploads.
   Future<ListMultipartUploadsOutput> listIncompleteUploadsQuery(
     String bucket,
     String prefix,
@@ -544,6 +377,7 @@ class Minio {
     return ListMultipartUploadsOutput.fromXml(node.root);
   }
 
+  /// List of buckets created.
   Future<List<Bucket>> listBuckets() async {
     final resp = await _client.request(
       method: 'GET',
@@ -583,6 +417,7 @@ class Minio {
     } while (isTruncated);
   }
 
+  /// list a batch of objects
   Future<ListObjectsOutput> listObjectsQuery(
     String bucket,
     String prefix,
@@ -655,6 +490,7 @@ class Minio {
     } while (isTruncated);
   }
 
+  /// listObjectsV2Query - (List Objects V2) - List some or all (up to 1000) of the objects in a bucket.
   Future<ListObjectsV2Output> listObjectsV2Query(
     String bucket,
     String prefix,
@@ -708,6 +544,7 @@ class Minio {
       ..nextContinuationToken = nextContinuationToken;
   }
 
+  /// Get part-info of all parts of an incomplete upload specified by uploadId.
   Stream<Part> listParts(
     String bucket,
     String object,
@@ -726,6 +563,7 @@ class Minio {
     } while (isTruncated);
   }
 
+  /// Called by listParts to fetch a batch of part-info
   Future<ListPartsOutput> listPartsQuery(
     String bucket,
     String object,
@@ -751,6 +589,7 @@ class Minio {
     return ListPartsOutput.fromXml(node.root);
   }
 
+  /// Creates the bucket [bucket].
   Future<void> makeBucket(String bucket, [String region]) async {
     MinioInvalidBucketNameError.check(bucket);
     if (this.region != null && region != null && this.region != region) {
@@ -774,6 +613,7 @@ class Minio {
     return resp.body;
   }
 
+  /// Uploads the object.
   Future<String> putObject(
     String bucket,
     String object,
@@ -790,7 +630,7 @@ class Minio {
     metadata = prependXAMZMeta(metadata ?? {});
 
     size ??= maxObjectSize;
-    size = calculatePartSize(size);
+    size = _calculatePartSize(size);
 
     final chunker = BlockStream(size);
     final uploader = MinioUploader(
@@ -805,6 +645,7 @@ class Minio {
     return etag.toString();
   }
 
+  /// Remove a bucket.
   Future<void> removeBucket(String bucket) async {
     MinioInvalidBucketNameError.check(bucket);
 
@@ -817,6 +658,7 @@ class Minio {
     _regionMap.remove(bucket);
   }
 
+  /// Remove the partially uploaded object.
   Future<void> removeIncompleteUpload(String bucket, String object) async {
     MinioInvalidBucketNameError.check(bucket);
     MinioInvalidObjectNameError.check(object);
@@ -834,6 +676,7 @@ class Minio {
     validate(resp, expect: 204);
   }
 
+  /// Remove the specified object.
   Future<void> removeObject(String bucket, String object) async {
     MinioInvalidBucketNameError.check(bucket);
     MinioInvalidObjectNameError.check(object);
@@ -847,6 +690,7 @@ class Minio {
     validate(resp, expect: 204);
   }
 
+  /// Remove all the objects residing in the objectsList.
   Future<void> removeObjects(String bucket, List<String> objects) async {
     MinioInvalidBucketNameError.check(bucket);
 
@@ -869,6 +713,7 @@ class Minio {
     }
   }
 
+  /// Stat information of the object.
   Future<StatObjectResult> statObject(String bucket, String object) async {
     MinioInvalidBucketNameError.check(bucket);
     MinioInvalidObjectNameError.check(object);
@@ -894,34 +739,3 @@ class Minio {
     );
   }
 }
-
-Future<void> validateStreamed(
-  StreamedResponse streamedResponse, {
-  int expect,
-}) async {
-  if (streamedResponse.statusCode >= 400) {
-    final response = await Response.fromStream(streamedResponse);
-    final body = xml.parse(response.body);
-    final error = Error.fromXml(body.rootElement);
-    throw MinioS3Error(error.message, error, response);
-  }
-
-  if (expect != null && streamedResponse.statusCode != expect) {
-    final response = await Response.fromStream(streamedResponse);
-    throw MinioS3Error(
-        '$expect expected, got ${streamedResponse.statusCode}', null, response);
-  }
-}
-
-void validate(Response response, {int expect}) {
-  if (response.statusCode >= 400) {
-    final body = xml.parse(response.body);
-    final error = Error.fromXml(body.rootElement);
-    throw MinioS3Error(error.message, error, response);
-  }
-
-  if (expect != null && response.statusCode != expect) {
-    throw MinioS3Error(
-        '$expect expected, got ${response.statusCode}', null, response);
-  }
-}
diff --git a/lib/src/minio_client.dart b/lib/src/minio_client.dart
new file mode 100644
index 0000000000000000000000000000000000000000..e6628c36325ffce22471063c15044297b2d4b044
--- /dev/null
+++ b/lib/src/minio_client.dart
@@ -0,0 +1,204 @@
+import 'dart:convert';
+
+import 'package:http/http.dart';
+import 'package:minio/minio.dart';
+import 'package:minio/src/minio_helpers.dart';
+import 'package:minio/src/minio_s3.dart';
+import 'package:minio/src/minio_sign.dart';
+import 'package:minio/src/utils.dart';
+
+class MinioRequest extends BaseRequest {
+  MinioRequest(String method, Uri url) : super(method, url);
+
+  dynamic body;
+
+  @override
+  ByteStream finalize() {
+    super.finalize();
+    if (body is String) {
+      return ByteStream.fromBytes(utf8.encode(body));
+    }
+    if (body is List<int>) {
+      return ByteStream.fromBytes(body);
+    }
+    if (body is Stream<List<int>>) {
+      return ByteStream(body);
+    }
+    throw UnsupportedError('unsupported body type: ${body.runtimeType}');
+  }
+}
+
+class MinioClient {
+  MinioClient(this.minio) {
+    anonymous = minio.accessKey.isEmpty && minio.secretKey.isEmpty;
+    enableSHA256 = !anonymous && !minio.useSSL;
+    port = minio.port ?? implyPort(minio.useSSL);
+  }
+
+  final Minio minio;
+  final String userAgent = 'MinIO (Unknown; Unknown) minio-js/0.0.1';
+
+  bool enableSHA256;
+  bool anonymous;
+  int port;
+
+  Future<StreamedResponse> _request({
+    String method,
+    String bucket,
+    String object,
+    String region,
+    String resource,
+    dynamic payload = '',
+    Map<String, String> queries,
+    Map<String, String> headers,
+  }) async {
+    final url = getRequestUrl(bucket, object, resource, queries);
+    final request = MinioRequest(method, url);
+    final date = DateTime.now().toUtc();
+    final sha256sum = enableSHA256 ? sha256Hex(payload) : 'UNSIGNED-PAYLOAD';
+
+    region ??= await minio.getBucketRegion(bucket);
+
+    request.body = payload;
+
+    request.headers.addAll({
+      'host': url.host,
+      'user-agent': userAgent,
+      'x-amz-date': makeDateLong(date),
+      'x-amz-content-sha256': sha256sum,
+    });
+
+    if (headers != null) {
+      request.headers.addAll(headers);
+    }
+
+    final authorization = signV4(minio, request, date, 'us-east-1');
+    request.headers['authorization'] = authorization;
+
+    logRequest(request);
+    final response = await request.send();
+    return response;
+  }
+
+  Future<Response> request({
+    String method,
+    String bucket,
+    String object,
+    String region,
+    String resource,
+    dynamic payload = '',
+    Map<String, String> queries,
+    Map<String, String> headers,
+  }) async {
+    final stream = _request(
+      method: method,
+      bucket: bucket,
+      object: object,
+      region: region,
+      payload: payload,
+      resource: resource,
+      queries: queries,
+      headers: headers,
+    );
+
+    final response = await Response.fromStream(await stream);
+    logResponse(response);
+
+    return response;
+  }
+
+  Future<StreamedResponse> requestStream({
+    String method,
+    String bucket,
+    String object,
+    String region,
+    String resource,
+    dynamic payload = '',
+    Map<String, String> queries,
+    Map<String, String> headers,
+  }) async {
+    final response = await _request(
+      method: method,
+      bucket: bucket,
+      object: object,
+      region: region,
+      payload: payload,
+      resource: resource,
+      queries: queries,
+      headers: headers,
+    );
+
+    logResponse(response);
+    return response;
+  }
+
+  Uri getRequestUrl(
+    String bucket,
+    String object,
+    String resource,
+    Map<String, String> queries,
+  ) {
+    var host = minio.endPoint.toLowerCase();
+    var path = '/';
+
+    if (isAmazonEndpoint(host)) {
+      host = getS3Endpoint(minio.region);
+    }
+
+    if (isVirtualHostStyle(host, minio.useSSL, bucket)) {
+      if (bucket != null) host = '${bucket}.${host}';
+      if (object != null) path = '/${object}';
+    } else {
+      if (bucket != null) path = '/${bucket}';
+      if (object != null) path = '/${bucket}/${object}';
+    }
+
+    final resourcePart = resource == null ? '' : '$resource';
+    final queryPart = queries == null ? '' : '&${encodeQueries(queries)}';
+    final query = resourcePart + queryPart;
+
+    return Uri(
+      scheme: minio.useSSL ? 'https' : 'http',
+      host: host,
+      port: minio.port,
+      pathSegments: path.split('/'),
+      query: query,
+    );
+  }
+
+  void logRequest(MinioRequest request) {
+    if (!minio.enableTrace) return;
+
+    final buffer = StringBuffer();
+    buffer.writeln('REQUEST: ${request.method} ${request.url}');
+    for (var header in request.headers.entries) {
+      buffer.writeln('${header.key}: ${header.value}');
+    }
+
+    if (request.body is List<int>) {
+      buffer.writeln('List<int> of size ${request.body.length}');
+    } else {
+      buffer.writeln(request.body);
+    }
+
+    print(buffer.toString());
+  }
+
+  void logResponse(BaseResponse response) {
+    if (!minio.enableTrace) return;
+
+    final buffer = StringBuffer();
+    buffer.writeln('RESPONSE: ${response.statusCode} ${response.reasonPhrase}');
+    for (var header in response.headers.entries) {
+      buffer.writeln('${header.key}: ${header.value}');
+    }
+
+    if (response is Response) {
+      buffer.writeln(response.body);
+    } else if (response is StreamedResponse) {
+      buffer.writeln('STREAMED BODY');
+    }
+
+    print(buffer.toString());
+  }
+}
\ No newline at end of file
diff --git a/lib/src/minio_helpers.dart b/lib/src/minio_helpers.dart
index 5c0bf5457790a99f2981d3d80c14fbe764d8d3cf..0f76f98fd8da708a225fb1ef6e34d5f542f49f9c 100644
--- a/lib/src/minio_helpers.dart
+++ b/lib/src/minio_helpers.dart
@@ -1,4 +1,8 @@
+import 'package:http/http.dart';
 import 'package:mime/mime.dart' show lookupMimeType;
+import 'package:minio/src/minio_errors.dart';
+import 'package:minio/src/minio_models_generated.dart';
+import 'package:xml/xml.dart' as xml;
 
 bool isValidBucketName(String bucket) {
   if (bucket == null) return false;
@@ -192,3 +196,34 @@ Map<String, String> insertContentType(
   newMetadata['content-type'] = probeContentType(filePath);
   return newMetadata;
 }
+
+Future<void> validateStreamed(
+  StreamedResponse streamedResponse, {
+  int expect,
+}) async {
+  if (streamedResponse.statusCode >= 400) {
+    final response = await Response.fromStream(streamedResponse);
+    final body = xml.parse(response.body);
+    final error = Error.fromXml(body.rootElement);
+    throw MinioS3Error(error.message, error, response);
+  }
+
+  if (expect != null && streamedResponse.statusCode != expect) {
+    final response = await Response.fromStream(streamedResponse);
+    throw MinioS3Error(
+        '$expect expected, got ${streamedResponse.statusCode}', null, response);
+  }
+}
+
+void validate(Response response, {int expect}) {
+  if (response.statusCode >= 400) {
+    final body = xml.parse(response.body);
+    final error = Error.fromXml(body.rootElement);
+    throw MinioS3Error(error.message, error, response);
+  }
+
+  if (expect != null && response.statusCode != expect) {
+    throw MinioS3Error(
+        '$expect expected, got ${response.statusCode}', null, response);
+  }
+}
diff --git a/lib/src/minio_models_generated.dart b/lib/src/minio_models_generated.dart
index 36a67a6c07998d29c43b876e8115d366dc9a0eb8..f23949cc0e548bcc7421530a709ebd96867662aa 100644
--- a/lib/src/minio_models_generated.dart
+++ b/lib/src/minio_models_generated.dart
@@ -486,7 +486,7 @@ class Condition {
 class ContinuationEvent {
   ContinuationEvent();
 
-  ContinuationEvent.fromXml(XmlElement xml) {}
+  ContinuationEvent.fromXml(XmlElement xml);
 
   XmlNode toXml() {
     final builder = XmlBuilder();
@@ -1042,7 +1042,7 @@ class EncryptionConfiguration {
 class EndEvent {
   EndEvent();
 
-  EndEvent.fromXml(XmlElement xml) {}
+  EndEvent.fromXml(XmlElement xml);
 
   XmlNode toXml() {
     final builder = XmlBuilder();
@@ -2540,7 +2540,7 @@ class Owner {
 class ParquetInput {
   ParquetInput();
 
-  ParquetInput.fromXml(XmlElement xml) {}
+  ParquetInput.fromXml(XmlElement xml);
 
   XmlNode toXml() {
     final builder = XmlBuilder();
@@ -3658,7 +3658,7 @@ class SseKmsEncryptedObjects {
 class SSES3 {
   SSES3();
 
-  SSES3.fromXml(XmlElement xml) {}
+  SSES3.fromXml(XmlElement xml);
 
   XmlNode toXml() {
     final builder = XmlBuilder();
diff --git a/lib/src/minio_sign.dart b/lib/src/minio_sign.dart
index 2915ed6d6c6dddf90d759043c7fa717564563271..f79fd7cecf1f66fa78f194ce8fa0264b2b2d0ae2 100644
--- a/lib/src/minio_sign.dart
+++ b/lib/src/minio_sign.dart
@@ -1,6 +1,7 @@
 import 'package:convert/convert.dart';
 import 'package:crypto/crypto.dart';
 import 'package:minio/minio.dart';
+import 'package:minio/src/minio_client.dart';
 import 'package:minio/src/minio_helpers.dart';
 import 'package:minio/src/utils.dart';
 
diff --git a/lib/src/minio_uploader.dart b/lib/src/minio_uploader.dart
index 8c88c16569ba0bf9776db1c67a1aa66257d54323..dff64bd2d7ccc16f905f39c94fad7e3b42a5e3e2 100644
--- a/lib/src/minio_uploader.dart
+++ b/lib/src/minio_uploader.dart
@@ -5,6 +5,8 @@ import 'package:convert/convert.dart';
 import 'package:crypto/crypto.dart';
 import 'package:minio/minio.dart';
 import 'package:minio/models.dart';
+import 'package:minio/src/minio_client.dart';
+import 'package:minio/src/minio_helpers.dart';
 import 'package:minio/src/utils.dart';
 
 class MinioUploader implements StreamConsumer<List<int>> {
diff --git a/pubspec.yaml b/pubspec.yaml
index 3bd02a1d1c6da1168e40b85f0da540a0150db926..1cf0fc12435f06f6b3af19b677b6ddd1ad2c99a4 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,8 +1,8 @@
 name: minio
-description: Unofficial MinIO Dart Client for Amazon S3 Compatible Cloud Storage.
+description: Unofficial MinIO Dart Client SDK that provides simple APIs to access any Amazon S3 compatible object storage server.
 version: 0.1.0
-# homepage: https://www.example.com
-author: xuty <root@xuty.tk>
+homepage: https://github.com/xtyxtyx/minio-dart
+issue_tracker: https://github.com/xtyxtyx/minio-dart/issues
 
 environment:
   sdk: ">=2.7.0 <3.0.0"
diff --git a/test/minio_dart_test.dart b/test/minio_dart_test.dart
index 3c666688378d15664ac3e0080204c2ead59ab16d..f6775d5abcf6a772254b007b6870b59be68f6721 100644
--- a/test/minio_dart_test.dart
+++ b/test/minio_dart_test.dart
@@ -1,5 +1,3 @@
-import 'package:minio/minio.dart';
-import 'package:test/test.dart';
 
 void main() {
   // group('A group of tests', () {
diff --git a/bin/generate_models.dart b/util/generate_models.dart
similarity index 100%
rename from bin/generate_models.dart
rename to util/generate_models.dart