diff --git a/packages/amplify_core/lib/src/category/amplify_api_category.dart b/packages/amplify_core/lib/src/category/amplify_api_category.dart index a1a59baa34..42080dd7c9 100644 --- a/packages/amplify_core/lib/src/category/amplify_api_category.dart +++ b/packages/amplify_core/lib/src/category/amplify_api_category.dart @@ -25,7 +25,7 @@ class APICategory extends AmplifyCategory { /// Sends a GraphQL query request and returns the response in a cancelable `GraphQLOperation`. /// /// See https://docs.amplify.aws/lib/graphqlapi/query-data/q/platform/flutter/ - /// and for more information. + /// for more information. GraphQLOperation query({required GraphQLRequest request}) => defaultPlugin.query(request: request); diff --git a/packages/api/amplify_api/analysis_options.yaml b/packages/api/amplify_api/analysis_options.yaml index cd02f7bc73..bacf2648c8 100644 --- a/packages/api/amplify_api/analysis_options.yaml +++ b/packages/api/amplify_api/analysis_options.yaml @@ -1,8 +1,5 @@ -include: package:amplify_lints/library_core.yaml +include: package:amplify_lints/library.yaml analyzer: exclude: - '**/*.mocks.dart' - errors: - # TODO: Renable when logging library is finished - avoid_print: ignore diff --git a/packages/api/amplify_api/example/lib/models/Blog.dart b/packages/api/amplify_api/example/lib/models/Blog.dart index e886cfe7a5..95f4c47382 100644 --- a/packages/api/amplify_api/example/lib/models/Blog.dart +++ b/packages/api/amplify_api/example/lib/models/Blog.dart @@ -1,4 +1,4 @@ -// ignore_for_file: prefer_const_literals_to_create_immutables, prefer_single_quotes +// ignore_for_file: prefer_const_literals_to_create_immutables, prefer_single_quotes, inference_failure_on_untyped_parameter /* * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/packages/api/amplify_api/example/lib/models/Comment.dart b/packages/api/amplify_api/example/lib/models/Comment.dart index a1d9fbed01..b37da1da20 100644 --- a/packages/api/amplify_api/example/lib/models/Comment.dart +++ b/packages/api/amplify_api/example/lib/models/Comment.dart @@ -1,4 +1,4 @@ -// ignore_for_file: avoid_catches_without_on_clauses, prefer_const_literals_to_create_immutables, prefer_single_quotes +// ignore_for_file: avoid_catches_without_on_clauses, prefer_const_literals_to_create_immutables, prefer_single_quotes, inference_failure_on_untyped_parameter /* * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/packages/api/amplify_api/example/lib/models/Post.dart b/packages/api/amplify_api/example/lib/models/Post.dart index eeea418bb8..019a02e484 100644 --- a/packages/api/amplify_api/example/lib/models/Post.dart +++ b/packages/api/amplify_api/example/lib/models/Post.dart @@ -1,4 +1,4 @@ -// ignore_for_file: avoid_catches_without_on_clauses, prefer_single_quotes, prefer_const_literals_to_create_immutables +// ignore_for_file: avoid_catches_without_on_clauses, prefer_single_quotes, prefer_const_literals_to_create_immutables, inference_failure_on_untyped_parameter /* * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. @@ -50,7 +50,6 @@ class Post extends Model { String get title { try { return _title!; - // ignore: avoid_catches_without_on_clauses } catch (e) { throw new AmplifyCodeGenModelException( AmplifyExceptionMessages diff --git a/packages/api/amplify_api/lib/amplify_api.dart b/packages/api/amplify_api/lib/amplify_api.dart index 0d5f25fed7..de7e78d3ff 100644 --- a/packages/api/amplify_api/lib/amplify_api.dart +++ b/packages/api/amplify_api/lib/amplify_api.dart @@ -19,12 +19,11 @@ import 'package:amplify_api/src/api_plugin_impl.dart'; import 'package:amplify_core/amplify_core.dart'; import 'package:meta/meta.dart'; +export 'package:amplify_api/src/graphql/model_mutations.dart'; +export 'package:amplify_api/src/graphql/model_queries.dart'; +export 'package:amplify_api/src/graphql/model_subscriptions.dart'; export 'package:amplify_core/src/types/api/api_types.dart'; -export './model_mutations.dart'; -export './model_queries.dart'; -export './model_subscriptions.dart'; - /// {@template amplify_api.amplify_api} /// The AWS implementation of the Amplify API category. /// {@endtemplate} diff --git a/packages/api/amplify_api/lib/src/api_plugin_impl.dart b/packages/api/amplify_api/lib/src/api_plugin_impl.dart index 95ee34a6b6..bfd3d65e3c 100644 --- a/packages/api/amplify_api/lib/src/api_plugin_impl.dart +++ b/packages/api/amplify_api/lib/src/api_plugin_impl.dart @@ -17,13 +17,13 @@ library amplify_api; import 'dart:io'; import 'package:amplify_api/amplify_api.dart'; -import 'package:amplify_api/src/amplify_api_config.dart'; -import 'package:amplify_api/src/amplify_authorization_rest_client.dart'; import 'package:amplify_api/src/graphql/app_sync_api_key_auth_provider.dart'; +import 'package:amplify_api/src/graphql/oidc_function_api_auth_provider.dart'; import 'package:amplify_api/src/graphql/send_graphql_request.dart'; import 'package:amplify_api/src/graphql/ws/web_socket_connection.dart'; import 'package:amplify_api/src/native_api_plugin.dart'; -import 'package:amplify_api/src/oidc_function_api_auth_provider.dart'; +import 'package:amplify_api/src/util/amplify_api_config.dart'; +import 'package:amplify_api/src/util/amplify_authorization_rest_client.dart'; import 'package:amplify_core/amplify_core.dart'; import 'package:flutter/services.dart'; import 'package:meta/meta.dart'; @@ -65,9 +65,11 @@ class AmplifyAPIDart extends AmplifyAPI { }) async { final apiConfig = config?.api?.awsPlugin; if (apiConfig == null) { - throw const ApiException('No AWS API config found', - recoverySuggestion: 'Add API from the Amplify CLI. See ' - 'https://docs.amplify.aws/lib/graphqlapi/getting-started/q/platform/flutter/#configure-api'); + throw const ApiException( + 'No AWS API config found', + recoverySuggestion: 'Add API from the Amplify CLI. See ' + 'https://docs.amplify.aws/lib/graphqlapi/getting-started/q/platform/flutter/#configure-api', + ); } _apiConfig = apiConfig; _authProviderRepo = authProviderRepo; @@ -119,9 +121,10 @@ class AmplifyAPIDart extends AmplifyAPI { } on PlatformException catch (e) { if (e.code == 'AmplifyAlreadyConfiguredException') { throw const AmplifyAlreadyConfiguredException( - AmplifyExceptionMessages.alreadyConfiguredDefaultMessage, - recoverySuggestion: - AmplifyExceptionMessages.alreadyConfiguredDefaultSuggestion); + AmplifyExceptionMessages.alreadyConfiguredDefaultMessage, + recoverySuggestion: + AmplifyExceptionMessages.alreadyConfiguredDefaultSuggestion, + ); } throw AmplifyException.fromMap((e.details as Map).cast()); } diff --git a/packages/api/amplify_api/lib/src/decorators/authorize_http_request.dart b/packages/api/amplify_api/lib/src/decorators/authorize_http_request.dart index 854372aa89..09005a5de4 100644 --- a/packages/api/amplify_api/lib/src/decorators/authorize_http_request.dart +++ b/packages/api/amplify_api/lib/src/decorators/authorize_http_request.dart @@ -45,7 +45,8 @@ Future authorizeHttpRequest( final apiKey = endpointConfig.apiKey; if (apiKey == null) { throw const ApiException( - 'Auth mode is API Key, but no API Key was found in config.'); + 'Auth mode is API Key, but no API Key was found in config.', + ); } final authorizedRequest = await authProvider.authorizeRequest( @@ -55,9 +56,10 @@ Future authorizeHttpRequest( return authorizedRequest; case APIAuthorizationType.iam: final authProvider = _validateAuthProvider( - authProviderRepo - .getAuthProvider(APIAuthorizationType.iam.authProviderToken), - authType); + authProviderRepo + .getAuthProvider(APIAuthorizationType.iam.authProviderToken), + authType, + ); final service = endpointConfig.endpointType == EndpointType.graphQL ? AWSService.appSync : AWSService.apiGatewayManagementApi; // resolves to "execute-api" @@ -85,10 +87,14 @@ Future authorizeHttpRequest( } T _validateAuthProvider( - T? authProvider, APIAuthorizationType authType) { + T? authProvider, + APIAuthorizationType authType, +) { if (authProvider == null) { - throw ApiException('No auth provider found for auth mode ${authType.name}.', - recoverySuggestion: 'Ensure auth plugin correctly configured.'); + throw ApiException( + 'No auth provider found for auth mode ${authType.name}.', + recoverySuggestion: 'Ensure auth plugin correctly configured.', + ); } return authProvider; } diff --git a/packages/api/amplify_api/lib/src/graphql/app_sync_api_key_auth_provider.dart b/packages/api/amplify_api/lib/src/graphql/app_sync_api_key_auth_provider.dart index bdafe6dbed..d0bcad9542 100644 --- a/packages/api/amplify_api/lib/src/graphql/app_sync_api_key_auth_provider.dart +++ b/packages/api/amplify_api/lib/src/graphql/app_sync_api_key_auth_provider.dart @@ -30,7 +30,8 @@ class AppSyncApiKeyAuthProvider extends ApiKeyAmplifyAuthProvider { }) async { if (options == null) { throw const ApiException( - 'Called API key auth provider without passing a valid API key.'); + 'Called API key auth provider without passing a valid API key.', + ); } request.headers.putIfAbsent(xApiKey, () => options.apiKey); return request; diff --git a/packages/api/amplify_api/lib/src/graphql/graphql_request_factory.dart b/packages/api/amplify_api/lib/src/graphql/graphql_request_factory.dart index cecb3a57bf..20ee037064 100644 --- a/packages/api/amplify_api/lib/src/graphql/graphql_request_factory.dart +++ b/packages/api/amplify_api/lib/src/graphql/graphql_request_factory.dart @@ -12,24 +12,24 @@ * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ - -// ignore_for_file: public_member_api_docs +import 'package:amplify_api/src/graphql/utils.dart'; import 'package:amplify_core/amplify_core.dart'; import 'package:flutter/foundation.dart'; -import 'utils.dart'; +// ignore_for_file: public_member_api_docs /// `"id"`, the name of the id field in every compatible model/schema. /// Eventually needs to be dynamic to accommodate custom primary keys. const idFieldName = 'id'; class DocumentInputs { + const DocumentInputs(this.upper, this.lower); + // Upper document input: ($id: ID!) final String upper; // Lower document input: (id: $id) final String lower; - const DocumentInputs(this.upper, this.lower); } class GraphQLRequestFactory { @@ -47,20 +47,25 @@ class GraphQLRequestFactory { } String _getSelectionSetFromModelSchema( - ModelSchema schema, GraphQLRequestOperation operation, - {bool ignoreParents = false}) { + ModelSchema schema, + GraphQLRequestOperation operation, { + bool ignoreParents = false, + }) { // Schema has been validated & schema.fields is non-nullable. // Get a list of field names to include in the request body. - List fields = schema.fields!.entries - .where((entry) => - entry.value.association == null) // ignore related model fields + final fields = schema.fields!.entries + .where( + (entry) => entry.value.association == null, + ) // ignore related model fields .map((entry) { if (entry.value.type.fieldType == ModelFieldTypeEnum.embedded || entry.value.type.fieldType == ModelFieldTypeEnum.embeddedCollection) { final embeddedSchema = getModelSchemaByModelName(entry.value.type.ofCustomTypeName!, null); final embeddedSelectionSet = _getSelectionSetFromModelSchema( - embeddedSchema, GraphQLRequestOperation.get); + embeddedSchema, + GraphQLRequestOperation.get, + ); return '${entry.key} { $embeddedSelectionSet }'; } return entry.key; @@ -68,17 +73,18 @@ class GraphQLRequestFactory { // If belongsTo, also add selection set of parent. final belongsToAssociation = getBelongsToFieldFromModelSchema(schema); - String? belongsToModelName = belongsToAssociation?.type.ofModelName; + final belongsToModelName = belongsToAssociation?.type.ofModelName; if (belongsToModelName != null && !ignoreParents) { final parentSchema = getModelSchemaByModelName(belongsToModelName, null); - String parentSelectionSet = _getSelectionSetFromModelSchema( - parentSchema, GraphQLRequestOperation.get, - ignoreParents: - true); // always format like a get, stop traversing parents + final parentSelectionSet = _getSelectionSetFromModelSchema( + parentSchema, + GraphQLRequestOperation.get, + ignoreParents: true, + ); // always format like a get, stop traversing parents fields.add('${belongsToAssociation!.name} { $parentSelectionSet }'); } - String fieldSelection = fields.join(' '); // e.g. "id name createdAt" + final fieldSelection = fields.join(' '); // e.g. "id name createdAt" if (operation == GraphQLRequestOperation.list) { return '$items { $fieldSelection } nextToken'; @@ -93,10 +99,12 @@ class GraphQLRequestFactory { s[0].toLowerCase() + s.substring(1); DocumentInputs _buildDocumentInputs( - ModelSchema schema, GraphQLRequestOperation operation) { - String upperOutput = ''; - String lowerOutput = ''; - String modelName = schema.name; + ModelSchema schema, + GraphQLRequestOperation operation, + ) { + var upperOutput = ''; + var lowerOutput = ''; + final modelName = schema.name; // build inputs based on request operation switch (operation) { @@ -113,7 +121,7 @@ class GraphQLRequestFactory { case GraphQLRequestOperation.create: case GraphQLRequestOperation.update: case GraphQLRequestOperation.delete: - String operationValue = _capitalize(describeEnum(operation)); + final operationValue = _capitalize(describeEnum(operation)); upperOutput = '(\$input: $operationValue${modelName}Input!, \$condition: Model${modelName}ConditionInput)'; @@ -136,45 +144,49 @@ class GraphQLRequestFactory { /// Example: /// query getBlog($id: ID!, $content: String) { getBlog(id: $id, content: $content) { id name createdAt } } - GraphQLRequest buildRequest( - {required ModelType modelType, - Model? model, - required GraphQLRequestType requestType, - required GraphQLRequestOperation requestOperation, - required Map variables, - int depth = 0}) { + GraphQLRequest buildRequest({ + required ModelType modelType, + Model? model, + required GraphQLRequestType requestType, + required GraphQLRequestOperation requestOperation, + required Map variables, + int depth = 0, + }) { // retrieve schema from ModelType and validate required properties - ModelSchema schema = + final schema = getModelSchemaByModelName(modelType.modelName(), requestOperation); // e.g. "Blog" or "Blogs" - String name = _getName(schema, requestOperation); + final name = _getName(schema, requestOperation); // e.g. "query" or "mutation" - String requestTypeVal = describeEnum(requestType); + final requestTypeVal = describeEnum(requestType); // e.g. "get" or "list" - String requestOperationVal = describeEnum(requestOperation); + final requestOperationVal = describeEnum(requestOperation); // e.g. "{upper: "($id: ID!)", lower: "(id: $id)"}" - DocumentInputs documentInputs = - _buildDocumentInputs(schema, requestOperation); + final documentInputs = _buildDocumentInputs(schema, requestOperation); // e.g. "id name createdAt" - fields to retrieve - String fields = _getSelectionSetFromModelSchema(schema, requestOperation); + final fields = _getSelectionSetFromModelSchema(schema, requestOperation); // e.g. "getBlog" - String requestName = '$requestOperationVal$name'; + final requestName = '$requestOperationVal$name'; // e.g. query getBlog($id: ID!, $content: String) { getBlog(id: $id, content: $content) { id name createdAt } } - String document = + final document = '''$requestTypeVal $requestName${documentInputs.upper} { $requestName${documentInputs.lower} { $fields } }'''; // e.g "listBlogs" - String decodePath = requestName; + final decodePath = requestName; return GraphQLRequest( - document: document, - variables: variables, - modelType: modelType, - decodePath: decodePath); + document: document, + variables: variables, + modelType: modelType, + decodePath: decodePath, + ); } - Map buildVariablesForListRequest( - {int? limit, String? nextToken, Map? filter}) { + Map buildVariablesForListRequest({ + int? limit, + String? nextToken, + Map? filter, + }) { return { 'filter': filter, 'limit': limit, @@ -182,8 +194,10 @@ class GraphQLRequestFactory { }; } - Map buildVariablesForMutationRequest( - {required Map input, Map? condition}) { + Map buildVariablesForMutationRequest({ + required Map input, + Map? condition, + }) { return { 'input': input, 'condition': condition, @@ -196,17 +210,19 @@ class GraphQLRequestFactory { /// `{'name': {'eq': 'foo'}}`. In the case of a mutation, it will apply to /// the "condition" field rather than "filter." Map? queryPredicateToGraphQLFilter( - QueryPredicate? queryPredicate, ModelType modelType) { + QueryPredicate? queryPredicate, + ModelType modelType, + ) { if (queryPredicate == null) { return null; } - ModelSchema schema = getModelSchemaByModelName(modelType.modelName(), null); + final schema = getModelSchemaByModelName(modelType.modelName(), null); // e.g. { 'name': { 'eq': 'foo }} if (queryPredicate is QueryPredicateOperation) { final associatedTargetName = schema.fields?[queryPredicate.field]?.association?.targetName; - String fieldName = queryPredicate.field; + var fieldName = queryPredicate.field; if (queryPredicate.field == '${_lowerCaseFirstCharacter(schema.name)}.$idFieldName') { // check for the IDs where fieldName set to e.g. "blog.id" and convert to "id" @@ -218,7 +234,8 @@ class GraphQLRequestFactory { return { fieldName: _queryFieldOperatorToPartialGraphQLFilter( - queryPredicate.queryFieldOperator) + queryPredicate.queryFieldOperator, + ) }; } @@ -233,7 +250,9 @@ class GraphQLRequestFactory { if (queryPredicate.predicates.length == 1) { return { typeExpression: queryPredicateToGraphQLFilter( - queryPredicate.predicates[0], modelType) + queryPredicate.predicates[0], + modelType, + ) }; } // Public not() API only allows 1 condition but QueryPredicateGroup @@ -246,14 +265,17 @@ class GraphQLRequestFactory { // and, or return >>{ typeExpression: queryPredicate.predicates - .map((predicate) => - queryPredicateToGraphQLFilter(predicate, modelType)!) + .map( + (predicate) => + queryPredicateToGraphQLFilter(predicate, modelType)!, + ) .toList() }; } throw ApiException( - 'Unable to translate the QueryPredicate $queryPredicate to a GraphQL filter.'); + 'Unable to translate the QueryPredicate $queryPredicate to a GraphQL filter.', + ); } /// Calls `toJson` on the model and removes key/value pairs that refer to @@ -264,7 +286,7 @@ class GraphQLRequestFactory { /// When the model has a parent via a belongsTo, the id from the parent is added /// as a field similar to "blogID" where the value is `post.blog.id`. Map buildInputVariableForMutations(Model model) { - ModelSchema schema = + final schema = getModelSchemaByModelName(model.getInstanceType().modelName(), null); final modelJson = model.toJson(); @@ -281,9 +303,10 @@ class GraphQLRequestFactory { } // Remove any relational fields or readonly. - Set fieldsToRemove = schema.fields!.entries - .where((entry) => - entry.value.association != null || entry.value.isReadOnly) + final fieldsToRemove = schema.fields!.entries + .where( + (entry) => entry.value.association != null || entry.value.isReadOnly, + ) .map((entry) => entry.key) .toSet(); modelJson.removeWhere((key, dynamic value) => fieldsToRemove.contains(key)); @@ -301,7 +324,8 @@ class GraphQLRequestFactory { /// e.g. `.eq('foo')` => `{ 'eq': 'foo' }` Map _queryFieldOperatorToPartialGraphQLFilter( - QueryFieldOperator queryFieldOperator) { + QueryFieldOperator queryFieldOperator, +) { final filterExpression = _getGraphQLFilterExpression(queryFieldOperator.type); if (queryFieldOperator is QueryFieldOperatorSingleValue) { return { @@ -318,7 +342,8 @@ Map _queryFieldOperatorToPartialGraphQLFilter( } throw ApiException( - 'Unable to translate the QueryFieldOperator ${queryFieldOperator.type} to a GraphQL filter.'); + 'Unable to translate the QueryFieldOperator ${queryFieldOperator.type} to a GraphQL filter.', + ); } String _getGraphQLFilterExpression(QueryFieldOperatorType operatorType) { @@ -336,7 +361,8 @@ String _getGraphQLFilterExpression(QueryFieldOperatorType operatorType) { final result = dictionary[operatorType]; if (result == null) { throw ApiException( - '$operatorType does not have a defined GraphQL filter string.'); + '$operatorType does not have a defined GraphQL filter string.', + ); } return result; } diff --git a/packages/api/amplify_api/lib/src/graphql/graphql_response_decoder.dart b/packages/api/amplify_api/lib/src/graphql/graphql_response_decoder.dart index 168af30042..3ea772945d 100644 --- a/packages/api/amplify_api/lib/src/graphql/graphql_response_decoder.dart +++ b/packages/api/amplify_api/lib/src/graphql/graphql_response_decoder.dart @@ -17,8 +17,8 @@ import 'dart:convert'; +import 'package:amplify_api/src/graphql/utils.dart'; import 'package:amplify_core/amplify_core.dart'; -import 'utils.dart'; const _nextToken = 'nextToken'; @@ -32,7 +32,7 @@ class GraphQLResponseDecoder { static GraphQLResponseDecoder get instance => _instance; GraphQLResponse decode({ - required GraphQLRequest request, + required GraphQLRequest request, required Map response, }) { final errors = _deserializeGraphQLResponseErrors(response); @@ -68,7 +68,7 @@ class GraphQLResponseDecoder { // nested in a small JSON object in the `decodePath`. Its structure varies by // platform when null. Unpack the JSON object and null check the result along // the way. If null at any point, return null response. - Map? dataJson = data as Map?; + var dataJson = data as Map?; if (dataJson == null) { return GraphQLResponse(data: null, errors: errors); } @@ -89,14 +89,16 @@ class GraphQLResponseDecoder { // Found a JSON object to represent the model, parse it using model's fromJSON. T decodedData; final modelSchema = getModelSchemaByModelName(modelType.modelName(), null); - dataJson = transformAppSyncJsonToModelJson(dataJson!, modelSchema, - isPaginated: modelType is PaginatedModelType); + dataJson = transformAppSyncJsonToModelJson( + dataJson!, + modelSchema, + isPaginated: modelType is PaginatedModelType, + ); if (modelType is PaginatedModelType) { - Map? filter = - request.variables['filter'] as Map?; - int? limit = request.variables['limit'] as int?; + final filter = request.variables['filter'] as Map?; + final limit = request.variables['limit'] as int?; - String? resultNextToken = dataJson![_nextToken] as String?; + final resultNextToken = dataJson![_nextToken] as String?; dynamic requestForNextResult; // If result has nextToken property, prepare a request for the next page of results. if (resultNextToken != null) { @@ -105,10 +107,11 @@ class GraphQLResponseDecoder { _nextToken: resultNextToken }; requestForNextResult = GraphQLRequest( - document: request.document, - decodePath: request.decodePath, - variables: variablesWithNextToken, - modelType: request.modelType); + document: request.document, + decodePath: request.decodePath, + variables: variablesWithNextToken, + modelType: request.modelType, + ); } decodedData = modelType.fromJson( dataJson!, @@ -132,9 +135,7 @@ List? _deserializeGraphQLResponseErrors( return null; } return errors - .cast() - .map((message) => GraphQLResponseError.fromJson( - message.cast(), - )) + .cast>() + .map(GraphQLResponseError.fromJson) .toList(); } diff --git a/packages/api/amplify_api/lib/src/graphql/graphql_subscription_event.dart b/packages/api/amplify_api/lib/src/graphql/graphql_subscription_event.dart deleted file mode 100644 index 931857ab14..0000000000 --- a/packages/api/amplify_api/lib/src/graphql/graphql_subscription_event.dart +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2021 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_api/amplify_api.dart'; -import 'package:amplify_core/amplify_core.dart'; - -/// GraphQL subscription event types. -enum GraphQLSubscriptionEventType { - /// Triggered when data is received from a GraphQL subscription. - data, - - /// Triggered when a GraphQL subscription completes either due to cancellation - /// or a platform error. - done, - - /// Triggered when a platform error occurs for a subscription. - /// - /// Currently only valid in Dart. - error, -} - -/// Helper functions for [GraphQLSubscriptionEventType]. -extension GraphQLSubscriptionEventTypeX on GraphQLSubscriptionEventType { - /// Converts a platform string to a [GraphQLSubscriptionEventType]. - static GraphQLSubscriptionEventType fromString(String type) { - switch (type) { - case 'DATA': - return GraphQLSubscriptionEventType.data; - case 'DONE': - return GraphQLSubscriptionEventType.done; - default: - throw UnsupportedError('Unsupported type: $type'); - } - } -} - -/// {@template graphql_subscription_event} -/// An event which occurs on a GraphQL subscription. -/// {@endtemplate} -class GraphQLSubscriptionEvent { - /// The ID of the subscription. - final String subscriptionId; - - /// The type of event. - final GraphQLSubscriptionEventType type; - - /// The GraphQL response, if a data event. - final GraphQLResponse? rawResponse; - - /// Platform error, if an error event. - final ApiException? error; - - /// {@macro graphql_subscription_event} - const GraphQLSubscriptionEvent({ - required this.subscriptionId, - required this.type, - this.rawResponse, - this.error, - }); - - /// Deserializes a platform channel event map. - static GraphQLSubscriptionEvent fromJson(Map json) { - GraphQLResponse? rawResponse; - final payload = json['payload'] as Map?; - if (payload != null) { - rawResponse = GraphQLResponse.raw( - data: payload['data'] as String?, - errors: (payload['errors'] as List?) - ?.cast() - .map((error) => GraphQLResponseError.fromJson( - error.cast(), - )) - .toList(), - ); - } - return GraphQLSubscriptionEvent( - subscriptionId: json['id'] as String, - type: GraphQLSubscriptionEventTypeX.fromString(json['type'] as String), - rawResponse: rawResponse, - ); - } -} diff --git a/packages/api/amplify_api/lib/model_mutations.dart b/packages/api/amplify_api/lib/src/graphql/model_mutations.dart similarity index 89% rename from packages/api/amplify_api/lib/model_mutations.dart rename to packages/api/amplify_api/lib/src/graphql/model_mutations.dart index dfbb6eebba..869f5321b4 100644 --- a/packages/api/amplify_api/lib/model_mutations.dart +++ b/packages/api/amplify_api/lib/src/graphql/model_mutations.dart @@ -39,8 +39,10 @@ class ModelMutations { /// ``` /// /// An optional `where` parameter can be supplied as a condition for the deletion to be evaluated on the server. - static GraphQLRequest delete(T model, - {QueryPredicate? where}) { + static GraphQLRequest delete( + T model, { + QueryPredicate? where, + }) { return ModelMutationsFactory.instance.delete(model, where: where); } @@ -52,8 +54,10 @@ class ModelMutations { /// /// An optional `where` parameter can be supplied as a condition for the deletion to be evaluated on the server. static GraphQLRequest deleteById( - ModelType modelType, String id, - {QueryPredicate? where}) { + ModelType modelType, + String id, { + QueryPredicate? where, + }) { return ModelMutationsFactory.instance .deleteById(modelType, id, where: where); } @@ -66,8 +70,10 @@ class ModelMutations { /// ``` /// /// An optional `where` parameter can be supplied as a condition for the update to be evaluated on the server. - static GraphQLRequest update(T model, - {QueryPredicate? where}) { + static GraphQLRequest update( + T model, { + QueryPredicate? where, + }) { return ModelMutationsFactory.instance.update(model, where: where); } } diff --git a/packages/api/amplify_api/lib/src/graphql/model_mutations_factory.dart b/packages/api/amplify_api/lib/src/graphql/model_mutations_factory.dart index 1793cbee49..13d6f7cfd6 100644 --- a/packages/api/amplify_api/lib/src/graphql/model_mutations_factory.dart +++ b/packages/api/amplify_api/lib/src/graphql/model_mutations_factory.dart @@ -34,23 +34,29 @@ class ModelMutationsFactory extends ModelMutationsInterface { final variables = {'input': input}; return GraphQLRequestFactory.instance.buildRequest( - model: model, - variables: variables, - modelType: model.getInstanceType(), - requestType: GraphQLRequestType.mutation, - requestOperation: GraphQLRequestOperation.create); + model: model, + variables: variables, + modelType: model.getInstanceType(), + requestType: GraphQLRequestType.mutation, + requestOperation: GraphQLRequestOperation.create, + ); } @override GraphQLRequest delete(T model, {QueryPredicate? where}) { - return deleteById(model.getInstanceType() as ModelType, model.getId(), - where: where); + return deleteById( + model.getInstanceType() as ModelType, + model.getId(), + where: where, + ); } @override GraphQLRequest deleteById( - ModelType modelType, String id, - {QueryPredicate? where}) { + ModelType modelType, + String id, { + QueryPredicate? where, + }) { final condition = GraphQLRequestFactory.instance .queryPredicateToGraphQLFilter(where, modelType); final input = { @@ -60,10 +66,11 @@ class ModelMutationsFactory extends ModelMutationsInterface { .buildVariablesForMutationRequest(input: input, condition: condition); return GraphQLRequestFactory.instance.buildRequest( - variables: variables, - modelType: modelType, - requestType: GraphQLRequestType.mutation, - requestOperation: GraphQLRequestOperation.delete); + variables: variables, + modelType: modelType, + requestType: GraphQLRequestType.mutation, + requestOperation: GraphQLRequestOperation.delete, + ); } @override @@ -77,10 +84,11 @@ class ModelMutationsFactory extends ModelMutationsInterface { .buildVariablesForMutationRequest(input: input, condition: condition); return GraphQLRequestFactory.instance.buildRequest( - model: model, - variables: variables, - modelType: model.getInstanceType(), - requestType: GraphQLRequestType.mutation, - requestOperation: GraphQLRequestOperation.update); + model: model, + variables: variables, + modelType: model.getInstanceType(), + requestType: GraphQLRequestType.mutation, + requestOperation: GraphQLRequestOperation.update, + ); } } diff --git a/packages/api/amplify_api/lib/model_queries.dart b/packages/api/amplify_api/lib/src/graphql/model_queries.dart similarity index 97% rename from packages/api/amplify_api/lib/model_queries.dart rename to packages/api/amplify_api/lib/src/graphql/model_queries.dart index 2998bffee2..fe878e8d0c 100644 --- a/packages/api/amplify_api/lib/model_queries.dart +++ b/packages/api/amplify_api/lib/src/graphql/model_queries.dart @@ -27,7 +27,9 @@ class ModelQueries { /// final request = ModelQueries.get(Todo.classType, 'some-todo-id-123'); /// ``` static GraphQLRequest get( - ModelType modelType, String id) { + ModelType modelType, + String id, + ) { return ModelQueriesFactory.instance.get(modelType, id); } diff --git a/packages/api/amplify_api/lib/src/graphql/model_queries_factory.dart b/packages/api/amplify_api/lib/src/graphql/model_queries_factory.dart index 06b753a4f1..a72d9c5c5d 100644 --- a/packages/api/amplify_api/lib/src/graphql/model_queries_factory.dart +++ b/packages/api/amplify_api/lib/src/graphql/model_queries_factory.dart @@ -29,12 +29,13 @@ class ModelQueriesFactory extends ModelQueriesInterface { @override GraphQLRequest get(ModelType modelType, String id) { - Map variables = {idFieldName: id}; + final variables = {idFieldName: id}; return GraphQLRequestFactory.instance.buildRequest( - modelType: modelType, - variables: variables, - requestType: GraphQLRequestType.query, - requestOperation: GraphQLRequestOperation.get); + modelType: modelType, + variables: variables, + requestType: GraphQLRequestType.query, + requestOperation: GraphQLRequestOperation.get, + ); } @override @@ -49,9 +50,10 @@ class ModelQueriesFactory extends ModelQueriesInterface { .buildVariablesForListRequest(limit: limit, filter: filter); return GraphQLRequestFactory.instance.buildRequest>( - modelType: PaginatedModelType(modelType), - variables: variables, - requestType: GraphQLRequestType.query, - requestOperation: GraphQLRequestOperation.list); + modelType: PaginatedModelType(modelType), + variables: variables, + requestType: GraphQLRequestType.query, + requestOperation: GraphQLRequestOperation.list, + ); } } diff --git a/packages/api/amplify_api/lib/model_subscriptions.dart b/packages/api/amplify_api/lib/src/graphql/model_subscriptions.dart similarity index 100% rename from packages/api/amplify_api/lib/model_subscriptions.dart rename to packages/api/amplify_api/lib/src/graphql/model_subscriptions.dart diff --git a/packages/api/amplify_api/lib/src/graphql/model_subscriptions_factory.dart b/packages/api/amplify_api/lib/src/graphql/model_subscriptions_factory.dart index 4fb2c765ee..a9578baf62 100644 --- a/packages/api/amplify_api/lib/src/graphql/model_subscriptions_factory.dart +++ b/packages/api/amplify_api/lib/src/graphql/model_subscriptions_factory.dart @@ -31,27 +31,30 @@ class ModelSubscriptionsFactory extends ModelSubscriptionsInterface { @override GraphQLRequest onCreate(ModelType modelType) { return GraphQLRequestFactory.instance.buildRequest( - modelType: modelType, - variables: {}, - requestType: GraphQLRequestType.subscription, - requestOperation: GraphQLRequestOperation.onCreate); + modelType: modelType, + variables: {}, + requestType: GraphQLRequestType.subscription, + requestOperation: GraphQLRequestOperation.onCreate, + ); } @override GraphQLRequest onUpdate(ModelType modelType) { return GraphQLRequestFactory.instance.buildRequest( - modelType: modelType, - variables: {}, - requestType: GraphQLRequestType.subscription, - requestOperation: GraphQLRequestOperation.onUpdate); + modelType: modelType, + variables: {}, + requestType: GraphQLRequestType.subscription, + requestOperation: GraphQLRequestOperation.onUpdate, + ); } @override GraphQLRequest onDelete(ModelType modelType) { return GraphQLRequestFactory.instance.buildRequest( - modelType: modelType, - variables: {}, - requestType: GraphQLRequestType.subscription, - requestOperation: GraphQLRequestOperation.onDelete); + modelType: modelType, + variables: {}, + requestType: GraphQLRequestType.subscription, + requestOperation: GraphQLRequestOperation.onDelete, + ); } } diff --git a/packages/api/amplify_api/lib/src/oidc_function_api_auth_provider.dart b/packages/api/amplify_api/lib/src/graphql/oidc_function_api_auth_provider.dart similarity index 99% rename from packages/api/amplify_api/lib/src/oidc_function_api_auth_provider.dart rename to packages/api/amplify_api/lib/src/graphql/oidc_function_api_auth_provider.dart index 2d31eb5c42..151d84fe37 100644 --- a/packages/api/amplify_api/lib/src/oidc_function_api_auth_provider.dart +++ b/packages/api/amplify_api/lib/src/graphql/oidc_function_api_auth_provider.dart @@ -21,12 +21,11 @@ import 'package:meta/meta.dart'; /// user-defined authProvider passed when instantiating the API plugin. @internal class OidcFunctionAuthProvider extends TokenAmplifyAuthProvider { - // ignore: public_member_api_docs - final APIAuthProvider authProvider; - /// [APIAuthProvider] will simply pass its `getLatestAuthToken` value to "Authorization" /// header of authorized HTTP request. OidcFunctionAuthProvider(this.authProvider); + // ignore: public_member_api_docs + final APIAuthProvider authProvider; @override Future getLatestAuthToken() async { diff --git a/packages/api/amplify_api/lib/src/graphql/send_graphql_request.dart b/packages/api/amplify_api/lib/src/graphql/send_graphql_request.dart index ebef48df61..0b7cfa7f77 100644 --- a/packages/api/amplify_api/lib/src/graphql/send_graphql_request.dart +++ b/packages/api/amplify_api/lib/src/graphql/send_graphql_request.dart @@ -27,11 +27,13 @@ GraphQLOperation sendGraphQLRequest({ required Uri uri, }) { final body = {'variables': request.variables, 'query': request.document}; - final graphQLOperation = client.send(AWSStreamedHttpRequest.post( - uri, - body: HttpPayload.json(body), - headers: request.headers, - )); + final graphQLOperation = client.send( + AWSStreamedHttpRequest.post( + uri, + body: HttpPayload.json(body), + headers: request.headers, + ), + ); return GraphQLOperation( graphQLOperation.operation.then( diff --git a/packages/api/amplify_api/lib/src/graphql/utils.dart b/packages/api/amplify_api/lib/src/graphql/utils.dart index 9a771ad294..57e711af95 100644 --- a/packages/api/amplify_api/lib/src/graphql/utils.dart +++ b/packages/api/amplify_api/lib/src/graphql/utils.dart @@ -22,20 +22,24 @@ const _serializedData = 'serializedData'; const items = 'items'; class _RelatedFields { + _RelatedFields(this.singleFields, this.hasManyFields); + final Iterable singleFields; final Iterable hasManyFields; - - _RelatedFields(this.singleFields, this.hasManyFields); } _RelatedFields _getRelatedFieldsUncached(ModelSchema modelSchema) { - final singleFields = modelSchema.fields!.values.where((field) => - field.association?.associationType == ModelAssociationEnum.HasOne || - field.association?.associationType == ModelAssociationEnum.BelongsTo || - field.type.fieldType == ModelFieldTypeEnum.embedded || - field.type.fieldType == ModelFieldTypeEnum.embeddedCollection); - final hasManyFields = modelSchema.fields!.values.where((field) => - field.association?.associationType == ModelAssociationEnum.HasMany); + final singleFields = modelSchema.fields!.values.where( + (field) => + field.association?.associationType == ModelAssociationEnum.HasOne || + field.association?.associationType == ModelAssociationEnum.BelongsTo || + field.type.fieldType == ModelFieldTypeEnum.embedded || + field.type.fieldType == ModelFieldTypeEnum.embeddedCollection, + ); + final hasManyFields = modelSchema.fields!.values.where( + (field) => + field.association?.associationType == ModelAssociationEnum.HasMany, + ); return _RelatedFields(singleFields, hasManyFields); } @@ -51,14 +55,20 @@ _RelatedFields _getRelatedFields(ModelSchema modelSchema) { return _fieldsMemo[modelSchema]!; } +// ignore: public_member_api_docs ModelField? getBelongsToFieldFromModelSchema(ModelSchema modelSchema) { - return _getRelatedFields(modelSchema).singleFields.firstWhereOrNull((entry) => - entry.association?.associationType == ModelAssociationEnum.BelongsTo); + return _getRelatedFields(modelSchema).singleFields.firstWhereOrNull( + (entry) => + entry.association?.associationType == + ModelAssociationEnum.BelongsTo, + ); } /// Gets the modelSchema from provider that matches the name and validates its fields. ModelSchema getModelSchemaByModelName( - String modelName, GraphQLRequestOperation? operation) { + String modelName, + GraphQLRequestOperation? operation, +) { // ignore: invalid_use_of_protected_member final provider = Amplify.API.defaultPlugin.modelProvider; if (provider == null) { @@ -68,14 +78,15 @@ ModelSchema getModelSchemaByModelName( 'Pass in a modelProvider instance while instantiating APIPlugin', ); } - final schema = (provider.modelSchemas + provider.customTypeSchemas) - .firstWhere((elem) => elem.name == modelName, - orElse: () => throw ApiException( - 'No schema found for the ModelType provided: $modelName', - recoverySuggestion: - 'Pass in a valid modelProvider instance while ' - 'instantiating APIPlugin or provide a valid ModelType', - )); + final schema = + (provider.modelSchemas + provider.customTypeSchemas).firstWhere( + (elem) => elem.name == modelName, + orElse: () => throw ApiException( + 'No schema found for the ModelType provided: $modelName', + recoverySuggestion: 'Pass in a valid modelProvider instance while ' + 'instantiating APIPlugin or provide a valid ModelType', + ), + ); if (schema.fields == null) { throw const ApiException( @@ -100,17 +111,23 @@ ModelSchema getModelSchemaByModelName( /// 1) Look for a parent in the schema. If that parent exists in the JSON, transform it. /// 2) Look for list of children under [fieldName]["items"] and hoist up so no more ["items"]. Map transformAppSyncJsonToModelJson( - Map input, ModelSchema modelSchema, - {bool isPaginated = false}) { + Map input, + ModelSchema modelSchema, { + bool isPaginated = false, +}) { input = {...input}; // avoid mutating original - // check for list at top-level and tranform each entry + // check for list at top-level and transform each entry if (isPaginated && input[items] is List) { final transformedItems = (input[items] as List) - .map((dynamic e) => e != null - ? transformAppSyncJsonToModelJson( - e as Map, modelSchema) - : null) + .map( + (dynamic e) => e != null + ? transformAppSyncJsonToModelJson( + e as Map, + modelSchema, + ) + : null, + ) .toList(); input.update(items, (dynamic value) => transformedItems); return input; @@ -119,43 +136,52 @@ Map transformAppSyncJsonToModelJson( final relatedFields = _getRelatedFields(modelSchema); // transform parents/hasOne recursively - for (var parentField in relatedFields.singleFields) { + for (final parentField in relatedFields.singleFields) { final ofModelName = parentField.type.ofModelName ?? parentField.type.ofCustomTypeName; - dynamic inputValue = input[parentField.name]; + final inputValue = input[parentField.name]; if ((inputValue is Map || inputValue is List) && ofModelName != null) { final parentSchema = getModelSchemaByModelName(ofModelName, null); input.update(parentField.name, (dynamic parentData) { if (parentData is List) { // only used for embeddedCollection return parentData - .map((dynamic e) => { - _serializedData: transformAppSyncJsonToModelJson( - e as Map, parentSchema) - }) + .map( + (dynamic e) => { + _serializedData: transformAppSyncJsonToModelJson( + e as Map, + parentSchema, + ) + }, + ) .toList(); } return { _serializedData: transformAppSyncJsonToModelJson( - parentData as Map, parentSchema) + parentData as Map, + parentSchema, + ) }; }); } } // transform children recursively - for (var childField in relatedFields.hasManyFields) { + for (final childField in relatedFields.hasManyFields) { final ofModelName = childField.type.ofModelName; - dynamic inputValue = input[childField.name]; - List? inputItems = - (inputValue is Map) ? inputValue[items] as List? : null; + final inputValue = input[childField.name]; + final inputItems = (inputValue is Map) ? inputValue[items] as List? : null; if (inputItems is List && ofModelName != null) { final childSchema = getModelSchemaByModelName(ofModelName, null); final transformedItems = inputItems - .map((dynamic item) => { - _serializedData: transformAppSyncJsonToModelJson( - item as Map, childSchema) - }) + .map( + (dynamic item) => { + _serializedData: transformAppSyncJsonToModelJson( + item as Map, + childSchema, + ) + }, + ) .toList(); input.update(childField.name, (dynamic value) => transformedItems); } diff --git a/packages/api/amplify_api/lib/src/graphql/ws/web_socket_connection.dart b/packages/api/amplify_api/lib/src/graphql/ws/web_socket_connection.dart index 939aab96b8..160adf9b68 100644 --- a/packages/api/amplify_api/lib/src/graphql/ws/web_socket_connection.dart +++ b/packages/api/amplify_api/lib/src/graphql/ws/web_socket_connection.dart @@ -16,15 +16,14 @@ import 'dart:async'; import 'dart:convert'; import 'package:amplify_api/src/decorators/web_socket_auth_utils.dart'; +import 'package:amplify_api/src/graphql/ws/web_socket_message_stream_transformer.dart'; +import 'package:amplify_api/src/graphql/ws/web_socket_types.dart'; import 'package:amplify_core/amplify_core.dart'; import 'package:async/async.dart'; import 'package:meta/meta.dart'; import 'package:web_socket_channel/status.dart' as status; import 'package:web_socket_channel/web_socket_channel.dart'; -import 'web_socket_message_stream_transformer.dart'; -import 'web_socket_types.dart'; - /// 1001, going away const _defaultCloseStatus = status.goingAway; @@ -33,6 +32,13 @@ const _defaultCloseStatus = status.goingAway; /// {@endtemplate} @internal class WebSocketConnection implements Closeable { + /// {@macro amplify_api.ws.web_socket_connection} + WebSocketConnection( + this._config, + this._authProviderRepo, { + required AmplifyLogger logger, + }) : _logger = logger; + /// Allowed protocols for this connection. static const webSocketProtocols = ['graphql-ws']; final AmplifyLogger _logger; @@ -63,15 +69,11 @@ class WebSocketConnection implements Closeable { /// `connection_ack` message. Future get ready => _connectionReady.future; - /// {@macro amplify_api.ws.web_socket_connection} - WebSocketConnection(this._config, this._authProviderRepo, - {required AmplifyLogger logger}) - : _logger = logger; - /// Connects _subscription stream to _onData handler. @visibleForTesting StreamSubscription getStreamSubscription( - Stream stream) { + Stream stream, + ) { return stream.transform(const WebSocketMessageStreamTransformer()).listen( _onData, cancelOnError: true, @@ -138,7 +140,8 @@ class WebSocketConnection implements Closeable { } Future _sendSubscriptionRegistrationMessage( - GraphQLRequest request) async { + GraphQLRequest request, + ) async { await init(); // no-op if already connected final subscriptionRegistrationMessage = await generateSubscriptionRegistrationMessage( @@ -169,11 +172,13 @@ class WebSocketConnection implements Closeable { // stream with messages converted to GraphQLResponse. _messageStream .where((msg) => msg.id == request.id) - .transform(WebSocketSubscriptionStreamTransformer( - request, - onEstablished, - logger: _logger, - )) + .transform( + WebSocketSubscriptionStreamTransformer( + request, + onEstablished, + logger: _logger, + ), + ) .listen( controller.add, onError: controller.addError, @@ -232,8 +237,11 @@ class WebSocketConnection implements Closeable { _logger.verbose('Connection established. Registered timer'); return; case MessageType.connectionError: - _connectionError(const ApiException( - 'Error occurred while connecting to the websocket')); + _connectionError( + const ApiException( + 'Error occurred while connecting to the websocket', + ), + ); return; case MessageType.keepAlive: _timeoutTimer?.reset(); diff --git a/packages/api/amplify_api/lib/src/graphql/ws/web_socket_message_stream_transformer.dart b/packages/api/amplify_api/lib/src/graphql/ws/web_socket_message_stream_transformer.dart index 07ce5fba15..4a039ee52b 100644 --- a/packages/api/amplify_api/lib/src/graphql/ws/web_socket_message_stream_transformer.dart +++ b/packages/api/amplify_api/lib/src/graphql/ws/web_socket_message_stream_transformer.dart @@ -18,12 +18,11 @@ library amplify_api.graphql.ws.web_socket_message_stream_transformer; import 'dart:async'; import 'dart:convert'; +import 'package:amplify_api/src/graphql/graphql_response_decoder.dart'; +import 'package:amplify_api/src/graphql/ws/web_socket_types.dart'; import 'package:amplify_core/amplify_core.dart'; import 'package:meta/meta.dart'; -import '../graphql_response_decoder.dart'; -import 'web_socket_types.dart'; - /// Top-level transformer. class WebSocketMessageStreamTransformer extends StreamTransformerBase { @@ -43,15 +42,6 @@ class WebSocketMessageStreamTransformer /// of `GraphQLResponse` that is eventually passed to public API `Amplify.API.subscribe`. class WebSocketSubscriptionStreamTransformer extends StreamTransformerBase> { - /// request for this stream, needed to properly decode response events - final GraphQLRequest request; - - /// logs complete messages to better provide visibility to cancels - final AmplifyLogger logger; - - /// executes when start_ack message received - final void Function()? onEstablished; - /// [request] is used to properly decode response events /// [onEstablished] is executed when start_ack message received /// [logger] logs cancel messages when complete message received @@ -61,9 +51,18 @@ class WebSocketSubscriptionStreamTransformer required this.logger, }); + /// request for this stream, needed to properly decode response events + final GraphQLRequest request; + + /// logs complete messages to better provide visibility to cancels + final AmplifyLogger logger; + + /// executes when start_ack message received + final void Function()? onEstablished; + @override Stream> bind(Stream stream) async* { - await for (var event in stream) { + await for (final event in stream) { switch (event.messageType) { case MessageType.startAck: onEstablished?.call(); diff --git a/packages/api/amplify_api/lib/src/graphql/ws/web_socket_types.dart b/packages/api/amplify_api/lib/src/graphql/ws/web_socket_types.dart index c957b82641..97d81dc40d 100644 --- a/packages/api/amplify_api/lib/src/graphql/ws/web_socket_types.dart +++ b/packages/api/amplify_api/lib/src/graphql/ws/web_socket_types.dart @@ -54,27 +54,30 @@ enum MessageType { @JsonValue('complete') complete('complete'); - final String type; - const MessageType(this.type); factory MessageType.fromJson(dynamic json) { return MessageType.values.firstWhere((el) => json == el.type); } + + final String type; } @immutable abstract class WebSocketMessagePayload { const WebSocketMessagePayload(); - static const Map - _factories = { + static const Map)> _factories = { MessageType.connectionAck: ConnectionAckMessagePayload.fromJson, MessageType.data: SubscriptionDataPayload.fromJson, MessageType.error: WebSocketError.fromJson, }; - static WebSocketMessagePayload? fromJson(Map json, MessageType type) { + static WebSocketMessagePayload? fromJson( + Map json, + MessageType type, + ) { return _factories[type]?.call(json); } @@ -86,11 +89,10 @@ abstract class WebSocketMessagePayload { @internal class ConnectionAckMessagePayload extends WebSocketMessagePayload { - final int connectionTimeoutMs; - const ConnectionAckMessagePayload(this.connectionTimeoutMs); + final int connectionTimeoutMs; - static ConnectionAckMessagePayload fromJson(Map json) { + static ConnectionAckMessagePayload fromJson(Map json) { final connectionTimeoutMs = json['connectionTimeoutMs'] as int; return ConnectionAckMessagePayload(connectionTimeoutMs); } @@ -102,21 +104,21 @@ class ConnectionAckMessagePayload extends WebSocketMessagePayload { } class SubscriptionRegistrationPayload extends WebSocketMessagePayload { - final GraphQLRequest request; - final AWSApiConfig config; - final Map authorizationHeaders; - const SubscriptionRegistrationPayload({ required this.request, required this.config, required this.authorizationHeaders, }); + final GraphQLRequest request; + final AWSApiConfig config; + final Map authorizationHeaders; @override Map toJson() { return { 'data': jsonEncode( - {'variables': request.variables, 'query': request.document}), + {'variables': request.variables, 'query': request.document}, + ), 'extensions': >{ 'authorization': authorizationHeaders } @@ -125,12 +127,11 @@ class SubscriptionRegistrationPayload extends WebSocketMessagePayload { } class SubscriptionDataPayload extends WebSocketMessagePayload { + const SubscriptionDataPayload(this.data, this.errors); final Map? data; final Map? errors; - const SubscriptionDataPayload(this.data, this.errors); - - static SubscriptionDataPayload fromJson(Map json) { + static SubscriptionDataPayload fromJson(Map json) { final data = json['data'] as Map?; final errors = json['errors'] as Map?; return SubscriptionDataPayload( @@ -147,11 +148,10 @@ class SubscriptionDataPayload extends WebSocketMessagePayload { } class WebSocketError extends WebSocketMessagePayload implements Exception { - final List errors; - const WebSocketError(this.errors); + final List> errors; - static WebSocketError fromJson(Map json) { + static WebSocketError fromJson(Map json) { final errors = json['errors'] as List?; return WebSocketError(errors?.cast() ?? []); } @@ -164,27 +164,25 @@ class WebSocketError extends WebSocketMessagePayload implements Exception { @immutable class WebSocketMessage { - final String? id; - final MessageType messageType; - final WebSocketMessagePayload? payload; - WebSocketMessage({ String? id, required this.messageType, this.payload, }) : id = id ?? uuid(); - const WebSocketMessage._({ this.id, required this.messageType, this.payload, }); + final String? id; + final MessageType messageType; + final WebSocketMessagePayload? payload; - static WebSocketMessage fromJson(Map json) { + static WebSocketMessage fromJson(Map json) { final id = json['id'] as String?; final type = json['type'] as String; final messageType = MessageType.fromJson(type); - final payloadMap = json['payload'] as Map?; + final payloadMap = json['payload'] as Map?; final payload = payloadMap == null ? null : WebSocketMessagePayload.fromJson( diff --git a/packages/api/amplify_api/lib/src/native_api_plugin.dart b/packages/api/amplify_api/lib/src/native_api_plugin.dart index 975b5c7446..fb0a85ba23 100644 --- a/packages/api/amplify_api/lib/src/native_api_plugin.dart +++ b/packages/api/amplify_api/lib/src/native_api_plugin.dart @@ -14,7 +14,7 @@ // // Autogenerated from Pigeon (v3.2.9), do not edit directly. // See also: https://pub.dev/packages/pigeon -// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, require_trailing_commas import 'dart:async'; import 'dart:typed_data' show Uint8List, Int32List, Int64List, Float64List; diff --git a/packages/api/amplify_api/lib/src/amplify_api_config.dart b/packages/api/amplify_api/lib/src/util/amplify_api_config.dart similarity index 81% rename from packages/api/amplify_api/lib/src/amplify_api_config.dart rename to packages/api/amplify_api/lib/src/util/amplify_api_config.dart index 4d4c21e9fa..b4a2c44665 100644 --- a/packages/api/amplify_api/lib/src/amplify_api_config.dart +++ b/packages/api/amplify_api/lib/src/util/amplify_api_config.dart @@ -17,11 +17,16 @@ import 'package:meta/meta.dart'; const _slash = '/'; +/// Allows formatting the URL from the config with new paths/query params. @internal class EndpointConfig with AWSEquatable { + // ignore: public_member_api_docs const EndpointConfig(this.name, this.config); + /// The key used in the Amplify configuration file for this config entry. final String name; + + /// The value in the Amplify configuration file which as config details. final AWSApiConfig config; @override @@ -39,15 +44,20 @@ class EndpointConfig with AWSEquatable { } // Avoid URI-encoding slashes in path from caller. final pathSegmentsFromPath = path.split(_slash); - return parsed.replace(pathSegments: [ - ...parsed.pathSegments, - ...pathSegmentsFromPath, - ], queryParameters: queryParameters); + return parsed.replace( + pathSegments: [ + ...parsed.pathSegments, + ...pathSegmentsFromPath, + ], + queryParameters: queryParameters, + ); } } +/// Allows getting desired endpoint more easily. @internal extension AWSApiPluginConfigHelpers on AWSApiPluginConfig { + /// Finds the first endpoint matching the type and apiName. EndpointConfig getEndpoint({ required EndpointType type, String? apiName, diff --git a/packages/api/amplify_api/lib/src/amplify_authorization_rest_client.dart b/packages/api/amplify_api/lib/src/util/amplify_authorization_rest_client.dart similarity index 100% rename from packages/api/amplify_api/lib/src/amplify_authorization_rest_client.dart rename to packages/api/amplify_api/lib/src/util/amplify_authorization_rest_client.dart diff --git a/packages/api/amplify_api/test/amplify_api_config_test.dart b/packages/api/amplify_api/test/amplify_api_config_test.dart index 036bc634e5..cc20b11c45 100644 --- a/packages/api/amplify_api/test/amplify_api_config_test.dart +++ b/packages/api/amplify_api/test/amplify_api_config_test.dart @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import 'package:amplify_api/src/amplify_api_config.dart'; +import 'package:amplify_api/src/util/amplify_api_config.dart'; import 'package:amplify_core/amplify_core.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/packages/api/amplify_api/test/authorize_http_request_test.dart b/packages/api/amplify_api/test/authorize_http_request_test.dart index a4cfe49aa3..0bb3217536 100644 --- a/packages/api/amplify_api/test/authorize_http_request_test.dart +++ b/packages/api/amplify_api/test/authorize_http_request_test.dart @@ -13,7 +13,7 @@ import 'package:amplify_api/src/decorators/authorize_http_request.dart'; import 'package:amplify_api/src/graphql/app_sync_api_key_auth_provider.dart'; -import 'package:amplify_api/src/oidc_function_api_auth_provider.dart'; +import 'package:amplify_api/src/graphql/oidc_function_api_auth_provider.dart'; import 'package:amplify_core/amplify_core.dart'; import 'package:flutter_test/flutter_test.dart'; import 'util.dart'; @@ -74,8 +74,10 @@ void main() { endpointConfig: endpointConfig, authProviderRepo: authProviderRepo, ); - expect(authorizedRequest.headers.containsKey(AWSHeaders.authorization), - isFalse); + expect( + authorizedRequest.headers.containsKey(AWSHeaders.authorization), + isFalse, + ); expect(authorizedRequest, inputRequest); }); @@ -97,7 +99,9 @@ void main() { authProviderRepo: authProviderRepo, ); expect( - authorizedRequest.headers[AWSHeaders.authorization], testAuthValue); + authorizedRequest.headers[AWSHeaders.authorization], + testAuthValue, + ); expect(authorizedRequest, inputRequest); }); @@ -147,13 +151,14 @@ void main() { region: _region, ); final inputRequest = _generateTestRequest(endpointConfig.endpoint); - expectLater( - authorizeHttpRequest( - inputRequest, - endpointConfig: endpointConfig, - authProviderRepo: authProviderRepo, - ), - throwsA(isA())); + await expectLater( + authorizeHttpRequest( + inputRequest, + endpointConfig: endpointConfig, + authProviderRepo: authProviderRepo, + ), + throwsA(isA()), + ); }); test('authorizes with Cognito User Pools auth mode', () async { @@ -250,7 +255,7 @@ void main() { region: _region, ); final inputRequest = _generateTestRequest(endpointConfig.endpoint); - expectLater( + await expectLater( authorizeHttpRequest( inputRequest, endpointConfig: endpointConfig, diff --git a/packages/api/amplify_api/test/graphql_helpers_test.dart b/packages/api/amplify_api/test/graphql_helpers_test.dart index f9f9c6c2cf..c58547d500 100644 --- a/packages/api/amplify_api/test/graphql_helpers_test.dart +++ b/packages/api/amplify_api/test/graphql_helpers_test.dart @@ -26,10 +26,13 @@ import 'package:flutter_test/flutter_test.dart'; final _deepEquals = const DeepCollectionEquality().equals; +// Local variable types used as a type check. +// ignore_for_file: omit_local_variable_types + class MockAmplifyAPI extends AmplifyAPIDart { MockAmplifyAPI({ - ModelProviderInterface? modelProvider, - }) : super(modelProvider: modelProvider); + super.modelProvider, + }); @override void registerAuthProvider(APIAuthProvider authProvider) {} @@ -67,11 +70,12 @@ void main() { group('ModelQueries', () { test('ModelQueries.get() should build a valid request', () { - String id = UUID.getUUID(); + final String id = UUID.getUUID(); const expected = 'query getBlog(\$id: ID!) { getBlog(id: \$id) { $blogSelectionSet } }'; - GraphQLRequest req = ModelQueries.get(Blog.classType, id); + final GraphQLRequest req = + ModelQueries.get(Blog.classType, id); expect(req.document, expected); expect(_deepEquals(req.variables, {'id': id}), isTrue); @@ -82,9 +86,10 @@ void main() { test( 'ModelQueries.get() returns a GraphQLRequest when provided a modelType', () async { - String id = UUID.getUUID(); - GraphQLRequest req = ModelQueries.get(Blog.classType, id); - String data = '''{ + final String id = UUID.getUUID(); + final GraphQLRequest req = + ModelQueries.get(Blog.classType, id); + final String data = '''{ "getBlog": { "createdAt": "2021-01-01T01:00:00.000000000Z", "id": "$id", @@ -92,7 +97,7 @@ void main() { } }'''; - GraphQLResponse response = _decodeResponseData(req, data); + final GraphQLResponse response = _decodeResponseData(req, data); expect(response.data, isA()); expect(response.data?.id, id); @@ -101,7 +106,7 @@ void main() { const expected = 'query listBlogs(\$filter: ModelBlogFilterInput, \$limit: Int, \$nextToken: String) { listBlogs(filter: \$filter, limit: \$limit, nextToken: \$nextToken) { items { $blogSelectionSet } nextToken } }'; - GraphQLRequest> req = + final GraphQLRequest> req = ModelQueries.list(Blog.classType); expect(req.document, expected); @@ -114,7 +119,7 @@ void main() { const expected = 'query listBlogs(\$filter: ModelBlogFilterInput, \$limit: Int, \$nextToken: String) { listBlogs(filter: \$filter, limit: \$limit, nextToken: \$nextToken) { items { $blogSelectionSet } nextToken } }'; - GraphQLRequest> req = + final GraphQLRequest> req = ModelQueries.list(Blog.classType, limit: 1); expect(req.document, expected); @@ -126,17 +131,17 @@ void main() { test( 'ModelQueries.get() returns a GraphQLRequest when not provided a modelType', () async { - String id = UUID.getUUID(); - String doc = '''query MyQuery { + final String id = UUID.getUUID(); + const doc = '''query MyQuery { getBlog { id name createdAt } }'''; - GraphQLRequest req = + final GraphQLRequest req = GraphQLRequest(document: doc, variables: {id: id}); - String data = '''{ + final String data = '''{ "getBlog": { "createdAt": "2021-01-01T01:00:00.000000000Z", "id": "$id", @@ -144,7 +149,7 @@ void main() { } }'''; - GraphQLResponse response = _decodeResponseData(req, data); + final GraphQLResponse response = _decodeResponseData(req, data); expect(response.data, isA()); }); @@ -152,10 +157,10 @@ void main() { test( 'ModelQueries.list() returns a GraphQLRequest> when provided a modelType', () async { - GraphQLRequest> req = + final GraphQLRequest> req = ModelQueries.list(Blog.classType, limit: 2); - String data = '''{ + const data = '''{ "listBlogs": { "items": [ { @@ -173,7 +178,7 @@ void main() { } }'''; - GraphQLResponse> response = + final GraphQLResponse> response = _decodeResponseData(req, data); expect(response.data, isA>()); @@ -184,10 +189,10 @@ void main() { test( 'ModelQueries.list() should decode gracefully when there is a null in the items list', () async { - GraphQLRequest> req = + final GraphQLRequest> req = ModelQueries.list(Blog.classType, limit: 2); - String data = '''{ + const data = '''{ "listBlogs": { "items": [ { @@ -201,7 +206,7 @@ void main() { } }'''; - GraphQLResponse> response = + final GraphQLResponse> response = _decodeResponseData(req, data); expect(response.data, isA>()); @@ -214,10 +219,10 @@ void main() { 'GraphQLResponse> can get the request for next page of data', () async { const limit = 2; - GraphQLRequest> req = + final GraphQLRequest> req = ModelQueries.list(Blog.classType, limit: limit); - String data = '''{ + const data = '''{ "listBlogs": { "items": [ { @@ -235,12 +240,12 @@ void main() { } }'''; - GraphQLResponse> response = + final GraphQLResponse> response = _decodeResponseData(req, data); expect(response.data?.hasNextResult, true); const expectedDocument = 'query listBlogs(\$filter: ModelBlogFilterInput, \$limit: Int, \$nextToken: String) { listBlogs(filter: \$filter, limit: \$limit, nextToken: \$nextToken) { items { $blogSelectionSet } nextToken } }'; - final resultRequest = response.data?.requestForNextResult!; + final resultRequest = response.data?.requestForNextResult; expect(resultRequest?.document, expectedDocument); expect(resultRequest?.variables['nextToken'], response.data?.nextToken); expect(resultRequest?.variables['limit'], limit); @@ -250,10 +255,10 @@ void main() { 'GraphQLResponse> will not have data for next page when result has no nextToken', () async { const limit = 2; - GraphQLRequest> req = + final GraphQLRequest> req = ModelQueries.list(Blog.classType, limit: limit); - String data = '''{ + const data = '''{ "listBlogs": { "items": [ { @@ -270,7 +275,7 @@ void main() { } }'''; - GraphQLResponse> response = + final GraphQLResponse> response = _decodeResponseData(req, data); expect(response.data?.hasNextResult, false); }); @@ -286,7 +291,7 @@ void main() { }; final queryPredicate = Blog.NAME.eq(expectedTitle); - GraphQLRequest> req = + final GraphQLRequest> req = ModelQueries.list(Blog.classType, where: queryPredicate); expect(req.document, expectedDocument); @@ -305,11 +310,13 @@ void main() { }; final queryPredicate = Blog.NAME.eq(expectedTitle); - GraphQLRequest> req = ModelQueries.list( - Blog.classType, - limit: limit, - where: queryPredicate); - String data = '''{ + final GraphQLRequest> req = + ModelQueries.list( + Blog.classType, + limit: limit, + where: queryPredicate, + ); + const data = '''{ "listBlogs": { "items": [ { @@ -326,11 +333,11 @@ void main() { "nextToken": "super-secret-next-token" } }'''; - GraphQLResponse> response = + final GraphQLResponse> response = _decodeResponseData(req, data); - Map firstRequestFilter = + final Map firstRequestFilter = req.variables['filter'] as Map; - final resultRequest = response.data?.requestForNextResult!; + final resultRequest = response.data?.requestForNextResult; expect(resultRequest?.variables['filter'], firstRequestFilter); expect(resultRequest?.variables['filter'], expectedFilter); @@ -344,7 +351,7 @@ void main() { const time = '2021-08-03T16:39:18.000000651Z'; final createdAt = TemporalDateTime.fromString(time); - Blog blog = Blog(id: id, name: name, createdAt: createdAt); + final Blog blog = Blog(id: id, name: name, createdAt: createdAt); final expectedVars = { 'input': { 'id': id, @@ -357,7 +364,7 @@ void main() { const expectedDoc = 'mutation createBlog(\$input: CreateBlogInput!, \$condition: ModelBlogConditionInput) { createBlog(input: \$input, condition: \$condition) { $blogSelectionSet } }'; - GraphQLRequest req = ModelMutations.create(blog); + final GraphQLRequest req = ModelMutations.create(blog); expect(req.document, expectedDoc); expect(_deepEquals(req.variables, expectedVars), isTrue); @@ -372,12 +379,13 @@ void main() { const name = 'Test Blog'; const time = '2021-08-03T16:39:18.000000651Z'; final createdAt = TemporalDateTime.fromString(time); - Blog blog = Blog(id: blogId, name: name, createdAt: createdAt); + final Blog blog = Blog(id: blogId, name: name, createdAt: createdAt); final postId = UUID.getUUID(); const title = 'Lorem Ipsum'; const rating = 1; - Post post = Post(id: postId, title: title, rating: rating, blog: blog); + final Post post = + Post(id: postId, title: title, rating: rating, blog: blog); final expectedVars = { 'input': { @@ -390,7 +398,7 @@ void main() { }; const expectedDoc = 'mutation createPost(\$input: CreatePostInput!, \$condition: ModelPostConditionInput) { createPost(input: \$input, condition: \$condition) { id title rating created createdAt updatedAt blog { $blogSelectionSet } } }'; - GraphQLRequest req = ModelMutations.create(post); + final GraphQLRequest req = ModelMutations.create(post); expect(req.document, expectedDoc); expect(_deepEquals(req.variables, expectedVars), isTrue); @@ -404,9 +412,13 @@ void main() { final postId = UUID.getUUID(); const title = 'Lorem Ipsum'; const rating = 1; - Post post = Post(id: postId, title: title, rating: rating); - GraphQLRequest req = ModelMutations.create(post); - expect(req.variables['input'].containsKey('blogID'), isFalse); + final Post post = Post(id: postId, title: title, rating: rating); + final GraphQLRequest req = ModelMutations.create(post); + expect( + (req.variables['input'] as Map) + .containsKey('blogID'), + isFalse, + ); }); test('ModelMutations.delete() should build a valid request', () { @@ -415,7 +427,7 @@ void main() { const time = '2021-08-03T16:39:18.000000651Z'; final createdAt = TemporalDateTime.fromString(time); - Blog blog = Blog(id: id, name: name, createdAt: createdAt); + final Blog blog = Blog(id: id, name: name, createdAt: createdAt); final expectedVars = { 'input': {'id': id}, @@ -424,7 +436,7 @@ void main() { const expectedDoc = 'mutation deleteBlog(\$input: DeleteBlogInput!, \$condition: ModelBlogConditionInput) { deleteBlog(input: \$input, condition: \$condition) { $blogSelectionSet } }'; - GraphQLRequest req = ModelMutations.delete(blog); + final GraphQLRequest req = ModelMutations.delete(blog); expect(req.document, expectedDoc); expect(_deepEquals(req.variables, expectedVars), isTrue); @@ -442,7 +454,7 @@ void main() { const expectedDoc = 'mutation deleteBlog(\$input: DeleteBlogInput!, \$condition: ModelBlogConditionInput) { deleteBlog(input: \$input, condition: \$condition) { $blogSelectionSet } }'; - GraphQLRequest req = + final GraphQLRequest req = ModelMutations.deleteById(Blog.classType, id); expect(req.document, expectedDoc); @@ -457,7 +469,7 @@ void main() { const time = '2021-08-03T16:39:18.000000651Z'; final createdAt = TemporalDateTime.fromString(time); - Blog blog = Blog(id: id, name: name, createdAt: createdAt); + final Blog blog = Blog(id: id, name: name, createdAt: createdAt); final expectedVars = { 'input': { @@ -472,7 +484,7 @@ void main() { const expectedDoc = 'mutation updateBlog(\$input: UpdateBlogInput!, \$condition: ModelBlogConditionInput) { updateBlog(input: \$input, condition: \$condition) { $blogSelectionSet } }'; - GraphQLRequest req = ModelMutations.update(blog); + final GraphQLRequest req = ModelMutations.update(blog); expect(req.document, expectedDoc); expect(_deepEquals(req.variables, expectedVars), isTrue); @@ -487,12 +499,13 @@ void main() { const name = 'Test Blog'; const time = '2021-08-03T16:39:18.000000651Z'; final createdAt = TemporalDateTime.fromString(time); - Blog blog = Blog(id: blogId, name: name, createdAt: createdAt); + final Blog blog = Blog(id: blogId, name: name, createdAt: createdAt); final postId = UUID.getUUID(); const title = 'Lorem Ipsum'; const rating = 1; - Post post = Post(id: postId, title: title, rating: rating, blog: blog); + final Post post = + Post(id: postId, title: title, rating: rating, blog: blog); final expectedVars = { 'input': { @@ -506,7 +519,7 @@ void main() { }; const expectedDoc = 'mutation updatePost(\$input: UpdatePostInput!, \$condition: ModelPostConditionInput) { updatePost(input: \$input, condition: \$condition) { id title rating created createdAt updatedAt blog { $blogSelectionSet } } }'; - GraphQLRequest req = ModelMutations.update(post); + final GraphQLRequest req = ModelMutations.update(post); expect(req.document, expectedDoc); expect(_deepEquals(req.variables, expectedVars), isTrue); @@ -521,7 +534,7 @@ void main() { const name = 'Test Blog'; const time = '2021-08-03T16:39:18.000000651Z'; final createdAt = TemporalDateTime.fromString(time); - Blog blog = Blog(id: id, name: name, createdAt: createdAt); + final Blog blog = Blog(id: id, name: name, createdAt: createdAt); final expectedVars = { 'input': { 'id': id, @@ -537,7 +550,7 @@ void main() { const expectedDoc = 'mutation updateBlog(\$input: UpdateBlogInput!, \$condition: ModelBlogConditionInput) { updateBlog(input: \$input, condition: \$condition) { $blogSelectionSet } }'; - GraphQLRequest req = + final GraphQLRequest req = ModelMutations.update(blog, where: Blog.CREATEDAT.lt(createdAt)); expect(req.document, expectedDoc); @@ -551,7 +564,7 @@ void main() { const name = 'Test Blog'; const time = '2021-08-03T16:39:18.000000651Z'; final createdAt = TemporalDateTime.fromString(time); - Blog blog = Blog(id: id, name: name, createdAt: createdAt); + final Blog blog = Blog(id: id, name: name, createdAt: createdAt); final expectedVars = { 'input': {'id': id}, 'condition': { @@ -561,8 +574,10 @@ void main() { const expectedDoc = 'mutation deleteBlog(\$input: DeleteBlogInput!, \$condition: ModelBlogConditionInput) { deleteBlog(input: \$input, condition: \$condition) { $blogSelectionSet } }'; - GraphQLRequest req = ModelMutations.delete(blog, - where: Blog.CREATEDAT.lt(createdAt)); + final GraphQLRequest req = ModelMutations.delete( + blog, + where: Blog.CREATEDAT.lt(createdAt), + ); expect(req.document, expectedDoc); expect(_deepEquals(req.variables, expectedVars), isTrue); @@ -575,7 +590,7 @@ void main() { test('ModelSubscriptions.onCreate() should build a valid request', () { const expected = 'subscription onCreateBlog { onCreateBlog { $blogSelectionSet } }'; - GraphQLRequest req = + final GraphQLRequest req = ModelSubscriptions.onCreate(Blog.classType); expect(req.document, expected); @@ -586,7 +601,7 @@ void main() { test('ModelSubscriptions.onUpdate() should build a valid request', () { const expected = 'subscription onUpdateBlog { onUpdateBlog { $blogSelectionSet } }'; - GraphQLRequest req = + final GraphQLRequest req = ModelSubscriptions.onUpdate(Blog.classType); expect(req.document, expected); @@ -597,7 +612,7 @@ void main() { test('ModelSubscriptions.onDelete() should build a valid request', () { const expected = 'subscription onDeleteBlog { onDeleteBlog { $blogSelectionSet } }'; - GraphQLRequest req = + final GraphQLRequest req = ModelSubscriptions.onDelete(Blog.classType); expect(req.document, expected); @@ -731,8 +746,11 @@ void main() { } ] }; - final output = transformAppSyncJsonToModelJson(input, Post.schema, - isPaginated: true); + final output = transformAppSyncJsonToModelJson( + input, + Post.schema, + isPaginated: true, + ); expect(output, expectedOutput); }); @@ -761,8 +779,11 @@ void main() { null ] }; - final output = transformAppSyncJsonToModelJson(input, Post.schema, - isPaginated: true); + final output = transformAppSyncJsonToModelJson( + input, + Post.schema, + isPaginated: true, + ); expect(output, expectedOutput); }); @@ -813,8 +834,10 @@ void main() { ModelQueries.get(Blog.classType, ''); } on ApiException catch (e) { expect(e.message, 'No modelProvider found'); - expect(e.recoverySuggestion, - 'Pass in a modelProvider instance while instantiating APIPlugin'); + expect( + e.recoverySuggestion, + 'Pass in a modelProvider instance while instantiating APIPlugin', + ); return; } fail('Expected an ApiException'); diff --git a/packages/api/amplify_api/test/plugin_configuration_test.dart b/packages/api/amplify_api/test/plugin_configuration_test.dart index b77123b441..d6337152ca 100644 --- a/packages/api/amplify_api/test/plugin_configuration_test.dart +++ b/packages/api/amplify_api/test/plugin_configuration_test.dart @@ -15,7 +15,7 @@ import 'dart:convert'; import 'package:amplify_api/src/api_plugin_impl.dart'; import 'package:amplify_api/src/graphql/app_sync_api_key_auth_provider.dart'; -import 'package:amplify_api/src/oidc_function_api_auth_provider.dart'; +import 'package:amplify_api/src/graphql/oidc_function_api_auth_provider.dart'; import 'package:amplify_core/amplify_core.dart'; import 'package:aws_common/testing.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -67,9 +67,11 @@ final _mockRestClient = MockAWSHttpClient((request) async { void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final authProviderRepo = AmplifyAuthProviderRepository(); - authProviderRepo.registerAuthProvider( - APIAuthorizationType.iam.authProviderToken, TestIamAuthProvider()); + final authProviderRepo = AmplifyAuthProviderRepository() + ..registerAuthProvider( + APIAuthorizationType.iam.authProviderToken, + TestIamAuthProvider(), + ); final config = AmplifyConfig.fromJson( jsonDecode(amplifyconfig) as Map, ); @@ -146,9 +148,11 @@ void main() { () async { final plugin = AmplifyAPIDart(baseHttpClient: _mockGqlClient); await plugin.configure( - authProviderRepo: authProviderRepo, config: config); + authProviderRepo: authProviderRepo, + config: config, + ); - String graphQLDocument = '''query TestQuery { + const graphQLDocument = '''query TestQuery { listBlogs { items { id @@ -168,7 +172,9 @@ void main() { () async { final plugin = AmplifyAPIDart(baseHttpClient: _mockRestClient); await plugin.configure( - authProviderRepo: authProviderRepo, config: config); + authProviderRepo: authProviderRepo, + config: config, + ); await plugin.get('/items').response; // no assertion here because assertion implemented in mock HTTP client diff --git a/packages/api/amplify_api/test/query_predicate_graphql_filter_test.dart b/packages/api/amplify_api/test/query_predicate_graphql_filter_test.dart index 850fd5e1a4..fe996402c0 100644 --- a/packages/api/amplify_api/test/query_predicate_graphql_filter_test.dart +++ b/packages/api/amplify_api/test/query_predicate_graphql_filter_test.dart @@ -36,16 +36,18 @@ void main() { }); // helper method for all the tests - void _testQueryPredicateTranslation( - QueryPredicate? queryPredicate, Map? expectedFilter, - {ModelType modelType = Blog.classType}) { + void testQueryPredicateTranslation( + QueryPredicate? queryPredicate, + Map? expectedFilter, { + ModelType modelType = Blog.classType, + }) { final resultFilter = GraphQLRequestFactory.instance .queryPredicateToGraphQLFilter(queryPredicate, modelType); expect(resultFilter, expectedFilter); } test('should be null safe', () { - _testQueryPredicateTranslation(null, null); + testQueryPredicateTranslation(null, null); }); test('simple query predicate converts to expected filter', () { @@ -55,7 +57,7 @@ void main() { }; final queryPredicate = Blog.NAME.eq(expectedTitle); - _testQueryPredicateTranslation(queryPredicate, expectedFilter); + testQueryPredicateTranslation(queryPredicate, expectedFilter); }); test('and query with string, and date', () { @@ -72,7 +74,7 @@ void main() { }, ] }; - _testQueryPredicateTranslation(queryPredicate, expectedFilter); + testQueryPredicateTranslation(queryPredicate, expectedFilter); }); test('not query converts to expected filter', () { @@ -82,7 +84,7 @@ void main() { 'id': {'eq': 'id'} } }; - _testQueryPredicateTranslation(queryPredicate, expectedFilter); + testQueryPredicateTranslation(queryPredicate, expectedFilter); }); test( @@ -132,7 +134,7 @@ void main() { } ] }; - _testQueryPredicateTranslation(queryPredicate, expectedFilter); + testQueryPredicateTranslation(queryPredicate, expectedFilter); }); test('nested and(or()) operator converts to expected filter', () { @@ -155,7 +157,7 @@ void main() { } ] }; - _testQueryPredicateTranslation(queryPredicate, expectedFilter); + testQueryPredicateTranslation(queryPredicate, expectedFilter); }); test('nested or(and()) operator converts to expected filter', () { @@ -178,7 +180,7 @@ void main() { }, ] }; - _testQueryPredicateTranslation(queryPredicate, expectedFilter); + testQueryPredicateTranslation(queryPredicate, expectedFilter); }); test('TemporalDateTime query converts to expected filter', () { @@ -189,7 +191,7 @@ void main() { }; final queryPredicate = Blog.CREATEDAT.le(exampleValue); - _testQueryPredicateTranslation(queryPredicate, expectedFilter); + testQueryPredicateTranslation(queryPredicate, expectedFilter); }); test('TemporalDate query converts to expected filter', () { @@ -200,7 +202,7 @@ void main() { }; final queryPredicate = Blog.CREATEDAT.le(exampleValue); - _testQueryPredicateTranslation(queryPredicate, expectedFilter); + testQueryPredicateTranslation(queryPredicate, expectedFilter); }); test('TemporalTime query converts to expected filter', () { @@ -211,7 +213,7 @@ void main() { }; final queryPredicate = Blog.CREATEDAT.le(exampleValue); - _testQueryPredicateTranslation(queryPredicate, expectedFilter); + testQueryPredicateTranslation(queryPredicate, expectedFilter); }); test('DateTime converted to TemporalDateTime query', () { @@ -222,7 +224,7 @@ void main() { }; final queryPredicate = Blog.CREATEDAT.le(exampleValue); - _testQueryPredicateTranslation(queryPredicate, expectedFilter); + testQueryPredicateTranslation(queryPredicate, expectedFilter); }); test('query child by parent ID', () { @@ -232,8 +234,11 @@ void main() { 'blogID': {'eq': blogId} }; - _testQueryPredicateTranslation(queryPredicate, expectedFilter, - modelType: Post.classType); + testQueryPredicateTranslation( + queryPredicate, + expectedFilter, + modelType: Post.classType, + ); }); test('query with enum should serialize to string', () { @@ -244,8 +249,11 @@ void main() { 'title': {'eq': describeEnum(Size.medium)} }; - _testQueryPredicateTranslation(queryPredicate, expectedFilter, - modelType: Post.classType); + testQueryPredicateTranslation( + queryPredicate, + expectedFilter, + modelType: Post.classType, + ); }); }); } diff --git a/packages/api/amplify_api/test/util.dart b/packages/api/amplify_api/test/util.dart index 3b2af39293..009b4e5ec4 100644 --- a/packages/api/amplify_api/test/util.dart +++ b/packages/api/amplify_api/test/util.dart @@ -29,7 +29,9 @@ class TestIamAuthProvider extends AWSIamAmplifyAuthProvider { @override Future retrieve() async { return const AWSCredentials( - 'fake-access-key-123', 'fake-secret-access-key-456'); + 'fake-access-key-123', + 'fake-secret-access-key-456', + ); } @override @@ -79,11 +81,11 @@ const expectedApiKeyWebSocketConnectionUrl = 'wss://abc123.appsync-realtime-api.us-east-1.amazonaws.com/graphql?header=eyJBY2NlcHQiOiJhcHBsaWNhdGlvbi9qc29uLCB0ZXh0L2phdmFzY3JpcHQiLCJDb250ZW50LUVuY29kaW5nIjoiYW16LTEuMCIsIkNvbnRlbnQtVHlwZSI6ImFwcGxpY2F0aW9uL2pzb247IGNoYXJzZXQ9dXRmLTgiLCJYLUFwaS1LZXkiOiJhYmMtMTIzIiwiSG9zdCI6ImFiYzEyMy5hcHBzeW5jLWFwaS51cy1lYXN0LTEuYW1hem9uYXdzLmNvbSJ9&payload=e30%3D'; AmplifyAuthProviderRepository getTestAuthProviderRepo() { - final testAuthProviderRepo = AmplifyAuthProviderRepository(); - testAuthProviderRepo.registerAuthProvider( - APIAuthorizationType.apiKey.authProviderToken, - AppSyncApiKeyAuthProvider(), - ); + final testAuthProviderRepo = AmplifyAuthProviderRepository() + ..registerAuthProvider( + APIAuthorizationType.apiKey.authProviderToken, + AppSyncApiKeyAuthProvider(), + ); return testAuthProviderRepo; } @@ -111,6 +113,9 @@ const mockSubscriptionData = { /// Extension of [WebSocketConnection] that stores messages internally instead /// of sending them. class MockWebSocketConnection extends WebSocketConnection { + MockWebSocketConnection(super.config, super.authProviderRepo) + : super(logger: AmplifyLogger()); + /// Instead of actually connecting, just set the URI here so it can be inspected /// for testing. Uri? connectedUri; @@ -119,10 +124,6 @@ class MockWebSocketConnection extends WebSocketConnection { /// inspected for testing. final List sentMessages = []; - MockWebSocketConnection( - AWSApiConfig config, AmplifyAuthProviderRepository authProviderRepo) - : super(config, authProviderRepo, logger: AmplifyLogger()); - WebSocketMessage? get lastSentMessage => sentMessages.lastOrNull; final messageStream = StreamController(); @@ -132,35 +133,44 @@ class MockWebSocketConnection extends WebSocketConnection { connectedUri = connectionUri; // mock some message responses (acks and mock data) from server - final broadcast = messageStream.stream.asBroadcastStream(); - broadcast.listen((event) { - final eventJson = json.decode(event as String); - final messageFromEvent = WebSocketMessage.fromJson(eventJson as Map); - - // connection_init, respond with connection_ack - final mockResponseMessages = []; - if (messageFromEvent.messageType == MessageType.connectionInit) { - mockResponseMessages.add(WebSocketMessage( - messageType: MessageType.connectionAck, - payload: const ConnectionAckMessagePayload(10000), - )); - // start, respond with start_ack and mock data - } else if (messageFromEvent.messageType == MessageType.start) { - mockResponseMessages.add(WebSocketMessage( - messageType: MessageType.startAck, - id: messageFromEvent.id, - )); - mockResponseMessages.add(WebSocketMessage( - messageType: MessageType.data, - id: messageFromEvent.id, - payload: const SubscriptionDataPayload(mockSubscriptionData, null), - )); - } - - for (var mockMessage in mockResponseMessages) { - messageStream.add(json.encode(mockMessage)); - } - }); + final broadcast = messageStream.stream.asBroadcastStream() + ..listen((event) { + final eventJson = json.decode(event as String); + final messageFromEvent = + WebSocketMessage.fromJson(eventJson as Map); + + // connection_init, respond with connection_ack + final mockResponseMessages = []; + if (messageFromEvent.messageType == MessageType.connectionInit) { + mockResponseMessages.add( + WebSocketMessage( + messageType: MessageType.connectionAck, + payload: const ConnectionAckMessagePayload(10000), + ), + ); + // start, respond with start_ack and mock data + } else if (messageFromEvent.messageType == MessageType.start) { + mockResponseMessages + ..add( + WebSocketMessage( + messageType: MessageType.startAck, + id: messageFromEvent.id, + ), + ) + ..add( + WebSocketMessage( + messageType: MessageType.data, + id: messageFromEvent.id, + payload: + const SubscriptionDataPayload(mockSubscriptionData, null), + ), + ); + } + + for (final mockMessage in mockResponseMessages) { + messageStream.add(json.encode(mockMessage)); + } + }); // ensures connected to _onDone events in parent class getStreamSubscription(broadcast); diff --git a/packages/api/amplify_api/test/ws/web_socket_auth_utils_test.dart b/packages/api/amplify_api/test/ws/web_socket_auth_utils_test.dart index 307c992732..2044b1a767 100644 --- a/packages/api/amplify_api/test/ws/web_socket_auth_utils_test.dart +++ b/packages/api/amplify_api/test/ws/web_socket_auth_utils_test.dart @@ -43,7 +43,8 @@ void main() { final subscriptionRequest = GraphQLRequest(document: graphQLDocument); void assertBasicSubscriptionPayloadHeaders( - SubscriptionRegistrationPayload payload) { + SubscriptionRegistrationPayload payload, + ) { expect( payload.authorizationHeaders[AWSHeaders.contentType], 'application/json; charset=utf-8', diff --git a/packages/api/amplify_api/test/ws/web_socket_connection_test.dart b/packages/api/amplify_api/test/ws/web_socket_connection_test.dart index 9a1e3e6545..f9615f2db7 100644 --- a/packages/api/amplify_api/test/ws/web_socket_connection_test.dart +++ b/packages/api/amplify_api/test/ws/web_socket_connection_test.dart @@ -48,36 +48,37 @@ void main() { 'init() should connect with authorized query params in URI and send connection init message', () async { await connection.init(); - expectLater(connection.ready, completes); + await expectLater(connection.ready, completes); expect( connection.connectedUri.toString(), expectedApiKeyWebSocketConnectionUrl, ); expect( - connection.lastSentMessage?.messageType, MessageType.connectionInit); + connection.lastSentMessage?.messageType, + MessageType.connectionInit, + ); }); test('subscribe() should initialize the connection and call onEstablished', () async { connection.subscribe(subscriptionRequest, expectAsync0(() {})); - expectLater(connection.ready, completes); + await expectLater(connection.ready, completes); }); test( 'subscribe() should send SubscriptionRegistrationMessage with authorized payload correctly serialized', () async { - connection.init(); + await connection.init(); await connection.ready; - Completer establishedCompleter = Completer(); - connection.subscribe(subscriptionRequest, () { - establishedCompleter.complete(); - }); + final establishedCompleter = Completer(); + connection.subscribe(subscriptionRequest, establishedCompleter.complete); await establishedCompleter.future; final lastMessage = connection.lastSentMessage; expect(lastMessage?.messageType, MessageType.start); final payloadJson = lastMessage?.payload?.toJson(); final apiKeyFromPayload = + // ignore: avoid_dynamic_calls payloadJson?['extensions']['authorization'][xApiKey]; expect(apiKeyFromPayload, testApiKeyConfig.apiKey); }); @@ -98,13 +99,14 @@ void main() { }); test('cancel() should send a stop message', () async { - Completer dataCompleter = Completer(); + final dataCompleter = Completer(); final subscription = connection.subscribe(subscriptionRequest, null); final streamSub = subscription.listen( (event) => dataCompleter.complete(event.data), ); await dataCompleter.future; - streamSub.cancel(); + + unawaited(streamSub.cancel()); expect(connection.lastSentMessage?.messageType, MessageType.stop); }); });