Add service client support
This commit is contained in:
parent
327d7dfac6
commit
1774306b06
@ -1,12 +1,42 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import "./out/lib/example.dart";
|
import "./out/lib/example.dart";
|
||||||
import "dart:convert";
|
import "dart:convert";
|
||||||
|
|
||||||
int main() {
|
void main() async {
|
||||||
var t = TestAtom(val_boolean: false, val_number: 1, val_string: "hi");
|
var str = StreamController<Map<String, dynamic>>();
|
||||||
print(jsonEncode(t));
|
|
||||||
|
|
||||||
var t2 = TestEnum.VAL2;
|
var provider = ServiceProvider(str.stream);
|
||||||
print(jsonEncode(t2));
|
var sock = await Socket.connect("127.0.0.1", 8859);
|
||||||
|
|
||||||
return 0;
|
utf8.decoder.bind(sock).transform(new LineSplitter()).listen((line) {
|
||||||
|
str.add(jsonDecode(line));
|
||||||
|
});
|
||||||
|
|
||||||
|
provider.output.stream.listen((event) {
|
||||||
|
sock.writeln(jsonEncode(event));
|
||||||
|
});
|
||||||
|
|
||||||
|
var s = new TestServiceClient(provider);
|
||||||
|
|
||||||
|
var r = await s.AddValuesMultipleParams(10, 15);
|
||||||
|
print(r);
|
||||||
|
|
||||||
|
var r2 =
|
||||||
|
await s.AddValuesSingleParam(AddValueRequest(value1: 10, value2: 15));
|
||||||
|
print(r2?.value);
|
||||||
|
|
||||||
|
var catched = false;
|
||||||
|
await s.ThrowingError().catchError((err) {
|
||||||
|
catched = true;
|
||||||
|
print("Expected error was catched: " + err.toString());
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!catched) {
|
||||||
|
throw Error();
|
||||||
|
}
|
||||||
|
|
||||||
|
await sock.close();
|
||||||
|
// exit(0);
|
||||||
}
|
}
|
||||||
|
62
examples/Typescript/server.ts
Normal file
62
examples/Typescript/server.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
|
||||||
|
import * as net from "net";
|
||||||
|
import { Server, AddValueRequest, AddValueResponse } from "./out";
|
||||||
|
import * as readline from 'node:readline';
|
||||||
|
|
||||||
|
|
||||||
|
const server = new Server.ServiceProvider();
|
||||||
|
|
||||||
|
class TestService extends Server.TestService<undefined> {
|
||||||
|
async AddValuesSingleParam(
|
||||||
|
request: AddValueRequest,
|
||||||
|
ctx: undefined
|
||||||
|
): Promise<AddValueResponse> {
|
||||||
|
return {
|
||||||
|
value: request.value1 + request!.value2,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
async AddValuesMultipleParams(
|
||||||
|
value1: number,
|
||||||
|
value2: number,
|
||||||
|
ctx: undefined
|
||||||
|
): Promise<number> {
|
||||||
|
return value1 + value2;
|
||||||
|
}
|
||||||
|
|
||||||
|
async ReturningVoid(param1) {
|
||||||
|
console.log("Calling Returning Void");
|
||||||
|
}
|
||||||
|
|
||||||
|
OnEvent(param1: string, ctx: undefined): void {
|
||||||
|
console.log("Received notification", param1);
|
||||||
|
}
|
||||||
|
|
||||||
|
async FunctionWithArrayAsParamAndReturn(vals1, vals2) {
|
||||||
|
return [...vals1, ...vals2];
|
||||||
|
}
|
||||||
|
|
||||||
|
async ThrowingError(ctx: undefined): Promise<void> {
|
||||||
|
throw new Error("Remote error!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server.addService(new TestService());
|
||||||
|
|
||||||
|
net.createServer((socket) => {
|
||||||
|
socket.on("error", console.error);
|
||||||
|
|
||||||
|
console.log("Connection from:", socket.remoteAddress);
|
||||||
|
const sess = server.getSession(msg => {
|
||||||
|
const s = JSON.stringify(msg);
|
||||||
|
console.log("Sending:", s);
|
||||||
|
socket.write(s + "\n");
|
||||||
|
});
|
||||||
|
const rl = readline.createInterface(socket);
|
||||||
|
rl.on("line", line => {
|
||||||
|
console.log("Receiving:", line);
|
||||||
|
sess.onMessage(JSON.parse(line));
|
||||||
|
})
|
||||||
|
rl.on("error", console.error);
|
||||||
|
}).listen(8859).on("listening", () => {
|
||||||
|
console.log("Is listening on :8859");
|
||||||
|
}).on("error", console.error)
|
@ -22,7 +22,7 @@ class TestService extends Server.TestService<undefined> {
|
|||||||
ctx: undefined
|
ctx: undefined
|
||||||
): Promise<AddValueResponse> {
|
): Promise<AddValueResponse> {
|
||||||
return {
|
return {
|
||||||
value: request.value1 + request.value2,
|
value: request.value1 + request!.value2,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
async AddValuesMultipleParams(
|
async AddValuesMultipleParams(
|
||||||
|
@ -10,7 +10,7 @@ const conversion = {
|
|||||||
float: "double",
|
float: "double",
|
||||||
string: "String",
|
string: "String",
|
||||||
void: "void",
|
void: "void",
|
||||||
bytes: "Uint8List", //TODO: Check this
|
// bytes: "Uint8List", //TODO: Check this
|
||||||
};
|
};
|
||||||
|
|
||||||
function toDartType(type: string): string {
|
function toDartType(type: string): string {
|
||||||
@ -35,11 +35,20 @@ export class DartTarget extends CompileTarget<{ dart_library_name: string }> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
generateImports(a: lineAppender, def: TypeDefinition | ServiceDefinition) {
|
generateImports(a: lineAppender, def: TypeDefinition | ServiceDefinition) {
|
||||||
|
a(0, `import "./base.dart";`)
|
||||||
def.depends.forEach((dep) => {
|
def.depends.forEach((dep) => {
|
||||||
a(0, this.getImport(dep));
|
a(0, this.getImport(dep));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTypeParse(type: string, value: string) {
|
||||||
|
if (conversion[type]) {
|
||||||
|
return `${toDartType(type)}_fromJson(${value})`;
|
||||||
|
} else {
|
||||||
|
return `${toDartType(type)}.fromJson(${value})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
generateType(definition: TypeDefinition): void {
|
generateType(definition: TypeDefinition): void {
|
||||||
const { a, getResult } = LineAppender();
|
const { a, getResult } = LineAppender();
|
||||||
|
|
||||||
@ -53,8 +62,7 @@ export class DartTarget extends CompileTarget<{ dart_library_name: string }> {
|
|||||||
} else if (field.map) {
|
} else if (field.map) {
|
||||||
a(
|
a(
|
||||||
1,
|
1,
|
||||||
`Map<${toDartType(field.map)},${toDartType(field.type)}>? ${
|
`Map<${toDartType(field.map)},${toDartType(field.type)}>? ${field.name
|
||||||
field.name
|
|
||||||
};`
|
};`
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@ -74,18 +82,10 @@ export class DartTarget extends CompileTarget<{ dart_library_name: string }> {
|
|||||||
for (const field of definition.fields) {
|
for (const field of definition.fields) {
|
||||||
a(2, `if(json.containsKey("${field.name}")) {`);
|
a(2, `if(json.containsKey("${field.name}")) {`);
|
||||||
|
|
||||||
const parseField = (value: string) => {
|
|
||||||
if (conversion[field.type]) {
|
|
||||||
return value;
|
|
||||||
} else {
|
|
||||||
return `${field.type}.fromJson(${value})`;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (field.array) {
|
if (field.array) {
|
||||||
a(3, `this.${field.name} = [];`);
|
a(3, `this.${field.name} = [];`);
|
||||||
a(3, `(json["${field.name}"] as List<dynamic>).forEach((e) => {`);
|
a(3, `(json["${field.name}"] as List<dynamic>).forEach((e) => {`);
|
||||||
a(4, `this.${field.name}!.add(${parseField("e")})`);
|
a(4, `this.${field.name}!.add(${this.getTypeParse(field.type, "e")})`);
|
||||||
a(3, `});`);
|
a(3, `});`);
|
||||||
} else if (field.map) {
|
} else if (field.map) {
|
||||||
a(3, `this.${field.name} = {};`);
|
a(3, `this.${field.name} = {};`);
|
||||||
@ -95,12 +95,12 @@ export class DartTarget extends CompileTarget<{ dart_library_name: string }> {
|
|||||||
field.map
|
field.map
|
||||||
)},dynamic>).forEach((key, value) => {`
|
)},dynamic>).forEach((key, value) => {`
|
||||||
);
|
);
|
||||||
a(4, `this.${field.name}![key] = ${parseField("value")}`);
|
a(4, `this.${field.name}![key] = ${this.getTypeParse(field.type, "value")}`);
|
||||||
a(3, `});`);
|
a(3, `});`);
|
||||||
} else {
|
} else {
|
||||||
a(
|
a(
|
||||||
3,
|
3,
|
||||||
`this.${field.name} = ${parseField(`json["${field.name}"]`)};`
|
`this.${field.name} = ${this.getTypeParse(field.type, `json["${field.name}"]`)};`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
a(2, `} else {`);
|
a(2, `} else {`);
|
||||||
@ -178,13 +178,65 @@ export class DartTarget extends CompileTarget<{ dart_library_name: string }> {
|
|||||||
this.writeFile(`lib/src/${definition.name}.dart`, getResult());
|
this.writeFile(`lib/src/${definition.name}.dart`, getResult());
|
||||||
}
|
}
|
||||||
|
|
||||||
generateService(definition: ServiceDefinition): void {
|
generateServiceClient(definition: ServiceDefinition): void {
|
||||||
|
const { a, getResult } = LineAppender();
|
||||||
|
|
||||||
|
this.generateImports(a, definition);
|
||||||
|
a(0, `import "./service_client.dart";`);
|
||||||
|
a(0, ``);
|
||||||
|
a(0, `class ${definition.name}Client extends Service {`);
|
||||||
|
a(0, ``);
|
||||||
|
|
||||||
|
a(1, `${definition.name}Client(ServiceProvider provider):super(provider, "${definition.name}");`);
|
||||||
|
|
||||||
|
a(0, ``);
|
||||||
|
|
||||||
|
for (const func of definition.functions) {
|
||||||
|
const args = func.inputs.map(inp =>
|
||||||
|
(inp.array ? `List<${toDartType(inp.type)}>` : toDartType(inp.type)) + " " + inp.name
|
||||||
|
).join(", ");
|
||||||
|
|
||||||
|
const asParams = func.inputs.map(e => e.name).join(", ");
|
||||||
|
|
||||||
|
if (!func.return) {
|
||||||
|
a(1, `void ${func.name}(${args}) {`)
|
||||||
|
a(2, `provider.sendNotification("${definition.name}.${func.name}", [${asParams}]);`);
|
||||||
|
a(1, `}`);
|
||||||
|
} else {
|
||||||
|
const baseReturnType = func.return.type != "void" ? (toDartType(func.return.type) + "?") : toDartType(func.return.type);
|
||||||
|
const returnType = func.return.array ? `List<${baseReturnType}>` : baseReturnType;
|
||||||
|
|
||||||
|
a(1, `Future<${returnType}> ${func.name}(${args}) async {`);
|
||||||
|
a(2, `var res = await this.provider.sendRequest("${definition.name}.${func.name}", [${asParams}]);`);
|
||||||
|
if (func.return.type !== "void") {
|
||||||
|
if(func.return.array) {
|
||||||
|
a(2, `return res.map((entry) =>${this.getTypeParse(func.return.type, "entry")}).toList();`);
|
||||||
|
} else {
|
||||||
|
a(2, `return ${this.getTypeParse(func.return.type, "res")};`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a(1, `}`);
|
||||||
|
}
|
||||||
|
a(0, ``);
|
||||||
|
}
|
||||||
|
a(0, `}`);
|
||||||
|
a(0, ``);
|
||||||
|
|
||||||
|
this.writeFile(`lib/src/${definition.name}Client.dart`, getResult());
|
||||||
|
}
|
||||||
|
|
||||||
|
generateServiceServer(definition: ServiceDefinition): void {
|
||||||
console.log(
|
console.log(
|
||||||
chalk.yellow("[DART] WARNING:"),
|
chalk.yellow("[DART] WARNING:"),
|
||||||
"DART support for services is not yet there. Service generation is skipped!"
|
"DART support for services is not yet there. Service generation is currently limited to clients!"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
generateService(definition: ServiceDefinition): void {
|
||||||
|
this.generateServiceClient(definition);
|
||||||
|
this.writeFile("lib/src/service_client.dart", this.getTemplate("Dart/service_client.dart"))
|
||||||
|
}
|
||||||
|
|
||||||
finalize(steps: Step[]): void {
|
finalize(steps: Step[]): void {
|
||||||
const { a, getResult } = LineAppender();
|
const { a, getResult } = LineAppender();
|
||||||
|
|
||||||
@ -192,6 +244,9 @@ export class DartTarget extends CompileTarget<{ dart_library_name: string }> {
|
|||||||
a(0, ``);
|
a(0, ``);
|
||||||
|
|
||||||
|
|
||||||
|
let hasService = false;
|
||||||
|
|
||||||
|
|
||||||
steps.forEach(([type, def]) => {
|
steps.forEach(([type, def]) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "type":
|
case "type":
|
||||||
@ -200,18 +255,25 @@ export class DartTarget extends CompileTarget<{ dart_library_name: string }> {
|
|||||||
case "enum":
|
case "enum":
|
||||||
a(0, `export 'src/${def.name}.dart';`);
|
a(0, `export 'src/${def.name}.dart';`);
|
||||||
break;
|
break;
|
||||||
|
case "service":
|
||||||
|
a(0, `export 'src/${def.name}Client.dart';`);
|
||||||
|
hasService = true;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
console.warn(
|
console.warn(
|
||||||
chalk.yellow("[DART] WARNING:"),
|
chalk.yellow("[DART] WARNING:"),
|
||||||
"unimplemented step found:",
|
"unimplemented step found:",
|
||||||
type
|
type
|
||||||
);
|
);
|
||||||
// case "service":
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.writeFile(`lib/${this.options.dart_library_name}.dart`, getResult());
|
if (hasService) {
|
||||||
|
a(0, `export 'src/service_client.dart';`)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.writeFile(`lib/${this.options.dart_library_name}.dart`, getResult());
|
||||||
this.writeFile(`pubspec.yaml`, this.getTemplate("Dart/pubspec.yaml").replace("__NAME__", this.options.dart_library_name));
|
this.writeFile(`pubspec.yaml`, this.getTemplate("Dart/pubspec.yaml").replace("__NAME__", this.options.dart_library_name));
|
||||||
|
this.writeFile(`lib/src/base.dart`, this.getTemplate("Dart/base.dart"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
52
templates/Dart/base.dart
Normal file
52
templates/Dart/base.dart
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
class JRPCError extends Error {
|
||||||
|
String message;
|
||||||
|
JRPCError(this.message);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => message;
|
||||||
|
}
|
||||||
|
|
||||||
|
int? int_fromJson(dynamic val) {
|
||||||
|
if (val == null) {
|
||||||
|
return null;
|
||||||
|
} else if (val is int) {
|
||||||
|
return val;
|
||||||
|
} else if (val is double) {
|
||||||
|
return val.toInt();
|
||||||
|
} else {
|
||||||
|
throw JRPCError("Not a number!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double? double_fromJson(dynamic val) {
|
||||||
|
if (val == null) {
|
||||||
|
return null;
|
||||||
|
} else if (val is int) {
|
||||||
|
return val.toDouble();
|
||||||
|
} else if (val is double) {
|
||||||
|
return val;
|
||||||
|
} else {
|
||||||
|
throw JRPCError("Not a number!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool? bool_fromJson(dynamic val) {
|
||||||
|
if (val == null) {
|
||||||
|
return null;
|
||||||
|
} else if (val is bool) {
|
||||||
|
return val;
|
||||||
|
} else {
|
||||||
|
throw JRPCError("Not a bool!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String? String_fromJson(dynamic val) {
|
||||||
|
if (val == null) {
|
||||||
|
return null;
|
||||||
|
} else if (val is String) {
|
||||||
|
return val;
|
||||||
|
} else {
|
||||||
|
return val.toString(); // TODO: Either this or error
|
||||||
|
// throw JRPCError("Not a string!");
|
||||||
|
}
|
||||||
|
}
|
86
templates/Dart/service_client.dart
Normal file
86
templates/Dart/service_client.dart
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import "dart:async";
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import "./base.dart";
|
||||||
|
|
||||||
|
abstract class Service {
|
||||||
|
String name;
|
||||||
|
ServiceProvider provider;
|
||||||
|
|
||||||
|
Service(this.provider, this.name) {
|
||||||
|
provider._services[name] = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServiceProvider {
|
||||||
|
final Map<String, Service> _services = {};
|
||||||
|
final Map<String, Completer<dynamic>> _requests = {};
|
||||||
|
|
||||||
|
StreamController<Map<String, dynamic>> output = StreamController();
|
||||||
|
late StreamSubscription s;
|
||||||
|
|
||||||
|
ServiceProvider(Stream<Map<String, dynamic>> input) {
|
||||||
|
s = input.listen(onMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onMessage(Map<String, dynamic> msg) {
|
||||||
|
// print("Working on message");
|
||||||
|
if (msg.containsKey("method")) {
|
||||||
|
if (msg.containsKey("id")) {
|
||||||
|
print("Message is request");
|
||||||
|
// Request, not supported!
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// print("Message is notification");
|
||||||
|
// Notification
|
||||||
|
// TODO: Implement
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// print("Message is response");
|
||||||
|
// Response
|
||||||
|
var req = _requests[msg["id"]];
|
||||||
|
|
||||||
|
if (req == null) {
|
||||||
|
// print("Could not find related request. Ignoring");
|
||||||
|
return; // Irrelevant response
|
||||||
|
}
|
||||||
|
if (msg.containsKey("error")) {
|
||||||
|
//TODO:
|
||||||
|
req.completeError(JRPCError(msg["error"]["message"]));
|
||||||
|
} else {
|
||||||
|
req.complete(msg["result"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<dynamic> sendRequest(String method, dynamic params) {
|
||||||
|
var id = nanoid(10);
|
||||||
|
var req = {"jsonrpc": "2.0", "id": id, "method": method, "params": params};
|
||||||
|
|
||||||
|
var completer = Completer<dynamic>();
|
||||||
|
|
||||||
|
output.add(req);
|
||||||
|
_requests[id] = completer;
|
||||||
|
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendNotification(String method, dynamic params) {
|
||||||
|
var req = {"jsonrpc": "2.0", "method": method, "params": params};
|
||||||
|
output.add(req);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copied from: https://github.com/pd4d10/nanoid-dart (MIT License)
|
||||||
|
final _random = Random.secure();
|
||||||
|
const urlAlphabet =
|
||||||
|
'ModuleSymbhasOwnPr-0123456789ABCDEFGHNRVfgctiUvz_KqYTJkLxpZXIjQW';
|
||||||
|
|
||||||
|
String nanoid([int size = 21]) {
|
||||||
|
final len = urlAlphabet.length;
|
||||||
|
String id = '';
|
||||||
|
while (0 < size--) {
|
||||||
|
id += urlAlphabet[_random.nextInt(len)];
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user