14 Commits

Author SHA1 Message Date
f4604b077f Switch to newer esbuild target 2022-07-16 21:47:34 +00:00
4b4948207c Unify lineAppender 2022-07-16 21:43:56 +00:00
7de674ab3c Add lib to ignore 2022-07-16 21:37:28 +00:00
ff40c03600 Bumping version to 1.1.3 2022-07-16 21:37:02 +00:00
e110259684 Make C# tests not crash the whole test command! 2022-07-16 21:34:58 +00:00
df9576bbaf Add better error messages 2022-07-16 21:31:45 +00:00
7a3a4c6791 Remove built file from repository 2022-07-16 21:26:38 +00:00
2289a6d6ab Add zig output to package.json 2022-07-16 21:26:10 +00:00
8ee16fb09d Start implementing ZIG 2022-07-16 21:25:19 +00:00
af4902c9a8 Fix output directory 2022-07-16 21:24:21 +00:00
2e0adf047a Merge branch 'rust-target-threaded' into 'main'
Add Target: Rust

See merge request hibas123/JsonRPC!3
2022-07-16 21:22:17 +00:00
96d2d9ee76 Add Target: Rust 2022-07-16 21:22:16 +00:00
71a6e3f2e0 Fixing security bug 2022-05-21 17:55:14 +00:00
94131c55a0 Merge branch 'number-to-int-float' into 'main'
Remove number and add int and float insted

See merge request hibas123/JsonRPC!1
2022-04-13 19:08:28 +00:00
26 changed files with 1016 additions and 11489 deletions

View File

@ -9,3 +9,5 @@ insert_final_newline = true
indent_size = 2 indent_size = 2
[*.md] [*.md]
indent_size = 2 indent_size = 2
[*.zig]
indent_size = 4

4
.gitignore vendored
View File

@ -6,6 +6,8 @@ examples/CSharp/Generated
examples/CSharp/Example/bin examples/CSharp/Example/bin
examples/CSharp/Example/obj examples/CSharp/Example/obj
examples/definition.json examples/definition.json
examples/Rust/Generated examples/Rust/Gen
examples/Rust/Impl/target
templates/CSharp/bin templates/CSharp/bin
templates/CSharp/obj templates/CSharp/obj
lib/

View File

@ -1,4 +1,5 @@
nodeLinker: node-modules nodeLinker: node-modules
npmRegistryServer: https://npm.hibas123.de
plugins: plugins:
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs

View File

@ -4,47 +4,57 @@ using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
class TestSrvimpl : Example.TestServiceServer<int> { class TestSrvimpl : Example.TestServiceServer<int>
public TestSrvimpl(): base() {} {
public override async Task<Example.AddValueResponse> AddValuesSingleParam(AddValueRequest request, int ctx) { public TestSrvimpl() : base() { }
public override async Task<Example.AddValueResponse> AddValuesSingleParam(AddValueRequest request, int ctx)
{
var res = new Example.AddValueResponse(); var res = new Example.AddValueResponse();
res.value = 1; res.value = 1;
return res; return res;
} }
public override async Task<double> AddValuesMultipleParams(double value1,double value2,int ctx) { public override async Task<double> AddValuesMultipleParams(double value1, double value2, int ctx)
{
return value1 + value2; return value1 + value2;
} }
public override async Task ReturningVoid(double param1,int ctx) { public override async Task ReturningVoid(double param1, int ctx)
{
} }
public override void OnEvent(string param1,int ctx) { public override void OnEvent(string param1, int ctx)
{
Console.WriteLine($"OnEvent {param1}"); Console.WriteLine($"OnEvent {param1}");
} }
public override async Task<IList<double>> FunctionWithArrayAsParamAndReturn(List<double> values1,List<double> values2, int ctx) { public override async Task<IList<double>> FunctionWithArrayAsParamAndReturn(List<double> values1, List<double> values2, int ctx)
{
var l = new List<double>(); var l = new List<double>();
l.Append(1); l.Append(1);
return l; return l;
} }
public override async Task ThrowingError(int ctx) { public override async Task ThrowingError(int ctx)
{
throw new Exception("This is a remote error :)"); throw new Exception("This is a remote error :)");
} }
} }
class CopyTransportS2 : Example.JRpcTransport { class CopyTransportS2 : Example.JRpcTransport
{
CopyTransportS1 tr1; CopyTransportS1 tr1;
public Queue<string> backlog = new Queue<string>(); public Queue<string> backlog = new Queue<string>();
public CopyTransportS2(CopyTransportS1 tr1) { public CopyTransportS2(CopyTransportS1 tr1)
{
this.tr1 = tr1; this.tr1 = tr1;
} }
public override Task Write(string data) { public override Task Write(string data)
{
Console.WriteLine("--> " + data); Console.WriteLine("--> " + data);
this.tr1.SendPacketEvent(data); this.tr1.SendPacketEvent(data);
@ -52,28 +62,34 @@ class CopyTransportS2 : Example.JRpcTransport {
} }
} }
class CopyTransportS1 : Example.JRpcTransport { class CopyTransportS1 : Example.JRpcTransport
{
public Queue<string> backlog = new Queue<string>(); public Queue<string> backlog = new Queue<string>();
public CopyTransportS2 tr2; public CopyTransportS2 tr2;
public CopyTransportS1() { public CopyTransportS1()
{
this.tr2 = new CopyTransportS2(this); this.tr2 = new CopyTransportS2(this);
} }
public override Task Write(string data) { public override Task Write(string data)
{
Console.WriteLine("<-- " + data); Console.WriteLine("<-- " + data);
this.tr2.SendPacketEvent(data); this.tr2.SendPacketEvent(data);
return Task.CompletedTask; return Task.CompletedTask;
} }
} }
class Program { class Program
public static void Main() { {
public static void Main()
{
Program.Start().Wait(); Program.Start().Wait();
} }
public static async Task Start() { public static async Task Start()
{
var server = new Example.JRpcServer<int>(); var server = new Example.JRpcServer<int>();
server.AddService(new TestSrvimpl()); server.AddService(new TestSrvimpl());
var transport = new CopyTransportS1(); var transport = new CopyTransportS1();
@ -82,11 +98,19 @@ class Program {
var client = new Example.JRpcClient(transport.tr2); var client = new Example.JRpcClient(transport.tr2);
var testService = new Example.TestServiceClient(client); var testService = new Example.TestServiceClient(client);
var result = await testService.AddValuesMultipleParams(1,2); var result = await testService.AddValuesMultipleParams(1, 2);
Console.WriteLine($"Add 1 + 2 = {result}"); Console.WriteLine($"Add 1 + 2 = {result}");
await testService.ThrowingError(); try
{
await testService.ThrowingError();
throw new Exception("No error was issued, even though it was expected!");
}
catch (JRpcException err)
{
Console.WriteLine("Error successfully catched: ", err.Message);
}
} }
} }

2
examples/Rust/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
Generated/
Impl/target

248
examples/Rust/Impl/Cargo.lock generated Normal file
View File

@ -0,0 +1,248 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "getrandom"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
dependencies = [
"cfg-if 1.0.0",
"libc",
"wasi",
]
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "int-enum"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b1428b2b1abe959e6eedb0a17d0ab12f6ba20e1106cc29fc4874e3ba393c177"
dependencies = [
"cfg-if 0.1.10",
"int-enum-impl",
]
[[package]]
name = "int-enum-impl"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2c3cecaad8ca1a5020843500c696de2b9a07b63b624ddeef91f85f9bafb3671"
dependencies = [
"cfg-if 0.1.10",
"proc-macro-crate",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "itoa"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
[[package]]
name = "libc"
version = "0.2.123"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb691a747a7ab48abc15c5b42066eaafde10dc427e3b6ee2a1cf43db04c763bd"
[[package]]
name = "nanoid"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8"
dependencies = [
"rand",
]
[[package]]
name = "num_cpus"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "ppv-lite86"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
[[package]]
name = "proc-macro-crate"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785"
dependencies = [
"toml",
]
[[package]]
name = "proc-macro2"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
dependencies = [
"getrandom",
]
[[package]]
name = "ryu"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
[[package]]
name = "serde"
version = "1.0.136"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.136"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "syn"
version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "test"
version = "0.1.0"
dependencies = [
"int-enum",
"nanoid",
"serde",
"serde_json",
"threadpool",
]
[[package]]
name = "test-impl"
version = "0.1.0"
dependencies = [
"test",
]
[[package]]
name = "threadpool"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
dependencies = [
"num_cpus",
]
[[package]]
name = "toml"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
dependencies = [
"serde",
]
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"

View File

@ -0,0 +1,7 @@
[package]
name = "test-impl"
version = "0.1.0"
edition = "2021"
[dependencies]
test = { path = "../Generated/" }

View File

@ -0,0 +1,52 @@
use test::{JRPCServer,Result,Test};
use test::server::{TestService,TestServiceHandler};
use std::io::{BufReader,BufRead,Write};
use std::sync::mpsc::channel;
#[derive(Clone, Copy)]
struct MyCtx {}
struct TestServiceImplementation {}
impl TestService<MyCtx> for TestServiceImplementation {
fn GetTest(&self, name: String, age: i64, _context: &MyCtx) -> Result<Test> {
return Ok(Test { name, age });
}
fn TestNot(&self, _: &MyCtx) -> Result<()> {
return Ok(());
}
}
pub fn main() {
let mut srv = JRPCServer::<MyCtx>::new();
srv.add_service(TestServiceHandler::new(Box::from(TestServiceImplementation {})));
let listener = std::net::TcpListener::bind("127.0.0.1:4321").expect("Could not start listener!");
for stream in listener.incoming() {
println!("Got Connection!");
let (stx, srx) = channel::<String>();
let (rtx, rrx) = channel::<String>();
let mut stream = stream.expect("Bad stream");
let mut r = BufReader::new(stream.try_clone().unwrap());
let mut line = String::new();
srv.start_session(srx, rtx, MyCtx {});
r.read_line(&mut line).expect("Could not read line!");
println!("Got line: {}", line);
stx.send(line).expect("Could not send packet to handler!");
println!("Sending to handler succeeded");
let response = rrx.recv().expect("Could not get reponse from handler!");
println!("Prepared response {}", response);
stream.write((response + "\n").as_bytes()).expect("Could not send reponse!");
}
println!("Hello World");
// return Ok(());
}

2
examples/Zig/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
zig-cache/
generated/

34
examples/Zig/build.zig Normal file
View File

@ -0,0 +1,34 @@
const std = @import("std");
pub fn build(b: *std.build.Builder) void {
// Standard target options allows the person running `zig build` to choose
// what target to build for. Here we do not override the defaults, which
// means any target is allowed, and the default is native. Other options
// for restricting supported target set are available.
const target = b.standardTargetOptions(.{});
// Standard release options allow the person running `zig build` to select
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall.
const mode = b.standardReleaseOptions();
const exe = b.addExecutable("ZigTests", "src/main.zig");
exe.setTarget(target);
exe.setBuildMode(mode);
exe.install();
const run_cmd = exe.run();
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
const exe_tests = b.addTest("src/main.zig");
exe_tests.setTarget(target);
exe_tests.setBuildMode(mode);
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&exe_tests.step);
}

20
examples/Zig/src/main.zig Normal file
View File

@ -0,0 +1,20 @@
const std = @import("std");
const t = @import("./generated/mod.zig");
var mygpa = std.heap.GeneralPurposeAllocator(.{}){};
const gpa = mygpa.allocator();
const payload =
\\{
\\ "val_number": 0.12,
\\ "val_boolean": true,
\\ "val_string": "Hallo Welt"
\\}
;
pub fn main() !void {
var stream = std.json.TokenStream.init(payload);
const res = std.json.parse(t.TestAtom, &stream, .{ .allocator = gpa }) catch unreachable;
std.log.info("{} {s}", .{ res, res.val_string });
}

14
examples/test.jrpc Normal file
View File

@ -0,0 +1,14 @@
define rust_crate test;
type Test {
name: string;
age: int;
}
service TestService {
@Description("asdasdasd")
GetTest(name: string, age: int): Test;
notification TestNot();
}
// { "jsonrpc": "2.0", "method": "TestService.GetTest", "params": [ "SomeName", 25 ], "id": "someid" }

10980
lib/jrpc.js

File diff suppressed because it is too large Load Diff

View File

@ -1,17 +1,17 @@
{ {
"name": "@hibas123/jrpcgen", "name": "@hibas123/jrpcgen",
"version": "1.1.1", "version": "1.1.4",
"main": "lib/index.js", "main": "lib/index.js",
"license": "MIT", "license": "MIT",
"packageManager": "yarn@3.1.1", "packageManager": "yarn@3.1.1",
"scripts": { "scripts": {
"start": "ts-node src/index.ts", "start": "ts-node src/index.ts",
"test-start": "npm run start -- compile examples/example.jrpc --definition=examples/definition.json -o=ts-node:examples/Typescript/out -o=c#:examples/CSharp/Generated -o=rust:examples/Rust/Generated", "test-start": "npm run start -- compile examples/example.jrpc --definition=examples/definition.json -o=ts-node:examples/Typescript/out -o=c#:examples/CSharp/Generated -o=rust:examples/Rust/Generated -o=zig:examples/Zig/generated",
"test-csharp": "cd examples/CSharp/Example/ && dotnet run", "test-csharp": "cd examples/CSharp/Example/ && dotnet run",
"test-typescript": "cd examples/Typescript && ts-node test.ts",
"test-rust": "cd examples/Rust/Generated/ && cargo build", "test-rust": "cd examples/Rust/Generated/ && cargo build",
"test-typescript": "ts-node examples/test.ts",
"test": "npm run test-start && npm run test-csharp && npm run test-typescript", "test": "npm run test-start && npm run test-csharp && npm run test-typescript",
"build": "esbuild src/index.ts --bundle --platform=node --target=node14 --outfile=lib/jrpc.js", "build": "esbuild src/index.ts --bundle --platform=node --target=node16 --outfile=lib/jrpc.js",
"prepublishOnly": "npm run build" "prepublishOnly": "npm run build"
}, },
"bin": { "bin": {
@ -19,8 +19,12 @@
}, },
"files": [ "files": [
"lib/jrpc.js", "lib/jrpc.js",
"templates/**", "templates/*.ts",
"examples/*.jrpc", "templates/CSharp/*.cs",
"templates/CSharp/*.csproj",
"templates/Rust/Cargo.toml",
"templates/Rust/src/lib.rs",
"examples/*.jrpcproj",
"src/**", "src/**",
"tsconfig.json" "tsconfig.json"
], ],

View File

@ -44,22 +44,29 @@ yargs(hideBin(process.argv))
}); });
}, },
(argv) => { (argv) => {
if (argv.verbose) {dbg.enable("app");} if (argv.verbose) {
dbg.enable("app");
}
log("Received compile command with args", argv); log("Received compile command with args", argv);
startCompile({ startCompile({
input: argv.input, input: argv.input,
targets: argv.output as any, targets: argv.output as any,
emitDefinitions: argv.definition emitDefinitions: argv.definition,
}) });
}
)
.command(
"targets",
"List all targets",
(yargs) => yargs,
() => {
console.log("Targets:");
Targets.forEach((__dirname, target) => {
console.log(" " + target);
});
} }
) )
.command("targets", "List all targets", (yargs)=>yargs, ()=>{
console.log("Targets:")
Targets.forEach((__dirname, target) => {
console.log(" " + target);
})
})
.option("verbose", { .option("verbose", {
alias: "v", alias: "v",
type: "boolean", type: "boolean",

View File

@ -12,6 +12,7 @@ import {
} from "./targets/typescript"; } from "./targets/typescript";
import { CSharpTarget } from "./targets/csharp"; import { CSharpTarget } from "./targets/csharp";
import { RustTarget } from "./targets/rust"; import { RustTarget } from "./targets/rust";
import { ZIGTarget } from "./targets/zig";
class CatchedError extends Error {} class CatchedError extends Error {}
@ -23,6 +24,7 @@ Targets.set("ts-esm", ESMTypescriptTarget);
Targets.set("ts-node", NodeJSTypescriptTarget); Targets.set("ts-node", NodeJSTypescriptTarget);
Targets.set("c#", CSharpTarget as typeof CompileTarget); Targets.set("c#", CSharpTarget as typeof CompileTarget);
Targets.set("rust", RustTarget as typeof CompileTarget); Targets.set("rust", RustTarget as typeof CompileTarget);
Targets.set("zig", ZIGTarget as typeof CompileTarget);
function indexToLineAndCol(src: string, index: number) { function indexToLineAndCol(src: string, index: number) {
let line = 1; let line = 1;

View File

@ -7,8 +7,7 @@ import {
} from "../ir"; } from "../ir";
import { CompileTarget } from "../compile"; import { CompileTarget } from "../compile";
import { LineAppender } from "../utils";
type lineAppender = (ind: number, line: string | string[]) => void;
const conversion = { const conversion = {
boolean: "bool", boolean: "bool",
@ -52,13 +51,7 @@ export class CSharpTarget extends CompileTarget<{ csharp_namespace: string }> {
} }
generateType(definition: TypeDefinition): void { generateType(definition: TypeDefinition): void {
let lines: string[] = []; const { a, getResult } = LineAppender();
const a: lineAppender = (i, t) => {
if (!Array.isArray(t)) {
t = [t];
}
t.forEach((l) => lines.push(" ".repeat(i) + l.trim()));
};
a(0, `using System.Text.Json;`); a(0, `using System.Text.Json;`);
a(0, `using System.Text.Json.Serialization;`); a(0, `using System.Text.Json.Serialization;`);
@ -91,17 +84,11 @@ export class CSharpTarget extends CompileTarget<{ csharp_namespace: string }> {
} }
a(0, `}`); a(0, `}`);
this.writeFile(`${definition.name}.cs`, lines.join("\n")); this.writeFile(`${definition.name}.cs`, getResult());
} }
generateEnum(definition: EnumDefinition): void { generateEnum(definition: EnumDefinition): void {
let lines: string[] = []; const { a, getResult } = LineAppender();
const a: lineAppender = (i, t) => {
if (!Array.isArray(t)) {
t = [t];
}
t.forEach((l) => lines.push(" ".repeat(i) + l.trim()));
};
a(0, `using System.Text.Json;`); a(0, `using System.Text.Json;`);
a(0, `using System.Text.Json.Serialization;`); a(0, `using System.Text.Json.Serialization;`);
@ -114,17 +101,11 @@ export class CSharpTarget extends CompileTarget<{ csharp_namespace: string }> {
} }
a(0, `}`); a(0, `}`);
this.writeFile(`${definition.name}.cs`, lines.join("\n")); this.writeFile(`${definition.name}.cs`, getResult());
} }
generateServiceClient(definition: ServiceDefinition) { generateServiceClient(definition: ServiceDefinition) {
let lines: string[] = []; const { a, getResult } = LineAppender();
const a: lineAppender = (i, t) => {
if (!Array.isArray(t)) {
t = [t];
}
t.forEach((l) => lines.push(" ".repeat(i) + l.trim()));
};
a(0, `using System;`); a(0, `using System;`);
a(0, `using System.Text.Json;`); a(0, `using System.Text.Json;`);
@ -202,17 +183,11 @@ export class CSharpTarget extends CompileTarget<{ csharp_namespace: string }> {
// a(0, ``); // a(0, ``);
a(0, `}`); a(0, `}`);
this.writeFile(`${definition.name}Client.cs`, lines.join("\n")); this.writeFile(`${definition.name}Client.cs`, getResult());
} }
generateServiceServer(definition: ServiceDefinition) { generateServiceServer(definition: ServiceDefinition) {
let lines: string[] = []; const { a, getResult } = LineAppender();
const a: lineAppender = (i, t) => {
if (!Array.isArray(t)) {
t = [t];
}
t.forEach((l) => lines.push(" ".repeat(i) + l.trim()));
};
a(0, `using System;`); a(0, `using System;`);
a(0, `using System.Text.Json;`); a(0, `using System.Text.Json;`);
@ -331,7 +306,7 @@ export class CSharpTarget extends CompileTarget<{ csharp_namespace: string }> {
a(1, `}`); a(1, `}`);
a(0, `}`); a(0, `}`);
this.writeFile(`${definition.name}Server.cs`, lines.join("\n")); this.writeFile(`${definition.name}Server.cs`, getResult());
} }
generateService(definition: ServiceDefinition): void { generateService(definition: ServiceDefinition): void {

View File

@ -1,14 +1,14 @@
import chalk from "chalk";
import { CompileTarget } from "../compile"; import { CompileTarget } from "../compile";
import { TypeDefinition, EnumDefinition, ServiceDefinition, Step } from "../ir"; import { TypeDefinition, EnumDefinition, ServiceDefinition, Step } from "../ir";
import { lineAppender, LineAppender } from "../utils";
type lineAppender = (ind: number, line: string | string[]) => void;
const conversion = { const conversion = {
boolean: "bool", boolean: "bool",
float: "f64", float: "f64",
int: "i64", int: "i64",
string: "String", string: "String",
void: "void", void: "()",
bytes: "Vec<u8>", bytes: "Vec<u8>",
}; };
@ -44,6 +44,8 @@ export class RustTarget extends CompileTarget<{ rust_crate: string }> {
"Cargo.toml", "Cargo.toml",
this.getTemplate("Rust/Cargo.toml").replace("__name__", this.crate) this.getTemplate("Rust/Cargo.toml").replace("__name__", this.crate)
); );
this.writeFile("src/base_lib.rs", this.getTemplate("Rust/src/lib.rs"));
} }
private addDependencies( private addDependencies(
@ -59,13 +61,8 @@ export class RustTarget extends CompileTarget<{ rust_crate: string }> {
} }
generateType(definition: TypeDefinition): void { generateType(definition: TypeDefinition): void {
let lines: string[] = []; const { a, getResult } = LineAppender();
const a: lineAppender = (i, t) => {
if (!Array.isArray(t)) {
t = [t];
}
t.forEach((l) => lines.push(" ".repeat(i) + l.trim()));
};
if (definition.fields.find((e) => e.map)) if (definition.fields.find((e) => e.map))
a(0, `use std::collections::hash_map::HashMap;`); a(0, `use std::collections::hash_map::HashMap;`);
a(0, `use serde::{Deserialize, Serialize};`); a(0, `use serde::{Deserialize, Serialize};`);
@ -89,17 +86,11 @@ export class RustTarget extends CompileTarget<{ rust_crate: string }> {
} }
a(0, `}`); a(0, `}`);
this.writeFile(`src/${toSnake(definition.name)}.rs`, lines.join("\n")); this.writeFile(`src/${toSnake(definition.name)}.rs`, getResult());
} }
generateEnum(definition: EnumDefinition): void { generateEnum(definition: EnumDefinition): void {
let lines: string[] = []; const { a, getResult } = LineAppender();
const a: lineAppender = (i, t) => {
if (!Array.isArray(t)) {
t = [t];
}
t.forEach((l) => lines.push(" ".repeat(i) + l.trim()));
};
a(0, `use int_enum::IntEnum;`); a(0, `use int_enum::IntEnum;`);
a(0, `use serde::{Deserialize, Serialize};`); a(0, `use serde::{Deserialize, Serialize};`);
@ -118,120 +109,219 @@ export class RustTarget extends CompileTarget<{ rust_crate: string }> {
} }
a(0, `}`); a(0, `}`);
this.writeFile(`src/${toSnake(definition.name)}.rs`, lines.join("\n")); this.writeFile(`src/${toSnake(definition.name)}.rs`, getResult());
} }
private generateServiceReqResTypes(definition: ServiceDefinition) { private generateServiceClient(definition: ServiceDefinition): void {
let lines: string[] = []; const { a, getResult } = LineAppender();
const a: lineAppender = (i, t) => {
if (!Array.isArray(t)) {
t = [t];
}
t.forEach((l) => lines.push(" ".repeat(i) + l.trim()));
};
const paramToRust = (type: string, array: boolean) => { const typeToRust = (type: string, array: boolean) => {
let rt = toRustType(type); let rt = toRustType(type);
return array ? `Vec<${rt}>` : rt; return array ? `Vec<${rt}>` : rt;
}; };
this.addDependencies(a, definition); this.addDependencies(a, definition);
let baseName = definition.name;
for (const fnc of definition.functions) {
let name = definition.name + "_" + toSnake(fnc.name);
a(
0,
`struct ${name}_Params(${fnc.inputs
.map((i) => paramToRust(i.type, i.array))
.join(",")});`
);
a(0, ``);
a(0, ``);
a(0, ``);
a(0, ``);
a(0, ``);
}
this.writeFile(
`src/${toSnake(definition.name)}_types.rs`,
lines.join("\n")
);
}
private generateServiceClient(definition: ServiceDefinition): void {} a(0, `use crate::base_lib::{JRPCClient,JRPCRequest,Result};`);
a(0, `use serde_json::{json};`);
a(0, ``);
a(0, `pub struct ${definition.name}{`);
a(1, `client: JRPCClient,`);
a(0, `}`);
a(0, ``);
a(0, `impl ${definition.name} {`);
a(1, `pub fn new(client: JRPCClient) -> Self {`);
a(2, `return Self { client };`);
a(1, `}`);
a(0, ``);
for (const fnc of definition.functions) {
let params = fnc.inputs
.map((i) => i.name + ": " + typeToRust(i.type, i.array))
.join(", ");
let ret = fnc.return
? typeToRust(fnc.return.type, fnc.return.array)
: "()";
a(1, `pub fn ${fnc.name}(&self, ${params}) -> Result<${ret}> {`);
a(2, `let l_req = JRPCRequest {`);
a(3, `jsonrpc: "2.0".to_owned(),`);
a(3, `id: None, // 'id' will be set by the send_request function`);
a(3, `method: "${definition.name}.${fnc.name}".to_owned(),`);
a(3, `params: json!([${fnc.inputs.map((e) => e.name)}])`);
a(2, `};`);
a(2, ``);
if (fnc.return) {
a(2, `let l_res = self.client.send_request(l_req);`);
a(2, `if let Err(e) = l_res {`);
a(3, `return Err(e);`);
a(2, `} else if let Ok(o) = l_res {`);
if (fnc.return.type == "void") {
a(3, `return ();`);
} else {
a(
3,
`return serde_json::from_value(o).map_err(|e| Box::from(e));`
);
}
a(2, `} else { panic!("What else cases could there be?"); }`);
} else {
a(2, `self.client.send_notification(l_req);`);
a(2, `return Ok(());`);
}
a(1, `}`);
}
a(0, `}`);
a(0, ``);
this.writeFile(`src/client/${toSnake(definition.name)}.rs`, getResult());
}
private generateServiceServer(definition: ServiceDefinition): void { private generateServiceServer(definition: ServiceDefinition): void {
let lines: string[] = []; const { a, getResult } = LineAppender();
const a: lineAppender = (i, t) => {
if (!Array.isArray(t)) {
t = [t];
}
t.forEach((l) => lines.push(" ".repeat(i) + l.trim()));
};
this.addDependencies(a, definition); this.addDependencies(a, definition);
a(0, `use crate::base_lib::{JRPCServiceHandler,JRPCRequest,Result};`);
a(0, `use serde_json::{Value};`);
const retToRust = (type: string, array: boolean) => { const typeToRust = (type: string, array: boolean) => {
let rt = toRustType(type); let rt = toRustType(type);
return array ? `Vec<${rt}>` : rt; return array ? `Vec<${rt}>` : rt;
}; };
const CTX_TYPE = "'static + Sync + Send + Copy";
a(0, ``); a(0, ``);
a(0, `struct ${definition.name}ServiceHandler {`); a(0, `pub trait ${definition.name}<C: ${CTX_TYPE}> {`);
a(1, `service: impl ${definition.name}`); for (const fnc of definition.functions) {
a(1, ``); let params =
a(1, ``); fnc.inputs.length > 0
? fnc.inputs
.map((i) => i.name + ": " + typeToRust(i.type, i.array))
.join(", ") + ", "
: "";
let ret = fnc.return
? typeToRust(fnc.return.type, fnc.return.array)
: "()";
a(1, `fn ${fnc.name}(&self, ${params}context: &C) -> Result<${ret}>;`);
}
a(0, `}`);
a(0, ``);
a(0, `pub struct ${definition.name}Handler<C: ${CTX_TYPE}> {`);
a(1, `implementation: Box<dyn ${definition.name}<C> + Sync + Send>,`);
a(0, `}`); a(0, `}`);
a(0, ``); a(0, ``);
a(0, `impl ${definition.name}ServiceHandler {`); a(0, `impl<C: ${CTX_TYPE}> ${definition.name}Handler<C> {`);
a( a(
1, 1,
`fn new(service: impl ${definition.name}) -> ${definition.name}ServiceHandler {` `pub fn new(implementation: Box<dyn ${definition.name}<C> + Sync + Send>) -> Box<Self> {`
); );
a(2, ``); a(2, `return Box::from(Self { implementation });`);
a(2, ``);
a(2, ``);
a(1, `}`); a(1, `}`);
a(0, `}`); a(0, `}`);
a(0, ``); a(0, ``);
a(0, `trait ${definition.name} {`);
for (const fnc of definition.functions) { a(
let returnType = !fnc.return 0,
? "()" `impl<C: ${CTX_TYPE}> JRPCServiceHandler<C> for ${definition.name}Handler<C> {`
: retToRust(fnc.return.type, fnc.return.array);
a(1, `pub async fn ${fnc.name}(&self) -> Result<${returnType}>;`);
// a(0, ``);
}
a(0, `}`);
this.writeFile(
`src/${toSnake(definition.name)}_types.rs`,
lines.join("\n")
); );
a(1, `fn get_name(&self) -> String { "${definition.name}".to_owned() }`);
a(0, ``);
a(
1,
`fn on_message(&self, msg: JRPCRequest, function: String, ctx: &C) -> Result<(bool, Value)> {`
);
a(2, `match function.as_str() {`);
for (const fnc of definition.functions) {
a(3, `"${fnc.name}" => {`);
a(4, `if msg.params.is_array() {`);
if (fnc.inputs.length > 0) {
a(
5,
`let arr = msg.params.as_array().unwrap(); //TODO: Check if this can fail.`
);
}
a(5, `let res = self.implementation.${fnc.name}(`);
for (let i = 0; i < fnc.inputs.length; i++) {
const inp = fnc.inputs[i];
a(6, `serde_json::from_value(arr[${i}].clone())`);
a(
7,
`.map_err(|_| "Parameter for field '${inp.name}' should be of type '${inp.type}'!")?,` //TODO: Array
);
}
a(5, `ctx)?;`);
if (fnc.return) {
a(5, `return Ok((true, serde_json::to_value(res)?));`);
} else {
a(5, `_ = res;`);
a(5, `return Ok((false, Value::Null))`);
}
a(4, `} else if msg.params.is_object() {`);
a(5, `return Err(Box::from("Not implemented yet".to_owned()));`);
a(4, `} else {`);
a(5, `return Err(Box::from("Invalid parameters??".to_owned()));`);
a(4, `}`);
a(3, `},`);
}
a(
3,
`_ => { return Err(Box::from(format!("Invalid function {}", function).to_owned())) ;},`
);
a(2, `}`);
a(1, `}`);
a(0, `}`);
this.writeFile(`src/server/${toSnake(definition.name)}.rs`, getResult());
} }
generateService(definition: ServiceDefinition): void { generateService(definition: ServiceDefinition): void {
console.log(
chalk.yellow("[RUST] WARNING:"),
"Rust support for services is WIP. Use with care!"
);
this.generateServiceServer(definition); this.generateServiceServer(definition);
// throw new Error("Service not implemented."); this.generateServiceClient(definition);
} }
private generateLib(steps: Step[]) { private generateLib(steps: Step[]) {
let lines: string[] = []; const { a, getResult } = LineAppender();
const a: lineAppender = (i, t) => {
if (!Array.isArray(t)) { const lc = LineAppender();
t = [t]; const ls = LineAppender();
}
t.forEach((l) => lines.push(" ".repeat(i) + l.trim())); const ac = lc.a;
}; const as = ls.a;
a(0, `pub mod base_lib;`);
a(0, `pub use base_lib::{JRPCServer,Result};`);
for (const [typ, def] of steps) { for (const [typ, def] of steps) {
if (typ == "type" || typ == "enum") { if (typ == "type" || typ == "enum") {
a(0, `mod ${toSnake(def.name)};`); a(0, `mod ${toSnake(def.name)};`);
a(0, `pub use ${toSnake(def.name)}::${def.name};`); a(0, `pub use ${toSnake(def.name)}::${def.name};`);
} else if (typ == "service") {
as(0, `mod ${toSnake(def.name)};`);
as(
0,
`pub use ${toSnake(def.name)}::{${def.name}, ${
def.name
}Handler};`
);
ac(0, `mod ${toSnake(def.name)};`);
ac(0, `pub use ${toSnake(def.name)}::${def.name};`);
a(0, `pub mod server;`);
a(0, `pub mod client;`);
} }
} }
this.writeFile(`src/lib.rs`, lines.join("\n")); this.writeFile(`src/lib.rs`, getResult());
this.writeFile(`src/server/mod.rs`, ls.getResult());
this.writeFile(`src/client/mod.rs`, lc.getResult());
} }
finalize(steps: Step[]): void { finalize(steps: Step[]): void {

View File

@ -7,10 +7,9 @@ import {
} from "../ir"; } from "../ir";
import { CompileTarget } from "../compile"; import { CompileTarget } from "../compile";
import { LineAppender, lineAppender } from "../utils";
// import * as prettier from "prettier"; // import * as prettier from "prettier";
type lineAppender = (ind: number, line: string | string[]) => void;
const conversion = { const conversion = {
boolean: "boolean", boolean: "boolean",
int: "number", int: "number",
@ -73,13 +72,7 @@ export class TypescriptTarget extends CompileTarget {
} }
generateType(def: TypeDefinition) { generateType(def: TypeDefinition) {
let lines: string[] = []; const { a, getResult } = LineAppender();
const a: lineAppender = (i, t) => {
if (!Array.isArray(t)) {
t = [t];
}
t.forEach((l) => lines.push(" ".repeat(i) + l.trim()));
};
this.generateImports(a, def); this.generateImports(a, def);
a(0, `export default class ${def.name} {`); a(0, `export default class ${def.name} {`);
@ -168,17 +161,12 @@ export class TypescriptTarget extends CompileTarget {
a(0, ``); a(0, ``);
this.writeFormattedFile(this.getFileName(def.name), lines.join("\n")); this.writeFormattedFile(this.getFileName(def.name), getResult());
} }
generateEnum(def: EnumDefinition) { generateEnum(def: EnumDefinition) {
let lines: string[] = []; const { a, getResult } = LineAppender();
const a: lineAppender = (i, t) => {
if (!Array.isArray(t)) {
t = [t];
}
t.forEach((l) => lines.push(" ".repeat(i) + l.trim()));
};
a(0, this.generateImport("{ VerificationError }", "./ts_base")); a(0, this.generateImport("{ VerificationError }", "./ts_base"));
a(0, `enum ${def.name} {`); a(0, `enum ${def.name} {`);
@ -205,7 +193,7 @@ export class TypescriptTarget extends CompileTarget {
a(1, `return data;`); a(1, `return data;`);
a(0, `}`); a(0, `}`);
this.writeFormattedFile(this.getFileName(def.name), lines.join("\n")); this.writeFormattedFile(this.getFileName(def.name), getResult());
} }
generateServiceClient(def: ServiceDefinition) { generateServiceClient(def: ServiceDefinition) {
@ -220,13 +208,7 @@ export class TypescriptTarget extends CompileTarget {
this.getTemplate("ts_service_client.ts") this.getTemplate("ts_service_client.ts")
); );
let lines: string[] = []; const { a, getResult } = LineAppender();
const a: lineAppender = (i, t) => {
if (!Array.isArray(t)) {
t = [t];
}
t.forEach((l) => lines.push(" ".repeat(i) + l.trim()));
};
this.generateImports(a, def); this.generateImports(a, def);
@ -305,18 +287,12 @@ export class TypescriptTarget extends CompileTarget {
this.writeFormattedFile( this.writeFormattedFile(
this.getFileName(def.name + "_client"), this.getFileName(def.name + "_client"),
lines.join("\n") getResult()
); );
} }
generateServiceServer(def: ServiceDefinition) { generateServiceServer(def: ServiceDefinition) {
let lines: string[] = []; const { a, getResult } = LineAppender();
const a: lineAppender = (i, t) => {
if (!Array.isArray(t)) {
t = [t];
}
t.forEach((l) => lines.push(" ".repeat(i) + l.trim()));
};
this.writeFormattedFile( this.writeFormattedFile(
"service_server.ts", "service_server.ts",
@ -372,7 +348,9 @@ export class TypescriptTarget extends CompileTarget {
a(2, `if(Array.isArray(params)){`); a(2, `if(Array.isArray(params)){`);
a(3, `p = params;`); a(3, `p = params;`);
a(3, `while(p.length < ${fnc.inputs.length})`); a(3, `while(p.length < ${fnc.inputs.length})`);
a(4, `p.push(undefined)`); a(4, `p.push(undefined);`);
a(3, `if(p.length > ${fnc.inputs.length})`);
a(4, `throw new Error("Too many parameters!");`);
a(2, `} else {`); a(2, `} else {`);
for (const param of fnc.inputs) { for (const param of fnc.inputs) {
a(3, `p.push(params["${param.name}"])`); a(3, `p.push(params["${param.name}"])`);
@ -418,7 +396,7 @@ export class TypescriptTarget extends CompileTarget {
this.writeFormattedFile( this.writeFormattedFile(
this.getFileName(def.name + "_server"), this.getFileName(def.name + "_server"),
lines.join("\n") getResult()
); );
} }
@ -432,30 +410,13 @@ export class TypescriptTarget extends CompileTarget {
} }
finalize(steps: Step[]) { finalize(steps: Step[]) {
let linesClient: string[] = []; const lc = LineAppender();
let linesServer: string[] = []; const ls = LineAppender();
const ac: lineAppender = (i, t) => { const ac = lc.a;
if (!Array.isArray(t)) { const as = ls.a;
t = [t];
}
t.forEach((l) => linesClient.push(" ".repeat(i) + l.trim()));
};
const as: lineAppender = (i, t) => { const { a, getResult } = LineAppender();
if (!Array.isArray(t)) {
t = [t];
}
t.forEach((l) => linesServer.push(" ".repeat(i) + l.trim()));
};
let lines: string[] = [];
const a: lineAppender = (i, t) => {
if (!Array.isArray(t)) {
t = [t];
}
t.forEach((l) => lines.push(" ".repeat(i) + l.trim()));
};
let hasService = false; let hasService = false;
steps.forEach(([type, def]) => { steps.forEach(([type, def]) => {
@ -515,17 +476,9 @@ export class TypescriptTarget extends CompileTarget {
} }
}); });
this.writeFormattedFile(this.getFileName("index"), lines.join("\n")); this.writeFormattedFile(this.getFileName("index"), getResult());
this.writeFormattedFile(this.getFileName("index_client"), lc.getResult());
this.writeFormattedFile( this.writeFormattedFile(this.getFileName("index_server"), ls.getResult());
this.getFileName("index_client"),
linesClient.join("\n")
);
this.writeFormattedFile(
this.getFileName("index_server"),
linesServer.join("\n")
);
} }
} }

110
src/targets/zig.ts Normal file
View File

@ -0,0 +1,110 @@
import { TypeDefinition, ServiceDefinition, EnumDefinition, Step } from "../ir";
import { CompileTarget } from "../compile";
import { LineAppender, lineAppender } from "../utils";
import chalk from "chalk";
const conversion = {
boolean: "bool",
int: "i64",
float: "f64",
string: "[]u8",
void: "void",
bytes: "[]u8",
};
function toZigType(type: string): string {
return (conversion as any)[type] || type;
}
export class ZIGTarget extends CompileTarget<{ csharp_namespace: string }> {
name: string = "zig";
start(): void {
if (this.options.allow_bytes == true) {
throw new Error("Zig has no support for 'bytes' yet!");
}
}
getImport(name: string) {
return `const ${name} = @import("./${name}.zig").${name};`;
}
generateImports(a: lineAppender, def: TypeDefinition | ServiceDefinition) {
a(0, `const std = @import("std");`);
def.depends.forEach((dep) => {
a(0, this.getImport(dep));
});
}
generateType(definition: TypeDefinition): void {
const { a, getResult } = LineAppender();
this.generateImports(a, definition);
a(0, ``);
a(0, `pub const ${definition.name} = struct {`);
for (const field of definition.fields) {
if (field.array) {
a(1, `${field.name}: std.ArrayList(${toZigType(field.type)}),`);
} else if (field.map) {
a(
1,
`${field.name}: std.AutoHashMap(${toZigType(
field.map
)}, ${toZigType(field.type)}),`
);
} else {
a(1, `${field.name}: ${toZigType(field.type)},`);
}
}
a(0, `};`);
this.writeFile(`${definition.name}.zig`, getResult());
}
generateEnum(definition: EnumDefinition): void {
const { a, getResult } = LineAppender();
a(0, `pub const ${definition.name} = enum(i32) {`);
for (const entry of definition.values) {
a(1, `${entry.name} = ${entry.value},`);
}
a(0, `};`);
this.writeFile(`${definition.name}.zig`, getResult());
}
generateService(definition: ServiceDefinition): void {
console.log(
chalk.yellow("[ZIG] WARNING:"),
"ZIG support for services is not yet there. Service generation is skipped!"
);
}
finalize(steps: Step[]): void {
const { a, getResult } = LineAppender();
steps.forEach(([type, def]) => {
switch (type) {
case "type":
a(0, `pub ${this.getImport(def.name)}`);
break;
case "enum":
a(0, `pub ${this.getImport(def.name)}`);
break;
default:
console.warn(
chalk.yellow("[ZIG] WARNING:"),
"unimplemented step found:",
type
);
// case "service":
}
});
this.writeFile(`mod.zig`, getResult());
}
}

20
src/utils.ts Normal file
View File

@ -0,0 +1,20 @@
export type lineAppender = (ind: number, line: string | string[]) => void;
export function LineAppender(indentSize = 3): {
a: lineAppender;
getResult: () => string;
} {
const lines: string[] = [];
return {
a: (indentation: number, line: string | string[]) => {
if (!Array.isArray(line)) {
line = [line];
}
line.forEach((l) =>
lines.push(" ".repeat(indentation * indentSize) + l.trim())
);
},
getResult: () => lines.join("\n"),
};
}

View File

@ -7,7 +7,9 @@ edition = "2021"
[dependencies] [dependencies]
int-enum = "0.4.0" int-enum = "0.4.0"
tokio = { version = "1.17.0", features = ["full"] }
serde = { version = "1.0.136", features = ["derive"] } serde = { version = "1.0.136", features = ["derive"] }
serde_json = "1.0.79" serde_json = "1.0.79"
async-trait = "0.1.7" threadpool = "1.8.1"
nanoid = "0.4.0"

View File

@ -1,22 +0,0 @@
use std::future::Future;
use std::collections::HashMap;
use serde_json::Value;
pub struct ServiceClientRequest {
}
impl Future for ServiceClientRequest {
type Output = ()
}
pub struct ServiceClient {
channel: Box<dny ComChannel>,
requests: HashMap<String, Box<dyn >>
}
pub trait ServiceClient {
}

View File

@ -1,134 +1,188 @@
mod service; use nanoid::nanoid;
pub use service::{ComChannel, JRPCError, JRPCRequest, JRPCResult, Result};
use async_trait::async_trait;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
use std::boxed::Box; use std::boxed::Box;
use std::collections::HashMap; use std::collections::HashMap;
use std::error::Error; use std::error::Error;
use std::marker::PhantomData;
use std::marker::Send; use std::marker::Send;
use std::sync::mpsc::{Receiver, Sender};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use threadpool::ThreadPool;
// REGION: FIXED/STATIC Code pub type Result<T> = std::result::Result<T, Box<dyn Error>>;
// TODO: Check what happens when error code is not included
// #[repr(i64)]
// #[derive(Clone, Copy, Debug, Eq, PartialEq, IntEnum, Deserialize, Serialize)]
// pub enum ErrorCodes {
// ParseError = -32700,
// InvalidRequest = -32600,
// MethodNotFound = -32601,
// InvalidParams = -32602,
// InternalError = -32603,
// }
#[derive(Serialize, Deserialize)]
pub struct JRPCRequest {
pub jsonrpc: String,
pub id: Option<String>,
pub method: String,
pub params: Value,
}
#[derive(Serialize, Deserialize)]
pub struct JRPCError {
pub code: i64,
pub message: String,
pub data: Value,
}
#[derive(Serialize, Deserialize)]
pub struct JRPCResult {
pub jsonrpc: String,
pub id: String,
pub result: Value,
pub error: Option<JRPCError>,
}
// ******************************************************************************
// * SERVER
// ******************************************************************************
#[async_trait]
pub trait JRPCServiceHandler<C: Sync>: Send { pub trait JRPCServiceHandler<C: Sync>: Send {
fn get_name(&self) -> String; fn get_name(&self) -> String;
async fn on_message(&self, msg: JRPCRequest, function: String, ctx: &C) fn on_message(&self, msg: JRPCRequest, function: String, ctx: &C) -> Result<(bool, Value)>;
-> Result<(bool, Value)>;
} }
type Shared<T> = Arc<Mutex<T>>; type Shared<T> = Arc<Mutex<T>>;
type SharedHM<K, V> = Shared<HashMap<K, V>>; type SharedHM<K, V> = Shared<HashMap<K, V>>;
type ServiceSharedHM<C: 'static + Sync + Send> = SharedHM<String, Box<dyn JRPCServiceHandler<C>>>; type ServiceSharedHM<C> = SharedHM<String, Box<dyn JRPCServiceHandler<C>>>;
pub struct JRPCServer<C: 'static + Sync + Send> { type SharedThreadPool = Shared<ThreadPool>;
services: ServiceSharedHM<C>,
pub struct JRPCServer<CTX: 'static + Sync + Send + Copy> {
services: ServiceSharedHM<CTX>,
pool: SharedThreadPool,
} }
impl<C: 'static + Sync + Send> JRPCServer<C> { impl<CTX: 'static + Sync + Send + Copy> JRPCServer<CTX> {
fn new() -> Self { pub fn new() -> Self {
return Self { return Self {
services: Arc::new(Mutex::new(HashMap::new())), services: Arc::new(Mutex::new(HashMap::new())),
pool: Arc::new(Mutex::new(ThreadPool::new(32))),
}; };
} }
fn add_service(&mut self, service: Box<dyn JRPCServiceHandler<C>>) { pub fn add_service(&mut self, service: Box<dyn JRPCServiceHandler<CTX>>) {
let mut services = self.services.lock().unwrap(); let mut services = self.services.lock().unwrap();
services.insert(service.get_name(), service); services.insert(service.get_name(), service);
} }
fn start_session(&mut self, channel: Box<dyn ComChannel>, context: C) { pub fn start_session(
JRPCSession::start(channel, context, self.services.clone()); &mut self,
} read_ch: Receiver<String>,
} write_ch: Sender<String>,
context: CTX,
pub struct JRPCSession<C: 'static + Sync + Send> { ) {
context: C, let services = self.services.clone();
channel: Box<dyn ComChannel>, let p = self.pool.lock().unwrap();
services: ServiceSharedHM<C>, let pool = self.pool.clone();
} p.execute(move || {
JRPCSession::start(read_ch, write_ch, context, services, pool);
unsafe impl<C: 'static + Sync + Send> Sync for JRPCSession<C> {}
impl<C: 'static + Sync + Send> JRPCSession<C> {
fn start(channel: Box<dyn ComChannel>, context: C, services: ServiceSharedHM<C>) {
tokio::spawn(async move {
// let res = Self {
// channel,
// context,
// services,
// };
// let ch = &channel;
loop {
let pkg = channel.read_packet().await;
let data = match pkg {
Err(_) => return,
Ok(res) => res,
};
let req: Result<JRPCRequest> =
serde_json::from_str(data.as_str()).map_err(|err| Box::from(err));
let req = match req {
Err(err) => {
continue;
}
Ok(parsed) => parsed,
};
let construct_err = |err: Box<dyn Error>, code: i64| {
if let Some(id) = &req.id {
let error = JRPCError {
code,
message: err.to_string(),
data: Value::Null,
};
let r = JRPCResult {
jsonrpc: "2.0".to_owned(),
id: id.clone(),
result: Value::Null,
error: Option::from(error),
};
let s = serde_json::to_string(&r);
return Some(s.unwrap());
}
return None;
};
let parts: Vec<String> = req.method.split('.').map(|e| e.to_owned()).collect();
if parts.len() != 2 {
Self::send_err_res(req, &channel, &Box::from("Error".to_owned())).await;
// let res = construct_err(Box::from("Method not found!".to_owned()), -32602);
// if let Some(err) = res {
// channel.send_packet(err).await;
// }
continue;
}
let service = parts[0];
let name = parts[1];
// JRPCSession::send_err_res(&res, &channel, "Error").await;
// let pkg = self.channel.lock().unwrap().read_packet();
// if !pkg {
// return Ok(());
// }
// // pkg behandelm
// self.channel.send_packet(vec![0, 1, 2]);
}
}); });
} }
}
async fn handle_packet(channel: &Box<dyn ComChannel>, context: C, services: ServiceSharedHM<C>) { pub struct JRPCSession<CTX: 'static + Sync + Send + Copy> {
_ctx: PhantomData<CTX>,
}
unsafe impl<CTX: 'static + Sync + Send + Copy> Sync for JRPCSession<CTX> {}
impl<CTX: 'static + Sync + Send + Copy> JRPCSession<CTX> {
fn start(
read_ch: Receiver<String>,
write_ch: Sender<String>,
context: CTX,
services: ServiceSharedHM<CTX>,
pool: SharedThreadPool,
) {
loop {
let pkg = read_ch.recv();
let data = match pkg {
Err(_) => return,
Ok(res) => res,
};
if data.len() == 0 {
//TODO: This can be done better
return;
}
let ctx = context.clone();
let svs = services.clone();
let wc = write_ch.clone();
pool.lock().unwrap().execute(move || {
JRPCSession::handle_packet(data, wc, ctx, svs);
})
}
} }
async fn send_err_res(req: JRPCRequest, channel: &Box<dyn ComChannel>, err: &Box<dyn Error>) {
if let Some(id) = &req.id { fn handle_packet(
data: String,
write_ch: Sender<String>,
context: CTX,
services: ServiceSharedHM<CTX>,
) {
let req: Result<JRPCRequest> =
serde_json::from_str(data.as_str()).map_err(|err| Box::from(err));
let req = match req {
Err(_) => {
return;
}
Ok(parsed) => parsed,
};
let req_id = req.id.clone();
let mut parts: Vec<String> = req.method.splitn(2, '.').map(|e| e.to_owned()).collect();
if parts.len() != 2 {
return Self::send_err_res(req_id, write_ch, Box::from("Error".to_owned()));
}
let service = parts.remove(0);
let function = parts.remove(0);
let svs = services.lock().unwrap();
let srv = svs.get(&service);
if let Some(srv) = srv {
match srv.on_message(req, function, &context) {
Ok((is_send, value)) => {
if is_send {
if let Some(id) = req_id {
let r = JRPCResult {
jsonrpc: "2.0".to_owned(),
id,
result: value,
error: None,
};
let s = serde_json::to_string(&r);
if s.is_ok() {
write_ch
.send(s.unwrap())
.expect("Sending data into channel failed!");
}
}
}
}
Err(err) => return Self::send_err_res(req_id, write_ch, err),
}
}
}
fn send_err_res(id: Option<String>, write_ch: Sender<String>, err: Box<dyn Error>) {
if let Some(id) = id {
let error = JRPCError { let error = JRPCError {
code: 0, //TODO: Make this better? code: 0, //TODO: Make this better?
message: err.to_string(), message: err.to_string(),
@ -145,105 +199,88 @@ impl<C: 'static + Sync + Send> JRPCSession<C> {
let s = serde_json::to_string(&r); let s = serde_json::to_string(&r);
if s.is_ok() { if s.is_ok() {
channel.send_packet(s.unwrap()).await; write_ch
.send(s.unwrap())
.expect("Sending data into channel failed!");
} }
} }
return (); return ();
} }
} }
// REGION: Generated Code
#[derive(Deserialize, Serialize, Debug, Clone)] // ******************************************************************************
struct Test { // * CLIENT
name: String, // ******************************************************************************
age: i64,
#[derive(Clone)]
pub struct JRPCClient {
write_ch: Sender<String>,
requests: SharedHM<String, Sender<Result<Value>>>,
} }
#[async_trait] unsafe impl Send for JRPCClient {} //TODO: Is this a problem
trait TestService<C: 'static + Sync + Send> {
async fn GetTest(&self, name: String, age: i64, context: &C) -> Result<Test>;
async fn TestNot(&self, ctx: &C) -> Result<()>;
}
struct TestServiceHandler<C: 'static + Sync + Send> { impl JRPCClient {
name: String, pub fn new(write_ch: Sender<String>, read_ch: Receiver<String>) -> Self {
implementation: Box<dyn TestService<C> + Sync + Send>, let n = Self {
} write_ch,
requests: Arc::new(Mutex::new(HashMap::new())),
impl<C: 'static + Sync + Send> TestServiceHandler<C> {
fn new(implementation: Box<dyn TestService<C> + Sync + Send>) -> Self {
return Self {
name: "TestService".to_owned(),
implementation,
}; };
}
}
#[async_trait] n.start(read_ch);
impl<C: 'static + Sync + Send> JRPCServiceHandler<C> for TestServiceHandler<C> { return n;
fn get_name(&self) -> String {
return "".to_owned();
} }
async fn on_message( pub fn start(&self, read_ch: Receiver<String>) {
&self, let s = self.clone();
msg: JRPCRequest, std::thread::spawn(move || {
function: String, s.start_reader(read_ch);
ctx: &C, });
) -> Result<(bool, Value)> { }
if function == "GetTest" {
if msg.params.is_array() {
let arr = msg.params.as_array().unwrap(); //TODO: Check this, but it should never ever happen
let res = self fn start_reader(&self, read_ch: Receiver<String>) {
.implementation loop {
.GetTest( let data = read_ch.recv().expect("Error receiving packet!");
serde_json::from_value(arr[0].clone()) let response: JRPCResult =
.map_err(|_| "Parameter for field 'name' should be a string!")?, serde_json::from_str(data.as_str()).expect("Error decoding response!");
serde_json::from_value(arr[1].clone()) let id = response.id;
.map_err(|_| "Parameter for field 'age' should be a int!")?,
ctx,
)
.await?;
return Ok((true, serde_json::to_value(res)?));
} else if msg.params.is_object() {
return Err(Box::from("Not implemented yet".to_owned()));
} else {
return Err(Box::from("Invalid parameters??".to_owned()));
}
} else if function == "TestNot" {
if msg.params.is_array() {
let arr = msg.params.as_array().unwrap(); //TODO: Check this, but it should never ever happen
self.implementation.TestNot(ctx).await?; let reqs = self.requests.lock().expect("Error locking requests map!");
return Ok((false, Value::Null)); let req = reqs.get(&id);
} else if msg.params.is_object() { if let Some(req) = req {
return Err(Box::from("Not implemented yet".to_owned())); let res = if let Some(err) = response.error {
} else { Err(Box::from(err.message))
return Err(Box::from("Invalid parameters??".to_owned())); } else {
Ok(response.result)
};
req.send(res).expect("Error sending reponse!");
} }
} }
return Err(Box::from(
format!("Invalid function {}", function).to_owned(),
));
}
}
// REGION: Not Generated. User implemented
struct MyCtx {
isAuthenticated: bool,
}
struct TestServiceImplementation {}
#[async_trait]
impl TestService<MyCtx> for TestServiceImplementation {
async fn GetTest(&self, name: String, age: i64, context: &MyCtx) -> Result<Test> {
return Ok(Test { name, age });
} }
async fn TestNot(&self, _: &MyCtx) -> Result<()> { pub fn send_request(&self, mut req: JRPCRequest) -> Result<Value> {
return Ok(()); let mut reqs = self.requests.lock().expect("Error locking requests map!");
let id = nanoid!();
req.id = Some(id.clone());
let (tx, rx) = std::sync::mpsc::channel();
reqs.insert(id, tx);
self
.write_ch
.send(serde_json::to_string(&req).expect("Error converting Request to JSON!"))
.expect("Error Sending to Channel!");
return rx.recv().expect("Error getting response!");
}
pub fn send_notification(&self, mut req: JRPCRequest) {
req.id = None;
self
.write_ch
.send(serde_json::to_string(&req).expect("Error converting Request to JSON!"))
.expect("Error Sending to Channel!");
} }
} }

View File

@ -1,27 +0,0 @@
use crate::service::{ComChannel, JRPCRequest, JRPCResult, Result};
use async_trait::async_trait;
use std::collections::HashMap;
use std::error::Error;
#[#[derive(Send)]]
pub struct JRPCServer {
channel: Box<dyn ComChannel>,
}
impl JRPCServer {
fn new(channel: Box<dyn ComChannel>) -> JRPCServer {
return JRPCServer {
channel: channel,
services: HashMap::new(),
};
}
async fn run(&self) -> Result<()> {
loop {
let p = self.channel.readPacket().await?;
}
}
fn addService(serv: impl JRPCService) {
}
}

View File

@ -1,52 +0,0 @@
use async_trait::async_trait;
use serde_json::Value;
use std::error::Error;
// use serde_json::from_value()
// use int_enum::IntEnum;
use serde::{Deserialize, Serialize};
// use std::collections::HashMap;
// use std::future::Future;
pub type Result<T> = std::result::Result<T, Box<dyn Error>>;
//TODO: Vec<u8> or String.
pub trait ComChannel: Send + Sync {
fn send_packet(&self, packet: String) -> Result<()>;
fn read_packet(&self) -> Result<String>;
}
// TODO: Check what happens when error code is not included
// #[repr(i64)]
// #[derive(Clone, Copy, Debug, Eq, PartialEq, IntEnum, Deserialize, Serialize)]
// pub enum ErrorCodes {
// ParseError = -32700,
// InvalidRequest = -32600,
// MethodNotFound = -32601,
// InvalidParams = -32602,
// InternalError = -32603,
// }
#[derive(Serialize, Deserialize)]
pub struct JRPCRequest {
pub jsonrpc: String,
pub id: Option<String>,
pub method: String,
pub params: Value,
}
#[derive(Serialize, Deserialize)]
pub struct JRPCError {
pub code: i64,
pub message: String,
pub data: Value,
}
#[derive(Serialize, Deserialize)]
pub struct JRPCResult {
pub jsonrpc: String,
pub id: String,
pub result: Value,
pub error: Option<JRPCError>,
}