From a0d90e355b2ab1a623c53abfad8047c3d995d8c9 Mon Sep 17 00:00:00 2001 From: Ricardo Boss Date: Thu, 28 Mar 2024 03:36:16 +0100 Subject: [PATCH 01/25] Started implementing http client --- packages/kiota_http/analysis_options.yaml | 1 + packages/kiota_http/lib/kiota_http.dart | 8 ++ .../lib/src/http_client_request_adapter.dart | 103 ++++++++++++++++++ packages/kiota_http/pubspec.yaml | 1 + 4 files changed, 113 insertions(+) create mode 100644 packages/kiota_http/analysis_options.yaml create mode 100644 packages/kiota_http/lib/kiota_http.dart create mode 100644 packages/kiota_http/lib/src/http_client_request_adapter.dart diff --git a/packages/kiota_http/analysis_options.yaml b/packages/kiota_http/analysis_options.yaml new file mode 100644 index 0000000..feb86ff --- /dev/null +++ b/packages/kiota_http/analysis_options.yaml @@ -0,0 +1 @@ +include: package:strict/analysis_options.yaml diff --git a/packages/kiota_http/lib/kiota_http.dart b/packages/kiota_http/lib/kiota_http.dart new file mode 100644 index 0000000..b470b35 --- /dev/null +++ b/packages/kiota_http/lib/kiota_http.dart @@ -0,0 +1,8 @@ +/// This library implements a request adapter for generated +/// [Kiota](https://github.com/microsoft/kiota) clients. +library kiota_http; + +import 'package:http/http.dart' as http; +import 'package:kiota_abstractions/kiota_abstractions.dart'; + +part 'src/http_client_request_adapter.dart'; diff --git a/packages/kiota_http/lib/src/http_client_request_adapter.dart b/packages/kiota_http/lib/src/http_client_request_adapter.dart new file mode 100644 index 0000000..47fc867 --- /dev/null +++ b/packages/kiota_http/lib/src/http_client_request_adapter.dart @@ -0,0 +1,103 @@ +part of '../kiota_http.dart'; + +class HttpClientRequestAdapter implements RequestAdapter { + HttpClientRequestAdapter({ + required http.Client client, + required AuthenticationProvider authProvider, + required ParseNodeFactory pNodeFactory, + required SerializationWriterFactory sWriterFactory, + }) : _client = client, + _authProvider = authProvider, + _pNodeFactory = pNodeFactory, + _sWriterFactory = sWriterFactory; + + final http.Client? _client; + final AuthenticationProvider _authProvider; + ParseNodeFactory _pNodeFactory; + SerializationWriterFactory _sWriterFactory; + String? _baseUrl; + + @override + String? get baseUrl => _baseUrl; + + @override + set baseUrl(String? value) { + if (value?.endsWith('/') ?? false) { + _baseUrl = value?.substring(0, value.length - 1); + } else { + _baseUrl = value; + } + } + + Future _getRequestFromInfo( + RequestInformation requestInfo, + ) async { + final request = http.Request(requestInfo.httpMethod!.name, requestInfo.uri); + + return request; + } + + @override + Future convertToNativeRequest(RequestInformation requestInfo) async { + await _authProvider.authenticateRequest(requestInfo); + + final request = await _getRequestFromInfo(requestInfo); + if (request is T) { + return request as T; + } + + throw ArgumentError( + 'The request could not be converted to the desired type', + ); + } + + @override + Future send( + RequestInformation requestInfo, + ParsableFactory factory, [ + Map? errorMapping, + ]) { + // TODO: implement send + throw UnimplementedError(); + } + + @override + Future?> sendCollection( + RequestInformation requestInfo, + ParsableFactory factory, [ + Map? errorMapping, + ]) { + // TODO: implement sendCollection + throw UnimplementedError(); + } + + @override + Future sendNoContent( + RequestInformation requestInfo, [ + Map? errorMapping, + ]) { + // TODO: implement sendNoContent + throw UnimplementedError(); + } + + @override + Future sendPrimitive( + RequestInformation requestInfo, [ + Map? errorMapping, + ]) { + // TODO: implement sendPrimitive + throw UnimplementedError(); + } + + @override + Future?> sendPrimitiveCollection( + RequestInformation requestInfo, [ + Map? errorMapping, + ]) { + // TODO: implement sendPrimitiveCollection + throw UnimplementedError(); + } + + @override + SerializationWriterFactory get serializationWriterFactory => _sWriterFactory; +} diff --git a/packages/kiota_http/pubspec.yaml b/packages/kiota_http/pubspec.yaml index 5f5fcc2..8185263 100644 --- a/packages/kiota_http/pubspec.yaml +++ b/packages/kiota_http/pubspec.yaml @@ -14,4 +14,5 @@ dependencies: http_parser: ^4.0.2 dev_dependencies: + strict: ^2.0.0 test: ^1.25.2 From fddcd4212cf6a6029be372967d76457b88472d6b Mon Sep 17 00:00:00 2001 From: Ricardo Boss Date: Sun, 31 Mar 2024 23:41:49 +0200 Subject: [PATCH 02/25] Implement http client request adapter --- .../lib/kiota_abstractions.dart | 1 + .../lib/src/api_exception.dart | 13 ++ packages/kiota_http/lib/kiota_http.dart | 1 + .../lib/src/http_client_request_adapter.dart | 217 ++++++++++++++++-- packages/kiota_http/pubspec.yaml | 1 + 5 files changed, 209 insertions(+), 24 deletions(-) diff --git a/packages/kiota_abstractions/lib/kiota_abstractions.dart b/packages/kiota_abstractions/lib/kiota_abstractions.dart index c8834c2..e8ee35f 100644 --- a/packages/kiota_abstractions/lib/kiota_abstractions.dart +++ b/packages/kiota_abstractions/lib/kiota_abstractions.dart @@ -15,6 +15,7 @@ import 'package:std_uritemplate/std_uritemplate.dart'; import 'package:uuid/uuid.dart'; part 'src/api_client_builder.dart'; +part 'src/api_exception.dart'; part 'src/authentication/access_token_provider.dart'; part 'src/authentication/allowed_hosts_validator.dart'; part 'src/authentication/anonymous_authentication_provider.dart'; diff --git a/packages/kiota_abstractions/lib/src/api_exception.dart b/packages/kiota_abstractions/lib/src/api_exception.dart index 118325a..a537c6e 100644 --- a/packages/kiota_abstractions/lib/src/api_exception.dart +++ b/packages/kiota_abstractions/lib/src/api_exception.dart @@ -23,4 +23,17 @@ class ApiException implements Exception { String toString() { return 'ApiException{statusCode: $statusCode, message: $message, headers: $responseHeaders}'; } + + /// Creates a new instance of [ApiException] with the given parameters. + ApiException copyWith({ + int? statusCode, + String? message, + Map>? responseHeaders, + }) { + return ApiException( + statusCode: statusCode ?? this.statusCode, + message: message ?? this.message, + responseHeaders: responseHeaders ?? this.responseHeaders, + ); + } } diff --git a/packages/kiota_http/lib/kiota_http.dart b/packages/kiota_http/lib/kiota_http.dart index b470b35..8f37665 100644 --- a/packages/kiota_http/lib/kiota_http.dart +++ b/packages/kiota_http/lib/kiota_http.dart @@ -4,5 +4,6 @@ library kiota_http; import 'package:http/http.dart' as http; import 'package:kiota_abstractions/kiota_abstractions.dart'; +import 'package:uuid/uuid.dart'; part 'src/http_client_request_adapter.dart'; diff --git a/packages/kiota_http/lib/src/http_client_request_adapter.dart b/packages/kiota_http/lib/src/http_client_request_adapter.dart index 47fc867..947bb72 100644 --- a/packages/kiota_http/lib/src/http_client_request_adapter.dart +++ b/packages/kiota_http/lib/src/http_client_request_adapter.dart @@ -11,7 +11,9 @@ class HttpClientRequestAdapter implements RequestAdapter { _pNodeFactory = pNodeFactory, _sWriterFactory = sWriterFactory; - final http.Client? _client; + static const String _claimsKey = 'claims'; + + final http.Client _client; final AuthenticationProvider _authProvider; ParseNodeFactory _pNodeFactory; SerializationWriterFactory _sWriterFactory; @@ -29,73 +31,240 @@ class HttpClientRequestAdapter implements RequestAdapter { } } - Future _getRequestFromInfo( + Future _getMessageFromInfo( RequestInformation requestInfo, ) async { final request = http.Request(requestInfo.httpMethod!.name, requestInfo.uri); + // TODO(ricardoboss): implement the rest of the method + return request; } + Future _getResponse( + RequestInformation requestInfo, [ + String? claims, + ]) async { + _setBaseUrl(requestInfo); + + Map? additionalAuthContext; + if (claims != null) { + additionalAuthContext = {_claimsKey: claims}; + } + + await _authProvider.authenticateRequest(requestInfo, additionalAuthContext); + + final message = await _getMessageFromInfo(requestInfo); + + return _client.send(message); + } + + void _setBaseUrl(RequestInformation requestInfo) { + requestInfo.queryParameters['baseUrl'] = baseUrl; + } + + ResponseHandler? _getResponseHandler(RequestInformation requestInfo) { + final option = requestInfo.getRequestOption(); + + return option?.responseHandler; + } + + Future _throwIfFailedResponse( + http.StreamedResponse response, + ErrorMappings? errorMapping, + ) async { + if (response.statusCode >= 200 && response.statusCode < 300) { + return; + } + + final statusCodeInt = response.statusCode; + final statusCodeString = response.statusCode.toString(); + final headers = response.headersSplitValues; + + final ParsableFactory? errorFactory; + if (errorMapping == null) { + errorFactory = null; + } else if (errorMapping.containsKey(statusCodeString)) { + errorFactory = errorMapping[statusCodeString]; + } else if (statusCodeInt >= 400 && + statusCodeInt < 500 && + errorMapping.containsKey('4XX')) { + errorFactory = errorMapping['4XX']; + } else if (statusCodeInt >= 500 && + statusCodeInt < 600 && + errorMapping.containsKey('5XX')) { + errorFactory = errorMapping['5XX']; + } else { + errorFactory = errorMapping['XXX']; + } + + if (errorFactory == null) { + throw ApiException( + statusCode: statusCodeInt, + message: + 'The server returned an unexpected status code and no error factory is registered for this code: $statusCodeString', + responseHeaders: headers, + ); + } + + final rootNode = await _getRootParseNode(response); + final result = rootNode?.getObjectValue(errorFactory); + + if (result case Exception exception) { + if (exception case final ApiException apiException) { + exception = apiException.copyWith( + statusCode: statusCodeInt, + responseHeaders: headers, + ); + } + + throw exception; + } + + throw ApiException( + statusCode: statusCodeInt, + message: + 'The server returned an unexpected status code and the error registered for this code failed to deserialize: $statusCodeString', + responseHeaders: headers, + ); + } + + bool _shouldReturnNull(http.StreamedResponse response) { + return response.statusCode == 204 || response.contentLength == 0; + } + + Future _getRootParseNode(http.StreamedResponse response) async { + final responseContentType = response.headers['content-type']; + if (responseContentType == null || responseContentType.isEmpty) { + return null; + } + + final content = await response.stream.toBytes(); + + return _pNodeFactory.getRootParseNode(responseContentType, content); + } + @override Future convertToNativeRequest(RequestInformation requestInfo) async { await _authProvider.authenticateRequest(requestInfo); - final request = await _getRequestFromInfo(requestInfo); + final request = await _getMessageFromInfo(requestInfo); if (request is T) { return request as T; } throw ArgumentError( - 'The request could not be converted to the desired type', + 'The request could not be converted to the desired type $T', ); } + Future _sendRequestAndHandleResponse( + RequestInformation requestInfo, + ErrorMappings? errorMapping, + ) async { + final response = await _getResponse(requestInfo); + + final handler = _getResponseHandler(requestInfo); + if (handler != null) { + return handler.handleResponse(response, errorMapping); + } + + await _throwIfFailedResponse(response, errorMapping); + + if (_shouldReturnNull(response)) { + return null; + } + + return _getRootParseNode(response); + } + @override Future send( RequestInformation requestInfo, ParsableFactory factory, [ - Map? errorMapping, - ]) { - // TODO: implement send - throw UnimplementedError(); + ErrorMappings? errorMapping, + ]) async { + final rootNode = + await _sendRequestAndHandleResponse(requestInfo, errorMapping); + + return rootNode?.getObjectValue(factory); } @override Future?> sendCollection( RequestInformation requestInfo, ParsableFactory factory, [ - Map? errorMapping, - ]) { - // TODO: implement sendCollection - throw UnimplementedError(); + ErrorMappings? errorMapping, + ]) async { + final rootNode = + await _sendRequestAndHandleResponse(requestInfo, errorMapping); + + return rootNode?.getCollectionOfObjectValues(factory); } @override Future sendNoContent( RequestInformation requestInfo, [ - Map? errorMapping, - ]) { - // TODO: implement sendNoContent - throw UnimplementedError(); + ErrorMappings? errorMapping, + ]) async { + final response = await _getResponse(requestInfo); + + final handler = _getResponseHandler(requestInfo); + if (handler != null) { + await handler.handleResponse(response, errorMapping); + + return; + } + + await _throwIfFailedResponse(response, errorMapping); } @override Future sendPrimitive( RequestInformation requestInfo, [ - Map? errorMapping, - ]) { - // TODO: implement sendPrimitive - throw UnimplementedError(); + ErrorMappings? errorMapping, + ]) async { + final rootNode = + await _sendRequestAndHandleResponse(requestInfo, errorMapping); + + if (rootNode == null) { + return null; + } + + if (ModelType is bool?) { + return rootNode.getBoolValue() as ModelType; + } else if (ModelType is int?) { + return rootNode.getIntValue() as ModelType; + } else if (ModelType is double?) { + return rootNode.getDoubleValue() as ModelType; + } else if (ModelType is String?) { + return rootNode.getStringValue() as ModelType; + } else if (ModelType is DateTime?) { + return rootNode.getDateTimeValue() as ModelType; + } else if (ModelType is DateOnly?) { + return rootNode.getDateOnlyValue() as ModelType; + } else if (ModelType is TimeOnly?) { + return rootNode.getTimeOnlyValue() as ModelType; + } else if (ModelType is Duration?) { + return rootNode.getDurationValue() as ModelType; + } else if (ModelType is UuidValue?) { + return rootNode.getGuidValue() as ModelType; + } else { + throw ArgumentError( + 'The type $ModelType is not supported for primitive deserialization', + ); + } } @override Future?> sendPrimitiveCollection( RequestInformation requestInfo, [ - Map? errorMapping, - ]) { - // TODO: implement sendPrimitiveCollection - throw UnimplementedError(); + ErrorMappings? errorMapping, + ]) async { + final rootNode = + await _sendRequestAndHandleResponse(requestInfo, errorMapping); + + return rootNode?.getCollectionOfPrimitiveValues(); } @override diff --git a/packages/kiota_http/pubspec.yaml b/packages/kiota_http/pubspec.yaml index 8185263..64abdb5 100644 --- a/packages/kiota_http/pubspec.yaml +++ b/packages/kiota_http/pubspec.yaml @@ -12,6 +12,7 @@ dependencies: path: ../kiota_abstractions http: ^1.2.1 http_parser: ^4.0.2 + uuid: ^4.3.3 dev_dependencies: strict: ^2.0.0 From b9a1ac733c6c2b66fac257c1a0dcef20e9996e1e Mon Sep 17 00:00:00 2001 From: Ricardo Boss Date: Mon, 1 Apr 2024 00:09:08 +0200 Subject: [PATCH 03/25] Added client factory --- packages/kiota_http/lib/kiota_http.dart | 1 + packages/kiota_http/lib/src/kiota_client_factory.dart | 9 +++++++++ 2 files changed, 10 insertions(+) create mode 100644 packages/kiota_http/lib/src/kiota_client_factory.dart diff --git a/packages/kiota_http/lib/kiota_http.dart b/packages/kiota_http/lib/kiota_http.dart index 8f37665..01706c2 100644 --- a/packages/kiota_http/lib/kiota_http.dart +++ b/packages/kiota_http/lib/kiota_http.dart @@ -7,3 +7,4 @@ import 'package:kiota_abstractions/kiota_abstractions.dart'; import 'package:uuid/uuid.dart'; part 'src/http_client_request_adapter.dart'; +part 'src/kiota_client_factory.dart'; diff --git a/packages/kiota_http/lib/src/kiota_client_factory.dart b/packages/kiota_http/lib/src/kiota_client_factory.dart new file mode 100644 index 0000000..8667e02 --- /dev/null +++ b/packages/kiota_http/lib/src/kiota_client_factory.dart @@ -0,0 +1,9 @@ +part of '../kiota_http.dart'; + +class KiotaClientFactory { + KiotaClientFactory._(); + + static http.Client createClient() { + return http.Client(); + } +} From 6fc4abbf9026ff0f36135e63909fdb75521cd8bd Mon Sep 17 00:00:00 2001 From: Ricardo Boss Date: Mon, 1 Apr 2024 12:47:10 +0200 Subject: [PATCH 04/25] Added run config --- .run/Test kiota_http.run.xml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .run/Test kiota_http.run.xml diff --git a/.run/Test kiota_http.run.xml b/.run/Test kiota_http.run.xml new file mode 100644 index 0000000..543ce3f --- /dev/null +++ b/.run/Test kiota_http.run.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file From 963e5300713820b02a52130d5d2539a59154e26a Mon Sep 17 00:00:00 2001 From: Ricardo Boss Date: Thu, 4 Apr 2024 02:36:12 +0200 Subject: [PATCH 05/25] Make content nullable for requests without body --- packages/kiota_abstractions/lib/src/request_information.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kiota_abstractions/lib/src/request_information.dart b/packages/kiota_abstractions/lib/src/request_information.dart index 0df02f0..411f8bb 100644 --- a/packages/kiota_abstractions/lib/src/request_information.dart +++ b/packages/kiota_abstractions/lib/src/request_information.dart @@ -34,7 +34,7 @@ class RequestInformation { HttpHeaders get headers => _headers; /// The request body. - Uint8List content = Uint8List(0); + Uint8List? content; final Map _requestOptions = {}; From 9fc8c0a5451ea78b79a784038b75650779e27c22 Mon Sep 17 00:00:00 2001 From: Ricardo Boss Date: Thu, 4 Apr 2024 02:36:42 +0200 Subject: [PATCH 06/25] Use explicit value instead of case name for consistency --- .../lib/src/http_method.dart | 23 +++++++++++-------- .../lib/src/http_client_request_adapter.dart | 3 ++- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/packages/kiota_abstractions/lib/src/http_method.dart b/packages/kiota_abstractions/lib/src/http_method.dart index a252226..ac6f946 100644 --- a/packages/kiota_abstractions/lib/src/http_method.dart +++ b/packages/kiota_abstractions/lib/src/http_method.dart @@ -3,29 +3,34 @@ part of '../kiota_abstractions.dart'; /// Represents the HTTP method used by a request. enum HttpMethod { /// The GET method requests a representation of the specified resource. Requests using GET should only retrieve data. - get, + get('GET'), /// The POST method is used to submit an entity to the specified resource, often causing a change in state or side effects on the server. - post, + post('POST'), /// The PATCH method is used to apply partial modifications to a resource. - patch, + patch('PATCH'), /// The DELETE method deletes the specified resource. - delete, + delete('DELETE'), /// The OPTIONS method is used to describe the communication options for the target resource. - options, + options('OPTIONS'), /// The PUT method replaces all current representations of the target resource with the request payload. - put, + put('PUT'), /// The HEAD method asks for a response identical to that of a GET request, but without the response body. - head, + head('HEAD'), /// The CONNECT method establishes a tunnel to the server identified by the target resource. - connect, + connect('CONNECT'), /// The TRACE method performs a message loop-back test along the path to the target resource. - trace, + trace('TRACE'); + + const HttpMethod(this.value); + + /// The value or name of the method (for example "GET" or "POST"). + final String value; } diff --git a/packages/kiota_http/lib/src/http_client_request_adapter.dart b/packages/kiota_http/lib/src/http_client_request_adapter.dart index 947bb72..ca7d048 100644 --- a/packages/kiota_http/lib/src/http_client_request_adapter.dart +++ b/packages/kiota_http/lib/src/http_client_request_adapter.dart @@ -34,7 +34,8 @@ class HttpClientRequestAdapter implements RequestAdapter { Future _getMessageFromInfo( RequestInformation requestInfo, ) async { - final request = http.Request(requestInfo.httpMethod!.name, requestInfo.uri); + final request = + http.Request(requestInfo.httpMethod!.value, requestInfo.uri); // TODO(ricardoboss): implement the rest of the method From 407aeaf05a7d89e077d3f96b845d93170bf948e4 Mon Sep 17 00:00:00 2001 From: Ricardo Boss Date: Thu, 4 Apr 2024 02:37:38 +0200 Subject: [PATCH 07/25] Use less else ifs for better readability --- .../lib/src/http_client_request_adapter.dart | 40 +++++++++++++------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/packages/kiota_http/lib/src/http_client_request_adapter.dart b/packages/kiota_http/lib/src/http_client_request_adapter.dart index ca7d048..61d6868 100644 --- a/packages/kiota_http/lib/src/http_client_request_adapter.dart +++ b/packages/kiota_http/lib/src/http_client_request_adapter.dart @@ -234,27 +234,43 @@ class HttpClientRequestAdapter implements RequestAdapter { if (ModelType is bool?) { return rootNode.getBoolValue() as ModelType; - } else if (ModelType is int?) { + } + + if (ModelType is int?) { return rootNode.getIntValue() as ModelType; - } else if (ModelType is double?) { + } + + if (ModelType is double?) { return rootNode.getDoubleValue() as ModelType; - } else if (ModelType is String?) { + } + + if (ModelType is String?) { return rootNode.getStringValue() as ModelType; - } else if (ModelType is DateTime?) { + } + + if (ModelType is DateTime?) { return rootNode.getDateTimeValue() as ModelType; - } else if (ModelType is DateOnly?) { + } + + if (ModelType is DateOnly?) { return rootNode.getDateOnlyValue() as ModelType; - } else if (ModelType is TimeOnly?) { + } + + if (ModelType is TimeOnly?) { return rootNode.getTimeOnlyValue() as ModelType; - } else if (ModelType is Duration?) { + } + + if (ModelType is Duration?) { return rootNode.getDurationValue() as ModelType; - } else if (ModelType is UuidValue?) { + } + + if (ModelType is UuidValue?) { return rootNode.getGuidValue() as ModelType; - } else { - throw ArgumentError( - 'The type $ModelType is not supported for primitive deserialization', - ); } + + throw ArgumentError( + 'The type $ModelType is not supported for primitive deserialization', + ); } @override From 7c90e6e402d7376c878a3ee4fb38df220cc66dce Mon Sep 17 00:00:00 2001 From: Ricardo Boss Date: Thu, 4 Apr 2024 02:38:16 +0200 Subject: [PATCH 08/25] Formatting --- .../kiota_http/lib/src/http_client_request_adapter.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/kiota_http/lib/src/http_client_request_adapter.dart b/packages/kiota_http/lib/src/http_client_request_adapter.dart index 61d6868..7f0bff6 100644 --- a/packages/kiota_http/lib/src/http_client_request_adapter.dart +++ b/packages/kiota_http/lib/src/http_client_request_adapter.dart @@ -34,8 +34,10 @@ class HttpClientRequestAdapter implements RequestAdapter { Future _getMessageFromInfo( RequestInformation requestInfo, ) async { - final request = - http.Request(requestInfo.httpMethod!.value, requestInfo.uri); + final request = http.Request( + requestInfo.httpMethod!.value, + requestInfo.uri, + ); // TODO(ricardoboss): implement the rest of the method From d576c87936ae860875ff6b67cbfbcaaca656d275 Mon Sep 17 00:00:00 2001 From: Ricardo Boss Date: Thu, 4 Apr 2024 02:44:19 +0200 Subject: [PATCH 09/25] Include body in request, if any --- packages/kiota_http/lib/kiota_http.dart | 2 ++ packages/kiota_http/lib/src/http_client_request_adapter.dart | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/kiota_http/lib/kiota_http.dart b/packages/kiota_http/lib/kiota_http.dart index 01706c2..0c06568 100644 --- a/packages/kiota_http/lib/kiota_http.dart +++ b/packages/kiota_http/lib/kiota_http.dart @@ -2,6 +2,8 @@ /// [Kiota](https://github.com/microsoft/kiota) clients. library kiota_http; +import 'dart:typed_data'; + import 'package:http/http.dart' as http; import 'package:kiota_abstractions/kiota_abstractions.dart'; import 'package:uuid/uuid.dart'; diff --git a/packages/kiota_http/lib/src/http_client_request_adapter.dart b/packages/kiota_http/lib/src/http_client_request_adapter.dart index 7f0bff6..772c9fa 100644 --- a/packages/kiota_http/lib/src/http_client_request_adapter.dart +++ b/packages/kiota_http/lib/src/http_client_request_adapter.dart @@ -39,7 +39,9 @@ class HttpClientRequestAdapter implements RequestAdapter { requestInfo.uri, ); - // TODO(ricardoboss): implement the rest of the method + if (requestInfo.content case final Uint8List content) { + request.bodyBytes = content; + } return request; } From c675d3cea29396ac44df7b7785360d849e1b4332 Mon Sep 17 00:00:00 2001 From: Ricardo Boss Date: Thu, 4 Apr 2024 02:52:45 +0200 Subject: [PATCH 10/25] Add HTTP headers from info to request --- .../lib/src/http_client_request_adapter.dart | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/kiota_http/lib/src/http_client_request_adapter.dart b/packages/kiota_http/lib/src/http_client_request_adapter.dart index 772c9fa..b08d849 100644 --- a/packages/kiota_http/lib/src/http_client_request_adapter.dart +++ b/packages/kiota_http/lib/src/http_client_request_adapter.dart @@ -39,6 +39,17 @@ class HttpClientRequestAdapter implements RequestAdapter { requestInfo.uri, ); + if (requestInfo.headers.isNotEmpty) { + final flattenedHeaders = requestInfo.headers.map((name, values) { + // according to https://stackoverflow.com/a/3097052/5107884 it is + // possible to concatenate multiple header values for the same name with + // a comma + return MapEntry(name, values.join(', ')); + }); + + request.headers.addAll(flattenedHeaders); + } + if (requestInfo.content case final Uint8List content) { request.bodyBytes = content; } From 24bf896a518524abe2b674d288ec3d7207b08a6c Mon Sep 17 00:00:00 2001 From: Ricardo Boss Date: Thu, 4 Apr 2024 02:58:10 +0200 Subject: [PATCH 11/25] Set baseUrl in path parameters instead of query parameters --- packages/kiota_http/lib/src/http_client_request_adapter.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kiota_http/lib/src/http_client_request_adapter.dart b/packages/kiota_http/lib/src/http_client_request_adapter.dart index b08d849..a31cb31 100644 --- a/packages/kiota_http/lib/src/http_client_request_adapter.dart +++ b/packages/kiota_http/lib/src/http_client_request_adapter.dart @@ -76,7 +76,7 @@ class HttpClientRequestAdapter implements RequestAdapter { } void _setBaseUrl(RequestInformation requestInfo) { - requestInfo.queryParameters['baseUrl'] = baseUrl; + requestInfo.pathParameters['baseUrl'] = baseUrl; } ResponseHandler? _getResponseHandler(RequestInformation requestInfo) { From 7d0436607413abde3d5b450ce2de63ad89629ae8 Mon Sep 17 00:00:00 2001 From: Ricardo Boss Date: Thu, 4 Apr 2024 03:02:07 +0200 Subject: [PATCH 12/25] Update test with empty request info having no content --- .run/All Tests.run.xml | 7 +++++++ .run/Test kiota_abstractions.run.xml | 7 +++++++ .../kiota_abstractions/test/request_information_test.dart | 6 +----- 3 files changed, 15 insertions(+), 5 deletions(-) create mode 100644 .run/All Tests.run.xml create mode 100644 .run/Test kiota_abstractions.run.xml diff --git a/.run/All Tests.run.xml b/.run/All Tests.run.xml new file mode 100644 index 0000000..0e1baf4 --- /dev/null +++ b/.run/All Tests.run.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.run/Test kiota_abstractions.run.xml b/.run/Test kiota_abstractions.run.xml new file mode 100644 index 0000000..82c9dbe --- /dev/null +++ b/.run/Test kiota_abstractions.run.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/packages/kiota_abstractions/test/request_information_test.dart b/packages/kiota_abstractions/test/request_information_test.dart index 962c677..f145fb3 100644 --- a/packages/kiota_abstractions/test/request_information_test.dart +++ b/packages/kiota_abstractions/test/request_information_test.dart @@ -21,11 +21,7 @@ void main() { expect(blank.pathParameters, {}); expect(blank.queryParameters, {}); expect(blank.headers, HttpHeaders()); - expect( - blank.content, - isA() - .having((content) => content.length, 'length', equals(0)), - ); + expect(blank.content, isNull); expect(blank.requestOptions, []); expect(() => blank.uri, throwsArgumentError); From fb3abd80eb01d95d52e9c1688ad2a47f85865c45 Mon Sep 17 00:00:00 2001 From: Ricardo Boss Date: Thu, 4 Apr 2024 03:32:24 +0200 Subject: [PATCH 13/25] Test and fix sendPrimitive --- .github/workflows/dart.yml | 2 +- .run/watch kiota_abstractions.run.xml | 17 + ...watch.run.xml => watch kiota_http.run.xml} | 4 +- .../lib/src/http_client_request_adapter.dart | 61 +-- packages/kiota_http/pubspec.yaml | 2 + packages/kiota_http/test/dummy_test.dart | 7 - .../http_client_request_adapter_test.dart | 67 +++ ...ttp_client_request_adapter_test.mocks.dart | 482 ++++++++++++++++++ 8 files changed, 594 insertions(+), 48 deletions(-) create mode 100644 .run/watch kiota_abstractions.run.xml rename .run/{build_runner watch.run.xml => watch kiota_http.run.xml} (87%) delete mode 100644 packages/kiota_http/test/dummy_test.dart create mode 100644 packages/kiota_http/test/http_client_request_adapter_test.dart create mode 100644 packages/kiota_http/test/http_client_request_adapter_test.mocks.dart diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index 8e04434..3485f35 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -42,7 +42,7 @@ jobs: working-directory: packages/${{ matrix.package }} - name: Run build_runner - if: contains(fromJson('[ "kiota_abstractions" ]'), matrix.package) + if: contains(fromJson('[ "kiota_abstractions", "kiota_http" ]'), matrix.package) run: dart run build_runner build --delete-conflicting-outputs working-directory: packages/${{ matrix.package }} diff --git a/.run/watch kiota_abstractions.run.xml b/.run/watch kiota_abstractions.run.xml new file mode 100644 index 0000000..6917eea --- /dev/null +++ b/.run/watch kiota_abstractions.run.xml @@ -0,0 +1,17 @@ + + + + \ No newline at end of file diff --git a/.run/build_runner watch.run.xml b/.run/watch kiota_http.run.xml similarity index 87% rename from .run/build_runner watch.run.xml rename to .run/watch kiota_http.run.xml index 2ee9648..bd6d3ce 100644 --- a/.run/build_runner watch.run.xml +++ b/.run/watch kiota_http.run.xml @@ -1,11 +1,11 @@ - +