From a3f5a396e5e0a1f62991cc253c2cef9c5fef9889 Mon Sep 17 00:00:00 2001 From: Fabian Stamm Date: Sun, 29 Sep 2024 23:46:29 +0200 Subject: [PATCH] Start working on rust compile step --- Cargo.toml | 1 + crates/libjrpc/import.jrpc | 7 + crates/libjrpc/src/compile.rs | 75 ++++++ crates/libjrpc/src/ir.rs | 74 +++--- crates/libjrpc/src/lib.rs | 27 +-- crates/libjrpc/src/process.rs | 44 +++- crates/libjrpc/src/shared.rs | 21 ++ crates/libjrpc/src/targets/mod.rs | 1 + crates/libjrpc/src/targets/rust.rs | 214 +++++++++++++++++ crates/libjrpc/templates/CSharp/CSharp.csproj | 10 + crates/libjrpc/templates/CSharp/JRpcClient.cs | 170 +++++++++++++ crates/libjrpc/templates/CSharp/JRpcServer.cs | 199 +++++++++++++++ .../libjrpc/templates/CSharp/JRpcTransport.cs | 15 ++ .../templates/Dart/analysis_options.yaml | 30 +++ crates/libjrpc/templates/Dart/base.dart | 52 ++++ crates/libjrpc/templates/Dart/pubspec.yaml | 10 + .../templates/Dart/service_client.dart | 86 +++++++ crates/libjrpc/templates/Rust/.editorconfig | 2 + crates/libjrpc/templates/Rust/.gitignore | 2 + crates/libjrpc/templates/Rust/Cargo.toml | 15 ++ crates/libjrpc/templates/Rust/src/lib.rs | 226 ++++++++++++++++++ .../libjrpc/templates/TypeScript/ts_base.ts | 44 ++++ .../templates/TypeScript/ts_service_base.ts | 30 +++ .../templates/TypeScript/ts_service_client.ts | 105 ++++++++ .../templates/TypeScript/ts_service_server.ts | 130 ++++++++++ crates/libjrpc/test.jrpc | 2 +- 26 files changed, 1534 insertions(+), 58 deletions(-) create mode 100644 crates/libjrpc/import.jrpc create mode 100644 crates/libjrpc/src/compile.rs create mode 100644 crates/libjrpc/src/targets/mod.rs create mode 100644 crates/libjrpc/src/targets/rust.rs create mode 100644 crates/libjrpc/templates/CSharp/CSharp.csproj create mode 100644 crates/libjrpc/templates/CSharp/JRpcClient.cs create mode 100644 crates/libjrpc/templates/CSharp/JRpcServer.cs create mode 100644 crates/libjrpc/templates/CSharp/JRpcTransport.cs create mode 100644 crates/libjrpc/templates/Dart/analysis_options.yaml create mode 100644 crates/libjrpc/templates/Dart/base.dart create mode 100644 crates/libjrpc/templates/Dart/pubspec.yaml create mode 100644 crates/libjrpc/templates/Dart/service_client.dart create mode 100644 crates/libjrpc/templates/Rust/.editorconfig create mode 100644 crates/libjrpc/templates/Rust/.gitignore create mode 100644 crates/libjrpc/templates/Rust/Cargo.toml create mode 100644 crates/libjrpc/templates/Rust/src/lib.rs create mode 100644 crates/libjrpc/templates/TypeScript/ts_base.ts create mode 100644 crates/libjrpc/templates/TypeScript/ts_service_base.ts create mode 100644 crates/libjrpc/templates/TypeScript/ts_service_client.ts create mode 100644 crates/libjrpc/templates/TypeScript/ts_service_server.ts diff --git a/Cargo.toml b/Cargo.toml index 9bd654d..5dde922 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,2 +1,3 @@ [workspace] +resolver = "2" members = ["crates/zed", "crates/libjrpc"] diff --git a/crates/libjrpc/import.jrpc b/crates/libjrpc/import.jrpc new file mode 100644 index 0000000..f823010 --- /dev/null +++ b/crates/libjrpc/import.jrpc @@ -0,0 +1,7 @@ +import "./import.jrpc"; + +type TestAtom { + val_number?: float; + val_boolean?: boolean; + val_string?: string; +} diff --git a/crates/libjrpc/src/compile.rs b/crates/libjrpc/src/compile.rs new file mode 100644 index 0000000..edb121f --- /dev/null +++ b/crates/libjrpc/src/compile.rs @@ -0,0 +1,75 @@ +use std::{collections::HashMap, path::PathBuf}; + +use anyhow::{Context, Result}; + +use crate::ir::{EnumDefinition, ServiceDefinition, TypeDefinition}; + +pub trait Compile { + fn name(&self) -> String; + + fn start(&mut self, ctx: &mut CompileContext, options: HashMap) -> Result<()>; + + fn generate_type(&mut self, ctx: &mut CompileContext, definition: TypeDefinition) + -> Result<()>; + fn generate_enum(&mut self, ctx: &mut CompileContext, definition: EnumDefinition) + -> Result<()>; + fn generate_service( + &mut self, + ctx: &mut CompileContext, + definition: ServiceDefinition, + ) -> Result<()>; + + fn finalize(&mut self, ctx: &mut CompileContext) -> Result<()>; +} + +pub struct CompileContext { + output_folder: PathBuf, +} +impl CompileContext { + pub fn new(output_folder: &str) -> Self { + CompileContext { + output_folder: output_folder.into(), + } + } + + pub fn write_file(&self, filename: &str, content: String) -> Result<()> { + let res_path = self.output_folder.clone().join(filename); + let res_dir = res_path.parent().context("Path has no parent!")?; + std::fs::create_dir_all(res_dir)?; + std::fs::write(res_path, content)?; + Ok(()) + } +} + +pub struct FileGenerator { + content: String, +} + +impl FileGenerator { + pub fn new() -> Self { + FileGenerator { + content: String::new(), + } + } + + pub fn a(&mut self, indent: u32, content: T) { + for _ in 0..indent { + self.content.push_str(" "); + } + self.content.push_str(&content.to_string()); + self.content.push_str("\n"); + } + + pub fn add_line(&mut self, line: &str) { + self.content.push_str(line); + self.content.push_str("\n"); + } + + pub fn get_content(&mut self) -> String { + self.content.clone() + } + + pub fn into_content(self) -> String { + self.content + } +} diff --git a/crates/libjrpc/src/ir.rs b/crates/libjrpc/src/ir.rs index b49429c..3afc2d8 100644 --- a/crates/libjrpc/src/ir.rs +++ b/crates/libjrpc/src/ir.rs @@ -11,7 +11,7 @@ use crate::parser::{ EnumStatement, Node, ParserPosition, RootNode, ServiceStatement, TypeStatement, }; -static BUILT_INS: [&str; 4] = ["int", "float", "string", "boolean"]; +static BUILT_INS: [&str; 5] = ["int", "float", "string", "boolean", "bytes"]; pub trait Definition { fn get_position(&self) -> ParserPosition; @@ -19,8 +19,8 @@ pub trait Definition { #[derive(Debug, Clone)] pub struct IR { - options: HashMap, - steps: Vec, + pub options: HashMap, + pub steps: Vec, } #[derive(Debug, Clone)] @@ -36,6 +36,7 @@ pub enum Type { Float, String, Bool, + Bytes, Custom(String), } impl Hash for Type { @@ -45,6 +46,7 @@ impl Hash for Type { Type::Float => "float".hash(state), Type::String => "string".hash(state), Type::Bool => "bool".hash(state), + Type::Bytes => "bytes".hash(state), Type::Custom(name) => name.hash(state), } } @@ -52,10 +54,10 @@ impl Hash for Type { #[derive(Debug, Clone)] pub struct TypeDefinition { - name: String, - depends: HashSet, - fields: Vec, - position: ParserPosition, + pub name: String, + pub depends: HashSet, + pub fields: Vec, + pub position: ParserPosition, } impl Definition for TypeDefinition { @@ -66,18 +68,18 @@ impl Definition for TypeDefinition { #[derive(Debug, Clone)] pub struct Field { - name: String, - typ: Type, - array: bool, - optional: bool, - map: Option, + pub name: String, + pub typ: Type, + pub array: bool, + pub optional: bool, + pub map: Option, } #[derive(Debug, Clone)] pub struct EnumDefinition { - name: String, - values: Vec, - position: ParserPosition, + pub name: String, + pub values: Vec, + pub position: ParserPosition, } impl Definition for EnumDefinition { @@ -88,16 +90,16 @@ impl Definition for EnumDefinition { #[derive(Debug, Clone)] pub struct EnumField { - name: String, - value: i32, + pub name: String, + pub value: i32, } #[derive(Debug, Clone)] pub struct ServiceDefinition { - name: String, - depends: HashSet, - methods: Vec, - position: ParserPosition, + pub name: String, + pub depends: HashSet, + pub methods: Vec, + pub position: ParserPosition, } impl Definition for ServiceDefinition { @@ -108,31 +110,31 @@ impl Definition for ServiceDefinition { #[derive(Debug, Clone)] pub struct Method { - name: String, - inputs: Vec, - output: Option, - decorators: MethodDecorators, + pub name: String, + pub inputs: Vec, + pub output: Option, + pub decorators: MethodDecorators, } #[derive(Debug, Clone)] pub struct MethodInput { - name: String, - typ: Type, - array: bool, - optional: bool, + pub name: String, + pub typ: Type, + pub array: bool, + pub optional: bool, } #[derive(Debug, Clone)] pub struct MethodOutput { - typ: Type, - array: bool, + pub typ: Type, + pub array: bool, } #[derive(Debug, Clone)] pub struct MethodDecorators { - description: Option, - parameter_descriptions: HashMap, - return_description: Option, + pub description: Option, + pub parameter_descriptions: HashMap, + pub return_description: Option, } fn typename_to_type(name: &str) -> Type { @@ -221,7 +223,9 @@ fn build_service(stmt: &ServiceStatement) -> Result { inputs: Vec::new(), output: method.return_type.as_ref().map(|rt| { let typ = typename_to_type(&rt.fieldtype); - servdef.depends.insert(typ.clone()); + if typ != Type::Custom("void".to_string()) { + servdef.depends.insert(typ.clone()); + } MethodOutput { typ, array: rt.array, diff --git a/crates/libjrpc/src/lib.rs b/crates/libjrpc/src/lib.rs index 691a2e7..d3f7464 100644 --- a/crates/libjrpc/src/lib.rs +++ b/crates/libjrpc/src/lib.rs @@ -1,15 +1,18 @@ +mod compile; mod ir; mod parser; mod process; mod shared; +mod targets; mod tokenizer; +pub use ir::IR; +pub use parser::{Parser, RootNode}; +pub use process::FileProcessor; pub use tokenizer::{tokenize, Token, TokenError, TokenPosition, TokenType}; #[cfg(test)] mod test { - use std::sync::Arc; - #[cfg(test)] #[ctor::ctor] fn init() { @@ -18,22 +21,10 @@ mod test { #[test] pub fn parse_jrpc() { - let tokens = crate::tokenizer::tokenize( - Arc::new("../test.jrpc".to_owned()), - include_str!("../test.jrpc").to_owned(), - ) - .unwrap(); + let mut fp = crate::process::FileProcessor::new(); + // let ir = fp.start_compile("./test.jrpc").unwrap(); + let ir = fp.start_compile("http://127.0.0.1:7878/test.jrpc").unwrap(); - let parsed = crate::parser::Parser::new(tokens).parse().unwrap(); - // println!("Parsed: {:?}", parsed); - - let ir = crate::ir::build_ir(&parsed).unwrap(); - println!("IR: {:?}", ir); - - // let result = crate::JRPCParser::parse(crate::Rule::root, ); - // match result { - // Ok(result) => println!("{:?}", result), - // Err(err) => println!("{:?}", err), - // } + println!("{:?}", ir); } } diff --git a/crates/libjrpc/src/process.rs b/crates/libjrpc/src/process.rs index 876df3f..4f0ec49 100644 --- a/crates/libjrpc/src/process.rs +++ b/crates/libjrpc/src/process.rs @@ -1,12 +1,14 @@ use std::{ collections::{HashMap, HashSet}, path::PathBuf, + sync::Arc, }; use anyhow::Result; +use log::trace; use url::Url; -use crate::ir::IR; +use crate::{ir::IR, parser::RootNode}; pub struct FileProcessor { file_cache: HashMap, @@ -15,6 +17,7 @@ pub struct FileProcessor { impl FileProcessor { pub fn new() -> Self { + trace!("FileProcessor::new()"); Self { file_cache: HashMap::new(), processed_files: HashSet::new(), @@ -22,6 +25,7 @@ impl FileProcessor { } fn is_url(inp: Option<&str>) -> bool { + trace!("FileProcessor::is_url({:?})", inp); #[cfg(feature = "http")] if let Some(inp) = inp { if inp.starts_with("http://") || inp.starts_with("https://") { @@ -32,13 +36,14 @@ impl FileProcessor { } fn resolve_path(input: &str, context: Option<&str>) -> String { + trace!("FileProcessor::resolve_path({}, {:?})", input, context); let mut input = input.to_string(); #[cfg(feature = "http")] if cfg!(feature = "http") && (Self::is_url(Some(&input)) || Self::is_url(context)) { if Self::is_url(Some(&input)) { input.to_string() } else { - let mut url = Url::parse(context.unwrap()).unwrap(); + let url = Url::parse(context.unwrap()).unwrap(); if !input.ends_with(".jrpc") { input = format!("{}.jrpc", input); } @@ -50,6 +55,7 @@ impl FileProcessor { } if let Some(context) = context { let mut path = PathBuf::from(context); + path.pop(); path = path.join(input); path.to_str().unwrap().to_string() } else { @@ -59,6 +65,7 @@ impl FileProcessor { } fn get_file_url(&mut self, url: &str) -> Result { + trace!("FileProcessor::get_file_url({})", url); let resp = reqwest::blocking::get(url)?; let body = resp.text()?; self.file_cache.insert(url.to_string(), body.clone()); @@ -66,12 +73,14 @@ impl FileProcessor { } fn get_file_path(&mut self, path: &str) -> Result { + trace!("FileProcessor::get_file_path({})", path); let body = std::fs::read_to_string(path)?; self.file_cache.insert(path.to_string(), body.clone()); Ok(body) } fn get_file(&mut self, name: &str) -> Result { + trace!("FileProcessor::get_file({})", name); if self.file_cache.contains_key(name) { return Ok(self.file_cache.get(name).unwrap().clone()); } else { @@ -91,9 +100,36 @@ impl FileProcessor { } } - fn process_file(&mut self, file: &str, root: bool) {} + fn process_file(&mut self, file: &str, root: bool) -> Result> { + let file = Self::resolve_path(file, None); + trace!("FileProcessor::process_file({}, {})", file, root); + if self.processed_files.contains(&file) { + return Ok(vec![]); + } + + self.processed_files.insert(file.clone()); + + let content = self.get_file(&file)?; + let tokens = crate::tokenizer::tokenize(Arc::new(file.to_string()), content)?; + let parsed = crate::parser::Parser::new(tokens).parse()?; + let mut result = Vec::new(); + for stmt in &parsed { + if let crate::parser::RootNode::Import(stmt) = stmt { + let file = Self::resolve_path(&stmt.path, Some(&file)); + let s = self.process_file(&file, false)?; + result.extend(s); + } else { + result.push(stmt.clone()); + } + } + + Ok(result) + } pub fn start_compile(&mut self, file: &str) -> Result { - Err(anyhow::anyhow!("Not implemented")) + trace!("FileProcessor::start_compile({})", file); + let parsed = self.process_file(file, true)?; + let ir = crate::ir::build_ir(&parsed)?; + Ok(ir) } } diff --git a/crates/libjrpc/src/shared.rs b/crates/libjrpc/src/shared.rs index 61fbb1a..2cd3276 100644 --- a/crates/libjrpc/src/shared.rs +++ b/crates/libjrpc/src/shared.rs @@ -5,3 +5,24 @@ pub enum Keywords { Service, Define, } + +impl Keywords { + pub fn is_keyword(input: &str) -> bool { + match input { + "type" | "enum" | "import" | "service" | "define" => true, + _ => false, + } + } +} + +impl ToString for Keywords { + fn to_string(&self) -> String { + match self { + Keywords::Type => "type".to_string(), + Keywords::Enum => "enum".to_string(), + Keywords::Import => "import".to_string(), + Keywords::Service => "service".to_string(), + Keywords::Define => "define".to_string(), + } + } +} diff --git a/crates/libjrpc/src/targets/mod.rs b/crates/libjrpc/src/targets/mod.rs new file mode 100644 index 0000000..3065205 --- /dev/null +++ b/crates/libjrpc/src/targets/mod.rs @@ -0,0 +1 @@ +mod rust; diff --git a/crates/libjrpc/src/targets/rust.rs b/crates/libjrpc/src/targets/rust.rs new file mode 100644 index 0000000..ce7e982 --- /dev/null +++ b/crates/libjrpc/src/targets/rust.rs @@ -0,0 +1,214 @@ +use anyhow::Result; +use log::warn; +use std::collections::{HashMap, HashSet}; + +use crate::compile::{Compile, CompileContext, FileGenerator}; +use crate::ir::{EnumDefinition, ServiceDefinition, Type, TypeDefinition}; +use crate::shared::Keywords; + +pub struct RustCompiler { + crate_name: String, +} + +impl RustCompiler { + fn type_to_rust(typ: Type) -> String { + match typ { + Type::String => "String".to_string(), + Type::Int => "i64".to_string(), + Type::Float => "f64".to_string(), + Type::Bool => "bool".to_string(), + Type::Bytes => "Vec".to_string(), + Type::Custom(name) => name, + } + } + + fn add_dependencies( + &mut self, + file: &mut FileGenerator, + depends: &HashSet, + ) -> Result<()> { + for dep in depends { + match dep { + Type::Custom(name) => { + file.a(0, &format!("use crate::{};", name)); + } + _ => {} + } + } + file.a(0, ""); + file.a(0, ""); + Ok(()) + } + + fn fix_keyword_name(name: &str) -> String { + if Keywords::is_keyword(name) { + format!("{}_", name) + } else { + name.to_string() + } + } + + fn to_snake(name: &str) -> String { + let mut result = String::new(); + let mut last_upper = false; + for c in name.chars() { + if c.is_uppercase() { + if last_upper { + result.push(c.to_ascii_lowercase()); + } else { + if !result.is_empty() { + result.push('_'); + } + result.push(c.to_ascii_lowercase()); + } + last_upper = true; + } else { + result.push(c); + last_upper = false; + } + } + result + } +} + +impl Compile for RustCompiler { + fn name(&self) -> String { + "rust".to_string() + } + + fn start( + &mut self, + ctx: &mut CompileContext, + options: HashMap, + ) -> anyhow::Result<()> { + if let Some(crate_name) = options.get("crate") { + self.crate_name = crate_name.to_string(); + } else { + anyhow::bail!("crate option is required for rust compiler"); + } + + if let Some(allow_bytes) = options.get("allow_bytes") { + if allow_bytes == "true" { + anyhow::bail!("allow_bytes option is not supported for rust compiler"); + } + } + + ctx.write_file( + "Cargo.toml", + include_str!("../../templates/Rust/Cargo.toml") + .to_owned() + .replace("__name__", &self.crate_name), + )?; + + ctx.write_file( + "src/base_lib.rs", + include_str!("../../templates/Rust/src/lib.rs").to_owned(), + )?; + + todo!() + } + + fn generate_type( + &mut self, + ctx: &mut CompileContext, + definition: TypeDefinition, + ) -> anyhow::Result<()> { + let mut f = FileGenerator::new(); + + if definition.fields.iter().any(|e| e.map.is_some()) { + f.a(0, "use std::collections::hash_map::HashMap;") + } + f.a(0, "use serde::{Deserialize, Serialize};"); + + self.add_dependencies(&mut f, &definition.depends)?; + + f.a(0, "#[derive(Clone, Debug, Serialize, Deserialize)]"); + f.a(0, format!("pub struct {} {{", definition.name)); + for field in definition.fields { + f.a(1, "#[allow(non_snake_case)]"); + let mut func = format!("pub {}: ", Self::fix_keyword_name(&field.name)); + + if Keywords::is_keyword(&field.name) { + warn!( + "[RUST] Warning: Field name '{}' is not allowed in Rust. Renaming to '{}_'", + field.name, field.name + ); + + f.a(1, "#[serde(rename = \"type\")]"); + } + + let mut opts = String::new(); + let mut opte = String::new(); + + if field.optional { + opts = "Option<".to_string(); + opte = ">".to_string(); + } + + if field.array { + f.a( + 1, + format!( + "{} {}Vec<{}>{},", + func, + opts, + Self::type_to_rust(field.typ), + opte + ), + ); + } else if let Some(map) = field.map { + f.a( + 1, + format!( + "{} {}HashMap<{},{}>{},", + func, + opts, + Self::type_to_rust(map), + Self::type_to_rust(field.typ), + opte + ), + ); + } else { + f.a( + 1, + format!( + "{} {}{}{},", + func, + opts, + Self::type_to_rust(field.typ), + opte + ), + ); + } + } + + f.a(0, "}"); + + ctx.write_file( + &format!("src/{}.rs", Self::to_snake(&definition.name)), + f.into_content(), + )?; + + Ok(()) + } + + fn generate_enum( + &mut self, + ctx: &mut CompileContext, + definition: EnumDefinition, + ) -> anyhow::Result<()> { + todo!() + } + + fn generate_service( + &mut self, + ctx: &mut CompileContext, + definition: ServiceDefinition, + ) -> anyhow::Result<()> { + todo!() + } + + fn finalize(&mut self, ctx: &mut CompileContext) -> anyhow::Result<()> { + todo!() + } +} diff --git a/crates/libjrpc/templates/CSharp/CSharp.csproj b/crates/libjrpc/templates/CSharp/CSharp.csproj new file mode 100644 index 0000000..6afe43a --- /dev/null +++ b/crates/libjrpc/templates/CSharp/CSharp.csproj @@ -0,0 +1,10 @@ + + + + net6.0 + enable + enable + 10.0 + + + diff --git a/crates/libjrpc/templates/CSharp/JRpcClient.cs b/crates/libjrpc/templates/CSharp/JRpcClient.cs new file mode 100644 index 0000000..d58d23f --- /dev/null +++ b/crates/libjrpc/templates/CSharp/JRpcClient.cs @@ -0,0 +1,170 @@ +using System; +using System.Text; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Threading.Tasks; +using System.Collections.Generic; + +namespace __NAMESPACE__; + +public class JRpcClient +{ + private JRpcTransport Transport; + private IDictionary> Requests; + private int? Timeout = null; + + public JRpcClient(JRpcTransport transport, int? timeout = null) + { + this.Transport = transport; + this.Timeout = timeout; + this.Requests = new Dictionary>(); + + this.Transport.OnPacket += this.HandlePacket; + } + + private void HandlePacket(string packet) + { + try + { + var parsed = JsonNode.Parse(packet); + if (parsed == null || (string?)parsed["jsonrpc"] != "2.0") + return; + + if (parsed["method"] != null) + { // Request or Notification + if (parsed["id"] != null) // Requests are not supported on the Client + return; + //TODO: implement Notifications + } + else if (parsed["id"] != null && parsed["method"] == null) + { + // Response + //TODO: Somehow remove this warning, since it has no meaning in this context. + // ID has to be something, that was checked before + var id = (string)parsed["id"]!; + + var task = this.Requests[id]; + if (task == null) + return; //This Request was not from here + + if (parsed["error"] != null) + { + var err = parsed["error"].Deserialize(); + if (err == null) + { + task.SetException(new JRpcException("Internal Server Error")); + } + else + { + task.SetException(err.ToException()); + } + } + else + { + task.SetResult(parsed["result"]); + } + } + else + { + // Ignoring invalid packet! + return; + } + } + catch (Exception) + { + //TODO: Maybe log exception, but don't break! + } + } + + public async Task SendRequestRaw(string method, JsonArray param) + { + var id = Guid.NewGuid().ToString(); + var request = new JsonObject + { + ["jsonrpc"] = "2.0", + ["id"] = id, + ["method"] = method, + ["params"] = param + }; + + var task = new TaskCompletionSource(); + this.Requests.Add(id, task); + + try + { + _ = Task.Run(async () => + { + try + { + await this.Transport.Write(request.ToJsonString()); + } + catch (Exception e) + { + task.SetException(e); + } + }); + + if (this.Timeout.HasValue) + { + if (await Task.WhenAny(task.Task, Task.Delay(this.Timeout.Value)) == task.Task) + { + return await task.Task; + } + else + { + throw new JRpcTimeoutException(); + } + + } + else + { + return await task.Task; + } + } + finally + { + this.Requests.Remove(id); + } + } + + public async Task SendRequest(string method, JsonArray param) + { + var result = await this.SendRequestRaw(method, param); + return result.Deserialize(); + } + + public async void SendNotification(string method, JsonArray param) + { + var not = new JsonObject + { + ["jsonrpc"] = "2.0", + ["method"] = method, + ["params"] = param, + }; + + await this.Transport.Write(not.ToJsonString()); + } +} + + +class JRpcError +{ + public int code { get; set; } + public string? message { get; set; } + public JsonNode? data { get; set; } + + public JRpcException ToException() + { + return new JRpcException(this.message!); + } +} + +public class JRpcException : Exception +{ + public JRpcException(string message) : base(message) { } +} + +public class JRpcTimeoutException : JRpcException +{ + public JRpcTimeoutException() : base("Request Timeout") { } +} diff --git a/crates/libjrpc/templates/CSharp/JRpcServer.cs b/crates/libjrpc/templates/CSharp/JRpcServer.cs new file mode 100644 index 0000000..c2e9362 --- /dev/null +++ b/crates/libjrpc/templates/CSharp/JRpcServer.cs @@ -0,0 +1,199 @@ +using System; +using System.Text; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Threading.Tasks; +using System.Collections.Generic; + +namespace __NAMESPACE__; + +public class JRpcServer +{ + public IDictionary> Services; + + public JRpcServer() + { + this.Services = new Dictionary>(); + } + + public void AddService(JRpcService service) + { + this.Services.Add(service.Name, service); + } + + public JRpcServerSession GetSession(JRpcTransport transport, TContext context) + { + return new JRpcServerSession(this, transport, context); + } +} + +public class JRpcServerSession +{ + public JRpcServer Server { get; private set; } + public JRpcTransport Transport { get; private set; } + public TContext Context { get; private set; } + + public JRpcServerSession(JRpcServer server, JRpcTransport transport, TContext context) + { + this.Server = server; + this.Transport = transport; + this.Context = context; + + this.Transport.OnPacket += this.HandlePacket; + } + + private void HandlePacket(string packet) + { + try + { + var parsed = JsonNode.Parse(packet); + if (parsed == null || (string?)parsed["jsonrpc"] != "2.0") + return; + + if (parsed["method"] != null) // Request or Notification + { + var id = (string?)parsed["id"]; + var splitted = ((string)parsed["method"]!).Split(".", 2); + var serviceName = splitted[0]; + var functionName = splitted[1]; + + if (serviceName == null || functionName == null) + { + if (id != null) + { + var response = new JsonObject + { + ["jsonrpc"] = "2.0", + ["id"] = id, + ["error"] = new JsonObject + { + ["code"] = -32700, + ["message"] = "Method not found!", + } + }; + _ = this.Transport.Write(response.ToJsonString()!); + } + return; + } + + var service = this.Server.Services[serviceName]; + if (service == null) + { + if (id != null) + { + var response = new JsonObject + { + ["jsonrpc"] = "2.0", + ["id"] = id, + ["error"] = new JsonObject + { + ["code"] = -32700, + ["message"] = "Method not found!", + } + }; + _ = this.Transport.Write(response.ToJsonString()!); + } + return; + } + + if (!service.Functions.Contains(functionName)) + { + if (id != null) + { + var response = new JsonObject + { + ["jsonrpc"] = "2.0", + ["id"] = id, + ["error"] = new JsonObject + { + ["code"] = -32700, + ["message"] = "Method not found!", + } + }; + _ = this.Transport.Write(response.ToJsonString()!); + } + return; + } + + if (!(parsed["params"] is JsonArray || parsed["params"] is JsonObject)) + { + if (id != null) + { + var response = new JsonObject + { + ["jsonrpc"] = "2.0", + ["id"] = id, + ["error"] = new JsonObject + { + ["code"] = -32602, + ["message"] = "Invalid Parameters!", + } + }; + _ = this.Transport.Write(response.ToJsonString()!); + } + return; + } + + _ = service.HandleRequest(functionName, parsed["params"]!, this.Context).ContinueWith(result => + { + if (id != null) + { + var response = new JsonObject + { + ["jsonrpc"] = "2.0", + ["id"] = id, + ["error"] = result.IsFaulted ? new JsonObject + { + ["code"] = -32603, + ["message"] = result.Exception!.InnerException!.Message + } : null, + ["result"] = !result.IsFaulted ? result.Result : null, + }; + _ = this.Transport.Write(response.ToJsonString()!); + } + }); + + + //TODO: implement Notifications + } + else + { + // Ignoring everyting else. Don't care! + return; + } + } + catch (Exception) + { + //TODO: Maybe log exception, but don't break! + } + } + + + public async void SendNotification(string method, JsonArray param) + { + var not = new JsonObject + { + ["jsonrpc"] = "2.0", + ["method"] = method, + ["params"] = param, + }; + + await this.Transport.Write(not.ToJsonString()); + } +} + +public abstract class JRpcService +{ + public abstract string Name { get; } + + public HashSet Functions = new HashSet(); + + + protected void RegisterFunction(string name) + { + this.Functions.Add(name); + } + + public abstract Task HandleRequest(string function, JsonNode param, TContext context); + // public abstract Task HandleNotification(string notification, JsonNode param); +} diff --git a/crates/libjrpc/templates/CSharp/JRpcTransport.cs b/crates/libjrpc/templates/CSharp/JRpcTransport.cs new file mode 100644 index 0000000..5f0d2c3 --- /dev/null +++ b/crates/libjrpc/templates/CSharp/JRpcTransport.cs @@ -0,0 +1,15 @@ +using System.Threading.Tasks; + +namespace __NAMESPACE__; + +public delegate void NotifyPacket(string data); + +public abstract class JRpcTransport { + public event NotifyPacket? OnPacket; + public abstract Task Write(string data); + + // TODO: Spend some time finding a better permission, but it is fine for now + public void SendPacketEvent(string data) { + this.OnPacket?.Invoke(data); + } +} diff --git a/crates/libjrpc/templates/Dart/analysis_options.yaml b/crates/libjrpc/templates/Dart/analysis_options.yaml new file mode 100644 index 0000000..55c7822 --- /dev/null +++ b/crates/libjrpc/templates/Dart/analysis_options.yaml @@ -0,0 +1,30 @@ +# This file configures the static analysis results for your project (errors, +# warnings, and lints). +# +# This enables the 'recommended' set of lints from `package:lints`. +# This set helps identify many issues that may lead to problems when running +# or consuming Dart code, and enforces writing Dart using a single, idiomatic +# style and format. +# +# If you want a smaller set of lints you can change this to specify +# 'package:lints/core.yaml'. These are just the most critical lints +# (the recommended set includes the core lints). +# The core lints are also what is used by pub.dev for scoring packages. + +include: package:lints/recommended.yaml + +# Uncomment the following section to specify additional rules. + +# linter: +# rules: +# - camel_case_types + +# analyzer: +# exclude: +# - path/to/excluded/files/** + +# For more information about the core and recommended set of lints, see +# https://dart.dev/go/core-lints + +# For additional information about configuring this file, see +# https://dart.dev/guides/language/analysis-options \ No newline at end of file diff --git a/crates/libjrpc/templates/Dart/base.dart b/crates/libjrpc/templates/Dart/base.dart new file mode 100644 index 0000000..1321f27 --- /dev/null +++ b/crates/libjrpc/templates/Dart/base.dart @@ -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!"); + } +} diff --git a/crates/libjrpc/templates/Dart/pubspec.yaml b/crates/libjrpc/templates/Dart/pubspec.yaml new file mode 100644 index 0000000..882b2b1 --- /dev/null +++ b/crates/libjrpc/templates/Dart/pubspec.yaml @@ -0,0 +1,10 @@ +name: __NAME__ +description: JRPC +version: 1.0.0 + +environment: + sdk: ">=2.17.6 <3.0.0" + +dependencies: {} +dev_dependencies: + lints: ^2.0.0 diff --git a/crates/libjrpc/templates/Dart/service_client.dart b/crates/libjrpc/templates/Dart/service_client.dart new file mode 100644 index 0000000..658ef92 --- /dev/null +++ b/crates/libjrpc/templates/Dart/service_client.dart @@ -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 _services = {}; + final Map> _requests = {}; + + StreamController> output = StreamController(); + late StreamSubscription s; + + ServiceProvider(Stream> input) { + s = input.listen(onMessage); + } + + void onMessage(Map 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 sendRequest(String method, dynamic params) { + var id = nanoid(10); + var req = {"jsonrpc": "2.0", "id": id, "method": method, "params": params}; + + var completer = Completer(); + + 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; +} diff --git a/crates/libjrpc/templates/Rust/.editorconfig b/crates/libjrpc/templates/Rust/.editorconfig new file mode 100644 index 0000000..1c9705c --- /dev/null +++ b/crates/libjrpc/templates/Rust/.editorconfig @@ -0,0 +1,2 @@ +[*.rs] +indent_size = 4 diff --git a/crates/libjrpc/templates/Rust/.gitignore b/crates/libjrpc/templates/Rust/.gitignore new file mode 100644 index 0000000..a9d37c5 --- /dev/null +++ b/crates/libjrpc/templates/Rust/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock diff --git a/crates/libjrpc/templates/Rust/Cargo.toml b/crates/libjrpc/templates/Rust/Cargo.toml new file mode 100644 index 0000000..8ac3329 --- /dev/null +++ b/crates/libjrpc/templates/Rust/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "__name__" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +int-enum = { version ="0.5.0", features = ["serde", "convert"] } +serde = { version = "1.0.147", features = ["derive"] } +serde_json = "1.0.88" +nanoid = "0.4.0" +tokio = { version = "1.22.0", features = ["full"] } +log = "0.4.17" +async-trait = "0.1.59" diff --git a/crates/libjrpc/templates/Rust/src/lib.rs b/crates/libjrpc/templates/Rust/src/lib.rs new file mode 100644 index 0000000..2dc2f96 --- /dev/null +++ b/crates/libjrpc/templates/Rust/src/lib.rs @@ -0,0 +1,226 @@ +use log::{info, trace, warn}; +use nanoid::nanoid; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::boxed::Box; +use std::error::Error; +use std::marker::Send; +use std::{collections::HashMap, sync::Arc}; +use tokio::sync::{mpsc::Sender, Mutex}; + +pub type Result = std::result::Result>; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct JRPCRequest { + pub jsonrpc: String, + pub id: Option, + pub method: String, + pub params: Value, +} + +impl JRPCRequest { + pub fn new_request(method: String, params: Value) -> JRPCRequest { + JRPCRequest { + jsonrpc: "2.0".to_string(), + id: None, + method, + params, + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct JRPCError { + pub code: i64, + pub message: String, + pub data: Value, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct JRPCResult { + pub jsonrpc: String, + pub id: String, + pub result: Option, + pub error: Option, +} + +#[derive(Debug, Clone)] +pub struct JRPCClient { + message_sender: Sender, + requests: Arc>>>, +} + +impl JRPCClient { + pub fn new(sender: Sender) -> JRPCClient { + JRPCClient { + message_sender: sender, + requests: Arc::new(Mutex::new(HashMap::new())), + } + } + + pub async fn send_request(&self, mut request: JRPCRequest) -> Result { + let (sender, mut receiver) = tokio::sync::mpsc::channel(1); + + if request.id.is_none() { + request.id = Some(nanoid!()); + } + + { + let mut self_requests = self.requests.lock().await; + self_requests.insert(request.id.clone().unwrap(), sender); + } + self.message_sender.send(request).await?; + + let result = receiver.recv().await; + + if let Some(result) = result { + if let Some(error) = result.error { + return Err(format!("Error while receiving result: {}", error.message).into()); + } else if let Some(result) = result.result { + return Ok(result); + } else { + return Ok(Value::Null); + // return Err(format!("No result received").into()); + } + } else { + return Err("Error while receiving result".into()); + } + } + + pub async fn send_notification(&self, mut request: JRPCRequest) { + request.id = None; + + _ = self.message_sender.send(request).await; + } + + pub async fn on_result(&self, result: JRPCResult) { + let id = result.id.clone(); + let mut self_requests = self.requests.lock().await; + let sender = self_requests.get(&id); + if let Some(sender) = sender { + _ = sender.send(result).await; + self_requests.remove(&id); + } + } +} + +#[async_trait::async_trait] +pub trait JRPCServerService: Send + Sync + 'static { + fn get_id(&self) -> String; + async fn handle(&self, request: &JRPCRequest, function: &str) -> Result<(bool, Value)>; +} + +pub type JRPCServiceHandle = Arc; + +#[derive(Clone)] +pub struct JRPCSession { + server: JRPCServer, + message_sender: Sender, +} + +impl JRPCSession { + pub fn new(server: JRPCServer, sender: Sender) -> JRPCSession { + JRPCSession { + server, + message_sender: sender, + } + } + + async fn send_error(&self, request: JRPCRequest, error_msg: String, error_code: i64) -> () { + if let Some(request_id) = request.id { + let error = JRPCError { + code: error_code, + message: error_msg, + data: Value::Null, + }; + let result = JRPCResult { + jsonrpc: "2.0".to_string(), + id: request_id, + result: None, + error: Some(error), + }; + + // Send result + let result = self.message_sender.send(result).await; + if let Err(err) = result { + warn!("Error while sending result: {}", err); + } + } + } + + pub fn handle_request(&self, request: JRPCRequest) -> () { + let session = self.clone(); + tokio::task::spawn(async move { + info!("Received request: {}", request.method); + trace!("Request data: {:?}", request); + let method: Vec<&str> = request.method.split('.').collect(); + if method.len() != 2 { + warn!("Invalid method received: {}", request.method); + return; + } + let service = method[0]; + let function = method[1]; + + let service = session.server.services.get(service); + if let Some(service) = service { + let result = service.handle(&request, function).await; + match result { + Ok((is_send, result)) => { + if is_send && request.id.is_some() { + let result = session + .message_sender + .send(JRPCResult { + jsonrpc: "2.0".to_string(), + id: request.id.unwrap(), + result: Some(result), + error: None, + }) + .await; + if let Err(err) = result { + warn!("Error while sending result: {}", err); + } + } + } + Err(err) => { + warn!("Error while handling request: {}", err); + session + .send_error( + request, + format!("Error while handling request: {}", err), + 1, + ) + .await; + } + } + } else { + warn!("Service not found: {}", method[0]); + session + .send_error(request, "Service not found".to_string(), 1) + .await; + return; + } + }); + } +} + +#[derive(Clone)] +pub struct JRPCServer { + services: HashMap, +} + +impl JRPCServer { + pub fn new() -> JRPCServer { + JRPCServer { + services: HashMap::new(), + } + } + + pub fn add_service(&mut self, service: JRPCServiceHandle) -> () { + let id = service.get_id(); + self.services.insert(id, service); + } + + pub fn get_session(&self, sender: Sender) -> JRPCSession { + JRPCSession::new(self.clone(), sender) + } +} diff --git a/crates/libjrpc/templates/TypeScript/ts_base.ts b/crates/libjrpc/templates/TypeScript/ts_base.ts new file mode 100644 index 0000000..d296810 --- /dev/null +++ b/crates/libjrpc/templates/TypeScript/ts_base.ts @@ -0,0 +1,44 @@ +function form_verficiation_error_message(type?: string, field?: string) { + let msg = "Parameter verification failed! "; + if (type && field) { + msg += `At ${type}.${field}! `; + } else if (type) { + msg += `At type ${type}! `; + } else if (field) { + msg += `At field ${field}! `; + } + return msg; +} + +export class VerificationError extends Error { + constructor( + public readonly type?: string, + public readonly field?: string, + public readonly value?: any + ) { + super(form_verficiation_error_message(type, field)); + } +} + +export function apply_int(data: any) { + data = Math.floor(Number(data)); + if (Number.isNaN(data)) throw new VerificationError("int", undefined, data); + return data; +} + +export function apply_float(data: any) { + data = Number(data); + if (Number.isNaN(data)) + throw new VerificationError("float", undefined, data); + return data; +} + +export function apply_string(data: any) { + return String(data); +} + +export function apply_boolean(data: any) { + return Boolean(data); +} + +export function apply_void(data: any) { } diff --git a/crates/libjrpc/templates/TypeScript/ts_service_base.ts b/crates/libjrpc/templates/TypeScript/ts_service_base.ts new file mode 100644 index 0000000..f67c7fe --- /dev/null +++ b/crates/libjrpc/templates/TypeScript/ts_service_base.ts @@ -0,0 +1,30 @@ +export const Logging = { + verbose: false, + log(...args: any[]) { + if (Logging.verbose) { + console.log(...args); + } + }, +}; + +export enum ErrorCodes { + ParseError = -32700, + InvalidRequest = -32600, + MethodNotFound = -32601, + InvalidParams = -32602, + InternalError = -32603, +} + +export interface RequestObject { + jsonrpc: "2.0"; + method: string; + params?: any[] | { [key: string]: any }; + id?: string | null; +} + +export interface ResponseObject { + jsonrpc: "2.0"; + result?: any; + error?: { code: ErrorCodes; message: string; data?: any }; + id: string; +} diff --git a/crates/libjrpc/templates/TypeScript/ts_service_client.ts b/crates/libjrpc/templates/TypeScript/ts_service_client.ts new file mode 100644 index 0000000..0048b83 --- /dev/null +++ b/crates/libjrpc/templates/TypeScript/ts_service_client.ts @@ -0,0 +1,105 @@ +//@template-ignore +import { VerificationError } from "./ts_base"; +//@template-ignore +import { type RequestObject, type ResponseObject, ErrorCodes, Logging } from "./ts_service_base"; + + +export type IMessageCallback = (data: any) => void; + +export type ResponseListener = { + ok: (response:any)=>void; + err: (error: Error)=>void; +} + +export class Service { + public _name: string = null as any; + + constructor(protected _provider: ServiceProvider, name: string) { + this._name = name; + this._provider.services.set(name, this); + } +} + +export class ServiceProvider { + services = new Map(); + requests = new Map(); + + constructor(private sendPacket: IMessageCallback) {} + + onPacket(msg: RequestObject | ResponseObject) { + Logging.log("CLIENT: Received message:", msg); + if("method" in msg) { + if(msg.id){ + Logging.log("CLIENT: Determined type is Request"); + // Request, which are not supported by client, so ignore + return; + } else { + Logging.log("CLIENT: Determined type is Notification"); + //Notification. Send to Notification handler + const [srvName, fncName] = msg.method.split("."); + let service = this.services.get(srvName) + if(!service) { + Logging.log("CLIENT: Did not find Service wanted by Notification!", srvName); + } else { + //TODO: Implement Event thingy (or so :)) + } + } + } 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); + } + } + + } + + sendMessage(msg: RequestObject, res?: ResponseListener) { + Logging.log("CLIENT: Sending Messgage", msg); + if(msg.id) { + this.requests.set(msg.id, res); + } + this.sendPacket(msg) + } +} + + + + + + + +declare var require: any; +export const getRandomBytes = ( + typeof self !== "undefined" && (self.crypto || (self as any).msCrypto) + ? function () { + // Browsers + var crypto = self.crypto || (self as any).msCrypto; + var QUOTA = 65536; + return function (n: number) { + var a = new Uint8Array(n); + for (var i = 0; i < n; i += QUOTA) { + crypto.getRandomValues( + a.subarray(i, i + Math.min(n - i, QUOTA)) + ); + } + return a; + }; + } + : function () { + // Node + return require("crypto").randomBytes; + } +)() as (cnt: number) => Uint8Array; + +export const getRandomID = (length: number) => { + return btoa(String.fromCharCode.apply(null, getRandomBytes(length) as any)); +}; diff --git a/crates/libjrpc/templates/TypeScript/ts_service_server.ts b/crates/libjrpc/templates/TypeScript/ts_service_server.ts new file mode 100644 index 0000000..0a4e011 --- /dev/null +++ b/crates/libjrpc/templates/TypeScript/ts_service_server.ts @@ -0,0 +1,130 @@ +//@template-ignore +import { VerificationError } from "./ts_base"; +//@template-ignore +import { type RequestObject, type ResponseObject, ErrorCodes, Logging } from "./ts_service_base"; + + +export class Service { + public name: string = null as any; + public functions = new Set(); + + constructor() { } +} + +type ISendMessageCB = (data: any, catchedErr?: Error) => void; + +export class ServiceProvider { + services = new Map>(); + addService(service: Service) { + this.services.set(service.name, service); + Logging.log("SERVER: Adding Service to provider:", service.name); + Logging.log("SERVER: Service provides:", [...service.functions.keys()]) + } + + getSession(send: ISendMessageCB, ctx?: Partial): Session { + return new Session(this, send, ctx); + } +} + +class Session { + ctx: Partial; + + constructor( + private provider: ServiceProvider, + private _send: ISendMessageCB, + ctx?: Partial + ) { + this.ctx = ctx || {}; + } + + send(data: any, catchedErr?: Error) { + Logging.log("SERVER: Sending Message", data) + this._send(data, catchedErr); + } + + async onMessage(data: RequestObject) { + Logging.log("SERVER: Received Message", data); + try { + if (!data.method) { + if (data.id) { + this.send({ + jsonrpc: "2.0", + id: data.id, + error: { + code: ErrorCodes.InvalidRequest, + message: "No method defined!", + }, + } as ResponseObject); + } + return; + } + + const [srvName, fncName] = data.method.split("."); + Logging.log("SERVER: Message for", srvName, fncName); + + const service = this.provider.services.get(srvName); + if (!service) { + Logging.log("SERVER: Did not find Service"); + if (data.id) { + this.send({ + jsonrpc: "2.0", + id: data.id, + error: { + code: ErrorCodes.MethodNotFound, + message: "Service not found!", + }, + } as ResponseObject); + } + return; + } + + const fnc = service.functions.has(fncName); + if (!fnc) { + Logging.log("SERVER: Did not find Function"); + if (data.id) { + this.send({ + jsonrpc: "2.0", + id: data.id, + error: { + code: ErrorCodes.MethodNotFound, + message: "Function not found!", + }, + } as ResponseObject); + } + return; + } + + let result = await (service as any)["_" + fncName](data.params, this.ctx); + if (data.id) { //Request + this.send({ + jsonrpc: "2.0", + id: data.id, + result: result, + } as ResponseObject); + } //else Notification and response is ignored + } catch (err) { + if (data.id) { + this.send( + { + jsonrpc: "2.0", + id: data.id, + error: { + code: ErrorCodes.InternalError, + message: err.message, + data: err instanceof VerificationError ? { + $: "verification_error", + type: err.type, + field: err.field, + value: err.value + } : { + $: "unknown_error" + }, + }, + } as ResponseObject, + err + ); + } + //TODO: Think about else case + } + } +} diff --git a/crates/libjrpc/test.jrpc b/crates/libjrpc/test.jrpc index 50c6bd2..5cd5024 100644 --- a/crates/libjrpc/test.jrpc +++ b/crates/libjrpc/test.jrpc @@ -1,5 +1,5 @@ // Test -// import "./import"; +import "./import"; define csharp_namespace Example; define rust_crate example;