1 | | | import 'dart:async'; |
2 | | |
|
3 | | | import 'package:logger/web.dart'; |
4 | | | import 'package:meta/meta.dart'; |
5 | | |
|
6 | | | import '../_impl/xplat/_time_stamp.dart'; |
7 | | | import '../channel.dart'; |
8 | | | import '../exceptions/squadron_error.dart'; |
9 | | | import '../exceptions/squadron_exception.dart'; |
10 | | | import 'worker_message.dart'; |
11 | | |
|
12 | | | /// [WorkerResponse]s are used to communicate from [Worker]s to clients and |
13 | | | /// carry a single piece of data. [Future]-based services simply return a |
14 | | | /// single [WorkerResponse] with the result. [Stream]ing services will return |
15 | | | /// one [WorkerResponse]s for each stream item and mmust send a |
16 | | | /// [WorkerResponse.closeStream] message to indicate completion. |
17 | | | /// [WorkerResponse]s can also send error messages and log events. |
18 | | | extension type WorkerResponse._(List data) implements WorkerMessage { |
19 | | | /// [WorkerResponse] with a valid [result]. If [result] is an [Iterable] but |
20 | | | /// not a [List], it will be converted to a [List] by [wrapInPlace]. |
21 | | 0 | static WorkerResponse ready([bool status = true]) => WorkerResponse._([ |
22 | | 0 | microsecTimeStamp(), // 0 - travel time |
23 | | | status, // 1 - ready |
24 | | | null, // 2 - error |
25 | | | null, // 3 - end of stream |
26 | | | null, // 4 - log message |
27 | | | ]); |
28 | | |
|
29 | | | /// [WorkerResponse] with a valid [result]. If [result] is an [Iterable] but |
30 | | | /// not a [List], it will be converted to a [List] by [wrapInPlace]. |
31 | | 27 | static WorkerResponse withResult(dynamic result) => WorkerResponse._([ |
32 | | 10 | microsecTimeStamp(), // 0 - travel time |
33 | | | result, // 1 - result |
34 | | | null, // 2 - error |
35 | | | null, // 3 - end of stream |
36 | | | null, // 4 - log message |
37 | | | ]); |
38 | | |
|
39 | | | /// [WorkerResponse] with an error message and an optional (string) [StackTrace]. |
40 | | 5 | static WorkerResponse withError(SquadronException exception, |
41 | | | [StackTrace? stackTrace]) => |
42 | | 10 | WorkerResponse._([ |
43 | | 6 | microsecTimeStamp(), // 0 - travel time |
44 | | | null, // 1 - result |
45 | | | exception, // 2 - error |
46 | | | null, // 3 - end of stream |
47 | | | null, // 4 - log message |
48 | | | ]); |
49 | | |
|
50 | | | /// [WorkerResponse] with log event information. |
51 | | 27 | static WorkerResponse log(LogEvent message) => WorkerResponse._([ |
52 | | 9 | microsecTimeStamp(), // 0 - travel time |
53 | | | null, // 1 - result |
54 | | | null, // 2 - error |
55 | | | null, // 3 - end of stream |
56 | | 9 | message.serialize(), // 4 - log message |
57 | | | ]); |
58 | | |
|
59 | | | /// Special [WorkerResponse] message to indicate the end of a stream. |
60 | | 18 | static WorkerResponse closeStream() => WorkerResponse._([ |
61 | | 7 | microsecTimeStamp(), // 0 - travel time |
62 | | | null, // 1 - result |
63 | | | null, // 2 - error |
64 | | | true, // 3 - end of stream |
65 | | | null, // 4 - log message |
66 | | | ]); |
67 | | |
|
68 | | | /// Flag indicating the end of the [Stream]ing operation. |
69 | | 28 | bool get endOfStream => data[_$endOfStream]; |
70 | | |
|
71 | | | /// The [WorkerResponse] exception, if any. |
72 | | 30 | SquadronException? get error => data[_$error]; |
73 | | |
|
74 | | | /// Retrieves the result associated to this [WorkerResponse]. If the |
75 | | | /// [WorkerResponse] contains an error, an the [error] exception is thrown. |
76 | | 20 | dynamic get result { |
77 | | 9 | final err = error; |
78 | | 11 | if (err != null) { |
79 | | 0 | throw err; |
80 | | | } else { |
81 | | 20 | return data[_$result]; |
82 | | | } |
83 | | 11 | } |
84 | | | } |
85 | | |
|
86 | | | // 0 is reserved for travel time |
87 | | | const _$result = 1; |
88 | | | const _$error = 2; |
89 | | | const _$endOfStream = 3; |
90 | | | const _$log = 4; |
91 | | |
|
92 | | | @internal |
93 | | | extension WorkerResponseExt on WorkerResponse { |
94 | | | /// In-place deserialization of a [WorkerResponse] sent by the worker. |
95 | | | /// Returns `false` if the message requires no further processing (currently |
96 | | | /// used for log messages only). |
97 | | 21 | bool unwrapInPlace(Channel channel) { |
98 | | 10 | unwrapTravelTime(); |
99 | | 30 | final log = _LogEventSerializationExt.deserialize(data[_$log]); |
100 | | 12 | if (log != null) { |
101 | | 24 | channel.logger?.log(log.level, log.message, |
102 | | 8 | time: log.time, error: log.error, stackTrace: log.stackTrace); |
103 | | 9 | return false; |
104 | | | } else { |
105 | | 48 | data[_$error] = channel.exceptionManager.deserialize(data[_$error]); |
106 | | 21 | data[_$endOfStream] ??= false; |
107 | | 12 | return true; |
108 | | | } |
109 | | 12 | } |
110 | | |
|
111 | | | /// In-place serialization of a [WorkerResponse]. |
112 | | 10 | List wrapInPlace() { |
113 | | 10 | final result = data[_$result]; |
114 | | 12 | if (result is Iterable && result is! List) { |
115 | | 0 | data[_$result] = result.toList(); |
116 | | | } |
117 | | 24 | data[_$error] = (data[_$error] as SquadronException?)?.serialize(); |
118 | | 1 | return data; |
119 | | 1 | } |
120 | | |
|
121 | | 21 | static WorkerResponse from(List data) { |
122 | | 30 | if (data.length != 5) { |
123 | | 0 | throw SquadronErrorExt.create('Invalid worker response'); |
124 | | | } |
125 | | 21 | return WorkerResponse._(data); |
126 | | 12 | } |
127 | | | } |
128 | | |
|
129 | | | extension _LogEventSerializationExt on LogEvent { |
130 | | 18 | List serialize() => [ |
131 | | 18 | level.value, |
132 | | 18 | _stringify(message), |
133 | | 18 | microsecTimeStamp(time), |
134 | | 9 | error?.toString(), |
135 | | 9 | stackTrace?.toString(), |
136 | | | ]; |
137 | | |
|
138 | | 9 | static LogEvent? deserialize(List? props) => (props == null) |
139 | | | ? null |
140 | | 9 | : LogEvent( |
141 | | 36 | _getLevel((props[0] as num?)?.toInt()), |
142 | | 18 | props[1], |
143 | | 36 | time: fromMicrosecTimeStamp((props[2] as num?)?.toInt()), |
144 | | 18 | error: props[3], |
145 | | 27 | stackTrace: SquadronException.loadStackTrace(props[4]), |
146 | | | ); |
147 | | |
|
148 | | 18 | static Level _getLevel(int? value) { |
149 | | 9 | if (value == null) return Level.debug; |
150 | | 56 | return Level.values.where((l) => l.value == value).first; |
151 | | 9 | } |
152 | | |
|
153 | | 9 | static String? _stringify(dynamic message) { |
154 | | 9 | if (message is Function) { |
155 | | | try { |
156 | | 4 | return _stringify(message()); |
157 | | | } catch (ex) { |
158 | | 0 | return 'Deferred message failed with error: $ex'; |
159 | | | } |
160 | | | } else { |
161 | | 9 | return message.toString(); |
162 | | | } |
163 | | | } |
164 | | | } |