From 2e491a7c54ebb77a1552cde4cda7e638af866e82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kl=C4=81vs=20Pried=C4=ABtis?= <klavs@prieditis.lv> Date: Sun, 16 Feb 2020 19:21:40 +0200 Subject: [PATCH 1/2] feat: drop Link layer in favor of package:gql_link and package:gql_exec BREAKING CHANGE: Link layer is now implemented via package:gql_link and package:gql_exec --- packages/graphql/example/bin/main.dart | 17 +- packages/graphql/example/pubspec.yaml | 4 +- packages/graphql/lib/client.dart | 9 - packages/graphql/lib/internal.dart | 3 - .../legacy_socket_client.dart | 408 ------------- .../legacy_socket_api/legacy_socket_link.dart | 97 ---- .../graphql/lib/src/core/query_manager.dart | 74 +-- .../graphql/lib/src/core/query_options.dart | 9 +- .../lib/src/core/raw_operation_data.dart | 25 +- .../lib/src/exceptions/exceptions.dart | 14 +- .../lib/src/exceptions/graphql_error.dart | 61 -- .../src/exceptions/io_network_exception.dart | 20 - .../exceptions/network_exception_stub.dart | 31 - .../src/exceptions/operation_exception.dart | 2 +- packages/graphql/lib/src/graphql_client.dart | 13 +- .../graphql/lib/src/link/auth/link_auth.dart | 38 -- .../lib/src/link/error/link_error.dart | 70 --- .../graphql/lib/src/link/fetch_result.dart | 19 - .../src/link/http/fallback_http_config.dart | 24 - .../lib/src/link/http/http_config.dart | 43 -- .../graphql/lib/src/link/http/link_http.dart | 373 ------------ .../http/link_http_helper_deprecated_io.dart | 34 -- .../link_http_helper_deprecated_stub.dart | 15 - packages/graphql/lib/src/link/link.dart | 43 -- packages/graphql/lib/src/link/operation.dart | 51 -- .../src/link/web_socket/link_web_socket.dart | 55 -- packages/graphql/lib/src/socket_client.dart | 383 ------------ packages/graphql/lib/src/utilities/file.dart | 3 - .../graphql/lib/src/utilities/file_html.dart | 28 - .../graphql/lib/src/utilities/file_io.dart | 23 - .../graphql/lib/src/utilities/file_stub.dart | 6 - .../graphql/lib/src/websocket/messages.dart | 224 -------- packages/graphql/lib/utilities.dart | 1 - packages/graphql/pubspec.yaml | 8 +- .../test/anonymous_operations_test.dart | 169 +++--- .../graphql/test/graphql_client_test.dart | 207 ++++--- packages/graphql/test/helpers.dart | 14 - .../test/link/error/link_error_test.dart | 110 ---- .../test/link/http/link_http_test.dart | 543 ------------------ packages/graphql/test/link/link_test.dart | 36 -- .../test/multipart_upload_io_test.dart | 94 --- .../graphql/test/multipart_upload_test.dart | 202 ------- packages/graphql/test/socket_client_test.dart | 178 ------ .../test/websocket_legacy_io_test.dart | 96 ---- .../graphql/test/websocket_legacy_test.dart | 54 -- packages/graphql/test/websocket_test.dart | 37 -- .../lib/src/widgets/subscription.dart | 17 +- packages/graphql_flutter/pubspec.yaml | 3 +- 48 files changed, 284 insertions(+), 3704 deletions(-) delete mode 100644 packages/graphql/lib/legacy_socket_api/legacy_socket_client.dart delete mode 100644 packages/graphql/lib/legacy_socket_api/legacy_socket_link.dart delete mode 100644 packages/graphql/lib/src/exceptions/graphql_error.dart delete mode 100644 packages/graphql/lib/src/exceptions/io_network_exception.dart delete mode 100644 packages/graphql/lib/src/exceptions/network_exception_stub.dart delete mode 100644 packages/graphql/lib/src/link/auth/link_auth.dart delete mode 100644 packages/graphql/lib/src/link/error/link_error.dart delete mode 100644 packages/graphql/lib/src/link/fetch_result.dart delete mode 100644 packages/graphql/lib/src/link/http/fallback_http_config.dart delete mode 100644 packages/graphql/lib/src/link/http/http_config.dart delete mode 100644 packages/graphql/lib/src/link/http/link_http.dart delete mode 100644 packages/graphql/lib/src/link/http/link_http_helper_deprecated_io.dart delete mode 100644 packages/graphql/lib/src/link/http/link_http_helper_deprecated_stub.dart delete mode 100644 packages/graphql/lib/src/link/link.dart delete mode 100644 packages/graphql/lib/src/link/operation.dart delete mode 100644 packages/graphql/lib/src/link/web_socket/link_web_socket.dart delete mode 100644 packages/graphql/lib/src/socket_client.dart delete mode 100644 packages/graphql/lib/src/utilities/file.dart delete mode 100644 packages/graphql/lib/src/utilities/file_html.dart delete mode 100644 packages/graphql/lib/src/utilities/file_io.dart delete mode 100644 packages/graphql/lib/src/utilities/file_stub.dart delete mode 100644 packages/graphql/lib/src/websocket/messages.dart delete mode 100644 packages/graphql/lib/utilities.dart delete mode 100644 packages/graphql/test/link/error/link_error_test.dart delete mode 100644 packages/graphql/test/link/http/link_http_test.dart delete mode 100644 packages/graphql/test/link/link_test.dart delete mode 100644 packages/graphql/test/multipart_upload_io_test.dart delete mode 100644 packages/graphql/test/multipart_upload_test.dart delete mode 100644 packages/graphql/test/socket_client_test.dart delete mode 100644 packages/graphql/test/websocket_legacy_io_test.dart delete mode 100644 packages/graphql/test/websocket_legacy_test.dart delete mode 100644 packages/graphql/test/websocket_test.dart diff --git a/packages/graphql/example/bin/main.dart b/packages/graphql/example/bin/main.dart index 2d224c90a..ab184ad68 100644 --- a/packages/graphql/example/bin/main.dart +++ b/packages/graphql/example/bin/main.dart @@ -1,10 +1,13 @@ import 'dart:io' show stdout, stderr, exit; import 'package:args/args.dart'; +import 'package:gql_http_link/gql_http_link.dart'; +import 'package:gql_link/gql_link.dart'; import 'package:graphql/client.dart'; import './graphql_operation/mutations/mutations.dart'; import './graphql_operation/queries/readRepositories.dart'; + // to run the example, create a file ../local.dart with the content: // const String YOUR_PERSONAL_ACCESS_TOKEN = // '<YOUR_PERSONAL_ACCESS_TOKEN>'; @@ -15,17 +18,13 @@ ArgResults argResults; // client - create a graphql client GraphQLClient client() { - final HttpLink _httpLink = HttpLink( - uri: 'https://api.github.com/graphql', - ); - - final AuthLink _authLink = AuthLink( - // ignore: undefined_identifier - getToken: () async => 'Bearer $YOUR_PERSONAL_ACCESS_TOKEN', + final Link _link = HttpLink( + 'https://api.github.com/graphql', + defaultHeaders: { + 'Authorization': 'Bearer $YOUR_PERSONAL_ACCESS_TOKEN', + }, ); - final Link _link = _authLink.concat(_httpLink); - return GraphQLClient( cache: InMemoryCache(), link: _link, diff --git a/packages/graphql/example/pubspec.yaml b/packages/graphql/example/pubspec.yaml index a73bdaee6..fce16de6b 100644 --- a/packages/graphql/example/pubspec.yaml +++ b/packages/graphql/example/pubspec.yaml @@ -6,7 +6,9 @@ environment: sdk: ">=2.6.0 <3.0.0" dependencies: - args: + args: + gql_link: ^0.2.3 + gql_http_link: ^0.2.7 graphql: path: .. diff --git a/packages/graphql/lib/client.dart b/packages/graphql/lib/client.dart index a11f2b297..00c91ddaa 100644 --- a/packages/graphql/lib/client.dart +++ b/packages/graphql/lib/client.dart @@ -10,12 +10,3 @@ export 'package:graphql/src/core/query_options.dart'; export 'package:graphql/src/core/query_result.dart'; export 'package:graphql/src/exceptions/exceptions.dart'; export 'package:graphql/src/graphql_client.dart'; -export 'package:graphql/src/link/auth/link_auth.dart'; -export 'package:graphql/src/link/error/link_error.dart'; -export 'package:graphql/src/link/fetch_result.dart'; -export 'package:graphql/src/link/http/link_http.dart'; -export 'package:graphql/src/link/link.dart'; -export 'package:graphql/src/link/operation.dart'; -export 'package:graphql/src/link/web_socket/link_web_socket.dart'; -export 'package:graphql/src/socket_client.dart'; -export 'package:graphql/src/websocket/messages.dart'; diff --git a/packages/graphql/lib/internal.dart b/packages/graphql/lib/internal.dart index 79b90c4c1..d38c2a107 100644 --- a/packages/graphql/lib/internal.dart +++ b/packages/graphql/lib/internal.dart @@ -1,6 +1,3 @@ export 'package:graphql/src/utilities/helpers.dart'; export 'package:graphql/src/core/observable_query.dart'; - -export 'package:graphql/src/link/operation.dart'; -export 'package:graphql/src/link/fetch_result.dart'; diff --git a/packages/graphql/lib/legacy_socket_api/legacy_socket_client.dart b/packages/graphql/lib/legacy_socket_api/legacy_socket_client.dart deleted file mode 100644 index b47f5ccb2..000000000 --- a/packages/graphql/lib/legacy_socket_api/legacy_socket_client.dart +++ /dev/null @@ -1,408 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; -import 'dart:typed_data'; - -import 'package:meta/meta.dart'; -import 'package:rxdart/rxdart.dart'; - -import 'package:uuid_enhanced/uuid.dart'; - -import 'package:graphql/src/websocket/messages.dart'; - -enum SocketConnectionState { NOT_CONNECTED, CONNECTING, CONNECTED } - -@deprecated -class SocketClientConfig { - const SocketClientConfig({ - this.autoReconnect = true, - this.queryAndMutationTimeout = const Duration(seconds: 10), - this.inactivityTimeout = const Duration(seconds: 30), - this.delayBetweenReconnectionAttempts = const Duration(seconds: 5), - this.compression = CompressionOptions.compressionDefault, - this.initPayload, - // @todo please review this ignore rule - // ignore: deprecated_member_use_from_same_package - @deprecated this.legacyInitPayload, - }); - - /// Whether to reconnect to the server after detecting connection loss. - final bool autoReconnect; - - /// The duration after which the connection is considered unstable, because no keep alive message - /// was received from the server in the given time-frame. The connection to the server will be closed. - /// If [autoReconnect] is set to true, we try to reconnect to the server after the specified [delayBetweenReconnectionAttempts]. - /// - /// If null, the keep alive messages will be ignored. - final Duration inactivityTimeout; - - /// The duration that needs to pass before trying to reconnect to the server after a connection loss. - /// This only takes effect when [autoReconnect] is set to true. - /// - /// If null, the reconnection will occur immediately, although not recommended. - final Duration delayBetweenReconnectionAttempts; - - // The duration after which a query or mutation should time out. - // If null, no timeout is applied, although not recommended. - final Duration queryAndMutationTimeout; - - final CompressionOptions compression; - - /// The initial payload that will be sent to the server upon connection. - /// Can be null, but must be a valid json structure if provided. - final dynamic initPayload; - - final Map<String, dynamic> legacyInitPayload; - - InitOperation get initOperation { - if (legacyInitPayload != null) { - print( - 'WARNING: Using a legacyInitPayload which will be removed soon. ' - 'If you need this particular payload serialization behavior, ' - 'please comment on this issue with details on your usecase: ' - 'https://github.com/zino-app/graphql-flutter/pull/277', - ); - // @todo please review this ignore rule - // ignore: deprecated_member_use_from_same_package - return LegacyInitOperation(legacyInitPayload); - } - return InitOperation(initPayload); - } -} - -/// @deprecated Old SocketClient that accepts and handles headers. -/// WebSocket headers are not usable in the browser. -/// So, to encourage universality, -/// they are not usable in the main socket link and client any longer -/// -/// Wraps a standard web socket instance to marshal and un-marshal the server / -/// client payloads into dart object representation. -/// -/// This class also deals with reconnection, handles timeout and keep alive messages. -/// -/// It is meant to be instantiated once, and you can let this class handle all the heavy- -/// lifting of socket state management. Once you're done with the socket connection, make sure -/// you call the [dispose] method to release all allocated resources. -@deprecated -class SocketClient { - SocketClient( - this.url, { - this.protocols = const <String>[ - 'graphql-ws', - ], - this.headers = const <String, String>{ - 'content-type': 'application/json', - }, - this.config = const SocketClientConfig(), - @visibleForTesting this.randomBytesForUuid, - }) { - _connect(); - } - - Uint8List randomBytesForUuid; - final String url; - final SocketClientConfig config; - final Iterable<String> protocols; - final Map<String, dynamic> headers; - final BehaviorSubject<SocketConnectionState> _connectionStateController = - BehaviorSubject<SocketConnectionState>(); - - Timer _reconnectTimer; - WebSocket _socket; - - @visibleForTesting - WebSocket get socket => _socket; - - Stream<dynamic> _stream; - - @visibleForTesting - @protected - Stream<dynamic> get stream => _stream ??= _socket.asBroadcastStream(); - - Stream<GraphQLSocketMessage> _messageStream; - - StreamSubscription<ConnectionKeepAlive> _keepAliveSubscription; - StreamSubscription<GraphQLSocketMessage> _messageSubscription; - - /// Connects to the server. - /// - /// If this instance is disposed, this method does nothing. - Future<void> _connect() async { - if (_connectionStateController.isClosed) { - return; - } - - _connectionStateController.value = SocketConnectionState.CONNECTING; - print('Connecting to websocket: $url...'); - - try { - _socket = await WebSocket.connect(url, - protocols: protocols, - headers: headers, - compression: config.compression); - _connectionStateController.value = SocketConnectionState.CONNECTED; - print('Connected to websocket.'); - _write(config.initOperation); - - _stream ??= _socket.asBroadcastStream(); - _messageStream = _stream.map<GraphQLSocketMessage>(_parseSocketMessage); - - if (config.inactivityTimeout != null) { - _keepAliveSubscription = _messagesOfType<ConnectionKeepAlive>().timeout( - config.inactivityTimeout, - onTimeout: (EventSink<ConnectionKeepAlive> event) { - print( - "Haven't received keep alive message for ${config.inactivityTimeout.inSeconds} seconds. Disconnecting.."); - event.close(); - _socket.close(WebSocketStatus.goingAway); - _connectionStateController.value = - SocketConnectionState.NOT_CONNECTED; - }, - ).listen(null); - } - - _messageSubscription = _messageStream.listen( - (dynamic data) { - // print('data: $data'); - }, - onDone: () { - // print('done'); - onConnectionLost(); - }, - cancelOnError: true, - onError: (dynamic e) { - print('error: $e'); - }); - } catch (e) { - onConnectionLost(); - } - } - - void onConnectionLost() { - print('Disconnected from websocket.'); - _reconnectTimer?.cancel(); - _keepAliveSubscription?.cancel(); - _messageSubscription?.cancel(); - - if (_connectionStateController.isClosed) { - return; - } - - if (_connectionStateController.value != - SocketConnectionState.NOT_CONNECTED) { - _connectionStateController.value = SocketConnectionState.NOT_CONNECTED; - } - - if (config.autoReconnect && !_connectionStateController.isClosed) { - if (config.delayBetweenReconnectionAttempts != null) { - print( - 'Scheduling to connect in ${config.delayBetweenReconnectionAttempts.inSeconds} seconds...'); - - _reconnectTimer = Timer( - config.delayBetweenReconnectionAttempts, - () { - _connect(); - }, - ); - } else { - Timer.run(() => _connect()); - } - } - } - - /// Closes the underlying socket if connected, and stops reconnection attempts. - /// After calling this method, this [SocketClient] instance must be considered - /// unusable. Instead, create a new instance of this class. - /// - /// Use this method if you'd like to disconnect from the specified server permanently, - /// and you'd like to connect to another server instead of the current one. - Future<void> dispose() async { - print('Disposing socket client..'); - _reconnectTimer?.cancel(); - await _socket?.close(); - await _keepAliveSubscription?.cancel(); - await _messageSubscription?.cancel(); - await _connectionStateController?.close(); - _stream = null; - } - - static GraphQLSocketMessage _parseSocketMessage(dynamic message) { - final Map<String, dynamic> map = - json.decode(message as String) as Map<String, dynamic>; - final String type = (map['type'] ?? 'unknown') as String; - final dynamic payload = map['payload'] ?? <String, dynamic>{}; - final String id = (map['id'] ?? 'none') as String; - - switch (type) { - case MessageTypes.GQL_CONNECTION_ACK: - return ConnectionAck(); - case MessageTypes.GQL_CONNECTION_ERROR: - return ConnectionError(payload); - case MessageTypes.GQL_CONNECTION_KEEP_ALIVE: - return ConnectionKeepAlive(); - case MessageTypes.GQL_DATA: - final dynamic data = payload['data']; - final dynamic errors = payload['errors']; - return SubscriptionData(id, data, errors); - case MessageTypes.GQL_ERROR: - return SubscriptionError(id, payload); - case MessageTypes.GQL_COMPLETE: - return SubscriptionComplete(id); - default: - return UnknownData(map); - } - } - - void _write(final GraphQLSocketMessage message) { - if (_connectionStateController.value == SocketConnectionState.CONNECTED) { - _socket.add( - json.encode( - message, - toEncodable: (dynamic m) => m.toJson(), - ), - ); - } - } - - /// Sends a query, mutation or subscription request to the server, and returns a stream of the response. - /// - /// If the request is a query or mutation, a timeout will be applied to the request as specified by - /// [SocketClientConfig]'s [queryAndMutationTimeout] field. - /// - /// If the request is a subscription, obviously no timeout is applied. - /// - /// In case of socket disconnection, the returned stream will be closed. - Stream<SubscriptionData> subscribe( - final SubscriptionRequest payload, final bool waitForConnection) { - final String id = Uuid.randomUuid(random: randomBytesForUuid).toString(); - final StreamController<SubscriptionData> response = - StreamController<SubscriptionData>(); - StreamSubscription<SocketConnectionState> sub; - final bool addTimeout = !payload.operation.isSubscription && - config.queryAndMutationTimeout != null; - - response.onListen = () { - final Stream<SocketConnectionState> waitForConnectedStateWithoutTimeout = - _connectionStateController - .startWith( - waitForConnection ? null : SocketConnectionState.CONNECTED) - .where((SocketConnectionState state) => - state == SocketConnectionState.CONNECTED) - .take(1); - - final Stream<SocketConnectionState> waitForConnectedState = addTimeout - ? waitForConnectedStateWithoutTimeout.timeout( - config.queryAndMutationTimeout, - onTimeout: (EventSink<SocketConnectionState> event) { - print('Connection timed out.'); - response.addError(TimeoutException('Connection timed out.')); - event.close(); - response.close(); - }, - ) - : waitForConnectedStateWithoutTimeout; - - sub = waitForConnectedState.listen((_) { - final Stream<GraphQLSocketMessage> dataErrorComplete = - _messageStream.where( - (GraphQLSocketMessage message) { - if (message is SubscriptionData) { - return message.id == id; - } - - if (message is SubscriptionError) { - return message.id == id; - } - - if (message is SubscriptionComplete) { - return message.id == id; - } - - return false; - }, - ).takeWhile((_) => !response.isClosed); - - final Stream<GraphQLSocketMessage> subscriptionComplete = addTimeout - ? dataErrorComplete - .where((GraphQLSocketMessage message) => - message is SubscriptionComplete) - .take(1) - .timeout( - config.queryAndMutationTimeout, - onTimeout: (EventSink<GraphQLSocketMessage> event) { - print('Request timed out.'); - response.addError(TimeoutException('Request timed out.')); - event.close(); - response.close(); - }, - ) - : dataErrorComplete - .where((GraphQLSocketMessage message) => - message is SubscriptionComplete) - .take(1); - - subscriptionComplete.listen((_) => response.close()); - - dataErrorComplete - .where( - (GraphQLSocketMessage message) => message is SubscriptionData) - .cast<SubscriptionData>() - .listen((SubscriptionData message) => response.add(message)); - - dataErrorComplete - .where( - (GraphQLSocketMessage message) => message is SubscriptionError) - .listen( - (GraphQLSocketMessage message) => response.addError(message)); - - _write(StartOperation(id, payload)); - }); - }; - - response.onCancel = () { - sub?.cancel(); - if (_connectionStateController.value == SocketConnectionState.CONNECTED && - _socket != null) { - _write(StopOperation(id)); - } - }; - - return response.stream; - } - - /// These streams will emit done events when the current socket is done. - - /// A stream that emits the last value of the connection state upon subscription. - Stream<SocketConnectionState> get connectionState => - _connectionStateController.stream; - - /// Filter `_messageStream` for messages of the given type of [GraphQLSocketMessage] - /// - /// Example usages: - /// `_messagesOfType<ConnectionAck>()` for init acknowledgments - /// `_messagesOfType<ConnectionError>()` for errors - /// `_messagesOfType<UnknownData>()` for unknown data messages - Stream<M> _messagesOfType<M extends GraphQLSocketMessage>() => _messageStream - .where((GraphQLSocketMessage message) => message is M) - .cast<M>(); -} - -/// The old implementation of [InitOperation] -// @todo please review this ignore rule -// ignore: deprecated_member_use_from_same_package -@deprecated -class LegacyInitOperation extends InitOperation { - LegacyInitOperation(dynamic payload) : super(payload); - - @override - Map<String, dynamic> toJson() { - final Map<String, dynamic> jsonMap = <String, dynamic>{}; - jsonMap['type'] = type; - - if (payload != null) { - jsonMap['payload'] = json.encode(payload); - } - - return jsonMap; - } -} diff --git a/packages/graphql/lib/legacy_socket_api/legacy_socket_link.dart b/packages/graphql/lib/legacy_socket_api/legacy_socket_link.dart deleted file mode 100644 index e72109fd7..000000000 --- a/packages/graphql/lib/legacy_socket_api/legacy_socket_link.dart +++ /dev/null @@ -1,97 +0,0 @@ -import 'package:meta/meta.dart'; -import 'package:graphql/src/link/fetch_result.dart'; -import 'package:graphql/src/link/link.dart'; -import 'package:graphql/src/link/operation.dart'; -import 'package:graphql/src/socket_client.dart'; -import 'package:graphql/src/websocket/messages.dart'; - -/// @deprecated Old SocketClient that accepts and handles headers. -/// WebSocket headers are not usable in the browser. -/// So, to encourage universality, -/// they are not usable in the main socket link and client any longer -/// -/// A websocket [Link] implementation to support the websocket transport. -/// It supports subscriptions, query and mutation operations as well. -/// -/// This link is aware of [AuthLink], so the headers specified there are automatically -/// applied when connecting to the socket server, unless explicitly overridden by the [headers] -/// parameter. -/// -/// There's an option called [reconnectOnHeaderChange] that makes it possible to reconnect to the server when the -/// headers have changed. For example, if the user logs in with another user account and the `Authorization` header changes. -/// This could be desired because of the nature of websocket connections: headers can only be specified upon connecting. -/// -/// NOTE: the actual socket connection will only get established after an [Operation] is handled by this [WebSocketLink]. -/// If you'd like to connect to the socket server instantly, call the [connectOrReconnect] method after creating this [WebSocketLink] instance. -@deprecated -class WebSocketLink extends Link { - /// Creates a new [WebSocketLink] instance with the specified config. - WebSocketLink({ - @required this.url, - this.headers, - this.reconnectOnHeaderChange = true, - this.config = const SocketClientConfig(), - }) : super() { - if (headers != null) { - print( - 'WARNING: Using direct websocket headers which will be removed soon, ' - 'as it is incompatable with dart:html. ' - 'If you need this direct header access, ' - 'please comment on this PR with details on your usecase: ' - 'https://github.com/zino-app/graphql-flutter/pull/323', - ); - } else { - print( - 'WARNING: You are using the deprecated websocket API, ' - 'but do not appear to need direct header access. ' - 'If you also do not need the legacyInitPayload, ' - 'please switch to the new link and client', - ); - } - request = _doOperation; - } - - final String url; - final Map<String, dynamic> headers; - final bool reconnectOnHeaderChange; - final SocketClientConfig config; - - // cannot be final because we're changing the instance upon a header change. - SocketClient _socketClient; - - Stream<FetchResult> _doOperation(Operation operation, [NextLink forward]) { - final Map<String, dynamic> concatHeaders = <String, dynamic>{}; - final Map<String, dynamic> context = operation.getContext(); - if (context != null && context.containsKey('headers')) { - concatHeaders.addAll(context['headers'] as Map<String, dynamic>); - } - // @todo deprecated - if (headers != null) { - concatHeaders.addAll(headers); - } - - if (_socketClient == null) { - connectOrReconnect(headers: concatHeaders); - } - - return _socketClient.subscribe(SubscriptionRequest(operation), true).map( - (SubscriptionData result) => FetchResult( - data: result.data, - errors: result.errors as List<dynamic>, - context: operation.getContext(), - extensions: operation.extensions)); - } - - /// Connects or reconnects to the server with the specified headers. - void connectOrReconnect({Map<String, dynamic> headers}) { - _socketClient?.dispose(); - _socketClient = SocketClient(url, config: config); - } - - /// Disposes the underlying socket client explicitly. Only use this, if you want to disconnect from - /// the current server in favour of another one. If that's the case, create a new [WebSocketLink] instance. - Future<void> dispose() async { - await _socketClient?.dispose(); - _socketClient = null; - } -} diff --git a/packages/graphql/lib/src/core/query_manager.dart b/packages/graphql/lib/src/core/query_manager.dart index 73c224b17..fdad03aa7 100644 --- a/packages/graphql/lib/src/core/query_manager.dart +++ b/packages/graphql/lib/src/core/query_manager.dart @@ -1,5 +1,7 @@ import 'dart:async'; +import 'package:gql_exec/gql_exec.dart'; +import 'package:gql_link/gql_link.dart'; import 'package:graphql/src/cache/cache.dart'; import 'package:graphql/src/cache/normalized_in_memory.dart' show NormalizedInMemoryCache; @@ -8,9 +10,6 @@ import 'package:graphql/src/core/observable_query.dart'; import 'package:graphql/src/core/query_options.dart'; import 'package:graphql/src/core/query_result.dart'; import 'package:graphql/src/exceptions/exceptions.dart'; -import 'package:graphql/src/link/fetch_result.dart'; -import 'package:graphql/src/link/link.dart'; -import 'package:graphql/src/link/operation.dart'; import 'package:graphql/src/scheduler/scheduler.dart'; import 'package:meta/meta.dart'; @@ -104,35 +103,44 @@ class QueryManager { String queryId, BaseOptions options, ) async { - // create a new operation to fetch - final Operation operation = Operation.fromOptions(options) - ..setContext(options.context); + // create a new request to execute + final Request request = Request( + operation: Operation( + document: options.documentNode, + operationName: options.operationName, + ), + variables: options.variables, + context: options.context ?? Context(), + ); - FetchResult fetchResult; + Response response; QueryResult queryResult; try { - // execute the operation through the provided link(s) - fetchResult = await execute( - link: link, - operation: operation, - ).first; - - // save the data from fetchResult to the cache - if (fetchResult.data != null && - options.fetchPolicy != FetchPolicy.noCache) { + // execute the request through the provided link(s) + response = await link + .request( + request, + ) + .first; + + // save the data from response to the cache + if (response.data != null && options.fetchPolicy != FetchPolicy.noCache) { cache.write( - operation.toKey(), - fetchResult.data, + // TODO: think of an alternative to the old toKey(), + request.hashCode.toString(), + response.data, ); } queryResult = mapFetchResultToQueryResult( - fetchResult, + response, options, source: QueryResultSource.Network, ); } catch (failure) { + // TODO: handle Link exceptions + // we set the source to indicate where the source of failure queryResult ??= QueryResult(source: QueryResultSource.Network); @@ -147,7 +155,10 @@ class QueryManager { if (options.fetchPolicy != FetchPolicy.noCache && cache is NormalizedInMemoryCache) { // normalize results if previously written - queryResult.data = cache.read(operation.toKey()); + queryResult.data = cache.read( + // TODO: think of an alternative to the old toKey(), + request.hashCode.toString(), + ); } addQueryResult(queryId, queryResult); @@ -193,7 +204,7 @@ class QueryManager { source: QueryResultSource.Cache, exception: OperationException( clientException: CacheMissException( - 'Could not find that operation in the cache. (FetchPolicy.cacheOnly)', + 'Could not find that request in the cache. (FetchPolicy.cacheOnly)', cacheKey, ), ), @@ -287,7 +298,7 @@ class QueryManager { if (cachedData != null) { query.addResult( mapFetchResultToQueryResult( - FetchResult(data: cachedData), + Response(data: cachedData), query.options, source: QueryResultSource.Cache, ), @@ -317,7 +328,7 @@ class QueryManager { } QueryResult mapFetchResultToQueryResult( - FetchResult fetchResult, + Response response, BaseOptions options, { @required QueryResultSource source, }) { @@ -326,26 +337,26 @@ class QueryManager { // check if there are errors and apply the error policy if so // in a nutshell: `ignore` swallows errors, `none` swallows data - if (fetchResult.errors != null && fetchResult.errors.isNotEmpty) { + if (response.errors != null && response.errors.isNotEmpty) { switch (options.errorPolicy) { case ErrorPolicy.all: // handle both errors and data - errors = _errorsFromResult(fetchResult); - data = fetchResult.data; + errors = response.errors; + data = response.data; break; case ErrorPolicy.ignore: // ignore errors - data = fetchResult.data; + data = response.data; break; case ErrorPolicy.none: default: // TODO not actually sure if apollo even casts graphql errors in `none` mode, // it's also kind of legacy - errors = _errorsFromResult(fetchResult); + errors = response.errors; break; } } else { - data = fetchResult.data; + data = response.data; } return QueryResult( @@ -354,9 +365,4 @@ class QueryManager { exception: coalesceErrors(graphqlErrors: errors), ); } - - List<GraphQLError> _errorsFromResult(FetchResult fetchResult) => - List<GraphQLError>.from(fetchResult.errors.map<GraphQLError>( - (dynamic rawError) => GraphQLError.fromJSON(rawError), - )); } diff --git a/packages/graphql/lib/src/core/query_options.dart b/packages/graphql/lib/src/core/query_options.dart index fd99be102..6605b92a9 100644 --- a/packages/graphql/lib/src/core/query_options.dart +++ b/packages/graphql/lib/src/core/query_options.dart @@ -1,5 +1,6 @@ import 'package:gql/ast.dart'; import 'package:gql/language.dart'; +import 'package:gql_exec/gql_exec.dart'; import 'package:graphql/client.dart'; import 'package:graphql/internal.dart'; import 'package:graphql/src/core/raw_operation_data.dart'; @@ -101,7 +102,7 @@ class BaseOptions extends RawOperationData { ErrorPolicy get errorPolicy => policies.error; /// Context to be passed to link execution chain. - Map<String, dynamic> context; + Context context; } /// Query options. @@ -115,7 +116,7 @@ class QueryOptions extends BaseOptions { ErrorPolicy errorPolicy, Object optimisticResult, this.pollInterval, - Map<String, dynamic> context, + Context context, }) : super( policies: Policies(fetch: fetchPolicy, error: errorPolicy), // ignore: deprecated_member_use_from_same_package @@ -144,7 +145,7 @@ class MutationOptions extends BaseOptions { Map<String, dynamic> variables, FetchPolicy fetchPolicy, ErrorPolicy errorPolicy, - Map<String, dynamic> context, + Context context, this.onCompleted, this.update, this.onError, @@ -262,7 +263,7 @@ class WatchQueryOptions extends QueryOptions { int pollInterval, this.fetchResults = false, this.eagerlyFetchResults, - Map<String, dynamic> context, + Context context, }) : super( // ignore: deprecated_member_use_from_same_package document: document, diff --git a/packages/graphql/lib/src/core/raw_operation_data.dart b/packages/graphql/lib/src/core/raw_operation_data.dart index a3c4a16e2..5d003a9e8 100644 --- a/packages/graphql/lib/src/core/raw_operation_data.dart +++ b/packages/graphql/lib/src/core/raw_operation_data.dart @@ -3,10 +3,7 @@ import 'dart:convert' show json; import 'package:gql/ast.dart'; import 'package:gql/language.dart'; -import 'package:graphql/src/link/http/link_http_helper_deprecated_stub.dart' - if (dart.library.io) 'package:graphql/src/link/http/link_http_helper_deprecated_io.dart'; import 'package:graphql/src/utilities/get_from_ast.dart'; -import 'package:http/http.dart'; class RawOperationData { RawOperationData({ @@ -74,20 +71,14 @@ class RawOperationData { String toKey() { /// SplayTreeMap is always sorted - final String encodedVariables = - json.encode(variables, toEncodable: (dynamic object) { - if (object is MultipartFile) { - return object.filename; - } - // @deprecated, backward compatible only - // in case the body is io.File - // in future release, io.File will no longer be supported - if (isIoFile(object)) { - return object.path; - } - // default toEncodable behavior - return object.toJson(); - }); + final String encodedVariables = json.encode( + variables, + toEncodable: (dynamic object) { + // TODO: transparently handle multipart file without introducing package:http + // default toEncodable behavior + return object.toJson(); + }, + ); // TODO: document is being depracated, find ways for generating key // ignore: deprecated_member_use_from_same_package diff --git a/packages/graphql/lib/src/exceptions/exceptions.dart b/packages/graphql/lib/src/exceptions/exceptions.dart index 80bc1bb5e..8875df2b8 100644 --- a/packages/graphql/lib/src/exceptions/exceptions.dart +++ b/packages/graphql/lib/src/exceptions/exceptions.dart @@ -1,14 +1,2 @@ -import 'package:graphql/src/exceptions/_base_exceptions.dart' as _b; -import 'package:graphql/src/exceptions/io_network_exception.dart' as _n; - -export 'package:graphql/src/exceptions/_base_exceptions.dart' - hide translateFailure; -export 'package:graphql/src/exceptions/graphql_error.dart'; +export 'package:graphql/src/exceptions/_base_exceptions.dart'; export 'package:graphql/src/exceptions/operation_exception.dart'; -export 'package:graphql/src/exceptions/network_exception_stub.dart' - if (dart.library.io) 'package:graphql/src/exceptions/io_network_exception.dart' - hide translateNetworkFailure; - -_b.ClientException translateFailure(dynamic failure) { - return _n.translateNetworkFailure(failure) ?? _b.translateFailure(failure); -} diff --git a/packages/graphql/lib/src/exceptions/graphql_error.dart b/packages/graphql/lib/src/exceptions/graphql_error.dart deleted file mode 100644 index a3940b102..000000000 --- a/packages/graphql/lib/src/exceptions/graphql_error.dart +++ /dev/null @@ -1,61 +0,0 @@ -/// A location where a [GraphQLError] appears. -class Location { - /// Constructs a [Location] from a JSON map. - Location.fromJSON(Map<String, int> data) - : line = data['line'], - column = data['column']; - - /// The line of the error in the query. - final int line; - - /// The column of the error in the query. - final int column; - - @override - String toString() => '{ line: $line, column: $column }'; -} - -/// A GraphQL error (returned by a GraphQL server). -class GraphQLError { - GraphQLError({ - this.raw, - this.message, - this.locations, - this.path, - this.extensions, - }); - - /// Constructs a [GraphQLError] from a JSON map. - GraphQLError.fromJSON(this.raw) - : message = raw['message'] is String - ? raw['message'] as String - : 'Invalid server response: message property needs to be of type String', - locations = raw['locations'] is List<Map<String, int>> - ? List<Location>.from( - (raw['locations'] as List<Map<String, int>>).map<Location>( - (Map<String, int> location) => Location.fromJSON(location), - ), - ) - : null, - path = raw['path'] as List<dynamic>, - extensions = raw['extensions'] as Map<String, dynamic>; - - /// The message of the error. - final dynamic raw; - - /// The message of the error. - final String message; - - /// Locations where the error appear. - final List<Location> locations; - - /// The path of the field in error. - final List<dynamic> path; - - /// Custom error data returned by your GraphQL API server - final Map<String, dynamic> extensions; - - @override - String toString() => - '$message: ${locations is List ? locations.map((Location l) => '[${l.toString()}]').join('') : "Undefined location"}'; -} diff --git a/packages/graphql/lib/src/exceptions/io_network_exception.dart b/packages/graphql/lib/src/exceptions/io_network_exception.dart deleted file mode 100644 index 2ac59fb83..000000000 --- a/packages/graphql/lib/src/exceptions/io_network_exception.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'dart:io' show SocketException; -import 'dart:io'; -import './network_exception_stub.dart' as stub; - -export './network_exception_stub.dart' show NetworkException; - -stub.NetworkException translateNetworkFailure(dynamic failure) { - if (failure is SocketException) { - return stub.NetworkException( - wrappedException: failure, - message: failure.message, - uri: Uri( - scheme: 'http', - host: failure.address?.host, - port: failure.port, - ), - ); - } - return stub.translateNetworkFailure(failure); -} diff --git a/packages/graphql/lib/src/exceptions/network_exception_stub.dart b/packages/graphql/lib/src/exceptions/network_exception_stub.dart deleted file mode 100644 index 52a6a6f21..000000000 --- a/packages/graphql/lib/src/exceptions/network_exception_stub.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:http/http.dart' as http; - -import './_base_exceptions.dart' show ClientException; - -class NetworkException implements ClientException { - covariant Exception wrappedException; - - String message; - - Uri uri; - - NetworkException({ - this.wrappedException, - this.message, - this.uri, - }); - - String toString() => - 'Failed to connect to $uri: ${message ?? wrappedException}'; -} - -NetworkException translateNetworkFailure(dynamic failure) { - if (failure is http.ClientException) { - return NetworkException( - wrappedException: failure, - message: failure.message, - uri: failure.uri, - ); - } - return null; -} diff --git a/packages/graphql/lib/src/exceptions/operation_exception.dart b/packages/graphql/lib/src/exceptions/operation_exception.dart index 2be0b531c..ca9a84e70 100644 --- a/packages/graphql/lib/src/exceptions/operation_exception.dart +++ b/packages/graphql/lib/src/exceptions/operation_exception.dart @@ -1,5 +1,5 @@ +import 'package:gql_exec/gql_exec.dart'; import 'package:graphql/src/exceptions/_base_exceptions.dart'; -import './graphql_error.dart'; class OperationException implements Exception { /// Any graphql errors returned from the operation diff --git a/packages/graphql/lib/src/graphql_client.dart b/packages/graphql/lib/src/graphql_client.dart index 48725bf19..a2c8ac022 100644 --- a/packages/graphql/lib/src/graphql_client.dart +++ b/packages/graphql/lib/src/graphql_client.dart @@ -1,13 +1,12 @@ import 'dart:async'; +import 'package:gql_exec/gql_exec.dart'; +import 'package:gql_link/gql_link.dart'; import 'package:graphql/src/cache/cache.dart'; import 'package:graphql/src/core/observable_query.dart'; import 'package:graphql/src/core/query_manager.dart'; import 'package:graphql/src/core/query_options.dart'; import 'package:graphql/src/core/query_result.dart'; -import 'package:graphql/src/link/fetch_result.dart'; -import 'package:graphql/src/link/link.dart'; -import 'package:graphql/src/link/operation.dart'; import 'package:meta/meta.dart'; /// The default [Policies] to set for each client action @@ -64,7 +63,7 @@ class DefaultPolicies { ); } -/// The link is a [Link] over which GraphQL documents will be resolved into a [FetchResult]. +/// The link is a [Link] over which GraphQL documents will be resolved into a [Response]. /// The cache is the initial [Cache] to use in the data store. class GraphQLClient { /// Constructs a [GraphQLClient] given a [Link] and a [Cache]. @@ -83,7 +82,7 @@ class GraphQLClient { /// The default [Policies] to set for each client action DefaultPolicies defaultPolicies; - /// The [Link] over which GraphQL documents will be resolved into a [FetchResult]. + /// The [Link] over which GraphQL documents will be resolved into a [Response]. final Link link; /// The initial [Cache] to use in the data store. @@ -115,7 +114,7 @@ class GraphQLClient { /// This subscribes to a GraphQL subscription according to the options specified and returns a /// [Stream] which either emits received data or an error. - Stream<FetchResult> subscribe(Operation operation) { - return execute(link: link, operation: operation); + Stream<Response> subscribe(Request request) { + return link.request(request); } } diff --git a/packages/graphql/lib/src/link/auth/link_auth.dart b/packages/graphql/lib/src/link/auth/link_auth.dart deleted file mode 100644 index ab2ba010e..000000000 --- a/packages/graphql/lib/src/link/auth/link_auth.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'dart:async'; - -import 'package:graphql/src/link/link.dart'; -import 'package:graphql/src/link/operation.dart'; -import 'package:graphql/src/link/fetch_result.dart'; - -typedef GetToken = FutureOr<String> Function(); - -class AuthLink extends Link { - AuthLink({ - this.getToken, - }) : super( - request: (Operation operation, [NextLink forward]) { - StreamController<FetchResult> controller; - - Future<void> onListen() async { - try { - final String token = await getToken(); - - operation.setContext(<String, Map<String, String>>{ - 'headers': <String, String>{'Authorization': token} - }); - } catch (error) { - controller.addError(error); - } - - await controller.addStream(forward(operation)); - await controller.close(); - } - - controller = StreamController<FetchResult>(onListen: onListen); - - return controller.stream; - }, - ); - - GetToken getToken; -} diff --git a/packages/graphql/lib/src/link/error/link_error.dart b/packages/graphql/lib/src/link/error/link_error.dart deleted file mode 100644 index d28e621b7..000000000 --- a/packages/graphql/lib/src/link/error/link_error.dart +++ /dev/null @@ -1,70 +0,0 @@ -import 'dart:async'; - -import 'package:graphql/src/link/link.dart'; -import 'package:graphql/src/link/operation.dart'; -import 'package:graphql/src/link/fetch_result.dart'; -import 'package:graphql/src/exceptions/exceptions.dart'; -import 'package:graphql/src/exceptions/graphql_error.dart'; -import 'package:graphql/src/exceptions/operation_exception.dart'; - -typedef ErrorHandler = void Function(ErrorResponse); - -class ErrorResponse { - ErrorResponse({ - this.operation, - this.fetchResult, - this.exception, - }); - - Operation operation; - FetchResult fetchResult; - OperationException exception; -} - -class ErrorLink extends Link { - ErrorLink({ - this.errorHandler, - }) : super( - request: (Operation operation, [NextLink forward]) { - StreamController<FetchResult> controller; - - Future<void> onListen() async { - Stream stream = forward(operation).map((FetchResult fetchResult) { - if (fetchResult.errors != null) { - List<GraphQLError> errors = fetchResult.errors - .map((json) => GraphQLError.fromJSON(json)) - .toList(); - - ErrorResponse response = ErrorResponse( - operation: operation, - fetchResult: fetchResult, - exception: OperationException(graphqlErrors: errors), - ); - - errorHandler(response); - } - return fetchResult; - }).handleError((error) { - ErrorResponse response = ErrorResponse( - operation: operation, - exception: OperationException( - clientException: translateFailure(error), - ), - ); - - errorHandler(response); - throw error; - }); - - await controller.addStream(stream); - await controller.close(); - } - - controller = StreamController<FetchResult>(onListen: onListen); - - return controller.stream; - }, - ); - - ErrorHandler errorHandler; -} diff --git a/packages/graphql/lib/src/link/fetch_result.dart b/packages/graphql/lib/src/link/fetch_result.dart deleted file mode 100644 index 0a6f7383e..000000000 --- a/packages/graphql/lib/src/link/fetch_result.dart +++ /dev/null @@ -1,19 +0,0 @@ -class FetchResult { - FetchResult({ - this.statusCode, - this.reasonPhrase, - this.errors, - this.data, - this.extensions, - this.context, - }); - int statusCode; - String reasonPhrase; - - List<dynamic> errors; - - /// List<dynamic> or Map<String, dynamic> - dynamic data; - Map<String, dynamic> extensions; - Map<String, dynamic> context; -} diff --git a/packages/graphql/lib/src/link/http/fallback_http_config.dart b/packages/graphql/lib/src/link/http/fallback_http_config.dart deleted file mode 100644 index 0dcabea6f..000000000 --- a/packages/graphql/lib/src/link/http/fallback_http_config.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:graphql/src/link/http/http_config.dart'; - -HttpQueryOptions defaultHttpOptions = HttpQueryOptions( - includeQuery: true, - includeExtensions: false, -); - -Map<String, dynamic> defaultOptions = <String, dynamic>{ - 'method': 'POST', -}; - -Map<String, String> defaultHeaders = <String, String>{ - 'accept': '*/*', - 'content-type': 'application/json', -}; - -Map<String, dynamic> defaultCredentials = <String, dynamic>{}; - -HttpConfig fallbackHttpConfig = HttpConfig( - http: defaultHttpOptions, - options: defaultOptions, - headers: defaultHeaders, - credentials: defaultCredentials, -); diff --git a/packages/graphql/lib/src/link/http/http_config.dart b/packages/graphql/lib/src/link/http/http_config.dart deleted file mode 100644 index ea3682f14..000000000 --- a/packages/graphql/lib/src/link/http/http_config.dart +++ /dev/null @@ -1,43 +0,0 @@ -class HttpQueryOptions { - HttpQueryOptions({ - this.includeQuery, - this.includeExtensions, - }); - - bool includeQuery; - bool includeExtensions; - - void addAll(HttpQueryOptions options) { - if (options.includeQuery != null) { - includeQuery = options.includeQuery; - } - - if (options.includeExtensions != null) { - includeExtensions = options.includeExtensions; - } - } -} - -class HttpConfig { - HttpConfig({ - this.http, - this.options, - this.credentials, - this.headers, - }); - - HttpQueryOptions http; - Map<String, dynamic> options; - Map<String, dynamic> credentials; - Map<String, String> headers; -} - -class HttpHeadersAndBody { - HttpHeadersAndBody({ - this.headers, - this.body, - }); - - final Map<String, String> headers; - final Map<String, dynamic> body; -} diff --git a/packages/graphql/lib/src/link/http/link_http.dart b/packages/graphql/lib/src/link/http/link_http.dart deleted file mode 100644 index 394ab79b6..000000000 --- a/packages/graphql/lib/src/link/http/link_http.dart +++ /dev/null @@ -1,373 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:typed_data'; - -import 'package:graphql/src/exceptions/exceptions.dart' as ex; -import 'package:meta/meta.dart'; -import 'package:http/http.dart'; -import 'package:http_parser/http_parser.dart'; - -import 'package:gql/language.dart'; -import 'package:graphql/src/utilities/helpers.dart' show notNull; -import 'package:graphql/src/link/link.dart'; -import 'package:graphql/src/link/operation.dart'; -import 'package:graphql/src/link/fetch_result.dart'; -import 'package:graphql/src/link/http/fallback_http_config.dart'; -import 'package:graphql/src/link/http/http_config.dart'; -import './link_http_helper_deprecated_stub.dart' - if (dart.library.io) './link_http_helper_deprecated_io.dart'; - -class HttpLink extends Link { - HttpLink({ - @required String uri, - bool includeExtensions, - - /// pass on customized httpClient, especially handy for mocking and testing - Client httpClient, - Map<String, String> headers, - Map<String, dynamic> credentials, - Map<String, dynamic> fetchOptions, - }) : super( - // @todo possibly this is a bug in dart analyzer - // ignore: undefined_named_parameter - request: ( - Operation operation, [ - NextLink forward, - ]) { - final parsedUri = Uri.parse(uri); - - if (operation.isSubscription) { - if (forward == null) { - throw Exception('This link does not support subscriptions.'); - } - return forward(operation); - } - - final Client fetcher = httpClient ?? Client(); - - final HttpConfig linkConfig = HttpConfig( - http: HttpQueryOptions( - includeExtensions: includeExtensions, - ), - options: fetchOptions, - credentials: credentials, - headers: headers, - ); - - final Map<String, dynamic> context = operation.getContext(); - HttpConfig contextConfig; - - if (context != null) { - // TODO: refactor context to use a [HttpConfig] object to avoid dynamic types - contextConfig = HttpConfig( - http: HttpQueryOptions( - includeExtensions: context['includeExtensions'] as bool, - ), - options: context['fetchOptions'] as Map<String, dynamic>, - credentials: context['credentials'] as Map<String, dynamic>, - headers: context['headers'] as Map<String, String>, - ); - } - - final HttpHeadersAndBody httpHeadersAndBody = - _selectHttpOptionsAndBody( - operation, - fallbackHttpConfig, - linkConfig, - contextConfig, - ); - - final Map<String, String> httpHeaders = httpHeadersAndBody.headers; - - StreamController<FetchResult> controller; - - Future<void> onListen() async { - StreamedResponse response; - - try { - // httpOptionsAndBody.body as String - final BaseRequest request = await _prepareRequest( - parsedUri, httpHeadersAndBody.body, httpHeaders); - - response = await fetcher.send(request); - - operation.setContext(<String, StreamedResponse>{ - 'response': response, - }); - final FetchResult parsedResponse = - await _parseResponse(response); - - controller.add(parsedResponse); - } catch (failure) { - // we overwrite socket uri for now: - // https://github.com/dart-lang/sdk/issues/12693 - dynamic translated = ex.translateFailure(failure); - if (translated is ex.NetworkException) { - translated.uri = parsedUri; - } - controller.addError(translated); - } - - await controller.close(); - } - - controller = StreamController<FetchResult>(onListen: onListen); - - return controller.stream; - }, - ); -} - -Future<Map<String, MultipartFile>> _getFileMap( - dynamic body, { - Map<String, MultipartFile> currentMap, - List<String> currentPath = const <String>[], -}) async { - currentMap ??= <String, MultipartFile>{}; - if (body is Map<String, dynamic>) { - final Iterable<MapEntry<String, dynamic>> entries = body.entries; - for (MapEntry<String, dynamic> element in entries) { - currentMap.addAll(await _getFileMap( - element.value, - currentMap: currentMap, - currentPath: List<String>.from(currentPath)..add(element.key), - )); - } - return currentMap; - } - if (body is List<dynamic>) { - for (int i = 0; i < body.length; i++) { - currentMap.addAll(await _getFileMap( - body[i], - currentMap: currentMap, - currentPath: List<String>.from(currentPath)..add(i.toString()), - )); - } - return currentMap; - } - if (body is MultipartFile) { - return currentMap - ..addAll(<String, MultipartFile>{currentPath.join('.'): body}); - } - - // @deprecated, backward compatible only - // in case the body is io.File - // in future release, io.File will no longer be supported - if (isIoFile(body)) { - return deprecatedHelper(body, currentMap, currentPath); - } - - // else should only be either String, num, null; NOTHING else - return currentMap; -} - -Future<BaseRequest> _prepareRequest( - Uri uri, - Map<String, dynamic> body, - Map<String, String> httpHeaders, -) async { - final Map<String, MultipartFile> fileMap = await _getFileMap(body); - if (fileMap.isEmpty) { - final Request r = Request('post', uri); - r.headers.addAll(httpHeaders); - r.body = json.encode(body); - return r; - } - - final MultipartRequest r = MultipartRequest('post', uri); - r.headers.addAll(httpHeaders); - r.fields['operations'] = json.encode(body, toEncodable: (dynamic object) { - if (object is MultipartFile) { - return null; - } - // @deprecated, backward compatible only - // in case the body is io.File - // in future release, io.File will no longer be supported - if (isIoFile(object)) { - return null; - } - return object.toJson(); - }); - - final Map<String, List<String>> fileMapping = <String, List<String>>{}; - final List<MultipartFile> fileList = <MultipartFile>[]; - - final List<MapEntry<String, MultipartFile>> fileMapEntries = - fileMap.entries.toList(growable: false); - - for (int i = 0; i < fileMapEntries.length; i++) { - final MapEntry<String, MultipartFile> entry = fileMapEntries[i]; - final String indexString = i.toString(); - fileMapping.addAll(<String, List<String>>{ - indexString: <String>[entry.key], - }); - final MultipartFile f = entry.value; - fileList.add(MultipartFile( - indexString, - f.finalize(), - f.length, - contentType: f.contentType, - filename: f.filename, - )); - } - - r.fields['map'] = json.encode(fileMapping); - - r.files.addAll(fileList); - return r; -} - -HttpHeadersAndBody _selectHttpOptionsAndBody( - Operation operation, - HttpConfig fallbackConfig, [ - HttpConfig linkConfig, - HttpConfig contextConfig, -]) { - final Map<String, dynamic> options = <String, dynamic>{ - 'headers': <String, String>{}, - 'credentials': <String, dynamic>{}, - }; - final HttpQueryOptions http = HttpQueryOptions(); - - // http options - - // initialize with fallback http options - http.addAll(fallbackConfig.http); - - // inject the configured http options - if (linkConfig.http != null) { - http.addAll(linkConfig.http); - } - - // override with context http options - if (contextConfig.http != null) { - http.addAll(contextConfig.http); - } - - // options - - // initialize with fallback options - options.addAll(fallbackConfig.options); - - // inject the configured options - if (linkConfig.options != null) { - options.addAll(linkConfig.options); - } - - // override with context options - if (contextConfig.options != null) { - options.addAll(contextConfig.options); - } - - // headers - - // initialze with fallback headers - options['headers'].addAll(fallbackConfig.headers); - - // inject the configured headers - if (linkConfig.headers != null) { - options['headers'].addAll(linkConfig.headers); - } - - // inject the context headers - if (contextConfig.headers != null) { - options['headers'].addAll(contextConfig.headers); - } - - // credentials - - // initialze with fallback credentials - options['credentials'].addAll(fallbackConfig.credentials); - - // inject the configured credentials - if (linkConfig.credentials != null) { - options['credentials'].addAll(linkConfig.credentials); - } - - // inject the context credentials - if (contextConfig.credentials != null) { - options['credentials'].addAll(contextConfig.credentials); - } - - // the body depends on the http options - final Map<String, dynamic> body = <String, dynamic>{ - 'operationName': operation.operationName, - 'variables': operation.variables, - }; - - // not sending the query (i.e persisted queries) - if (http.includeExtensions) { - body['extensions'] = operation.extensions; - } - - if (http.includeQuery) { - body['query'] = printNode(operation.documentNode); - } - - return HttpHeadersAndBody( - headers: options['headers'] as Map<String, String>, - body: body, - ); -} - -Future<FetchResult> _parseResponse(StreamedResponse response) async { - final int statusCode = response.statusCode; - - final Encoding encoding = _determineEncodingFromResponse(response); - // @todo limit bodyBytes - final Uint8List responseByte = await response.stream.toBytes(); - final String decodedBody = encoding.decode(responseByte); - - Map<String, dynamic> jsonResponse; - try { - jsonResponse= json.decode(decodedBody) as Map<String, dynamic>; - }catch(e){ - throw ClientException('Invalid response body: $decodedBody'); - } - final FetchResult fetchResult = FetchResult(); - - if (jsonResponse['errors'] != null) { - fetchResult.errors = - (jsonResponse['errors'] as List<dynamic>).where(notNull).toList(); - } - - if (jsonResponse['data'] != null) { - fetchResult.data = jsonResponse['data']; - } - - if (fetchResult.data == null && fetchResult.errors == null) { - if (statusCode < 200 || statusCode >= 400) { - throw ClientException( - 'Network Error: $statusCode $decodedBody', - ); - } - throw ClientException('Invalid response body: $decodedBody'); - } - - return fetchResult; -} - -/// Returns the charset encoding for the given response. -/// -/// The default fallback encoding is set to UTF-8 according to the IETF RFC4627 standard -/// which specifies the application/json media type: -/// "JSON text SHALL be encoded in Unicode. The default encoding is UTF-8." -Encoding _determineEncodingFromResponse(BaseResponse response, - [Encoding fallback = utf8]) { - final String contentType = response.headers['content-type']; - - if (contentType == null) { - return fallback; - } - - final MediaType mediaType = MediaType.parse(contentType); - final String charset = mediaType.parameters['charset']; - - if (charset == null) { - return fallback; - } - - final Encoding encoding = Encoding.getByName(charset); - - return encoding == null ? fallback : encoding; -} diff --git a/packages/graphql/lib/src/link/http/link_http_helper_deprecated_io.dart b/packages/graphql/lib/src/link/http/link_http_helper_deprecated_io.dart deleted file mode 100644 index 8288e86bc..000000000 --- a/packages/graphql/lib/src/link/http/link_http_helper_deprecated_io.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'dart:io' as io; - -import 'package:http/http.dart'; -import 'package:graphql/src/utilities/file_io.dart' show multipartFileFrom; - -// @deprecated, backward compatible only -// in case the body is io.File -// in future release, io.File will no longer be supported -Future<Map<String, MultipartFile>> deprecatedHelper( - body, currentMap, currentPath) async { - if (body is io.File) { - return currentMap - ..addAll(<String, MultipartFile>{ - currentPath.join('.'): await multipartFileFrom(body) - }); - } - return null; -} - -bool isIoFile(object) { - final r = object is io.File; - if (r) { - print(r''' -⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️ DEPRECATION WARNING ⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️ - -Please do not use `File` direcly anymore. Instead, use -`MultipartFile`. There's also a utitlity method to help you -`import 'package:graphql/utilities.dart' show multipartFileFrom;` - -⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️ DEPRECATION WARNING ⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️ - '''); - } - return r; -} diff --git a/packages/graphql/lib/src/link/http/link_http_helper_deprecated_stub.dart b/packages/graphql/lib/src/link/http/link_http_helper_deprecated_stub.dart deleted file mode 100644 index 23c32aac8..000000000 --- a/packages/graphql/lib/src/link/http/link_http_helper_deprecated_stub.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:http/http.dart'; - -// @deprecated, backward compatible only -// in case the body is io.File -// in future release, io.File will no longer be supported -// but this stub is noop -Future<Map<String, MultipartFile>> deprecatedHelper( - body, currentMap, currentPath) async => - null; - -// @deprecated, backward compatible only -// in case the body is io.File -// in future release, io.File will no longer be supported -// but this stub always returns false -bool isIoFile(object) => false; diff --git a/packages/graphql/lib/src/link/link.dart b/packages/graphql/lib/src/link/link.dart deleted file mode 100644 index d51d5a82b..000000000 --- a/packages/graphql/lib/src/link/link.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'dart:async'; - -import 'package:graphql/src/link/fetch_result.dart'; -import 'package:graphql/src/link/operation.dart'; - -typedef NextLink = Stream<FetchResult> Function( - Operation operation, -); - -typedef RequestHandler = Stream<FetchResult> Function( - Operation operation, [ - NextLink forward, -]); - -Link _concat( - Link first, - Link second, -) { - return Link(request: ( - Operation operation, [ - NextLink forward, - ]) { - return first.request(operation, (Operation op) { - return second.request(op, forward); - }); - }); -} - -class Link { - Link({this.request}); - - RequestHandler request; - - static Link from(List<Link> links) { - assert(links.isNotEmpty); - return links.reduce((first, second) => first.concat(second)); - } - - Link concat(Link next) => _concat(this, next); -} - -Stream<FetchResult> execute({Link link, Operation operation}) => - link.request(operation); diff --git a/packages/graphql/lib/src/link/operation.dart b/packages/graphql/lib/src/link/operation.dart deleted file mode 100644 index b8bcd1c51..000000000 --- a/packages/graphql/lib/src/link/operation.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'package:gql/ast.dart'; - -import 'package:graphql/src/core/raw_operation_data.dart'; -import 'package:graphql/src/utilities/get_from_ast.dart'; - -class Operation extends RawOperationData { - Operation({ - @Deprecated('The "document" option has been deprecated, use "documentNode" instead') - String document, - DocumentNode documentNode, - Map<String, dynamic> variables, - this.extensions, - String operationName, - }) : super( - // ignore: deprecated_member_use_from_same_package - document: document, - documentNode: documentNode, - variables: variables, - operationName: operationName); - - factory Operation.fromOptions(RawOperationData options) { - return Operation( - documentNode: options.documentNode, - variables: options.variables, - ); - } - - final Map<String, dynamic> extensions; - - final Map<String, dynamic> _context = <String, dynamic>{}; - - /// Sets the context of an operation by merging the new context with the old one. - void setContext(Map<String, dynamic> next) { - if (next != null) { - _context.addAll(next); - } - } - - Map<String, dynamic> getContext() { - final Map<String, dynamic> result = <String, dynamic>{}; - result.addAll(_context); - - return result; - } - - bool get isSubscription => isOfType( - OperationType.subscription, - documentNode, - operationName, - ); -} diff --git a/packages/graphql/lib/src/link/web_socket/link_web_socket.dart b/packages/graphql/lib/src/link/web_socket/link_web_socket.dart deleted file mode 100644 index acfd0fd46..000000000 --- a/packages/graphql/lib/src/link/web_socket/link_web_socket.dart +++ /dev/null @@ -1,55 +0,0 @@ -import 'package:meta/meta.dart'; - -import 'package:graphql/src/link/fetch_result.dart'; -import 'package:graphql/src/link/link.dart'; -import 'package:graphql/src/link/operation.dart'; -import 'package:graphql/src/socket_client.dart'; -import 'package:graphql/src/websocket/messages.dart'; - -/// A Universal Websocket [Link] implementation to support the websocket transport. -/// It supports subscriptions, query and mutation operations as well. -/// -/// NOTE: the actual socket connection will only get established after an [Operation] is handled by this [WebSocketLink]. -/// If you'd like to connect to the socket server instantly, call the [connectOrReconnect] method after creating this [WebSocketLink] instance. -class WebSocketLink extends Link { - /// Creates a new [WebSocketLink] instance with the specified config. - WebSocketLink({ - @required this.url, - this.config = const SocketClientConfig(), - }) : super() { - request = _doOperation; - } - - final String url; - final SocketClientConfig config; - - // cannot be final because we're changing the instance upon a header change. - SocketClient _socketClient; - - Stream<FetchResult> _doOperation(Operation operation, [NextLink forward]) { - if (_socketClient == null) { - connectOrReconnect(); - } - - return _socketClient.subscribe(SubscriptionRequest(operation), true).map( - (SubscriptionData result) => FetchResult( - data: result.data, - errors: result.errors as List<dynamic>, - context: operation.getContext(), - extensions: operation.extensions), - ); - } - - /// Connects or reconnects to the server with the specified headers. - void connectOrReconnect() { - _socketClient?.dispose(); - _socketClient = SocketClient(url, config: config); - } - - /// Disposes the underlying socket client explicitly. Only use this, if you want to disconnect from - /// the current server in favour of another one. If that's the case, create a new [WebSocketLink] instance. - Future<void> dispose() async { - await _socketClient?.dispose(); - _socketClient = null; - } -} diff --git a/packages/graphql/lib/src/socket_client.dart b/packages/graphql/lib/src/socket_client.dart deleted file mode 100644 index 306257903..000000000 --- a/packages/graphql/lib/src/socket_client.dart +++ /dev/null @@ -1,383 +0,0 @@ -import 'dart:async'; -import 'dart:collection'; -import 'dart:convert'; -import 'dart:typed_data'; -import 'package:meta/meta.dart'; -import 'package:websocket/websocket.dart' show WebSocket, WebSocketStatus; - -import 'package:rxdart/rxdart.dart'; -import 'package:uuid_enhanced/uuid.dart'; - -import 'package:graphql/src/websocket/messages.dart'; - -typedef GetInitPayload = FutureOr<dynamic> Function(); - -class SocketClientConfig { - const SocketClientConfig({ - this.autoReconnect = true, - this.queryAndMutationTimeout = const Duration(seconds: 10), - this.inactivityTimeout = const Duration(seconds: 30), - this.delayBetweenReconnectionAttempts = const Duration(seconds: 5), - this.initPayload, - }); - - /// Whether to reconnect to the server after detecting connection loss. - final bool autoReconnect; - - /// The duration after which the connection is considered unstable, because no keep alive message - /// was received from the server in the given time-frame. The connection to the server will be closed. - /// If [autoReconnect] is set to true, we try to reconnect to the server after the specified [delayBetweenReconnectionAttempts]. - /// - /// If null, the keep alive messages will be ignored. - final Duration inactivityTimeout; - - /// The duration that needs to pass before trying to reconnect to the server after a connection loss. - /// This only takes effect when [autoReconnect] is set to true. - /// - /// If null, the reconnection will occur immediately, although not recommended. - final Duration delayBetweenReconnectionAttempts; - - // The duration after which a query or mutation should time out. - // If null, no timeout is applied, although not recommended. - final Duration queryAndMutationTimeout; - - /// The initial payload that will be sent to the server upon connection. - /// Can be null, but must be a valid json structure if provided. - final GetInitPayload initPayload; - - Future<InitOperation> get initOperation async { - if (initPayload != null) { - dynamic payload = await initPayload(); - return InitOperation(payload); - } - - return InitOperation(null); - } -} - -enum SocketConnectionState { NOT_CONNECTED, CONNECTING, CONNECTED } - -/// Wraps a standard web socket instance to marshal and un-marshal the server / -/// client payloads into dart object representation. -/// -/// This class also deals with reconnection, handles timeout and keep alive messages. -/// -/// It is meant to be instantiated once, and you can let this class handle all the heavy- -/// lifting of socket state management. Once you're done with the socket connection, make sure -/// you call the [dispose] method to release all allocated resources. -class SocketClient { - SocketClient( - this.url, { - this.protocols = const <String>[ - 'graphql-ws', - ], - this.config = const SocketClientConfig(), - @visibleForTesting this.randomBytesForUuid, - }) { - _connect(); - } - - Uint8List randomBytesForUuid; - final String url; - final SocketClientConfig config; - final Iterable<String> protocols; - final BehaviorSubject<SocketConnectionState> _connectionStateController = - BehaviorSubject<SocketConnectionState>(); - - final HashMap<String, Function> _subscriptionInitializers = HashMap(); - bool _connectionWasLost = false; - - Timer _reconnectTimer; - WebSocket _socket; - - @visibleForTesting - WebSocket get socket => _socket; - Stream<GraphQLSocketMessage> _messageStream; - - StreamSubscription<ConnectionKeepAlive> _keepAliveSubscription; - StreamSubscription<GraphQLSocketMessage> _messageSubscription; - - /// Connects to the server. - /// - /// If this instance is disposed, this method does nothing. - Future<void> _connect() async { - final InitOperation initOperation = await config.initOperation; - - if (_connectionStateController.isClosed) { - return; - } - - _connectionStateController.value = SocketConnectionState.CONNECTING; - print('Connecting to websocket: $url...'); - - try { - _socket = await WebSocket.connect( - url, - protocols: protocols, - ); - _connectionStateController.value = SocketConnectionState.CONNECTED; - print('Connected to websocket.'); - _write(initOperation); - - _messageStream = - _socket.stream.map<GraphQLSocketMessage>(_parseSocketMessage); - - if (config.inactivityTimeout != null) { - _keepAliveSubscription = _messagesOfType<ConnectionKeepAlive>().timeout( - config.inactivityTimeout, - onTimeout: (EventSink<ConnectionKeepAlive> event) { - print( - "Haven't received keep alive message for ${config.inactivityTimeout.inSeconds} seconds. Disconnecting.."); - event.close(); - _socket.close(WebSocketStatus.goingAway); - _connectionStateController.value = - SocketConnectionState.NOT_CONNECTED; - }, - ).listen(null); - } - - _messageSubscription = _messageStream.listen( - (dynamic data) { - // print('data: $data'); - }, - onDone: () { - // print('done'); - onConnectionLost(); - }, - cancelOnError: true, - onError: (dynamic e) { - print('error: $e'); - }); - - if (_connectionWasLost) { - for (Function callback in _subscriptionInitializers.values) { - callback(); - } - - _connectionWasLost = false; - } - } catch (e) { - onConnectionLost(e); - } - } - - void onConnectionLost([e]) { - if (e != null) { - print('There was an error causing connection lost: $e'); - } - print('Disconnected from websocket.'); - _reconnectTimer?.cancel(); - _keepAliveSubscription?.cancel(); - _messageSubscription?.cancel(); - - if (_connectionStateController.isClosed) { - return; - } - - _connectionWasLost = true; - - if (_connectionStateController.value != - SocketConnectionState.NOT_CONNECTED) { - _connectionStateController.value = SocketConnectionState.NOT_CONNECTED; - } - - if (config.autoReconnect && !_connectionStateController.isClosed) { - if (config.delayBetweenReconnectionAttempts != null) { - print( - 'Scheduling to connect in ${config.delayBetweenReconnectionAttempts.inSeconds} seconds...'); - - _reconnectTimer = Timer( - config.delayBetweenReconnectionAttempts, - () { - _connect(); - }, - ); - } else { - Timer.run(() => _connect()); - } - } - } - - /// Closes the underlying socket if connected, and stops reconnection attempts. - /// After calling this method, this [SocketClient] instance must be considered - /// unusable. Instead, create a new instance of this class. - /// - /// Use this method if you'd like to disconnect from the specified server permanently, - /// and you'd like to connect to another server instead of the current one. - Future<void> dispose() async { - print('Disposing socket client..'); - _reconnectTimer?.cancel(); - await Future.wait([ - _socket?.close(), - _keepAliveSubscription?.cancel(), - _messageSubscription?.cancel(), - _connectionStateController?.close(), - ]); - } - - static GraphQLSocketMessage _parseSocketMessage(dynamic message) { - final Map<String, dynamic> map = - json.decode(message as String) as Map<String, dynamic>; - final String type = (map['type'] ?? 'unknown') as String; - final dynamic payload = map['payload'] ?? <String, dynamic>{}; - final String id = (map['id'] ?? 'none') as String; - - switch (type) { - case MessageTypes.GQL_CONNECTION_ACK: - return ConnectionAck(); - case MessageTypes.GQL_CONNECTION_ERROR: - return ConnectionError(payload); - case MessageTypes.GQL_CONNECTION_KEEP_ALIVE: - return ConnectionKeepAlive(); - case MessageTypes.GQL_DATA: - final dynamic data = payload['data']; - final dynamic errors = payload['errors']; - return SubscriptionData(id, data, errors); - case MessageTypes.GQL_ERROR: - return SubscriptionError(id, payload); - case MessageTypes.GQL_COMPLETE: - return SubscriptionComplete(id); - default: - return UnknownData(map); - } - } - - void _write(final GraphQLSocketMessage message) { - if (_connectionStateController.value == SocketConnectionState.CONNECTED) { - _socket.add( - json.encode( - message, - toEncodable: (dynamic m) => m.toJson(), - ), - ); - } - } - - /// Sends a query, mutation or subscription request to the server, and returns a stream of the response. - /// - /// If the request is a query or mutation, a timeout will be applied to the request as specified by - /// [SocketClientConfig]'s [queryAndMutationTimeout] field. - /// - /// If the request is a subscription, obviously no timeout is applied. - /// - /// In case of socket disconnection, the returned stream will be closed. - Stream<SubscriptionData> subscribe( - final SubscriptionRequest payload, final bool waitForConnection) { - final String id = Uuid.randomUuid(random: randomBytesForUuid).toString(); - final StreamController<SubscriptionData> response = - StreamController<SubscriptionData>(); - StreamSubscription<SocketConnectionState> sub; - final bool addTimeout = !payload.operation.isSubscription && - config.queryAndMutationTimeout != null; - - final onListen = () { - final Stream<SocketConnectionState> waitForConnectedStateWithoutTimeout = - _connectionStateController - .startWith( - waitForConnection ? null : SocketConnectionState.CONNECTED) - .where((SocketConnectionState state) => - state == SocketConnectionState.CONNECTED) - .take(1); - - final Stream<SocketConnectionState> waitForConnectedState = addTimeout - ? waitForConnectedStateWithoutTimeout.timeout( - config.queryAndMutationTimeout, - onTimeout: (EventSink<SocketConnectionState> event) { - print('Connection timed out.'); - response.addError(TimeoutException('Connection timed out.')); - event.close(); - response.close(); - }, - ) - : waitForConnectedStateWithoutTimeout; - - sub = waitForConnectedState.listen((_) { - final Stream<GraphQLSocketMessage> dataErrorComplete = - _messageStream.where( - (GraphQLSocketMessage message) { - if (message is SubscriptionData) { - return message.id == id; - } - - if (message is SubscriptionError) { - return message.id == id; - } - - if (message is SubscriptionComplete) { - return message.id == id; - } - - return false; - }, - ).takeWhile((_) => !response.isClosed); - - final Stream<GraphQLSocketMessage> subscriptionComplete = addTimeout - ? dataErrorComplete - .where((GraphQLSocketMessage message) => - message is SubscriptionComplete) - .take(1) - .timeout( - config.queryAndMutationTimeout, - onTimeout: (EventSink<GraphQLSocketMessage> event) { - print('Request timed out.'); - response.addError(TimeoutException('Request timed out.')); - event.close(); - response.close(); - }, - ) - : dataErrorComplete - .where((GraphQLSocketMessage message) => - message is SubscriptionComplete) - .take(1); - - subscriptionComplete.listen((_) => response.close()); - - dataErrorComplete - .where( - (GraphQLSocketMessage message) => message is SubscriptionData) - .cast<SubscriptionData>() - .listen((SubscriptionData message) => response.add(message)); - - dataErrorComplete - .where( - (GraphQLSocketMessage message) => message is SubscriptionError) - .listen( - (GraphQLSocketMessage message) => response.addError(message)); - - _write(StartOperation(id, payload)); - }); - }; - - response.onListen = onListen; - - response.onCancel = () { - _subscriptionInitializers.remove(id); - - sub?.cancel(); - if (_connectionStateController.value == SocketConnectionState.CONNECTED && - _socket != null) { - _write(StopOperation(id)); - } - }; - - _subscriptionInitializers[id] = onListen; - - return response.stream; - } - - /// These streams will emit done events when the current socket is done. - - /// A stream that emits the last value of the connection state upon subscription. - Stream<SocketConnectionState> get connectionState => - _connectionStateController.stream; - - /// Filter `_messageStream` for messages of the given type of [GraphQLSocketMessage] - /// - /// Example usages: - /// `_messagesOfType<ConnectionAck>()` for init acknowledgments - /// `_messagesOfType<ConnectionError>()` for errors - /// `_messagesOfType<UnknownData>()` for unknown data messages - Stream<M> _messagesOfType<M extends GraphQLSocketMessage>() => _messageStream - .where((GraphQLSocketMessage message) => message is M) - .cast<M>(); -} diff --git a/packages/graphql/lib/src/utilities/file.dart b/packages/graphql/lib/src/utilities/file.dart deleted file mode 100644 index 1ebf5ff6c..000000000 --- a/packages/graphql/lib/src/utilities/file.dart +++ /dev/null @@ -1,3 +0,0 @@ -export './file_stub.dart' - if (dart.library.html) './file_html.dart' - if (dart.library.io) './file_io.dart'; diff --git a/packages/graphql/lib/src/utilities/file_html.dart b/packages/graphql/lib/src/utilities/file_html.dart deleted file mode 100644 index c10ea16bf..000000000 --- a/packages/graphql/lib/src/utilities/file_html.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'dart:async'; -import 'dart:html' as html; -import 'package:http/http.dart'; -import 'package:http_parser/http_parser.dart'; - -Stream<List<int>> _readFile(html.File file) { - final reader = html.FileReader(); - final streamController = StreamController<List<int>>(); - - reader.onLoad.listen((_) { - // streamController.add(reader.result); - streamController.close(); - }); - - reader.onError.listen((error) => streamController.addError(error)); - - reader.readAsArrayBuffer(file); - - return streamController.stream; -} - -MultipartFile multipartFileFrom(html.File f) => MultipartFile( - '', - _readFile(f), - f.size, - contentType: MediaType.parse(f.type), - filename: f.name, - ); diff --git a/packages/graphql/lib/src/utilities/file_io.dart b/packages/graphql/lib/src/utilities/file_io.dart deleted file mode 100644 index 0ee0a00f2..000000000 --- a/packages/graphql/lib/src/utilities/file_io.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'dart:async'; -import 'dart:io' as io; -import 'package:http/http.dart'; -import 'package:http_parser/http_parser.dart'; -import 'package:mime/mime.dart'; -import 'package:path/path.dart'; - -MediaType contentType(f) { - final a = lookupMimeType(f.path); - if (a == null) { - return null; - } - final b = MediaType.parse(a); - return b; -} - -Future<MultipartFile> multipartFileFrom(io.File f) async => MultipartFile( - '', - f.openRead(), - await f.length(), - contentType: contentType(f), - filename: basename(f.path), - ); diff --git a/packages/graphql/lib/src/utilities/file_stub.dart b/packages/graphql/lib/src/utilities/file_stub.dart deleted file mode 100644 index f9ea1c01c..000000000 --- a/packages/graphql/lib/src/utilities/file_stub.dart +++ /dev/null @@ -1,6 +0,0 @@ -import 'dart:async'; - -import 'package:http/http.dart'; - -FutureOr<MultipartFile> multipartFileFrom(/*io.File or html.File*/ f) => - throw UnsupportedError('io or html'); diff --git a/packages/graphql/lib/src/websocket/messages.dart b/packages/graphql/lib/src/websocket/messages.dart deleted file mode 100644 index c426b78ae..000000000 --- a/packages/graphql/lib/src/websocket/messages.dart +++ /dev/null @@ -1,224 +0,0 @@ -import 'dart:convert'; - -import 'package:gql/language.dart'; -import 'package:graphql/src/link/operation.dart'; - -/// These messages represent the structures used for Client-server communication -/// in a GraphQL web-socket subscription. Each message is represented in a JSON -/// format where the data type is denoted by the `type` field. - -/// A list of constants used for identifying message types -class MessageTypes { - MessageTypes._(); - - // client connections - static const String GQL_CONNECTION_INIT = 'connection_init'; - static const String GQL_CONNECTION_TERMINATE = 'connection_terminate'; - - // server connections - static const String GQL_CONNECTION_ACK = 'connection_ack'; - static const String GQL_CONNECTION_ERROR = 'connection_error'; - static const String GQL_CONNECTION_KEEP_ALIVE = 'ka'; - - // client operations - static const String GQL_START = 'start'; - static const String GQL_STOP = 'stop'; - - // server operations - static const String GQL_DATA = 'data'; - static const String GQL_ERROR = 'error'; - static const String GQL_COMPLETE = 'complete'; - - // default tag for use in identifying issues - static const String GQL_UNKNOWN = 'unknown'; -} - -abstract class JsonSerializable { - Map<String, dynamic> toJson(); - - @override - String toString() => toJson().toString(); -} - -/// Base type for representing a server-client subscription message. -abstract class GraphQLSocketMessage extends JsonSerializable { - GraphQLSocketMessage(this.type); - - final String type; -} - -/// After establishing a connection with the server, the client will -/// send this message to tell the server that it is ready to begin sending -/// new subscription queries. -class InitOperation extends GraphQLSocketMessage { - InitOperation(this.payload) : super(MessageTypes.GQL_CONNECTION_INIT); - - final dynamic payload; - - @override - Map<String, dynamic> toJson() { - final Map<String, dynamic> jsonMap = <String, dynamic>{}; - jsonMap['type'] = type; - - if (payload != null) { - jsonMap['payload'] = payload; - } - - return jsonMap; - } -} - -/// Represent the payload used during a Start query operation. -/// The operationName should match one of the top level query definitions -/// defined in the query provided. Additional variables can be provided -/// and sent to the server for processing. -class SubscriptionRequest extends JsonSerializable { - SubscriptionRequest(this.operation); - final Operation operation; - - @override - Map<String, dynamic> toJson() => <String, dynamic>{ - 'operationName': operation.operationName, - 'query': printNode(operation.documentNode), - 'variables': operation.variables, - }; -} - -/// A message to tell the server to create a subscription. The contents of the -/// query will be defined by the payload request. The id provided will be used -/// to tag messages such that they can be identified for this subscription -/// instance. id values should be unique and not be re-used during the lifetime -/// of the server. -class StartOperation extends GraphQLSocketMessage { - StartOperation(this.id, this.payload) : super(MessageTypes.GQL_START); - - final String id; - final SubscriptionRequest payload; - - @override - Map<String, dynamic> toJson() => <String, dynamic>{ - 'type': type, - 'id': id, - 'payload': payload, - }; -} - -/// Tell the server to stop sending subscription data for a particular -/// subscription instance. See [StartOperation]. -class StopOperation extends GraphQLSocketMessage { - StopOperation(this.id) : super(MessageTypes.GQL_STOP); - - final String id; - - @override - Map<String, dynamic> toJson() => <String, dynamic>{ - 'type': type, - 'id': id, - }; -} - -/// The server will send this acknowledgment message after receiving the init -/// command from the client if the init was successful. -class ConnectionAck extends GraphQLSocketMessage { - ConnectionAck() : super(MessageTypes.GQL_CONNECTION_ACK); - - @override - Map<String, dynamic> toJson() => <String, dynamic>{ - 'type': type, - }; -} - -/// The server will send this error message after receiving the init command -/// from the client if the init was not successful. -class ConnectionError extends GraphQLSocketMessage { - ConnectionError(this.payload) : super(MessageTypes.GQL_CONNECTION_ERROR); - - final dynamic payload; - - @override - Map<String, dynamic> toJson() => <String, dynamic>{ - 'type': type, - 'payload': payload, - }; -} - -/// The server will send this message to keep the connection alive -class ConnectionKeepAlive extends GraphQLSocketMessage { - ConnectionKeepAlive() : super(MessageTypes.GQL_CONNECTION_KEEP_ALIVE); - - @override - Map<String, dynamic> toJson() => <String, dynamic>{ - 'type': type, - }; -} - -/// Data sent from the server to the client with subscription data or error -/// payload. The user should check the errors result before processing the -/// data value. These error are from the query resolvers. -class SubscriptionData extends GraphQLSocketMessage { - SubscriptionData(this.id, this.data, this.errors) - : super(MessageTypes.GQL_DATA); - - final String id; - final dynamic data; - final dynamic errors; - - @override - Map<String, dynamic> toJson() => <String, dynamic>{ - 'type': type, - 'data': data, - 'errors': errors, - }; - - @override - int get hashCode => toJson().hashCode; - - @override - bool operator ==(dynamic other) => - other is SubscriptionData && jsonEncode(other) == jsonEncode(this); -} - -/// Errors sent from the server to the client if the subscription operation was -/// not successful, usually due to GraphQL validation errors. -class SubscriptionError extends GraphQLSocketMessage { - SubscriptionError(this.id, this.payload) : super(MessageTypes.GQL_ERROR); - - final String id; - final dynamic payload; - - @override - Map<String, dynamic> toJson() => <String, dynamic>{ - 'type': type, - 'id': id, - 'payload': payload, - }; -} - -/// Server message to the client to indicate that no more data will be sent -/// for a particular subscription instance. -class SubscriptionComplete extends GraphQLSocketMessage { - SubscriptionComplete(this.id) : super(MessageTypes.GQL_COMPLETE); - - final String id; - - @override - Map<String, dynamic> toJson() => <String, dynamic>{ - 'type': type, - 'id': id, - }; -} - -/// Not expected to be created. Indicates there are problems parsing the server -/// response, or that new unsupported types have been added to the subscription -/// implementation. -class UnknownData extends GraphQLSocketMessage { - UnknownData(this.payload) : super(MessageTypes.GQL_UNKNOWN); - - final dynamic payload; - - @override - Map<String, dynamic> toJson() => <String, dynamic>{ - 'type': type, - 'payload': payload, - }; -} diff --git a/packages/graphql/lib/utilities.dart b/packages/graphql/lib/utilities.dart deleted file mode 100644 index dab0e3aff..000000000 --- a/packages/graphql/lib/utilities.dart +++ /dev/null @@ -1 +0,0 @@ -export 'package:graphql/src/utilities/file.dart' show multipartFileFrom; diff --git a/packages/graphql/pubspec.yaml b/packages/graphql/pubspec.yaml index 73c069193..c0b3cf979 100644 --- a/packages/graphql/pubspec.yaml +++ b/packages/graphql/pubspec.yaml @@ -10,14 +10,10 @@ authors: homepage: https://github.com/zino-app/graphql-flutter/tree/master/packages/graphql dependencies: meta: ^1.1.6 - http: ^0.12.0+4 - mime: ^0.9.6+2 path: ^1.6.2 - http_parser: ^3.1.3 - uuid_enhanced: ^3.0.2 gql: ^0.12.0 - rxdart: ^0.23.1 - websocket: ^0.0.5 + gql_exec: ^0.2.2 + gql_link: ^0.2.3 quiver: '>=2.0.0 <3.0.0' dev_dependencies: pedantic: ^1.8.0+1 diff --git a/packages/graphql/test/anonymous_operations_test.dart b/packages/graphql/test/anonymous_operations_test.dart index ea65d3e36..3dbcdca6f 100644 --- a/packages/graphql/test/anonymous_operations_test.dart +++ b/packages/graphql/test/anonymous_operations_test.dart @@ -1,13 +1,14 @@ import 'package:gql/language.dart'; +import 'package:gql_exec/gql_exec.dart'; +import 'package:gql_link/gql_link.dart'; import 'package:test/test.dart'; import 'package:mockito/mockito.dart'; -import 'package:http/http.dart' as http; import 'package:graphql/client.dart'; import './helpers.dart'; -class MockHttpClient extends Mock implements http.Client {} +class MockLink extends Mock implements Link {} void main() { const String readRepositories = r'''{ @@ -33,86 +34,77 @@ void main() { } '''; - HttpLink httpLink; - AuthLink authLink; - Link link; + MockLink link; GraphQLClient graphQLClientClient; - MockHttpClient mockHttpClient; + group('simple json', () { setUp(() { - mockHttpClient = MockHttpClient(); - - httpLink = HttpLink( - uri: 'https://api.github.com/graphql', httpClient: mockHttpClient); - - authLink = AuthLink( - getToken: () async => 'Bearer my-special-bearer-token', - ); - - link = authLink.concat(httpLink); + link = MockLink(); graphQLClientClient = GraphQLClient( cache: getTestCache(), link: link, ); }); + group('query', () { test('successful query', () async { final WatchQueryOptions _options = WatchQueryOptions( documentNode: parseString(readRepositories), variables: <String, dynamic>{}, ); + when( - mockHttpClient.send(any), - ).thenAnswer((Invocation a) async { - return simpleResponse(body: r''' -{ - "data": { - "viewer": { - "repositories": { - "nodes": [ - { - "__typename": "Repository", - "id": "MDEwOlJlcG9zaXRvcnkyNDgzOTQ3NA==", - "name": "pq", - "viewerHasStarred": false - }, - { - "__typename": "Repository", - "id": "MDEwOlJlcG9zaXRvcnkzMjkyNDQ0Mw==", - "name": "go-evercookie", - "viewerHasStarred": false - }, - { - "__typename": "Repository", - "id": "MDEwOlJlcG9zaXRvcnkzNTA0NjgyNA==", - "name": "watchbot", - "viewerHasStarred": false - } - ] - } - } - } -} - '''); - }); + link.request(any), + ).thenAnswer( + (_) => Stream.fromIterable( + [ + Response( + data: <String, dynamic>{ + 'viewer': { + 'repositories': { + 'nodes': [ + { + '__typename': 'Repository', + 'id': 'MDEwOlJlcG9zaXRvcnkyNDgzOTQ3NA==', + 'name': 'pq', + 'viewerHasStarred': false, + }, + { + '__typename': 'Repository', + 'id': 'MDEwOlJlcG9zaXRvcnkzMjkyNDQ0Mw==', + 'name': 'go-evercookie', + 'viewerHasStarred': false, + }, + { + '__typename': 'Repository', + 'id': 'MDEwOlJlcG9zaXRvcnkzNTA0NjgyNA==', + 'name': 'watchbot', + 'viewerHasStarred': false, + }, + ], + }, + }, + }, + ), + ], + ), + ); + final QueryResult r = await graphQLClientClient.query(_options); - final http.Request capt = verify(mockHttpClient.send(captureAny)) - .captured - .first as http.Request; - expect(capt.method, 'post'); - expect(capt.url.toString(), 'https://api.github.com/graphql'); - expect( - capt.headers, - <String, String>{ - 'accept': '*/*', - 'content-type': 'application/json; charset=utf-8', - 'Authorization': 'Bearer my-special-bearer-token', - }, + verify( + link.request( + Request( + operation: Operation( + document: parseString(readRepositories), + operationName: null, + ), + variables: <String, dynamic>{}, + context: Context(), + ), + ), ); - expect(await capt.finalize().bytesToString(), - r'{"operationName":null,"variables":{},"query":"query {\n viewer {\n repositories(last: 42) {\n nodes {\n __typename\n id\n name\n viewerHasStarred\n }\n }\n }\n}"}'); expect(r.exception, isNull); expect(r.data, isNotNull); @@ -136,30 +128,41 @@ void main() { }); group('mutation', () { test('successful mutation', () async { - final MutationOptions _options = - MutationOptions(documentNode: parseString(addStar)); - when(mockHttpClient.send(any)).thenAnswer((Invocation a) async => - simpleResponse( - body: - '{"data":{"action":{"starrable":{"viewerHasStarred":true}}}}')); + final MutationOptions _options = MutationOptions( + documentNode: parseString(addStar), + ); + + when( + link.request(any), + ).thenAnswer( + (_) => Stream.fromIterable( + [ + Response( + data: <String, dynamic>{ + 'action': { + 'starrable': { + 'viewerHasStarred': true, + }, + }, + }, + ), + ], + ), + ); final QueryResult response = await graphQLClientClient.mutate(_options); - final http.Request request = verify(mockHttpClient.send(captureAny)) - .captured - .first as http.Request; - expect(request.method, 'post'); - expect(request.url.toString(), 'https://api.github.com/graphql'); - expect( - request.headers, - <String, String>{ - 'accept': '*/*', - 'content-type': 'application/json; charset=utf-8', - 'Authorization': 'Bearer my-special-bearer-token', - }, + verify( + link.request( + Request( + operation: Operation( + document: parseString(addStar), + ), + variables: <String, dynamic>{}, + context: Context(), + ), + ), ); - expect(await request.finalize().bytesToString(), - r'{"operationName":null,"variables":{},"query":"mutation {\n action: addStar(input: {starrableId: \"some_repo\"}) {\n starrable {\n viewerHasStarred\n }\n }\n}"}'); expect(response.exception, isNull); expect(response.data, isNotNull); diff --git a/packages/graphql/test/graphql_client_test.dart b/packages/graphql/test/graphql_client_test.dart index 19d67f1f2..19e983f4e 100644 --- a/packages/graphql/test/graphql_client_test.dart +++ b/packages/graphql/test/graphql_client_test.dart @@ -1,13 +1,14 @@ +import 'package:gql_exec/gql_exec.dart'; +import 'package:gql_link/gql_link.dart'; import 'package:test/test.dart'; import 'package:mockito/mockito.dart'; -import 'package:http/http.dart' as http; import 'package:graphql/client.dart'; import 'package:gql/language.dart'; import './helpers.dart'; -class MockHttpClient extends Mock implements http.Client {} +class MockLink extends Mock implements Link {} void main() { const String readRepositories = r''' @@ -35,88 +36,81 @@ void main() { } '''; - HttpLink httpLink; - AuthLink authLink; - Link link; + MockLink link; GraphQLClient graphQLClientClient; - MockHttpClient mockHttpClient; + group('simple json', () { setUp(() { - mockHttpClient = MockHttpClient(); - - httpLink = HttpLink( - uri: 'https://api.github.com/graphql', httpClient: mockHttpClient); - - authLink = AuthLink( - getToken: () async => 'Bearer my-special-bearer-token', - ); - - link = authLink.concat(httpLink); + link = MockLink(); graphQLClientClient = GraphQLClient( cache: getTestCache(), link: link, ); }); + group('query', () { - test('successful query', () async { + test('successful response', () async { final WatchQueryOptions _options = WatchQueryOptions( documentNode: parseString(readRepositories), variables: <String, dynamic>{ 'nRepositories': 42, }, ); + when( - mockHttpClient.send(any), - ).thenAnswer((Invocation a) async { - return simpleResponse(body: r''' -{ - "data": { - "viewer": { - "repositories": { - "nodes": [ - { - "__typename": "Repository", - "id": "MDEwOlJlcG9zaXRvcnkyNDgzOTQ3NA==", - "name": "pq", - "viewerHasStarred": false - }, - { - "__typename": "Repository", - "id": "MDEwOlJlcG9zaXRvcnkzMjkyNDQ0Mw==", - "name": "go-evercookie", - "viewerHasStarred": false - }, - { - "__typename": "Repository", - "id": "MDEwOlJlcG9zaXRvcnkzNTA0NjgyNA==", - "name": "watchbot", - "viewerHasStarred": false - } - ] - } - } - } -} - '''); - }); + link.request(any), + ).thenAnswer( + (_) => Stream.fromIterable( + [ + Response( + data: <String, dynamic>{ + 'viewer': { + 'repositories': { + 'nodes': [ + { + '__typename': 'Repository', + 'id': 'MDEwOlJlcG9zaXRvcnkyNDgzOTQ3NA==', + 'name': 'pq', + 'viewerHasStarred': false, + }, + { + '__typename': 'Repository', + 'id': 'MDEwOlJlcG9zaXRvcnkzMjkyNDQ0Mw==', + 'name': 'go-evercookie', + 'viewerHasStarred': false, + }, + { + '__typename': 'Repository', + 'id': 'MDEwOlJlcG9zaXRvcnkzNTA0NjgyNA==', + 'name': 'watchbot', + 'viewerHasStarred': false, + }, + ], + }, + }, + }, + ), + ], + ), + ); + final QueryResult r = await graphQLClientClient.query(_options); - final http.Request capt = verify(mockHttpClient.send(captureAny)) - .captured - .first as http.Request; - expect(capt.method, 'post'); - expect(capt.url.toString(), 'https://api.github.com/graphql'); - expect( - capt.headers, - <String, String>{ - 'accept': '*/*', - 'content-type': 'application/json; charset=utf-8', - 'Authorization': 'Bearer my-special-bearer-token', - }, + verify( + link.request( + Request( + operation: Operation( + document: parseString(readRepositories), + operationName: 'ReadRepositories', + ), + variables: <String, dynamic>{ + 'nRepositories': 42, + }, + context: Context(), + ), + ), ); - expect(await capt.finalize().bytesToString(), - r'{"operationName":"ReadRepositories","variables":{"nRepositories":42},"query":"query ReadRepositories($nRepositories: Int!) {\n viewer {\n repositories(last: $nRepositories) {\n nodes {\n __typename\n id\n name\n viewerHasStarred\n }\n }\n }\n}"}'); expect(r.exception, isNull); expect(r.data, isNotNull); @@ -132,15 +126,23 @@ void main() { test('failed query because of an exception with null string', () async { final e = Exception(); - when(mockHttpClient.send(any)).thenAnswer((_) async { - throw e; - }); + + when( + link.request(any), + ).thenAnswer( + (_) => Stream.fromFuture(Future.error(e)), + ); final QueryResult r = await graphQLClientClient.query( - WatchQueryOptions(documentNode: parseString(readRepositories))); + WatchQueryOptions( + documentNode: parseString(readRepositories), + ), + ); - expect((r.exception.clientException as UnhandledFailureWrapper).failure, - e); + expect( + (r.exception.clientException as UnhandledFailureWrapper).failure, + e, + ); return; }); @@ -148,12 +150,17 @@ void main() { test('failed query because of an exception with empty string', () async { final e = Exception(''); - when(mockHttpClient.send(any)).thenAnswer((_) async { - throw e; - }); + when( + link.request(any), + ).thenAnswer( + (_) => Stream.fromFuture(Future.error(e)), + ); final QueryResult r = await graphQLClientClient.query( - WatchQueryOptions(documentNode: parseString(readRepositories))); + WatchQueryOptions( + documentNode: parseString(readRepositories), + ), + ); expect( (r.exception.clientException as UnhandledFailureWrapper).failure, @@ -172,30 +179,42 @@ void main() { }); group('mutation', () { test('successful mutation', () async { - final MutationOptions _options = - MutationOptions(documentNode: parseString(addStar)); - when(mockHttpClient.send(any)).thenAnswer((Invocation a) async => - simpleResponse( - body: - '{"data":{"action":{"starrable":{"viewerHasStarred":true}}}}')); + final MutationOptions _options = MutationOptions( + documentNode: parseString(addStar), + ); + + when( + link.request(any), + ).thenAnswer( + (_) => Stream.fromIterable( + [ + Response( + data: <String, dynamic>{ + 'action': { + 'starrable': { + 'viewerHasStarred': true, + }, + }, + }, + ), + ], + ), + ); final QueryResult response = await graphQLClientClient.mutate(_options); - final http.Request request = verify(mockHttpClient.send(captureAny)) - .captured - .first as http.Request; - expect(request.method, 'post'); - expect(request.url.toString(), 'https://api.github.com/graphql'); - expect( - request.headers, - <String, String>{ - 'accept': '*/*', - 'content-type': 'application/json; charset=utf-8', - 'Authorization': 'Bearer my-special-bearer-token', - }, + verify( + link.request( + Request( + operation: Operation( + document: parseString(addStar), + operationName: 'AddStar', + ), + variables: <String, dynamic>{}, + context: Context(), + ), + ), ); - expect(await request.finalize().bytesToString(), - r'{"operationName":"AddStar","variables":{},"query":"mutation AddStar($starrableId: ID!) {\n action: addStar(input: {starrableId: $starrableId}) {\n starrable {\n viewerHasStarred\n }\n }\n}"}'); expect(response.exception, isNull); expect(response.data, isNotNull); diff --git a/packages/graphql/test/helpers.dart b/packages/graphql/test/helpers.dart index 5c51940a5..04aeca0cb 100644 --- a/packages/graphql/test/helpers.dart +++ b/packages/graphql/test/helpers.dart @@ -1,8 +1,4 @@ import 'dart:async'; -import 'dart:convert'; - -import 'package:meta/meta.dart'; -import 'package:http/http.dart' as http; import 'package:graphql/client.dart'; @@ -17,13 +13,3 @@ overridePrint(testFn(List<String> log)) => () { NormalizedInMemoryCache getTestCache() => NormalizedInMemoryCache( dataIdFromObject: typenameDataIdFromObject, ); - -http.StreamedResponse simpleResponse({@required String body, int status}) { - final List<int> bytes = utf8.encode(body); - final Stream<List<int>> stream = - Stream<List<int>>.fromIterable(<List<int>>[bytes]); - - final http.StreamedResponse r = http.StreamedResponse(stream, status ?? 200); - - return r; -} diff --git a/packages/graphql/test/link/error/link_error_test.dart b/packages/graphql/test/link/error/link_error_test.dart deleted file mode 100644 index bcf56b35e..000000000 --- a/packages/graphql/test/link/error/link_error_test.dart +++ /dev/null @@ -1,110 +0,0 @@ -import "dart:async"; -import "dart:convert"; - -import 'package:gql/language.dart'; -import 'package:graphql/src/exceptions/exceptions.dart'; -import 'package:graphql/src/link/error/link_error.dart'; -import 'package:graphql/src/link/http/link_http.dart'; -import 'package:graphql/src/link/link.dart'; -import 'package:graphql/src/link/operation.dart'; -import "package:http/http.dart" as http; -import "package:mockito/mockito.dart"; -import "package:test/test.dart"; - -class MockClient extends Mock implements http.Client {} - -void main() { - group('error link', () { - MockClient client; - Operation query; - HttpLink httpLink; - - setUp(() { - client = MockClient(); - query = Operation( - documentNode: parseString('query Operation {}'), - operationName: 'Operation', - ); - httpLink = HttpLink( - uri: '/graphql-test', - httpClient: client, - ); - }); - - test('network error', () async { - bool called = false; - - when( - client.send(any), - ).thenAnswer( - (_) => Future.value( - http.StreamedResponse( - Stream.fromIterable( - [utf8.encode('{}')], - ), - 400, - ), - ), - ); - - final errorLink = ErrorLink(errorHandler: (response) { - if (response.exception.clientException != null) { - called = true; - } - }); - - Exception exception; - - try { - await execute( - link: errorLink.concat(httpLink), - operation: query, - ).first; - } on Exception catch (e) { - exception = e; - } - - expect( - exception, - const TypeMatcher<ClientException>(), - ); - expect( - called, - true, - ); - }); - - test('graphql error', () async { - bool called = false; - - when( - client.send(any), - ).thenAnswer( - (_) => Future.value( - http.StreamedResponse( - Stream.fromIterable( - [utf8.encode('{"errors":[{"message":"error"}]}')], - ), - 200, - ), - ), - ); - - final errorLink = ErrorLink(errorHandler: (response) { - if (response.exception.graphqlErrors != null) { - called = true; - } - }); - - await execute( - link: errorLink.concat(httpLink), - operation: query, - ).first; - - expect( - called, - true, - ); - }); - }); -} diff --git a/packages/graphql/test/link/http/link_http_test.dart b/packages/graphql/test/link/http/link_http_test.dart deleted file mode 100644 index b6e35ba4a..000000000 --- a/packages/graphql/test/link/http/link_http_test.dart +++ /dev/null @@ -1,543 +0,0 @@ -import "dart:async"; -import "dart:convert"; - -import 'package:gql/language.dart'; -import 'package:graphql/client.dart'; -import 'package:graphql/internal.dart'; -import 'package:graphql/src/link/http/link_http.dart'; -import 'package:graphql/src/link/link.dart'; -import 'package:graphql/src/link/operation.dart'; -import "package:http/http.dart" as http; -import 'package:http_parser/http_parser.dart'; -import "package:mockito/mockito.dart"; -import "package:test/test.dart"; - -class MockClient extends Mock implements http.Client {} - -void main() { - group('HTTP link', () { - MockClient client; - Operation query; - Operation subscription; - HttpLink link; - - setUp(() { - client = MockClient(); - query = Operation( - documentNode: parseString('query Operation {}'), - operationName: 'Operation', - ); - subscription = Operation( - documentNode: parseString('subscription Operation {}'), - operationName: 'Operation', - ); - link = HttpLink( - uri: '/graphql-test', - httpClient: client, - ); - }); - - test('exception on subscription', () { - expect( - () => execute(link: link, operation: subscription), - throwsA( - const TypeMatcher<Exception>(), - ), - ); - }); - - test('forward on subscription', () { - bool forwardCalled = false; - - final forwardLink = Link( - request: (Operation op, [NextLink forward]) { - forwardCalled = true; - - return null; - }, - ); - expect( - execute( - link: link.concat(forwardLink), - operation: subscription, - ), - null, - ); - - expect( - forwardCalled, - true, - ); - }); - - test('request', () async { - when( - client.send(any), - ).thenAnswer( - (_) => Future.value( - http.StreamedResponse( - Stream.fromIterable( - [utf8.encode('{"data":{}}')], - ), - 200, - ), - ), - ); - - await execute( - link: link, - operation: query, - ).first; - - final http.Request captured = verify( - client.send(captureAny), - ).captured.single; - - expect( - captured.url, - Uri.parse('/graphql-test'), - ); - expect( - captured.method, - 'post', - ); - expect( - captured.headers, - equals({ - 'accept': '*/*', - 'content-type': 'application/json; charset=utf-8', - }), - ); - expect( - captured.body, - '{"operationName":"Operation","variables":{},"query":"query Operation {\\n \\n}"}', - ); - }); - - test('request with link defaults', () async { - link = HttpLink( - uri: '/graphql-test', - httpClient: client, - includeExtensions: true, - fetchOptions: {'option-1:default': 'option-value-1:default'}, - credentials: {'credential-1:default': 'credential-value-1:default'}, - headers: {'header-1:default': 'header-value-1:default'}, - ); - - when( - client.send(any), - ).thenAnswer( - (_) => Future.value( - http.StreamedResponse( - Stream.fromIterable( - [utf8.encode('{"data":{}}')], - ), - 200, - ), - ), - ); - - await execute( - link: link, - operation: query, - ).first; - - final http.Request captured = verify( - client.send(captureAny), - ).captured.single; - - expect( - captured.url, - Uri.parse('/graphql-test'), - ); - expect( - captured.method, - 'post', - ); - expect( - captured.headers, - equals({ - 'accept': '*/*', - 'content-type': 'application/json; charset=utf-8', - 'header-1:default': 'header-value-1:default', - }), - ); - expect( - captured.body, - '{"operationName":"Operation","variables":{},"extensions":null,"query":"query Operation {\\n \\n}"}', - ); - }); - - test('request with context', () async { - when( - client.send(any), - ).thenAnswer( - (_) => Future.value( - http.StreamedResponse( - Stream.fromIterable( - [utf8.encode('{"data":{}}')], - ), - 200, - ), - ), - ); - - query.setContext({ - 'includeExtensions': true, - 'fetchOptions': {'option-1': 'option-value-1'}, - 'credentials': {'credential-1': 'credential-value-1'}, - 'headers': {'header-1': 'header-value-1'}, - }); - - await execute( - link: link, - operation: query, - ).first; - - final http.Request captured = verify( - client.send(captureAny), - ).captured.single; - - expect( - captured.url, - Uri.parse('/graphql-test'), - ); - expect( - captured.method, - 'post', - ); - expect( - captured.headers, - equals({ - 'accept': '*/*', - 'content-type': 'application/json; charset=utf-8', - 'header-1': 'header-value-1', - }), - ); - expect( - captured.body, - '{"operationName":"Operation","variables":{},"extensions":null,"query":"query Operation {\\n \\n}"}', - ); - }); - - test('request with extensions', () async { - when( - client.send(any), - ).thenAnswer( - (_) => Future.value( - http.StreamedResponse( - Stream.fromIterable( - [utf8.encode('{"data":{}}')], - ), - 200, - ), - ), - ); - - final query = Operation( - documentNode: parseString('{}'), - extensions: {'extension-1': 'extension-value-1'}, - ); - query.setContext({ - 'includeExtensions': true, - }); - - await execute( - link: link, - operation: query, - ).first; - - final http.Request captured = verify( - client.send(captureAny), - ).captured.single; - - expect( - captured.body, - '{"operationName":null,"variables":{},"extensions":{"extension-1":"extension-value-1"},"query":"query {\\n \\n}"}', - ); - }); - - test('successful data response', () async { - when( - client.send(any), - ).thenAnswer( - (_) => Future.value( - http.StreamedResponse( - Stream.fromIterable( - [utf8.encode('{"data":{}}')], - ), - 200, - ), - ), - ); - - final result = await execute( - link: link, - operation: query, - ).first; - - expect( - result.data, - equals({}), - ); - expect( - result.errors, - null, - ); - }); - - test('successful error response', () async { - when( - client.send(any), - ).thenAnswer( - (_) => Future.value( - http.StreamedResponse( - Stream.fromIterable( - [utf8.encode('{"errors":[]}')], - ), - 200, - ), - ), - ); - - final result = await execute( - link: link, - operation: query, - ).first; - - expect( - result.errors, - equals([]), - ); - expect( - result.data, - null, - ); - }); - - test('no data and errors suceessful response', () async { - when( - client.send(any), - ).thenAnswer( - (_) => Future.value( - http.StreamedResponse( - Stream.fromIterable( - [utf8.encode('{}')], - ), - 200, - ), - ), - ); - - Exception exception; - - try { - await execute( - link: link, - operation: query, - ).first; - } on Exception catch (e) { - exception = e; - } - - expect( - exception, - const TypeMatcher<NetworkException>(), - ); - - expect( - (exception as NetworkException).wrappedException, - const TypeMatcher<http.ClientException>(), - ); - - expect( - exception.toString(), - 'Failed to connect to /graphql-test: Invalid response body: {}', - ); - }); - - test('no data and errors failed response', () async { - when( - client.send(any), - ).thenAnswer( - (_) => Future.value( - http.StreamedResponse( - Stream.fromIterable( - [utf8.encode('{}')], - ), - 400, - ), - ), - ); - - Exception exception; - - try { - await execute( - link: link, - operation: query, - ).first; - } on Exception catch (e) { - exception = e; - } - - expect( - exception, - const TypeMatcher<NetworkException>(), - ); - - expect( - (exception as NetworkException).wrappedException, - const TypeMatcher<http.ClientException>(), - ); - - expect( - exception.toString(), - 'Failed to connect to /graphql-test: Network Error: 400 {}', - ); - }); - - test('data on failed response', () async { - when( - client.send(any), - ).thenAnswer( - (_) => Future.value( - http.StreamedResponse( - Stream.fromIterable( - [utf8.encode('{"data":{}}')], - ), - 300, - ), - ), - ); - - final result = await execute( - link: link, - operation: query, - ).first; - - expect( - result.data, - equals({}), - ); - expect( - result.errors, - null, - ); - }); - - test('non-json response', () async { - when( - client.send(any), - ).thenAnswer( - (_) => Future.value( - http.StreamedResponse( - Stream.fromIterable( - [utf8.encode('')], - ), - 200, - ), - ), - ); - - Exception exception; - - try { - await execute( - link: link, - operation: query, - ).first; - } on Exception catch (e) { - exception = e; - } - - expect( - exception, - const TypeMatcher<ClientException>(), - ); - - expect( - (exception as ClientException).message, - "Invalid response body: ", - ); - }); - - test('request with multipart file', () async { - when( - client.send(any), - ).thenAnswer( - (_) => Future.value( - http.StreamedResponse( - Stream.fromIterable( - [utf8.encode('{"data":{}}')], - ), - 200, - ), - ), - ); - - final query = Operation( - documentNode: parseString('{}'), - variables: { - 'files': [ - http.MultipartFile.fromString( - 'field-1', - 'just plain text 1', - filename: 'sample_upload1.txt', - contentType: MediaType('text', 'plain'), - ), - http.MultipartFile.fromString( - 'field-2', - 'just plain text 2', - filename: 'sample_upload2.txt', - contentType: MediaType('text', 'plain'), - ), - ], - }, - ); - - await execute( - link: link, - operation: query, - ).first; - - final http.MultipartRequest captured = verify( - client.send(captureAny), - ).captured.single; - - final req = await captured.finalize().bytesToString(); - - expect( - req - .replaceAll( - RegExp('--dart-http-boundary-.{51}'), - '--dart-http-boundary-REPLACED', - ) - .replaceAll( - '\r\n', - '\n', - ), - r'''--dart-http-boundary-REPLACED -content-disposition: form-data; name="operations" - -{"operationName":null,"variables":{"files":[null,null]},"query":"query {\n \n}"} ---dart-http-boundary-REPLACED -content-disposition: form-data; name="map" - -{"0":["variables.files.0"],"1":["variables.files.1"]} ---dart-http-boundary-REPLACED -content-type: text/plain; charset=utf-8 -content-disposition: form-data; name="0"; filename="sample_upload1.txt" - -just plain text 1 ---dart-http-boundary-REPLACED -content-type: text/plain; charset=utf-8 -content-disposition: form-data; name="1"; filename="sample_upload2.txt" - -just plain text 2 ---dart-http-boundary-REPLACED-- -''', - ); - }); - }); -} diff --git a/packages/graphql/test/link/link_test.dart b/packages/graphql/test/link/link_test.dart deleted file mode 100644 index a5373c462..000000000 --- a/packages/graphql/test/link/link_test.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:graphql/client.dart'; -import 'package:graphql/src/link/operation.dart'; -import 'package:test/test.dart'; - -void main() { - group('link', () { - test('multiple', () async { - final link1 = Link( - request: (Operation op, [NextLink forward]) { - return null; - }, - ); - - final link2 = Link( - request: (Operation op, [NextLink forward]) { - return null; - }, - ); - - final link3 = Link( - request: (Operation op, [NextLink forward]) { - return null; - }, - ); - - final linksFrom = Link.from([link1, link2, link3]); - - final linksConcat = link1..concat(link2)..concat(link3); - - var resultConcat = await execute(link: linksConcat); - var resultFrom = await execute(link: linksFrom); - - expect(resultConcat, resultFrom); - }); - }); -} diff --git a/packages/graphql/test/multipart_upload_io_test.dart b/packages/graphql/test/multipart_upload_io_test.dart deleted file mode 100644 index de2e56d79..000000000 --- a/packages/graphql/test/multipart_upload_io_test.dart +++ /dev/null @@ -1,94 +0,0 @@ -import 'package:gql/language.dart'; -@TestOn("vm") -import 'package:test/test.dart'; -import 'package:mockito/mockito.dart'; -import 'package:http/http.dart' as http; -import 'dart:io' as io; - -import 'package:graphql/client.dart'; - -import 'helpers.dart'; - -class MockHttpClient extends Mock implements http.Client {} - -void main() { - HttpLink httpLink; - AuthLink authLink; - Link link; - GraphQLClient graphQLClientClient; - MockHttpClient mockHttpClient; - - group( - 'upload', - () { - const String uploadMutation = r''' - mutation($files: [Upload!]!) { - multipleUpload(files: $files) { - id - filename - mimetype - path - } - } - '''; - - setUp(() { - mockHttpClient = MockHttpClient(); - - when(mockHttpClient.send(any)).thenAnswer((Invocation a) async { - return simpleResponse(body: '{"data": {}}'); - }); - - httpLink = HttpLink( - uri: 'http://localhost:3001/graphql', httpClient: mockHttpClient); - - authLink = AuthLink( - getToken: () async => 'Bearer my-special-bearer-token', - ); - - link = authLink.concat(httpLink); - - graphQLClientClient = GraphQLClient( - cache: getTestCache(), - link: link, - ); - }); - - test( - 'upload with io.File instance deprecation warning', - overridePrint((log) async { - final MutationOptions _options = MutationOptions( - documentNode: parseString(uploadMutation), - variables: <String, dynamic>{ - 'files': [ - io.File('pubspec.yaml'), - ], - }, - ); - final QueryResult r = await graphQLClientClient.mutate(_options); - - expect(r.exception, isNull); - expect(r.data, isNotNull); - expect(log, hasLength(5)); - final warningMessage = r''' -⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️ DEPRECATION WARNING ⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️ - -Please do not use `File` direcly anymore. Instead, use -`MultipartFile`. There's also a utitlity method to help you -`import 'package:graphql/utilities.dart' show multipartFileFrom;` - -⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️ DEPRECATION WARNING ⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️⚠️️️️️️️️ - '''; - expect(log[0], warningMessage); - expect(log[1], warningMessage); - expect(log[2], warningMessage); - expect(log[3], warningMessage); - expect(log[4], warningMessage); - }), - ); - }, - onPlatform: { - "!vm": Skip("This test is only for VM"), - }, - ); -} diff --git a/packages/graphql/test/multipart_upload_test.dart b/packages/graphql/test/multipart_upload_test.dart deleted file mode 100644 index 9212a5e84..000000000 --- a/packages/graphql/test/multipart_upload_test.dart +++ /dev/null @@ -1,202 +0,0 @@ -import 'package:gql/language.dart'; -import 'package:http_parser/http_parser.dart'; -import 'package:test/test.dart'; -import 'package:mockito/mockito.dart'; -import 'package:http/http.dart' as http; - -import 'package:graphql/client.dart'; - -import './helpers.dart'; - -class MockHttpClient extends Mock implements http.Client {} - -NormalizedInMemoryCache getTestCache() => NormalizedInMemoryCache( - dataIdFromObject: typenameDataIdFromObject, - ); - -void main() { - HttpLink httpLink; - AuthLink authLink; - Link link; - GraphQLClient graphQLClientClient; - MockHttpClient mockHttpClient; - - group('upload', () { - const String uploadMutation = r''' - mutation($files: [Upload!]!) { - multipleUpload(files: $files) { - id - filename - mimetype - path - } - } - '''; - - setUp(() { - mockHttpClient = MockHttpClient(); - - httpLink = HttpLink( - uri: 'http://localhost:3001/graphql', httpClient: mockHttpClient); - - authLink = AuthLink( - getToken: () async => 'Bearer my-special-bearer-token', - ); - - link = authLink.concat(httpLink); - - graphQLClientClient = GraphQLClient( - cache: getTestCache(), - link: link, - ); - }); - - test('upload success', () async { - Future<void> expectUploadBody( - http.ByteStream bodyBytesStream, String boundary) async { - final List<Function> expectContinuationList = (() { - int i = 0; - return <Function>[ - // ExpectString - (List<int> actual, String expected) => expect( - String.fromCharCodes(actual.sublist(i, i += expected.length)), - expected), - // ExpectBytes - (List<int> actual, List<int> expected) => - expect(actual.sublist(i, i += expected.length), expected), - // Expect final length - (int expectedLength) => expect(i, expectedLength), - ]; - })(); - final Function expectContinuationString = expectContinuationList[0]; - final Function expectContinuationBytes = expectContinuationList[1]; - final Function expectContinuationLength = expectContinuationList[2]; - final bodyBytes = await bodyBytesStream.toBytes(); - expectContinuationString(bodyBytes, '--'); - expectContinuationString(bodyBytes, boundary); - expectContinuationString(bodyBytes, - '\r\ncontent-disposition: form-data; name="operations"\r\n\r\n'); - // operationName of unamed operations is "UNNAMED/" + document.hashCode.toString() - expectContinuationString(bodyBytes, - r'{"operationName":null,"variables":{"files":[null,null]},"query":"mutation($files: [Upload!]!) {\n multipleUpload(files: $files) {\n id\n filename\n mimetype\n path\n }\n}"}'); - expectContinuationString(bodyBytes, '\r\n--'); - expectContinuationString(bodyBytes, boundary); - expectContinuationString(bodyBytes, - '\r\ncontent-disposition: form-data; name="map"\r\n\r\n{"0":["variables.files.0"],"1":["variables.files.1"]}'); - expectContinuationString(bodyBytes, '\r\n--'); - expectContinuationString(bodyBytes, boundary); - expectContinuationString(bodyBytes, - '\r\ncontent-type: image/jpeg\r\ncontent-disposition: form-data; name="0"; filename="sample_upload.jpg"\r\n\r\n'); - expectContinuationBytes(bodyBytes, [0, 1, 254, 255]); - expectContinuationString(bodyBytes, '\r\n--'); - expectContinuationString(bodyBytes, boundary); - expectContinuationString(bodyBytes, - '\r\ncontent-type: text/plain; charset=utf-8\r\ncontent-disposition: form-data; name="1"; filename="sample_upload.txt"\r\n\r\n'); - expectContinuationString(bodyBytes, 'just plain text'); - expectContinuationString(bodyBytes, '\r\n--'); - expectContinuationString(bodyBytes, boundary); - expectContinuationString(bodyBytes, '--\r\n'); - expectContinuationLength(bodyBytes.lengthInBytes); - } - - http.ByteStream bodyBytes; - when(mockHttpClient.send(any)).thenAnswer((Invocation a) async { - bodyBytes = (a.positionalArguments[0] as http.BaseRequest).finalize(); - return simpleResponse(body: r''' -{ - "data": { - "multipleUpload": [ - { - "id": "r1odc4PAz", - "filename": "sample_upload.jpg", - "mimetype": "image/jpeg", - "path": "./uploads/r1odc4PAz-sample_upload.jpg" - }, - { - "id": "5Ea18qlMur", - "filename": "sample_upload.txt", - "mimetype": "text/plain", - "path": "./uploads/5Ea18qlMur-sample_upload.txt" - } - ] - } -} - '''); - }); - - final MutationOptions _options = MutationOptions( - documentNode: parseString(uploadMutation), - variables: <String, dynamic>{ - 'files': [ - http.MultipartFile.fromBytes( - '', - [0, 1, 254, 255], - filename: 'sample_upload.jpg', - contentType: MediaType('image', 'jpeg'), - ), - http.MultipartFile.fromString( - '', - 'just plain text', - filename: 'sample_upload.txt', - contentType: MediaType('text', 'plain'), - ), - ], - }, - ); - final QueryResult r = await graphQLClientClient.mutate(_options); - - expect(r.exception, isNull); - expect(r.data, isNotNull); - - final http.MultipartRequest request = - verify(mockHttpClient.send(captureAny)).captured.first - as http.MultipartRequest; - expect(request.method, 'post'); - expect(request.url.toString(), 'http://localhost:3001/graphql'); - expect(request.headers['accept'], '*/*'); - expect( - request.headers['Authorization'], 'Bearer my-special-bearer-token'); - final List<String> contentTypeStringSplit = - request.headers['content-type'].split('; boundary='); - expect(contentTypeStringSplit[0], 'multipart/form-data'); - await expectUploadBody(bodyBytes, contentTypeStringSplit[1]); - - final List<Map<String, dynamic>> multipleUpload = - (r.data['multipleUpload'] as List<dynamic>) - .cast<Map<String, dynamic>>(); - - expect(multipleUpload, <Map<String, String>>[ - <String, String>{ - 'id': 'r1odc4PAz', - 'filename': 'sample_upload.jpg', - 'mimetype': 'image/jpeg', - 'path': './uploads/r1odc4PAz-sample_upload.jpg' - }, - <String, String>{ - 'id': '5Ea18qlMur', - 'filename': 'sample_upload.txt', - 'mimetype': 'text/plain', - 'path': './uploads/5Ea18qlMur-sample_upload.txt' - }, - ]); - }); - - //test('upload fail error response', () { - // const String responseBody = json.encode({ - // "errors":[ - // { - // "message": r'Variable "$files" of required type "[Upload!]!" was not provided.', - // "locations": [{ "line" :1, "column" :14 }], - // "extensions": { - // "code": "INTERNAL_SERVER_ERROR", - // "exception": { - // "stacktrace": [ r'GraphQLError: Variable "$files" of required type "[Upload!]!" was not provided.', ... ] - // } - // } - // } - // ] - // }); - // const int statusCode = 400; - //}); - }); -} diff --git a/packages/graphql/test/socket_client_test.dart b/packages/graphql/test/socket_client_test.dart deleted file mode 100644 index 1baa7cd76..000000000 --- a/packages/graphql/test/socket_client_test.dart +++ /dev/null @@ -1,178 +0,0 @@ -import 'dart:convert'; -import 'dart:typed_data'; - -import 'package:gql/language.dart'; -import 'package:graphql/client.dart'; -import 'package:graphql/src/link/operation.dart'; -import 'package:graphql/src/socket_client.dart' - show SocketClient, SocketConnectionState; -import 'package:graphql/src/websocket/messages.dart'; -import 'package:test/test.dart'; - -import 'helpers.dart'; - -void main() { - group('SocketClient without payload', () { - SocketClient socketClient; - setUp(overridePrint((log) { - socketClient = SocketClient( - 'ws://echo.websocket.org', - protocols: null, - randomBytesForUuid: Uint8List.fromList( - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]), - ); - })); - tearDown(overridePrint((log) async { - await socketClient.dispose(); - })); - test('connection', () async { - await expectLater( - socketClient.connectionState.asBroadcastStream(), - emitsInOrder( - [ - SocketConnectionState.CONNECTING, - SocketConnectionState.CONNECTED, - ], - ), - ); - }); - test('subscription data', () async { - final payload = SubscriptionRequest( - Operation(documentNode: parseString('subscription {}')), - ); - final waitForConnection = true; - final subscriptionDataStream = - socketClient.subscribe(payload, waitForConnection); - await socketClient.connectionState - .where((state) => state == SocketConnectionState.CONNECTED) - .first; - - // ignore: unawaited_futures - socketClient.socket.stream - .where((message) => - message == - r'{"type":"start","id":"01020304-0506-4708-890a-0b0c0d0e0f10","payload":{"operationName":null,"query":"subscription {\n \n}","variables":{}}}') - .first - .then((_) { - socketClient.socket.add(jsonEncode({ - 'type': 'data', - 'id': '01020304-0506-4708-890a-0b0c0d0e0f10', - 'payload': { - 'data': {'foo': 'bar'}, - 'errors': ['error and data can coexist'] - } - })); - }); - - await expectLater( - subscriptionDataStream, - emits( - SubscriptionData( - '01020304-0506-4708-890a-0b0c0d0e0f10', - {'foo': 'bar'}, - ['error and data can coexist'], - ), - ), - ); - }); - test('resubscribe', () async { - final payload = SubscriptionRequest( - Operation(documentNode: gql('subscription {}')), - ); - final waitForConnection = true; - final subscriptionDataStream = - socketClient.subscribe(payload, waitForConnection); - - socketClient.onConnectionLost(); - - await socketClient.connectionState - .where((state) => state == SocketConnectionState.CONNECTED) - .first; - - // ignore: unawaited_futures - socketClient.socket.stream - .where((message) => - message == - r'{"type":"start","id":"01020304-0506-4708-890a-0b0c0d0e0f10","payload":{"operationName":null,"query":"subscription {\n \n}","variables":{}}}') - .first - .then((_) { - socketClient.socket.add(jsonEncode({ - 'type': 'data', - 'id': '01020304-0506-4708-890a-0b0c0d0e0f10', - 'payload': { - 'data': {'foo': 'bar'}, - 'errors': ['error and data can coexist'] - } - })); - }); - - await expectLater( - subscriptionDataStream, - emits( - SubscriptionData( - '01020304-0506-4708-890a-0b0c0d0e0f10', - {'foo': 'bar'}, - ['error and data can coexist'], - ), - ), - ); - }); - }, tags: "integration"); - - group('SocketClient with const payload', () { - SocketClient socketClient; - const initPayload = {'token': 'mytoken'}; - - setUp(overridePrint((log) { - socketClient = SocketClient( - 'ws://echo.websocket.org', - config: SocketClientConfig(initPayload: () => initPayload), - ); - })); - - tearDown(overridePrint((log) async { - await socketClient.dispose(); - })); - - test('connection', () async { - await socketClient.connectionState - .where((state) => state == SocketConnectionState.CONNECTED) - .first; - - await expectLater(socketClient.socket.stream.map((s) { - return jsonDecode(s)['payload']; - }), emits(initPayload)); - }); - }); - - group('SocketClient with future payload', () { - SocketClient socketClient; - const initPayload = {'token': 'mytoken'}; - - setUp(overridePrint((log) { - socketClient = SocketClient( - 'ws://echo.websocket.org', - config: SocketClientConfig( - initPayload: () async { - await Future.delayed(Duration(seconds: 3)); - return initPayload; - }, - ), - ); - })); - - tearDown(overridePrint((log) async { - await socketClient.dispose(); - })); - - test('connection', () async { - await socketClient.connectionState - .where((state) => state == SocketConnectionState.CONNECTED) - .first; - - await expectLater(socketClient.socket.stream.map((s) { - return jsonDecode(s)['payload']; - }), emits(initPayload)); - }); - }); -} diff --git a/packages/graphql/test/websocket_legacy_io_test.dart b/packages/graphql/test/websocket_legacy_io_test.dart deleted file mode 100644 index c44317bcc..000000000 --- a/packages/graphql/test/websocket_legacy_io_test.dart +++ /dev/null @@ -1,96 +0,0 @@ -@TestOn('vm') - -import 'dart:convert'; -import 'dart:typed_data'; - -import 'package:gql/language.dart'; -import 'package:test/test.dart'; - -import 'package:graphql/src/link/operation.dart'; -import 'package:graphql/src/websocket/messages.dart'; - -import 'package:graphql/legacy_socket_api/legacy_socket_client.dart'; - -import 'helpers.dart'; - -void main() { - group( - 'SocketClient', - () { - // ignore: deprecated_member_use_from_same_package - SocketClient socketClient; - setUp(overridePrint((log) { - // ignore: deprecated_member_use_from_same_package - socketClient = SocketClient( - 'ws://echo.websocket.org', - protocols: null, - randomBytesForUuid: Uint8List.fromList( - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]), - ); - })); - tearDown(overridePrint((log) async { - await socketClient.dispose(); - })); - test('connection', () async { - await expectLater( - socketClient.connectionState.asBroadcastStream(), - emitsInOrder( - [ - SocketConnectionState.CONNECTING, - SocketConnectionState.CONNECTED, - ], - ), - ); - }); - test('subscription data', () async { - final payload = SubscriptionRequest( - Operation(documentNode: parseString('subscription {}')), - ); - final waitForConnection = true; - final subscriptionDataStream = - socketClient.subscribe(payload, waitForConnection); - await socketClient.connectionState - .where((state) => state == SocketConnectionState.CONNECTED) - .first; - - // ignore: unawaited_futures - socketClient.stream - .where( - (message) => - message == - r'{"type":"start","id":"01020304-0506-4708-890a-0b0c0d0e0f10","payload":{"operationName":null,"query":"subscription {\n \n}","variables":{}}}', - ) - .first - .then( - (_) { - socketClient.socket.add( - jsonEncode({ - 'type': 'data', - 'id': '01020304-0506-4708-890a-0b0c0d0e0f10', - 'payload': { - 'data': {'foo': 'bar'}, - 'errors': ['error and data can coexist'] - } - }), - ); - }, - ); - - await expectLater( - subscriptionDataStream, - emits( - SubscriptionData( - '01020304-0506-4708-890a-0b0c0d0e0f10', - {'foo': 'bar'}, - ['error and data can coexist'], - ), - ), - ); - }); - }, - tags: "integration", - onPlatform: { - "!vm": Skip("This test is only for VM"), - }, - ); -} diff --git a/packages/graphql/test/websocket_legacy_test.dart b/packages/graphql/test/websocket_legacy_test.dart deleted file mode 100644 index 042c0c190..000000000 --- a/packages/graphql/test/websocket_legacy_test.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'package:test/test.dart'; - -import 'package:graphql/legacy_socket_api/legacy_socket_link.dart'; -import 'package:graphql/legacy_socket_api/legacy_socket_client.dart'; - -import 'helpers.dart'; - -void main() { - group('Link Websocket', () { - test('simple connection', overridePrint((List<String> log) { - // ignore: deprecated_member_use_from_same_package - WebSocketLink( - url: 'ws://echo.websocket.org', - // ignore: deprecated_member_use_from_same_package - headers: {'foo': 'bar'}, - ); - expect(log, [ - 'WARNING: Using direct websocket headers which will be removed soon, ' - 'as it is incompatable with dart:html. ' - 'If you need this direct header access, ' - 'please comment on this PR with details on your usecase: ' - 'https://github.com/zino-app/graphql-flutter/pull/323' - ]); - })); - }); - - group('LegacyInitOperation', () { - test('null payload', () { - // ignore: deprecated_member_use_from_same_package - final operation = LegacyInitOperation(null); - expect(operation.toJson(), {'type': 'connection_init'}); - }); - test('simple payload', () { - // ignore: deprecated_member_use_from_same_package - final operation = LegacyInitOperation(42); - expect(operation.toJson(), {'type': 'connection_init', 'payload': '42'}); - }); - test('complex payload', () { - // ignore: deprecated_member_use_from_same_package - final operation = LegacyInitOperation({ - 'value': 42, - 'nested': { - 'number': [3, 7], - 'string': ['foo', 'bar'] - } - }); - expect(operation.toJson(), { - 'type': 'connection_init', - 'payload': - '{"value":42,"nested":{"number":[3,7],"string":["foo","bar"]}}' - }); - }); - }); -} diff --git a/packages/graphql/test/websocket_test.dart b/packages/graphql/test/websocket_test.dart deleted file mode 100644 index c70077808..000000000 --- a/packages/graphql/test/websocket_test.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:test/test.dart'; -import 'package:graphql/src/websocket/messages.dart' show InitOperation; - -void main() { - group('InitOperation', () { - test('null payload', () { - // ignore: deprecated_member_use_from_same_package - final operation = InitOperation(null); - expect(operation.toJson(), {'type': 'connection_init'}); - }); - test('simple payload', () { - // ignore: deprecated_member_use_from_same_package - final operation = InitOperation(42); - expect(operation.toJson(), {'type': 'connection_init', 'payload': 42}); - }); - test('complex payload', () { - // ignore: deprecated_member_use_from_same_package - final operation = InitOperation({ - 'value': 42, - 'nested': { - 'number': [3, 7], - 'string': ['foo', 'bar'] - } - }); - expect(operation.toJson(), { - 'type': 'connection_init', - 'payload': { - 'value': 42, - 'nested': { - 'number': [3, 7], - 'string': ['foo', 'bar'] - } - } - }); - }); - }); -} diff --git a/packages/graphql_flutter/lib/src/widgets/subscription.dart b/packages/graphql_flutter/lib/src/widgets/subscription.dart index 30a89e3bc..3030725d1 100644 --- a/packages/graphql_flutter/lib/src/widgets/subscription.dart +++ b/packages/graphql_flutter/lib/src/widgets/subscription.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'package:connectivity/connectivity.dart'; import 'package:flutter/widgets.dart'; import 'package:gql/language.dart'; +import 'package:gql_exec/gql_exec.dart'; import 'package:graphql/client.dart'; import 'package:graphql/internal.dart'; import 'package:graphql_flutter/src/widgets/graphql_provider.dart'; @@ -42,7 +43,7 @@ class _SubscriptionState<T> extends State<Subscription<T>> { bool _loading = true; T _data; dynamic _error; - StreamSubscription<FetchResult> _subscription; + StreamSubscription<Response> _subscription; ConnectivityResult _currentConnectivityResult; StreamSubscription<ConnectivityResult> _networkSubscription; @@ -50,13 +51,15 @@ class _SubscriptionState<T> extends State<Subscription<T>> { void _initSubscription() { final GraphQLClient client = GraphQLProvider.of(context).value; assert(client != null); - final Operation operation = Operation( - documentNode: parseString(widget.query), + final Request request = Request( + operation: Operation( + document: parseString(widget.query), + operationName: widget.operationName, + ), variables: widget.variables, - operationName: widget.operationName, ); - final Stream<FetchResult> stream = client.subscribe(operation); + final Stream<Response> stream = client.subscribe(request); if (_subscription == null) { // Set the initial value for the first time. @@ -109,7 +112,7 @@ class _SubscriptionState<T> extends State<Subscription<T>> { super.dispose(); } - void _onData(final FetchResult message) { + void _onData(final Response message) { setState(() { _loading = false; _data = message.data as T; @@ -121,7 +124,7 @@ class _SubscriptionState<T> extends State<Subscription<T>> { setState(() { _loading = false; _data = null; - _error = (error is SubscriptionError) ? error.payload : error; + _error = error; }); } diff --git a/packages/graphql_flutter/pubspec.yaml b/packages/graphql_flutter/pubspec.yaml index fc97d6062..aa20c4586 100644 --- a/packages/graphql_flutter/pubspec.yaml +++ b/packages/graphql_flutter/pubspec.yaml @@ -9,12 +9,11 @@ authors: homepage: https://github.com/zino-app/graphql-flutter/tree/master/packages/graphql_flutter dependencies: graphql: ^3.0.1-beta.2 + gql_exec: ^0.2.2 flutter: sdk: flutter meta: ^1.1.6 - path: ^1.6.2 path_provider: ^1.1.0 - rxdart: ^0.23.1 connectivity: ^0.4.4 dev_dependencies: pedantic: ^1.8.0+1 From 7499323673af6ea6c9889c828fc8ff80042f1a74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kl=C4=81vs=20Pried=C4=ABtis?= <klavs@prieditis.lv> Date: Sun, 16 Feb 2020 21:50:40 +0200 Subject: [PATCH 2/2] feat: move to DocumentNode-only documents BREAKING CHANGE: the deprecated string documents are no longer supported --- packages/graphql/README.md | 12 ++-- packages/graphql/example/bin/main.dart | 6 +- .../lib/src/core/observable_query.dart | 2 +- .../graphql/lib/src/core/query_manager.dart | 2 +- .../graphql/lib/src/core/query_options.dart | 67 +++---------------- .../lib/src/core/raw_operation_data.dart | 42 ++---------- .../test/anonymous_operations_test.dart | 4 +- .../test/core/raw_operation_data_test.dart | 26 +++---- .../graphql/test/graphql_client_test.dart | 8 +-- .../lib/src/widgets/mutation.dart | 2 - .../lib/src/widgets/query.dart | 2 - 11 files changed, 47 insertions(+), 126 deletions(-) diff --git a/packages/graphql/README.md b/packages/graphql/README.md index be46131c6..b9a5f3d7d 100644 --- a/packages/graphql/README.md +++ b/packages/graphql/README.md @@ -110,7 +110,7 @@ const String readRepositories = r''' Then create a `QueryOptions` object: -> **NB:** for `documentNode` - Use our built-in help function - `gql(query)` to convert your document string to **ASTs** `documentNode`. +> **NB:** for `document` - Use our built-in help function - `gql(query)` to convert your document string to **ASTs** `document`. In our case, we need to pass `nRepositories` variable and the document name is `readRepositories`. @@ -119,7 +119,7 @@ In our case, we need to pass `nRepositories` variable and the document name is ` const int nRepositories = 50; final QueryOptions options = QueryOptions( - documentNode: gql(readRepositories), + document: gql(readRepositories), variables: <String, dynamic>{ 'nRepositories': nRepositories, }, @@ -166,7 +166,7 @@ Then instead of the `QueryOptions`, for mutations we will `MutationOptions`, whi // ... final MutationOptions options = MutationOptions( - documentNode: gql(addStar), + document: gql(addStar), variables: <String, dynamic>{ 'starrableId': repositoryID, }, @@ -201,7 +201,7 @@ if (isStarred) { ### AST documents > We are deprecating `document` and recommend you update your application to use -`documentNode` instead. `document` will be removed from the api in a future version. +`document` instead. `document` will be removed from the api in a future version. For example: @@ -209,7 +209,7 @@ For example: // ... final MutationOptions options = MutationOptions( - documentNode: gql(addStar), + document: gql(addStar), variables: <String, dynamic>{ 'starrableId': repositoryID, }, @@ -238,7 +238,7 @@ import 'package:gql/add_star.ast.g.dart' as add_star; // ... final MutationOptions options = MutationOptions( - documentNode: add_star.document, + document: add_star.document, variables: <String, dynamic>{ 'starrableId': repositoryID, }, diff --git a/packages/graphql/example/bin/main.dart b/packages/graphql/example/bin/main.dart index ab184ad68..4e994cf35 100644 --- a/packages/graphql/example/bin/main.dart +++ b/packages/graphql/example/bin/main.dart @@ -38,7 +38,7 @@ void query() async { const int nRepositories = 50; final QueryOptions options = QueryOptions( - documentNode: gql(readRepositories), + document: gql(readRepositories), variables: <String, dynamic>{ 'nRepositories': nRepositories, }, @@ -70,7 +70,7 @@ void starRepository(String repositoryID) async { final GraphQLClient _client = client(); final MutationOptions options = MutationOptions( - documentNode: gql(addStar), + document: gql(addStar), variables: <String, dynamic>{ 'starrableId': repositoryID, }, @@ -103,7 +103,7 @@ void removeStarFromRepository(String repositoryID) async { final GraphQLClient _client = client(); final MutationOptions options = MutationOptions( - documentNode: gql(removeStar), + document: gql(removeStar), variables: <String, dynamic>{ 'starrableId': repositoryID, }, diff --git a/packages/graphql/lib/src/core/observable_query.dart b/packages/graphql/lib/src/core/observable_query.dart index b964b1cfc..54d7ebc8a 100644 --- a/packages/graphql/lib/src/core/observable_query.dart +++ b/packages/graphql/lib/src/core/observable_query.dart @@ -145,7 +145,7 @@ class ObservableQuery { final combinedOptions = QueryOptions( fetchPolicy: FetchPolicy.noCache, errorPolicy: options.errorPolicy, - documentNode: fetchMoreOptions.documentNode ?? options.documentNode, + document: fetchMoreOptions.document ?? options.document, context: options.context, variables: { ...options.variables, diff --git a/packages/graphql/lib/src/core/query_manager.dart b/packages/graphql/lib/src/core/query_manager.dart index fdad03aa7..da7e282da 100644 --- a/packages/graphql/lib/src/core/query_manager.dart +++ b/packages/graphql/lib/src/core/query_manager.dart @@ -106,7 +106,7 @@ class QueryManager { // create a new request to execute final Request request = Request( operation: Operation( - document: options.documentNode, + document: options.document, operationName: options.operationName, ), variables: options.variables, diff --git a/packages/graphql/lib/src/core/query_options.dart b/packages/graphql/lib/src/core/query_options.dart index 6605b92a9..21a0c1f1b 100644 --- a/packages/graphql/lib/src/core/query_options.dart +++ b/packages/graphql/lib/src/core/query_options.dart @@ -70,6 +70,7 @@ class Policies { overrides?.fetch ?? fetch, overrides?.error ?? error, ); + operator ==(Object other) => other is Policies && fetch == other.fetch && error == other.error; } @@ -77,17 +78,13 @@ class Policies { /// Base options. class BaseOptions extends RawOperationData { BaseOptions({ - @Deprecated('The "document" option has been deprecated, use "documentNode" instead') - String document, - DocumentNode documentNode, + @required DocumentNode document, Map<String, dynamic> variables, this.policies, this.context, this.optimisticResult, }) : super( - // ignore: deprecated_member_use_from_same_package document: document, - documentNode: documentNode, variables: variables, ); @@ -108,9 +105,7 @@ class BaseOptions extends RawOperationData { /// Query options. class QueryOptions extends BaseOptions { QueryOptions({ - @Deprecated('The "document" option has been deprecated, use "documentNode" instead') - String document, - DocumentNode documentNode, + @required DocumentNode document, Map<String, dynamic> variables, FetchPolicy fetchPolicy, ErrorPolicy errorPolicy, @@ -119,9 +114,7 @@ class QueryOptions extends BaseOptions { Context context, }) : super( policies: Policies(fetch: fetchPolicy, error: errorPolicy), - // ignore: deprecated_member_use_from_same_package document: document, - documentNode: documentNode, variables: variables, context: context, optimisticResult: optimisticResult, @@ -139,9 +132,7 @@ typedef OnError = void Function(OperationException error); /// Mutation options class MutationOptions extends BaseOptions { MutationOptions({ - @Deprecated('The "document" option has been deprecated, use "documentNode" instead') - String document, - DocumentNode documentNode, + @required DocumentNode document, Map<String, dynamic> variables, FetchPolicy fetchPolicy, ErrorPolicy errorPolicy, @@ -151,9 +142,7 @@ class MutationOptions extends BaseOptions { this.onError, }) : super( policies: Policies(fetch: fetchPolicy, error: errorPolicy), - // ignore: deprecated_member_use_from_same_package document: document, - documentNode: documentNode, variables: variables, context: context, ); @@ -253,9 +242,7 @@ class MutationCallbacks { // ObservableQuery options class WatchQueryOptions extends QueryOptions { WatchQueryOptions({ - @Deprecated('The "document" option has been deprecated, use "documentNode" instead') - String document, - DocumentNode documentNode, + @required DocumentNode document, Map<String, dynamic> variables, FetchPolicy fetchPolicy, ErrorPolicy errorPolicy, @@ -265,9 +252,7 @@ class WatchQueryOptions extends QueryOptions { this.eagerlyFetchResults, Context context, }) : super( - // ignore: deprecated_member_use_from_same_package document: document, - documentNode: documentNode, variables: variables, fetchPolicy: fetchPolicy, errorPolicy: errorPolicy, @@ -292,7 +277,7 @@ class WatchQueryOptions extends QueryOptions { WatchQueryOptions a, WatchQueryOptions b, ) { - if (a.documentNode != b.documentNode) { + if (a.document != b.document) { return true; } @@ -313,7 +298,7 @@ class WatchQueryOptions extends QueryOptions { } WatchQueryOptions copy() => WatchQueryOptions( - documentNode: documentNode, + document: document, variables: variables, fetchPolicy: fetchPolicy, errorPolicy: errorPolicy, @@ -334,33 +319,12 @@ typedef dynamic UpdateQuery( /// options for fetchmore operations class FetchMoreOptions { FetchMoreOptions({ - @Deprecated('The "document" option has been deprecated, use "documentNode" instead') - String document, - DocumentNode documentNode, + @required this.document, this.variables = const <String, dynamic>{}, @required this.updateQuery, - }) : assert( - // ignore: deprecated_member_use_from_same_package - _mutuallyExclusive(document, documentNode), - '"document" or "documentNode" options are mutually exclusive.', - ), - assert(updateQuery != null), - this.documentNode = - // ignore: deprecated_member_use_from_same_package - documentNode ?? document != null ? parseString(document) : null; - - DocumentNode documentNode; - - /// A string representation of [documentNode] - @Deprecated( - 'The "document" option has been deprecated, use "documentNode" instead') - String get document => printNode(documentNode); - - @Deprecated( - 'The "document" option has been deprecated, use "documentNode" instead') - set document(value) { - documentNode = parseString(value); - } + }) : assert(updateQuery != null); + + DocumentNode document; final Map<String, dynamic> variables; @@ -368,12 +332,3 @@ class FetchMoreOptions { /// with the result data already in the cache UpdateQuery updateQuery; } - -bool _mutuallyExclusive( - Object a, - Object b, { - bool required = false, -}) => - (!required && a == null && b == null) || - (a != null && b == null) || - (a == null && b != null); diff --git a/packages/graphql/lib/src/core/raw_operation_data.dart b/packages/graphql/lib/src/core/raw_operation_data.dart index 5d003a9e8..415e0a756 100644 --- a/packages/graphql/lib/src/core/raw_operation_data.dart +++ b/packages/graphql/lib/src/core/raw_operation_data.dart @@ -2,50 +2,21 @@ import 'dart:collection' show SplayTreeMap; import 'dart:convert' show json; import 'package:gql/ast.dart'; -import 'package:gql/language.dart'; import 'package:graphql/src/utilities/get_from_ast.dart'; +import 'package:meta/meta.dart'; class RawOperationData { RawOperationData({ - @Deprecated('The "document" option has been deprecated, use "documentNode" instead') - String document, - DocumentNode documentNode, + @required this.document, Map<String, dynamic> variables, String operationName, - }) : assert( - // ignore: deprecated_member_use_from_same_package - document != null || documentNode != null, - 'Either a "document" or "documentNode" option is required. ' - 'You must specify your GraphQL document in the query options.', - ), - // todo: Investigate why this assertion is failing - // assert( - // (document != null && documentNode == null) || - // (document == null && documentNode != null), - // '"document" or "documentNode" options are mutually exclusive.', - // ), - // ignore: deprecated_member_use_from_same_package - documentNode = documentNode ?? parseString(document), - _operationName = operationName, + }) : _operationName = operationName, variables = SplayTreeMap<String, dynamic>.of( variables ?? const <String, dynamic>{}, ); /// A GraphQL document that consists of a single query to be sent down to the server. - DocumentNode documentNode; - - /// A string representation of [documentNode] - @Deprecated( - 'The "document" option has been deprecated, use "documentNode" instead', - ) - String get document => printNode(documentNode); - - @Deprecated( - 'The "document" option has been deprecated, use "documentNode" instead', - ) - set document(value) { - documentNode = parseString(value); - } + DocumentNode document; /// A map going from variable name to variable value, where the variables are used /// within the GraphQL query. @@ -55,7 +26,7 @@ class RawOperationData { /// The last operation name appearing in the contained document. String get operationName { - _operationName ??= getLastOperationName(documentNode); + _operationName ??= getLastOperationName(document); return _operationName; } @@ -65,7 +36,7 @@ class RawOperationData { // TODO remove $document from key? A bit redundant, though that's not the worst thing String get _identifier { _documentIdentifier ??= - operationName ?? 'UNNAMED/' + documentNode.hashCode.toString(); + operationName ?? 'UNNAMED/' + document.hashCode.toString(); return _documentIdentifier; } @@ -81,7 +52,6 @@ class RawOperationData { ); // TODO: document is being depracated, find ways for generating key - // ignore: deprecated_member_use_from_same_package return '$document|$encodedVariables|$_identifier'; } } diff --git a/packages/graphql/test/anonymous_operations_test.dart b/packages/graphql/test/anonymous_operations_test.dart index 3dbcdca6f..e69536cbe 100644 --- a/packages/graphql/test/anonymous_operations_test.dart +++ b/packages/graphql/test/anonymous_operations_test.dart @@ -50,7 +50,7 @@ void main() { group('query', () { test('successful query', () async { final WatchQueryOptions _options = WatchQueryOptions( - documentNode: parseString(readRepositories), + document: parseString(readRepositories), variables: <String, dynamic>{}, ); @@ -129,7 +129,7 @@ void main() { group('mutation', () { test('successful mutation', () async { final MutationOptions _options = MutationOptions( - documentNode: parseString(addStar), + document: parseString(addStar), ); when( diff --git a/packages/graphql/test/core/raw_operation_data_test.dart b/packages/graphql/test/core/raw_operation_data_test.dart index 05c189ca2..60fe97622 100644 --- a/packages/graphql/test/core/raw_operation_data_test.dart +++ b/packages/graphql/test/core/raw_operation_data_test.dart @@ -7,7 +7,7 @@ void main() { group('single operation', () { test('query without name', () { final opData = RawOperationData( - documentNode: parseString('query {}'), + document: parseString('query {}'), ); expect(opData.operationName, null); @@ -15,7 +15,7 @@ void main() { test('query with explicit name', () { final opData = RawOperationData( - documentNode: parseString('query Operation {}'), + document: parseString('query Operation {}'), operationName: 'Operation', ); @@ -24,7 +24,7 @@ void main() { test('mutation with explicit name', () { final opData = RawOperationData( - documentNode: parseString('mutation Operation {}'), + document: parseString('mutation Operation {}'), operationName: 'Operation', ); @@ -33,7 +33,7 @@ void main() { test('subscription with explicit name', () { final opData = RawOperationData( - documentNode: parseString('subscription Operation {}'), + document: parseString('subscription Operation {}'), operationName: 'Operation', ); @@ -42,7 +42,7 @@ void main() { test('query with implicit name', () { final opData = RawOperationData( - documentNode: parseString('query Operation {}'), + document: parseString('query Operation {}'), ); expect(opData.operationName, 'Operation'); @@ -50,7 +50,7 @@ void main() { test('mutation with implicit name', () { final opData = RawOperationData( - documentNode: parseString('mutation Operation {}'), + document: parseString('mutation Operation {}'), ); expect(opData.operationName, 'Operation'); @@ -58,7 +58,7 @@ void main() { test('subscription with implicit name', () { final opData = RawOperationData( - documentNode: parseString('subscription Operation {}'), + document: parseString('subscription Operation {}'), ); expect(opData.operationName, 'Operation'); @@ -74,7 +74,7 @@ void main() { test('query with explicit name', () { final opData = RawOperationData( - documentNode: parseString(document), + document: parseString(document), operationName: 'OperationQ', ); @@ -83,7 +83,7 @@ void main() { test('mutation with explicit name', () { final opData = RawOperationData( - documentNode: parseString(document), + document: parseString(document), operationName: 'OperationM', ); @@ -92,7 +92,7 @@ void main() { test('subscription with explicit name', () { final opData = RawOperationData( - documentNode: parseString(document), + document: parseString(document), operationName: 'OperationS', ); @@ -101,7 +101,7 @@ void main() { test('query with implicit name', () { final opData = RawOperationData( - documentNode: parseString(document), + document: parseString(document), ); expect(opData.operationName, 'OperationS'); @@ -109,7 +109,7 @@ void main() { test('mutation with implicit name', () { final opData = RawOperationData( - documentNode: parseString(document), + document: parseString(document), ); expect(opData.operationName, 'OperationS'); @@ -117,7 +117,7 @@ void main() { test('subscription with implicit name', () { final opData = RawOperationData( - documentNode: parseString(document), + document: parseString(document), ); expect(opData.operationName, 'OperationS'); diff --git a/packages/graphql/test/graphql_client_test.dart b/packages/graphql/test/graphql_client_test.dart index 19e983f4e..0d983c75a 100644 --- a/packages/graphql/test/graphql_client_test.dart +++ b/packages/graphql/test/graphql_client_test.dart @@ -52,7 +52,7 @@ void main() { group('query', () { test('successful response', () async { final WatchQueryOptions _options = WatchQueryOptions( - documentNode: parseString(readRepositories), + document: parseString(readRepositories), variables: <String, dynamic>{ 'nRepositories': 42, }, @@ -135,7 +135,7 @@ void main() { final QueryResult r = await graphQLClientClient.query( WatchQueryOptions( - documentNode: parseString(readRepositories), + document: parseString(readRepositories), ), ); @@ -158,7 +158,7 @@ void main() { final QueryResult r = await graphQLClientClient.query( WatchQueryOptions( - documentNode: parseString(readRepositories), + document: parseString(readRepositories), ), ); @@ -180,7 +180,7 @@ void main() { group('mutation', () { test('successful mutation', () async { final MutationOptions _options = MutationOptions( - documentNode: parseString(addStar), + document: parseString(addStar), ); when( diff --git a/packages/graphql_flutter/lib/src/widgets/mutation.dart b/packages/graphql_flutter/lib/src/widgets/mutation.dart index 95aed793b..84886a101 100644 --- a/packages/graphql_flutter/lib/src/widgets/mutation.dart +++ b/packages/graphql_flutter/lib/src/widgets/mutation.dart @@ -39,9 +39,7 @@ class MutationState extends State<Mutation> { WatchQueryOptions get _providedOptions { final _options = WatchQueryOptions( - // ignore: deprecated_member_use document: widget.options.document, - documentNode: widget.options.documentNode, variables: widget.options.variables, fetchPolicy: widget.options.fetchPolicy, errorPolicy: widget.options.errorPolicy, diff --git a/packages/graphql_flutter/lib/src/widgets/query.dart b/packages/graphql_flutter/lib/src/widgets/query.dart index d21e4a1ca..66ad85fe9 100644 --- a/packages/graphql_flutter/lib/src/widgets/query.dart +++ b/packages/graphql_flutter/lib/src/widgets/query.dart @@ -39,9 +39,7 @@ class QueryState extends State<Query> { final QueryOptions options = widget.options; return WatchQueryOptions( - // ignore: deprecated_member_use document: options.document, - documentNode: options.documentNode, variables: options.variables, fetchPolicy: options.fetchPolicy, errorPolicy: options.errorPolicy,