2 Commits
0.1.2 ... 0.1.5

10 changed files with 146 additions and 112 deletions

4
Cargo.lock generated
View File

@ -561,7 +561,7 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]] [[package]]
name = "jrpc-cli" name = "jrpc-cli"
version = "0.1.2" version = "0.1.5"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@ -607,7 +607,7 @@ checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5"
[[package]] [[package]]
name = "libjrpc" name = "libjrpc"
version = "0.1.0" version = "0.1.5"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"lazy_static", "lazy_static",

View File

@ -1,7 +1,7 @@
[package] [package]
edition = "2021" edition = "2021"
name = "jrpc-cli" name = "jrpc-cli"
version = "0.1.2" version = "0.1.5"
[workspace] [workspace]
resolver = "2" resolver = "2"

View File

@ -1,6 +1,6 @@
[package] [package]
name = "libjrpc" name = "libjrpc"
version = "0.1.0" version = "0.1.5"
edition = "2021" edition = "2021"
[features] [features]

View File

@ -88,6 +88,7 @@ impl CompileContext {
pub fn write_file(&self, filename: &str, content: &str) -> Result<()> { pub fn write_file(&self, filename: &str, content: &str) -> Result<()> {
let res_path = self.output_folder.clone().join(filename); let res_path = self.output_folder.clone().join(filename);
let res_dir = res_path.parent().context("Path has no parent!")?; let res_dir = res_path.parent().context("Path has no parent!")?;
log::debug!("Writing to file {:?}", res_path);
std::fs::create_dir_all(res_dir)?; std::fs::create_dir_all(res_dir)?;
std::fs::write(res_path, content)?; std::fs::write(res_path, content)?;
Ok(()) Ok(())

View File

@ -4,6 +4,7 @@ use std::{
}; };
use anyhow::Result; use anyhow::Result;
use log::debug;
use crate::{ use crate::{
compile::{Compile, CompileContext}, compile::{Compile, CompileContext},
@ -51,6 +52,7 @@ pub fn compile<T: Compile>(ir: IR, output: &str) -> Result<()> {
for step in ir.steps.iter() { for step in ir.steps.iter() {
match step { match step {
crate::ir::Step::Type(definition) => { crate::ir::Step::Type(definition) => {
debug!("Generating type {}", definition.name);
match compiler.generate_type(&mut ctx, &definition) { match compiler.generate_type(&mut ctx, &definition) {
Ok(_) => (), Ok(_) => (),
Err(err) => { Err(err) => {
@ -59,6 +61,7 @@ pub fn compile<T: Compile>(ir: IR, output: &str) -> Result<()> {
} }
} }
crate::ir::Step::Enum(definition) => { crate::ir::Step::Enum(definition) => {
debug!("Generating enum {}", definition.name);
match compiler.generate_enum(&mut ctx, &definition) { match compiler.generate_enum(&mut ctx, &definition) {
Ok(_) => (), Ok(_) => (),
Err(err) => { Err(err) => {
@ -67,6 +70,7 @@ pub fn compile<T: Compile>(ir: IR, output: &str) -> Result<()> {
} }
} }
crate::ir::Step::Service(definition) => { crate::ir::Step::Service(definition) => {
debug!("Generating service {}", definition.name);
match compiler.generate_service(&mut ctx, &definition) { match compiler.generate_service(&mut ctx, &definition) {
Ok(_) => (), Ok(_) => (),
Err(err) => { Err(err) => {

View File

@ -463,7 +463,17 @@ impl Compile for RustCompiler {
self.add_dependencies(&mut f, &definition.depends)?; self.add_dependencies(&mut f, &definition.depends)?;
f.a0("#[derive(Clone, Debug, Serialize, Deserialize)]"); let only_optional = definition
.fields
.iter()
.find(|f| !f.typ.is_optional())
.is_none();
let derive_default_none = if only_optional { ", Default" } else { "" };
f.a0(format!(
"#[derive(Clone, Debug, Serialize, Deserialize{})]",
derive_default_none
));
f.a0(format!("pub struct {} {{", definition.name)); f.a0(format!("pub struct {} {{", definition.name));
for field in definition.fields.iter() { for field in definition.fields.iter() {
f.a(1, "#[allow(non_snake_case)]"); f.a(1, "#[allow(non_snake_case)]");

View File

@ -435,12 +435,8 @@ import {{ VerificationError }} from \"./ts_base{esm}\";
} }
impl<F: Flavour> Compile for TypeScriptCompiler<F> { impl<F: Flavour> Compile for TypeScriptCompiler<F> {
fn new(options: &BTreeMap<String, String>) -> Result<Self> { fn new(_options: &BTreeMap<String, String>) -> Result<Self> {
let flavour = options info!("TypeScript target initialized with flavour: {}", F::name());
.get("flavour")
.cloned()
.unwrap_or_else(|| "node".to_string());
info!("TypeScript target initialized with flavour: {}", flavour);
Ok(TypeScriptCompiler { Ok(TypeScriptCompiler {
flavour: std::marker::PhantomData, flavour: std::marker::PhantomData,
}) })

View File

@ -2,127 +2,142 @@
import { VerificationError } from "./ts_base"; import { VerificationError } from "./ts_base";
//@template-ignore //@template-ignore
import { import {
//@template-ignore //@template-ignore
type RequestObject, type RequestObject,
//@template-ignore //@template-ignore
type ResponseObject, type ResponseObject,
//@template-ignore //@template-ignore
Logging, Logging,
//@template-ignore //@template-ignore
} from "./ts_service_base"; } from "./ts_service_base";
export type IMessageCallback = (data: any) => void; export type IMessageCallback = (data: any) => void;
export type ResponseListener = { export type ResponseListener = {
ok: (response: any) => void; ok: (response: any) => void;
err: (error: Error) => void; err: (error: Error) => void;
}; };
export class Service { export class Service {
public _name: string = null as any; public _name: string = null as any;
constructor( constructor(
protected _provider: ServiceProvider, protected _provider: ServiceProvider,
name: string, name: string,
) { ) {
this._name = name; this._name = name;
this._provider.services.set(name, this); this._provider.services.set(name, this);
} }
} }
export class ServiceProvider { export class ServiceProvider {
services = new Map<string, Service>(); services = new Map<string, Service>();
requests = new Map<string, ResponseListener | undefined>(); requests = new Map<string, ResponseListener | undefined>();
constructor(private sendPacket: IMessageCallback) {} constructor(private sendPacket: IMessageCallback) {}
onPacket(msg: RequestObject | ResponseObject) { onPacket(msg: RequestObject | ResponseObject) {
Logging.log("CLIENT: Received message:", msg); Logging.log("CLIENT: Received message:", msg);
if ("method" in msg) { if ("method" in msg) {
if (msg.id) { if (msg.id) {
Logging.log("CLIENT: Determined type is Request"); Logging.log("CLIENT: Determined type is Request");
// Request, which are not supported by client, so ignore // Request, which are not supported by client, so ignore
return; return;
} else { } else {
Logging.log("CLIENT: Determined type is Notification"); Logging.log("CLIENT: Determined type is Notification");
//Notification. Send to Notification handler //Notification. Send to Notification handler
const [srvName, fncName] = msg.method.split("."); const [srvName, fncName] = msg.method.split(".");
let service = this.services.get(srvName); let service = this.services.get(srvName);
if (!service) { if (!service) {
Logging.log( Logging.log(
"CLIENT: Did not find Service wanted by Notification!", "CLIENT: Did not find Service wanted by Notification!",
srvName, srvName,
); );
} else {
//TODO: Implement Event thingy (or so :))
}
}
} else { } else {
//TODO: Implement Event thingy (or so :)) Logging.log("CLIENT: Determined type is Response");
// Response
let resListener = this.requests.get(msg.id);
if (!resListener) return; // Ignore wrong responses
if (msg.error) {
if (
msg.error.data &&
msg.error.data.$ == "verification_error"
) {
resListener.err(
new VerificationError(
msg.error.data.type,
msg.error.data.field,
msg.error.data.value,
),
);
} else {
resListener.err(new Error(msg.error.message));
}
} else {
resListener.ok(msg.result);
}
} }
}
} else {
Logging.log("CLIENT: Determined type is Response");
// Response
let resListener = this.requests.get(msg.id);
if (!resListener) return; // Ignore wrong responses
if (msg.error) {
if (msg.error.data && msg.error.data.$ == "verification_error") {
resListener.err(
new VerificationError(
msg.error.data.type,
msg.error.data.field,
msg.error.data.value,
),
);
} else {
resListener.err(new Error(msg.error.message));
}
} else {
resListener.ok(msg.result);
}
} }
}
sendNotification(method: string, params: any[]) { sendNotification(method: string, params: any[]) {
Logging.log("CLIENT: Sending Notification", method, params); Logging.log("CLIENT: Sending Notification", method, params);
this.sendPacket({ this.sendPacket({
jsonrpc: "2.0", jsonrpc: "2.0",
method, method,
params, params,
}); });
} }
sendRequest(method: string, params: any[], res?: ResponseListener) { sendRequest(method: string, params: any[], res?: ResponseListener) {
Logging.log("CLIENT: Sending Request", method, params); Logging.log("CLIENT: Sending Request", method, params);
const id = getRandomID(16); const id = getRandomID(16);
this.requests.set(id, res); this.requests.set(id, res);
this.sendPacket({ this.sendPacket({
jsonrpc: "2.0", jsonrpc: "2.0",
method, method,
params, params,
id, id,
}); });
} }
} }
declare var require: any; declare var require: any;
export const getRandomBytes = ( export const getRandomBytes = (
typeof self !== "undefined" && (self.crypto || (self as any).msCrypto) typeof self !== "undefined" && (self.crypto || (self as any).msCrypto)
? function () { ? function () {
// Browsers // Browsers
var crypto = self.crypto || (self as any).msCrypto; var crypto = self.crypto || (self as any).msCrypto;
var QUOTA = 65536; var QUOTA = 65536;
return function (n: number) { return function (n: number) {
var a = new Uint8Array(n); var a = new Uint8Array(n);
for (var i = 0; i < n; i += QUOTA) { for (var i = 0; i < n; i += QUOTA) {
crypto.getRandomValues(a.subarray(i, i + Math.min(n - i, QUOTA))); crypto.getRandomValues(
a.subarray(i, i + Math.min(n - i, QUOTA)),
);
}
return a;
};
}
: function () {
// Node
if (typeof require !== "undefined") {
return require("crypto").randomBytes;
} else {
return (n: number) => {
let a = new Uint8Array(n);
for (let i = 0; i < n; i++) {
a[i] = Math.floor(Math.random() * 256);
}
return a;
};
}
} }
return a;
};
}
: function () {
// Node
return require("crypto").randomBytes;
}
)() as (cnt: number) => Uint8Array; )() as (cnt: number) => Uint8Array;
export const getRandomID = (length: number) => { export const getRandomID = (length: number) => {
return btoa(String.fromCharCode.apply(null, getRandomBytes(length) as any)); return btoa(String.fromCharCode.apply(null, getRandomBytes(length) as any));
}; };

View File

@ -4,7 +4,7 @@ use libjrpc::{
targets::{ targets::{
csharp::CSharpCompiler, csharp::CSharpCompiler,
rust::RustCompiler, rust::RustCompiler,
typescript::{Node, TypeScriptCompiler}, typescript::{Node, TypeScriptCompiler, ESM},
}, },
FileProcessor, FileProcessor,
}; };
@ -66,7 +66,7 @@ pub fn main() -> Result<()> {
libjrpc::targets::compile::<TypeScriptCompiler<Node>>(ir, output_dir)? libjrpc::targets::compile::<TypeScriptCompiler<Node>>(ir, output_dir)?
} }
"ts-esm" => { "ts-esm" => {
libjrpc::targets::compile::<TypeScriptCompiler<Node>>(ir, output_dir)? libjrpc::targets::compile::<TypeScriptCompiler<ESM>>(ir, output_dir)?
} }
"csharp" => libjrpc::targets::compile::<CSharpCompiler>(ir, output_dir)?, "csharp" => libjrpc::targets::compile::<CSharpCompiler>(ir, output_dir)?,
_ => { _ => {

View File

@ -6,12 +6,12 @@ use std::{
#[test] #[test]
fn compare_tools() { fn compare_tools() {
let targets = vec!["rust"]; let targets = vec!["js-esm"];
for target in targets { for target in targets {
std::fs::remove_dir_all("./tests").unwrap(); std::fs::remove_dir_all("./tests").unwrap();
std::fs::create_dir_all("./tests").unwrap(); std::fs::create_dir_all("./tests").unwrap();
Command::new("cargo") let result1 = Command::new("cargo")
.arg("run") .arg("run")
.arg("--") .arg("--")
.arg("compile") .arg("compile")
@ -26,7 +26,11 @@ fn compare_tools() {
.wait() .wait()
.unwrap(); .unwrap();
Command::new("node") if !result1.success() {
panic!("Failed to generate Rust code");
}
let result2 = Command::new("node")
.arg("JsonRPC/lib/jrpc.js") .arg("JsonRPC/lib/jrpc.js")
.arg("compile") .arg("compile")
.arg("--verbose") .arg("--verbose")
@ -40,6 +44,10 @@ fn compare_tools() {
.wait() .wait()
.unwrap(); .unwrap();
if !result2.success() {
panic!("Failed to generate JavaScript code");
}
let rust_files = walkdir::WalkDir::new("tests/rust") let rust_files = walkdir::WalkDir::new("tests/rust")
.into_iter() .into_iter()
.map(|e| e.unwrap()) .map(|e| e.unwrap())