blob: 8972e0b39991fb068e477a91bb12566ae8d001ce [file] [log] [blame] [edit]
// Copyright (c) 2024, 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 'dart:convert';
import 'dart:typed_data';
import 'package:dart_model/serialization.dart';
import 'macro_service.g.dart';
import 'message_grouper.dart';
/// Service provided by the host to the macro.
abstract interface class HostService {
/// Handles [request].
///
/// Returns `null` if the request is of a type not handled by this service
/// instance.
Future<Response> handle(MacroRequest request);
}
/// Service provided by the macro to the host.
abstract interface class MacroService {
/// Handles [request].
Future<Response> handle(HostRequest request);
}
/// Shared implementation of auto incrementing 32 bit IDs.
///
/// These roll back to 0 once it is greater than 2^32.
///
/// These are only unique to the process which generates the request,
/// so for instance the host and macro services may generate conflicting ids
/// and that is allowed.
int get nextRequestId {
final next = _nextRequestId++;
if (_nextRequestId > 0x7fffffff) {
_nextRequestId = 0;
}
return next;
}
int _nextRequestId = 0;
final _jsonConverter = json.fuse(utf8);
extension ProtocolExtension on Protocol {
bool get isJson => encoding == 'json';
bool get isBinary => encoding == 'binary';
/// Serializes [node] and sends it to [sink].
void send(void Function(Uint8List) sink, Map<String, Object?> node) {
if (isJson) {
sink(_jsonConverter.encode(node) as Uint8List);
sink(_utf8Newline);
} else if (isBinary) {
// Four byte message length followed by message.
// TODO(davidmorgan): variable length int encoding probably makes more
// sense than fixed four bytes.
final binary = node.serializeToBinary();
final length = binary.length;
sink(Uint8List.fromList([
(length >> 24) & 255,
(length >> 16) & 255,
(length >> 8) & 255,
length & 255,
]));
sink(binary);
} else {
throw StateError('Unsupported protocol: $this.');
}
}
/// Deserializes [stream] to JSON objects.
///
/// The data on `stream` can be arbitrarily split to `Uint8List` instances,
/// it does not have to be one list per message.
Stream<Map<String, Object?>> decode(Stream<Uint8List> stream) {
if (isJson) {
return const Utf8Decoder()
.bind(stream)
.transform(const LineSplitter())
.map((line) => json.decode(line) as Map<String, Object?>);
} else if (isBinary) {
return MessageGrouper(stream)
.messageStream
.map((message) => message.deserializeFromBinary());
} else {
throw StateError('Unsupported protocol: $this');
}
}
}
final _utf8Newline = const Utf8Encoder().convert('\n');