From 0c22dbf46a53ccb1fbb86fd1688263932d02bd42 Mon Sep 17 00:00:00 2001 From: David Morgan Date: Tue, 10 Sep 2024 09:05:36 +0200 Subject: [PATCH] Add generation of enums, use an enum for ProtocolEncoding. (#61) --- pkgs/_analyzer_macros/test/analyzer_test.dart | 2 +- pkgs/_analyzer_macros/test/golden_test.dart | 2 +- pkgs/_cfe_macros/test/cfe_test.dart | 2 +- pkgs/_cfe_macros/test/golden_test.dart | 2 +- .../_macro_client/test/macro_client_test.dart | 4 +- pkgs/_macro_host/test/macro_host_test.dart | 4 +- .../_macro_runner/test/macro_runner_test.dart | 4 +- .../_macro_server/test/macro_server_test.dart | 4 +- pkgs/macro_service/lib/src/macro_service.dart | 63 +++++++++---------- .../lib/src/macro_service.g.dart | 13 +++- pkgs/macro_service/test/protocol_test.dart | 4 +- schemas/macro_service.schema.json | 8 ++- .../dart_model_generator/lib/definitions.dart | 7 ++- .../lib/generate_dart_model.dart | 42 +++++++++++++ 14 files changed, 107 insertions(+), 54 deletions(-) diff --git a/pkgs/_analyzer_macros/test/analyzer_test.dart b/pkgs/_analyzer_macros/test/analyzer_test.dart index 2b6ba2fa..3604d000 100644 --- a/pkgs/_analyzer_macros/test/analyzer_test.dart +++ b/pkgs/_analyzer_macros/test/analyzer_test.dart @@ -25,7 +25,7 @@ void main() { AnalysisContextCollection(includedPaths: [directory.path]); analysisContext = contextCollection.contexts.first; injected.macroImplementation = await AnalyzerMacroImplementation.start( - protocol: Protocol(encoding: 'binary'), + protocol: Protocol(encoding: ProtocolEncoding.binary), packageConfig: Isolate.packageConfigSync!); }); diff --git a/pkgs/_analyzer_macros/test/golden_test.dart b/pkgs/_analyzer_macros/test/golden_test.dart index d0da2889..361af6cc 100644 --- a/pkgs/_analyzer_macros/test/golden_test.dart +++ b/pkgs/_analyzer_macros/test/golden_test.dart @@ -30,7 +30,7 @@ void main() { AnalysisContextCollection(includedPaths: [directory.path]); analysisContext = contextCollection.contexts.first; injected.macroImplementation = await AnalyzerMacroImplementation.start( - protocol: Protocol(encoding: 'binary'), + protocol: Protocol(encoding: ProtocolEncoding.binary), packageConfig: Isolate.packageConfigSync!); }); diff --git a/pkgs/_cfe_macros/test/cfe_test.dart b/pkgs/_cfe_macros/test/cfe_test.dart index c94d78ee..738c53db 100644 --- a/pkgs/_cfe_macros/test/cfe_test.dart +++ b/pkgs/_cfe_macros/test/cfe_test.dart @@ -31,7 +31,7 @@ void main() { // Inject test macro implementation. injected.macroImplementation = await CfeMacroImplementation.start( - protocol: Protocol(encoding: 'json'), + protocol: Protocol(encoding: ProtocolEncoding.json), packageConfig: Isolate.packageConfigSync!); }); diff --git a/pkgs/_cfe_macros/test/golden_test.dart b/pkgs/_cfe_macros/test/golden_test.dart index 91e56f2d..14746386 100644 --- a/pkgs/_cfe_macros/test/golden_test.dart +++ b/pkgs/_cfe_macros/test/golden_test.dart @@ -38,7 +38,7 @@ void main() { // Inject test macro implementation. injected.macroImplementation = await CfeMacroImplementation.start( - protocol: Protocol(encoding: 'json'), + protocol: Protocol(encoding: ProtocolEncoding.json), packageConfig: Isolate.packageConfigSync!); }); diff --git a/pkgs/_macro_client/test/macro_client_test.dart b/pkgs/_macro_client/test/macro_client_test.dart index 6b91a4bf..dd9adf34 100644 --- a/pkgs/_macro_client/test/macro_client_test.dart +++ b/pkgs/_macro_client/test/macro_client_test.dart @@ -15,8 +15,8 @@ import 'package:test/test.dart'; void main() { for (final protocol in [ - Protocol(encoding: 'json'), - Protocol(encoding: 'binary') + Protocol(encoding: ProtocolEncoding.json), + Protocol(encoding: ProtocolEncoding.binary) ]) { group('MacroClient using ${protocol.encoding}', () { test('connects to service', () async { diff --git a/pkgs/_macro_host/test/macro_host_test.dart b/pkgs/_macro_host/test/macro_host_test.dart index 221094f4..928b55a3 100644 --- a/pkgs/_macro_host/test/macro_host_test.dart +++ b/pkgs/_macro_host/test/macro_host_test.dart @@ -11,8 +11,8 @@ import 'package:test/test.dart'; void main() { for (final protocol in [ - Protocol(encoding: 'json'), - Protocol(encoding: 'binary') + Protocol(encoding: ProtocolEncoding.json), + Protocol(encoding: ProtocolEncoding.binary) ]) { group('MacroHost using ${protocol.encoding}', () { test('hosts a macro, receives augmentations', () async { diff --git a/pkgs/_macro_runner/test/macro_runner_test.dart b/pkgs/_macro_runner/test/macro_runner_test.dart index a8774b45..b0a8d80e 100644 --- a/pkgs/_macro_runner/test/macro_runner_test.dart +++ b/pkgs/_macro_runner/test/macro_runner_test.dart @@ -13,8 +13,8 @@ import 'package:test/test.dart'; void main() { for (final protocol in [ - Protocol(encoding: 'json'), - Protocol(encoding: 'binary') + Protocol(encoding: ProtocolEncoding.json), + Protocol(encoding: ProtocolEncoding.binary) ]) { group('MacroRunner with ${protocol.encoding}', () { test('runs macros', () async { diff --git a/pkgs/_macro_server/test/macro_server_test.dart b/pkgs/_macro_server/test/macro_server_test.dart index 56024f3c..1b409e08 100644 --- a/pkgs/_macro_server/test/macro_server_test.dart +++ b/pkgs/_macro_server/test/macro_server_test.dart @@ -12,8 +12,8 @@ import 'package:test/test.dart'; void main() { for (final protocol in [ - Protocol(encoding: 'json'), - Protocol(encoding: 'binary') + Protocol(encoding: ProtocolEncoding.json), + Protocol(encoding: ProtocolEncoding.binary) ]) { group('MacroServer using ${protocol.encoding}', () { test('serves a macro service', () async { diff --git a/pkgs/macro_service/lib/src/macro_service.dart b/pkgs/macro_service/lib/src/macro_service.dart index 8972e0b3..a9198c1d 100644 --- a/pkgs/macro_service/lib/src/macro_service.dart +++ b/pkgs/macro_service/lib/src/macro_service.dart @@ -46,29 +46,27 @@ 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 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.'); + switch (encoding) { + case ProtocolEncoding.json: + sink(_jsonConverter.encode(node) as Uint8List); + sink(_utf8Newline); + case ProtocolEncoding.binary: + // 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); + default: + throw StateError('Unsupported protocol: $this.'); } } @@ -77,17 +75,18 @@ extension ProtocolExtension on Protocol { /// The data on `stream` can be arbitrarily split to `Uint8List` instances, /// it does not have to be one list per message. Stream> decode(Stream stream) { - if (isJson) { - return const Utf8Decoder() - .bind(stream) - .transform(const LineSplitter()) - .map((line) => json.decode(line) as Map); - } else if (isBinary) { - return MessageGrouper(stream) - .messageStream - .map((message) => message.deserializeFromBinary()); - } else { - throw StateError('Unsupported protocol: $this'); + switch (encoding) { + case ProtocolEncoding.json: + return const Utf8Decoder() + .bind(stream) + .transform(const LineSplitter()) + .map((line) => json.decode(line) as Map); + case ProtocolEncoding.binary: + return MessageGrouper(stream) + .messageStream + .map((message) => message.deserializeFromBinary()); + default: + throw StateError('Unsupported protocol: $this'); } } } diff --git a/pkgs/macro_service/lib/src/macro_service.g.dart b/pkgs/macro_service/lib/src/macro_service.g.dart index 3913334f..22b161db 100644 --- a/pkgs/macro_service/lib/src/macro_service.g.dart +++ b/pkgs/macro_service/lib/src/macro_service.g.dart @@ -199,13 +199,20 @@ extension type MacroRequest.fromJson(Map node) /// The macro to host protocol version and encoding. TODO(davidmorgan): add the version. extension type Protocol.fromJson(Map node) implements Object { Protocol({ - String? encoding, + ProtocolEncoding? encoding, }) : this.fromJson({ if (encoding != null) 'encoding': encoding, }); - /// The wire format: json or binary. TODO(davidmorgan): use an enum? - String get encoding => node['encoding'] as String; + /// The wire format: json or binary. + ProtocolEncoding get encoding => node['encoding'] as ProtocolEncoding; +} + +/// The wire encoding used. +extension type const ProtocolEncoding.fromJson(String string) + implements Object { + static const ProtocolEncoding json = ProtocolEncoding.fromJson('json'); + static const ProtocolEncoding binary = ProtocolEncoding.fromJson('binary'); } /// Macro's query about the code it should augment. diff --git a/pkgs/macro_service/test/protocol_test.dart b/pkgs/macro_service/test/protocol_test.dart index 2ea86171..f317e103 100644 --- a/pkgs/macro_service/test/protocol_test.dart +++ b/pkgs/macro_service/test/protocol_test.dart @@ -11,8 +11,8 @@ import 'package:test/test.dart'; void main() { for (final protocol in [ - Protocol(encoding: 'json'), - Protocol(encoding: 'binary') + Protocol(encoding: ProtocolEncoding.json), + Protocol(encoding: ProtocolEncoding.binary) ]) { group('Protocol using ${protocol.encoding}', () { test('can round trip JSON data', () async { diff --git a/schemas/macro_service.schema.json b/schemas/macro_service.schema.json index e79888f2..a4162d44 100644 --- a/schemas/macro_service.schema.json +++ b/schemas/macro_service.schema.json @@ -145,11 +145,15 @@ "description": "The macro to host protocol version and encoding. TODO(davidmorgan): add the version.", "properties": { "encoding": { - "type": "string", - "description": "The wire format: json or binary. TODO(davidmorgan): use an enum?" + "$comment": "The wire format: json or binary.", + "$ref": "#/$defs/ProtocolEncoding" } } }, + "ProtocolEncoding": { + "type": "string", + "description": "The wire encoding used." + }, "QueryRequest": { "type": "object", "description": "Macro's query about the code it should augment.", diff --git a/tool/dart_model_generator/lib/definitions.dart b/tool/dart_model_generator/lib/definitions.dart index abd8db88..14374732 100644 --- a/tool/dart_model_generator/lib/definitions.dart +++ b/tool/dart_model_generator/lib/definitions.dart @@ -332,10 +332,11 @@ final schemas = Schemas([ 'TODO(davidmorgan): add the version.', properties: [ Property('encoding', - type: 'String', - description: 'The wire format: json or binary. ' - 'TODO(davidmorgan): use an enum?'), + type: 'ProtocolEncoding', + description: 'The wire format: json or binary.'), ]), + Definition.$enum('ProtocolEncoding', + description: 'The wire encoding used.', values: ['json', 'binary']), Definition.clazz('QueryRequest', description: "Macro's query about the code it should augment.", properties: [ diff --git a/tool/dart_model_generator/lib/generate_dart_model.dart b/tool/dart_model_generator/lib/generate_dart_model.dart index 16a2d7de..69eff75b 100644 --- a/tool/dart_model_generator/lib/generate_dart_model.dart +++ b/tool/dart_model_generator/lib/generate_dart_model.dart @@ -209,6 +209,11 @@ abstract class Definition { required List properties, bool createInBuffer}) = UnionTypeDefinition; + /// Defines an enum. + factory Definition.$enum(String name, + {required String description, + required List values}) = EnumTypeDefinition; + /// Defines a named type represented in JSON as a string. factory Definition.stringTypedef(String name, {required String description}) = StringTypedefDefinition; @@ -651,6 +656,43 @@ class UnionTypeDefinition implements Definition { String get representationTypeName => 'Map'; } +/// Definition of an enum type. +class EnumTypeDefinition implements Definition { + @override + final String name; + final String description; + final List values; + + EnumTypeDefinition(this.name, + {required this.description, required this.values}); + + @override + Map generateSchema(GenerationContext context) => { + 'type': 'string', + 'description': description, + }; + + @override + String generateCode(GenerationContext context) { + final result = StringBuffer(); + + result.writeln('/// $description'); + result.write('extension type const $name.fromJson(String string)' + ' implements Object {'); + for (final value in values) { + result.writeln("static const $name $value = $name.fromJson('$value');"); + } + result.writeln('}'); + return result.toString(); + } + + @override + Set get allTypeNames => {}; + + @override + String get representationTypeName => 'String'; +} + /// Definition of a named type that is actually a String. class StringTypedefDefinition implements Definition { @override