blob: ab4378b2739ead1d9169b913c549857fa9686c35 [file] [log] [blame]
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'package:meta/meta.dart';
import 'codecs.dart';
import 'events.dart';
import 'recording_proxy_mixin.dart';
/// Wraps a raw invocation return value for the purpose of recording.
///
/// This class is intended for use with [RecordingProxyMixin]. Mixin subclasses
/// may configure a method or getter to return a [ResultReference] rather than
/// a raw result, and:
///
/// - [RecordingProxyMixin] will automatically return the reference's [value]
/// to callers (as if the mixin subclass had returned the raw value
/// directly).
/// - The recording's [InvocationEvent] will automatically record the
/// reference's [recordedValue].
/// - The recording's serialized value (written out during
/// [LiveRecording.flush]) will automatically serialize the reference's
/// [serializedValue].
abstract class ResultReference<T> {
/// Creates a new `ResultReference`.
const ResultReference();
/// The raw value to return to callers of the method or getter.
T get value;
/// The value to record in the recording's [InvocationEvent].
dynamic get recordedValue;
/// A JSON-serializable representation of this result, suitable for
/// encoding in a recording manifest.
///
/// The value of this property will be one of the JSON-native types: `num`,
/// `String`, `bool`, `Null`, `List`, or `Map`.
///
/// This allows for method-specific encoding routines. Take, for example, the
/// case of a method that returns `List<int>`. This type is natively
/// serializable by `JSONEncoder`, so if the raw value were directly returned
/// from an invocation, the recording would happily serialize the result as
/// a list of integers. However, the method may want to serialize the return
/// value differently, such as by writing it to file (if it knows the list is
/// actually a byte array that was read from a file). In this case, the
/// method can return a `ResultReference` to the list, and it will have a
/// hook into the serialization process.
dynamic get serializedValue => encode(recordedValue);
/// A [Future] that completes when [value] has completed.
///
/// If [value] is a [Future], this future will complete when [value] has
/// completed. If [value] is a [Stream], this future will complete when the
/// stream sends a "done" event. If value is neither a future nor a stream,
/// this future will complete immediately.
Future<Null> get complete => new Future<Null>.value();
}
/// Wraps a future result.
class FutureReference<T> extends ResultReference<Future<T>> {
final Future<T> _future;
T _value;
/// Creates a new `FutureReference` that wraps the specified [future].
FutureReference(Future<T> future) : _future = future;
/// The future value to return to callers of the method or getter.
@override
Future<T> get value {
return _future.then(
(T value) {
_value = value;
return value;
},
onError: (dynamic error) {
// TODO(tvolkert): Record errors
throw error;
},
);
}
/// The value returned by the completion of the future.
///
/// If the future threw an error, this value will be `null`.
@override
T get recordedValue => _value;
// TODO(tvolkert): remove `as Future<Null>` once Dart 1.22 is in stable
@override
Future<Null> get complete => value.catchError((_) {}) as Future<Null>;
}
/// Wraps a stream result.
class StreamReference<T> extends ResultReference<Stream<T>> {
final Stream<T> _stream;
final StreamController<T> _controller;
final Completer<Null> _completer = new Completer<Null>();
final List<T> _data = <T>[];
StreamSubscription<T> _subscription;
/// Creates a new `StreamReference` that wraps the specified [stream].
StreamReference(Stream<T> stream)
: _stream = stream,
_controller = stream.isBroadcast
? new StreamController<T>.broadcast()
: new StreamController<T>() {
_controller.onListen = () {
assert(_subscription == null);
_subscription = _listenToStream();
};
_controller.onCancel = () async {
assert(_subscription != null);
await _subscription.cancel();
_subscription = null;
};
_controller.onPause = () {
assert(_subscription != null && !_subscription.isPaused);
_subscription.pause();
};
_controller.onResume = () {
assert(_subscription != null && _subscription.isPaused);
_subscription.resume();
};
}
StreamSubscription<T> _listenToStream() {
return _stream.listen(
(T element) {
_data.add(element);
onData(element);
_controller.add(element);
},
onError: (dynamic error, StackTrace stackTrace) {
// TODO(tvolkert): Record errors
_controller.addError(error, stackTrace);
},
onDone: () {
_completer.complete();
_controller.close();
},
);
}
/// Called when an event is received from the underlying delegate stream.
///
/// Subclasses may override this method to be notified when events are
/// fired from the underlying stream.
@protected
void onData(T event) {}
@override
Stream<T> get value => _controller.stream;
@override
List<T> get recordedValue => _data;
@override
Future<Null> get complete => _completer.future.catchError((_) {});
}