Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(api): REST methods in dart with auth mode none #1783

Merged
merged 36 commits into from
Jun 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
03113a3
chore!(api): migrate API category type definitions
May 26, 2022
5f57bdc
deprecate rest exception
May 27, 2022
774d48e
change deprecation message
May 27, 2022
5198f9b
change constuctor
May 27, 2022
a63f126
Merge branch 'next' into chore/migrate-api-interface
May 31, 2022
3f1472a
change types for qp
May 31, 2022
c2aa31b
cleanup
May 31, 2022
f23b403
more cleanup
May 31, 2022
6fa8ded
fix response headers, other PT changes
Jun 1, 2022
5e993cb
Merge branch 'next' into chore/migrate-api-interface
Jun 1, 2022
1c9e8cb
change example app
Jun 2, 2022
780c654
Merge branch 'next' into chore/migrate-api-interface
Jun 2, 2022
6034d6b
feat(api): REST API in dart, auth mode NONE
Jun 2, 2022
5b8a8bd
Update packages/amplify_core/lib/src/types/api/rest/http_payload.dart
ragingsquirrel3 Jun 9, 2022
cfb1c4e
address some comments
Jun 9, 2022
828bde5
Merge branch 'next' into chore/migrate-api-interface
Jun 9, 2022
f756a17
address some comments
Jun 10, 2022
7dfad32
fix tests
Jun 13, 2022
fbaf480
Merge branch 'chore/migrate-api-interface' into feat/api-rest2
Jun 13, 2022
3f08e79
correct merge
Jun 13, 2022
72f6e4b
address comments, json constructor
Jun 15, 2022
d983026
tweak json encoding
Jun 15, 2022
2ffa106
Merge branch 'next' into chore/migrate-api-interface
Jun 15, 2022
b66ef3a
Merge branch 'chore/migrate-api-interface' into feat/api-rest2
Jun 15, 2022
e5d4239
chore!(api): migrate API category type definitions (#1640)
Jun 15, 2022
909bb4f
Merge branch 'feat/api-next' into feat/api-rest2
Jun 21, 2022
c1fc4f5
add tests
Jun 21, 2022
66704de
correct qp types
Jun 21, 2022
8f9c6d8
chore(api): API Native Bridge for .addPlugin() (#1756)
Equartey Jun 23, 2022
da4d327
Merge branch 'feat/api-next' into feat/api-rest2
Jun 24, 2022
fb338eb
address PR comments
Jun 27, 2022
ff2f0ba
Merge branch 'feat/api-next' into feat/api-rest2
Jun 27, 2022
6d823db
correct some merge mistakes
Jun 27, 2022
24a9926
correct another merge error
Jun 27, 2022
50404c9
correct merge error
Jun 27, 2022
467a792
Update packages/api/amplify_api/lib/src/util.dart
ragingsquirrel3 Jun 27, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/amplify/amplify_flutter/lib/src/hybrid_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class AmplifyHybridImpl extends AmplifyClassImpl {
);
await Future.wait(
[
...API.plugins,
...Auth.plugins,
].map((p) => p.configure(config: amplifyConfig)),
eagerError: true,
Expand Down
74 changes: 74 additions & 0 deletions packages/api/amplify_api/lib/src/amplify_api_config.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License").
// You may not use this file except in compliance with the License.
// A copy of the License is located at
//
// http://aws.amazon.com/apache2.0
//
// or in the "license" file accompanying this file. This file is distributed
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the License for the specific language governing
// permissions and limitations under the License.

import 'package:amplify_core/amplify_core.dart';
import 'package:collection/collection.dart';
import 'package:meta/meta.dart';

const _slash = '/';

@internal
class EndpointConfig with AWSEquatable<EndpointConfig> {
const EndpointConfig(this.name, this.config);

final String name;
final AWSApiConfig config;

@override
List<Object?> get props => [name, config];

/// Gets the host with environment path prefix from Amplify config and combines
/// with [path] and [queryParameters] to return a full [Uri].
Uri getUri(String path, Map<String, dynamic>? queryParameters) {
final parsed = Uri.parse(config.endpoint);
// Remove leading slashes which are suggested in public documentation.
ragingsquirrel3 marked this conversation as resolved.
Show resolved Hide resolved
// https://docs.amplify.aws/lib/restapi/getting-started/q/platform/flutter/#make-a-post-request
if (path.startsWith(_slash)) {
path = path.substring(1);
}
// Avoid URI-encoding slashes in path from caller.
final pathSegmentsFromPath = path.split(_slash);
return parsed.replace(pathSegments: [
...parsed.pathSegments,
...pathSegmentsFromPath,
], queryParameters: queryParameters);
}
}

@internal
extension AWSApiPluginConfigHelpers on AWSApiPluginConfig {
EndpointConfig getEndpoint({
required EndpointType type,
String? apiName,
}) {
final typeConfigs =
entries.where((config) => config.value.endpointType == type);
if (apiName != null) {
final config = typeConfigs.firstWhere(
(config) => config.key == apiName,
orElse: () => throw ApiException(
'No API endpoint found matching apiName $apiName.',
),
);
return EndpointConfig(config.key, config.value);
}
final onlyConfig = typeConfigs.singleOrNull;
if (onlyConfig == null) {
throw const ApiException(
'Multiple API endpoints defined. Pass apiName parameter to specify '
'which one to use.',
);
}
return EndpointConfig(onlyConfig.key, onlyConfig.value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import 'dart:async';

import 'package:amplify_core/amplify_core.dart';
import 'package:http/http.dart' as http;
import 'package:meta/meta.dart';

/// Implementation of http [http.Client] that authorizes HTTP requests with
/// Amplify.
@internal
class AmplifyAuthorizationRestClient extends http.BaseClient
implements Closeable {
/// Determines how requests with this client are authorized.
final AWSApiConfig endpointConfig;
final http.Client _baseClient;
final bool _useDefaultBaseClient;

/// Provide an [AWSApiConfig] which will determine how requests from this
/// client are authorized.
AmplifyAuthorizationRestClient({
required this.endpointConfig,
http.Client? baseClient,
}) : _useDefaultBaseClient = baseClient == null,
_baseClient = baseClient ?? http.Client();

/// Implementation of [send] that authorizes any request without "Authorization"
/// header already set.
@override
Future<http.StreamedResponse> send(http.BaseRequest request) async =>
_baseClient.send(_authorizeRequest(request));

@override
void close() {
if (_useDefaultBaseClient) _baseClient.close();
}

http.BaseRequest _authorizeRequest(http.BaseRequest request) {
if (!request.headers.containsKey(AWSHeaders.authorization) &&
endpointConfig.authorizationType != APIAuthorizationType.none) {
// ignore: todo
// TODO: Use auth providers from core to transform the request.
}
return request;
}
}
163 changes: 162 additions & 1 deletion packages/api/amplify_api/lib/src/api_plugin_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,36 @@ import 'dart:io';
import 'package:amplify_api/amplify_api.dart';
import 'package:amplify_api/src/native_api_plugin.dart';
import 'package:amplify_core/amplify_core.dart';
import 'package:async/async.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
import 'package:meta/meta.dart';

import 'amplify_api_config.dart';
import 'amplify_authorization_rest_client.dart';
import 'util.dart';

/// {@template amplify_api.amplify_api_dart}
/// The AWS implementation of the Amplify API category.
/// {@endtemplate}
class AmplifyAPIDart extends AmplifyAPI {
late final AWSApiPluginConfig _apiConfig;
final http.Client? _baseHttpClient;

/// A map of the keys from the Amplify API config to HTTP clients to use for
/// requests to that endpoint.
final Map<String, AmplifyAuthorizationRestClient> _clientPool = {};

/// The registered [APIAuthProvider] instances.
final Map<APIAuthorizationType, APIAuthProvider> _authProviders = {};

/// {@macro amplify_api.amplify_api_dart}
AmplifyAPIDart({
List<APIAuthProvider> authProviders = const [],
http.Client? baseHttpClient,
this.modelProvider,
}) : super.protected() {
}) : _baseHttpClient = baseHttpClient,
super.protected() {
authProviders.forEach(registerAuthProvider);
}

Expand Down Expand Up @@ -71,11 +85,158 @@ class AmplifyAPIDart extends AmplifyAPI {
}
}

/// Returns the HTTP client to be used for REST operations.
///
/// Use [apiName] if there are multiple REST endpoints.
@visibleForTesting
http.Client getRestClient({String? apiName}) {
final endpoint = _apiConfig.getEndpoint(
type: EndpointType.rest,
apiName: apiName,
);
return _clientPool[endpoint.name] ??= AmplifyAuthorizationRestClient(
endpointConfig: endpoint.config,
baseClient: _baseHttpClient,
);
}

Uri _getRestUri(
String path, String? apiName, Map<String, dynamic>? queryParameters) {
final endpoint = _apiConfig.getEndpoint(
type: EndpointType.rest,
apiName: apiName,
);
return endpoint.getUri(path, queryParameters);
}

/// NOTE: http does not support request abort https://github.com/dart-lang/http/issues/424.
/// For now, just make a [CancelableOperation] to cancel the future.
/// To actually abort calls at network layer, need to call in
/// dart:io/dart:html or other library with custom http default Client() implementation.
CancelableOperation<T> _makeCancelable<T>(Future<T> responseFuture) {
return CancelableOperation.fromFuture(responseFuture);
}

CancelableOperation<AWSStreamedHttpResponse> _prepareRestResponse(
Future<AWSStreamedHttpResponse> responseFuture) {
return _makeCancelable(responseFuture);
}

@override
final ModelProviderInterface? modelProvider;

@override
void registerAuthProvider(APIAuthProvider authProvider) {
_authProviders[authProvider.type] = authProvider;
}

// ====== REST =======

@override
CancelableOperation<AWSStreamedHttpResponse> delete(
String path, {
HttpPayload? body,
Map<String, String>? headers,
Map<String, String>? queryParameters,
String? apiName,
}) {
final uri = _getRestUri(path, apiName, queryParameters);
final client = getRestClient(apiName: apiName);
return _prepareRestResponse(AWSStreamedHttpRequest.delete(
uri,
body: body ?? HttpPayload.empty(),
headers: addContentTypeToHeaders(headers, body),
).send(client));
}

@override
CancelableOperation<AWSStreamedHttpResponse> get(
String path, {
Map<String, String>? headers,
Map<String, String>? queryParameters,
String? apiName,
}) {
final uri = _getRestUri(path, apiName, queryParameters);
final client = getRestClient(apiName: apiName);
return _prepareRestResponse(
AWSHttpRequest.get(
uri,
headers: headers,
).send(client),
);
}

@override
CancelableOperation<AWSStreamedHttpResponse> head(
String path, {
Map<String, String>? headers,
Map<String, String>? queryParameters,
String? apiName,
}) {
final uri = _getRestUri(path, apiName, queryParameters);
final client = getRestClient(apiName: apiName);
return _prepareRestResponse(
AWSHttpRequest.head(
uri,
headers: headers,
).send(client),
);
}

@override
CancelableOperation<AWSStreamedHttpResponse> patch(
String path, {
HttpPayload? body,
Map<String, String>? headers,
Map<String, String>? queryParameters,
String? apiName,
}) {
final uri = _getRestUri(path, apiName, queryParameters);
final client = getRestClient(apiName: apiName);
return _prepareRestResponse(
AWSStreamedHttpRequest.patch(
uri,
headers: addContentTypeToHeaders(headers, body),
body: body ?? HttpPayload.empty(),
).send(client),
);
}

@override
CancelableOperation<AWSStreamedHttpResponse> post(
String path, {
HttpPayload? body,
Map<String, String>? headers,
Map<String, String>? queryParameters,
String? apiName,
}) {
final uri = _getRestUri(path, apiName, queryParameters);
final client = getRestClient(apiName: apiName);
return _prepareRestResponse(
AWSStreamedHttpRequest.post(
uri,
headers: addContentTypeToHeaders(headers, body),
body: body ?? HttpPayload.empty(),
).send(client),
);
}

@override
CancelableOperation<AWSStreamedHttpResponse> put(
String path, {
HttpPayload? body,
Map<String, String>? headers,
Map<String, String>? queryParameters,
String? apiName,
}) {
final uri = _getRestUri(path, apiName, queryParameters);
final client = getRestClient(apiName: apiName);
return _prepareRestResponse(
AWSStreamedHttpRequest.put(
uri,
headers: addContentTypeToHeaders(headers, body),
body: body ?? HttpPayload.empty(),
).send(client),
);
}
}
10 changes: 2 additions & 8 deletions packages/api/amplify_api/lib/src/method_channel_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';

import '../amplify_api.dart';
import 'util.dart';

part 'auth_token.dart';

Expand Down Expand Up @@ -282,19 +283,12 @@ class AmplifyAPIMethodChannel extends AmplifyAPI {
}) {
// Send Request cancelToken to Native
String cancelToken = uuid();
// Ensure Content-Type header matches payload.
var modifiedHeaders = headers != null ? Map.of(headers) : null;
final contentType = body?.contentType;
if (contentType != null) {
modifiedHeaders = modifiedHeaders ?? {};
modifiedHeaders.putIfAbsent(AWSHeaders.contentType, () => contentType);
}
final responseFuture = _restResponseHelper(
methodName: methodName,
path: path,
cancelToken: cancelToken,
body: body,
headers: modifiedHeaders,
headers: addContentTypeToHeaders(headers, body),
queryParameters: queryParameters,
apiName: apiName,
);
Expand Down
Loading