Start working on rust compile step
This commit is contained in:
@ -1,2 +1,3 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
|
resolver = "2"
|
||||||
members = ["crates/zed", "crates/libjrpc"]
|
members = ["crates/zed", "crates/libjrpc"]
|
||||||
|
7
crates/libjrpc/import.jrpc
Normal file
7
crates/libjrpc/import.jrpc
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import "./import.jrpc";
|
||||||
|
|
||||||
|
type TestAtom {
|
||||||
|
val_number?: float;
|
||||||
|
val_boolean?: boolean;
|
||||||
|
val_string?: string;
|
||||||
|
}
|
75
crates/libjrpc/src/compile.rs
Normal file
75
crates/libjrpc/src/compile.rs
Normal file
@ -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<String, String>) -> 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<T: ToString>(&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
|
||||||
|
}
|
||||||
|
}
|
@ -11,7 +11,7 @@ use crate::parser::{
|
|||||||
EnumStatement, Node, ParserPosition, RootNode, ServiceStatement, TypeStatement,
|
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 {
|
pub trait Definition {
|
||||||
fn get_position(&self) -> ParserPosition;
|
fn get_position(&self) -> ParserPosition;
|
||||||
@ -19,8 +19,8 @@ pub trait Definition {
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct IR {
|
pub struct IR {
|
||||||
options: HashMap<String, String>,
|
pub options: HashMap<String, String>,
|
||||||
steps: Vec<Step>,
|
pub steps: Vec<Step>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -36,6 +36,7 @@ pub enum Type {
|
|||||||
Float,
|
Float,
|
||||||
String,
|
String,
|
||||||
Bool,
|
Bool,
|
||||||
|
Bytes,
|
||||||
Custom(String),
|
Custom(String),
|
||||||
}
|
}
|
||||||
impl Hash for Type {
|
impl Hash for Type {
|
||||||
@ -45,6 +46,7 @@ impl Hash for Type {
|
|||||||
Type::Float => "float".hash(state),
|
Type::Float => "float".hash(state),
|
||||||
Type::String => "string".hash(state),
|
Type::String => "string".hash(state),
|
||||||
Type::Bool => "bool".hash(state),
|
Type::Bool => "bool".hash(state),
|
||||||
|
Type::Bytes => "bytes".hash(state),
|
||||||
Type::Custom(name) => name.hash(state),
|
Type::Custom(name) => name.hash(state),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -52,10 +54,10 @@ impl Hash for Type {
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct TypeDefinition {
|
pub struct TypeDefinition {
|
||||||
name: String,
|
pub name: String,
|
||||||
depends: HashSet<Type>,
|
pub depends: HashSet<Type>,
|
||||||
fields: Vec<Field>,
|
pub fields: Vec<Field>,
|
||||||
position: ParserPosition,
|
pub position: ParserPosition,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Definition for TypeDefinition {
|
impl Definition for TypeDefinition {
|
||||||
@ -66,18 +68,18 @@ impl Definition for TypeDefinition {
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Field {
|
pub struct Field {
|
||||||
name: String,
|
pub name: String,
|
||||||
typ: Type,
|
pub typ: Type,
|
||||||
array: bool,
|
pub array: bool,
|
||||||
optional: bool,
|
pub optional: bool,
|
||||||
map: Option<Type>,
|
pub map: Option<Type>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct EnumDefinition {
|
pub struct EnumDefinition {
|
||||||
name: String,
|
pub name: String,
|
||||||
values: Vec<EnumField>,
|
pub values: Vec<EnumField>,
|
||||||
position: ParserPosition,
|
pub position: ParserPosition,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Definition for EnumDefinition {
|
impl Definition for EnumDefinition {
|
||||||
@ -88,16 +90,16 @@ impl Definition for EnumDefinition {
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct EnumField {
|
pub struct EnumField {
|
||||||
name: String,
|
pub name: String,
|
||||||
value: i32,
|
pub value: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ServiceDefinition {
|
pub struct ServiceDefinition {
|
||||||
name: String,
|
pub name: String,
|
||||||
depends: HashSet<Type>,
|
pub depends: HashSet<Type>,
|
||||||
methods: Vec<Method>,
|
pub methods: Vec<Method>,
|
||||||
position: ParserPosition,
|
pub position: ParserPosition,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Definition for ServiceDefinition {
|
impl Definition for ServiceDefinition {
|
||||||
@ -108,31 +110,31 @@ impl Definition for ServiceDefinition {
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Method {
|
pub struct Method {
|
||||||
name: String,
|
pub name: String,
|
||||||
inputs: Vec<MethodInput>,
|
pub inputs: Vec<MethodInput>,
|
||||||
output: Option<MethodOutput>,
|
pub output: Option<MethodOutput>,
|
||||||
decorators: MethodDecorators,
|
pub decorators: MethodDecorators,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct MethodInput {
|
pub struct MethodInput {
|
||||||
name: String,
|
pub name: String,
|
||||||
typ: Type,
|
pub typ: Type,
|
||||||
array: bool,
|
pub array: bool,
|
||||||
optional: bool,
|
pub optional: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct MethodOutput {
|
pub struct MethodOutput {
|
||||||
typ: Type,
|
pub typ: Type,
|
||||||
array: bool,
|
pub array: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct MethodDecorators {
|
pub struct MethodDecorators {
|
||||||
description: Option<String>,
|
pub description: Option<String>,
|
||||||
parameter_descriptions: HashMap<String, String>,
|
pub parameter_descriptions: HashMap<String, String>,
|
||||||
return_description: Option<String>,
|
pub return_description: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn typename_to_type(name: &str) -> Type {
|
fn typename_to_type(name: &str) -> Type {
|
||||||
@ -221,7 +223,9 @@ fn build_service(stmt: &ServiceStatement) -> Result<ServiceDefinition> {
|
|||||||
inputs: Vec::new(),
|
inputs: Vec::new(),
|
||||||
output: method.return_type.as_ref().map(|rt| {
|
output: method.return_type.as_ref().map(|rt| {
|
||||||
let typ = typename_to_type(&rt.fieldtype);
|
let typ = typename_to_type(&rt.fieldtype);
|
||||||
|
if typ != Type::Custom("void".to_string()) {
|
||||||
servdef.depends.insert(typ.clone());
|
servdef.depends.insert(typ.clone());
|
||||||
|
}
|
||||||
MethodOutput {
|
MethodOutput {
|
||||||
typ,
|
typ,
|
||||||
array: rt.array,
|
array: rt.array,
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
|
mod compile;
|
||||||
mod ir;
|
mod ir;
|
||||||
mod parser;
|
mod parser;
|
||||||
mod process;
|
mod process;
|
||||||
mod shared;
|
mod shared;
|
||||||
|
mod targets;
|
||||||
mod tokenizer;
|
mod tokenizer;
|
||||||
|
|
||||||
|
pub use ir::IR;
|
||||||
|
pub use parser::{Parser, RootNode};
|
||||||
|
pub use process::FileProcessor;
|
||||||
pub use tokenizer::{tokenize, Token, TokenError, TokenPosition, TokenType};
|
pub use tokenizer::{tokenize, Token, TokenError, TokenPosition, TokenType};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[ctor::ctor]
|
#[ctor::ctor]
|
||||||
fn init() {
|
fn init() {
|
||||||
@ -18,22 +21,10 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn parse_jrpc() {
|
pub fn parse_jrpc() {
|
||||||
let tokens = crate::tokenizer::tokenize(
|
let mut fp = crate::process::FileProcessor::new();
|
||||||
Arc::new("../test.jrpc".to_owned()),
|
// let ir = fp.start_compile("./test.jrpc").unwrap();
|
||||||
include_str!("../test.jrpc").to_owned(),
|
let ir = fp.start_compile("http://127.0.0.1:7878/test.jrpc").unwrap();
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let parsed = crate::parser::Parser::new(tokens).parse().unwrap();
|
println!("{:?}", ir);
|
||||||
// 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),
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use log::trace;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::ir::IR;
|
use crate::{ir::IR, parser::RootNode};
|
||||||
|
|
||||||
pub struct FileProcessor {
|
pub struct FileProcessor {
|
||||||
file_cache: HashMap<String, String>,
|
file_cache: HashMap<String, String>,
|
||||||
@ -15,6 +17,7 @@ pub struct FileProcessor {
|
|||||||
|
|
||||||
impl FileProcessor {
|
impl FileProcessor {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
|
trace!("FileProcessor::new()");
|
||||||
Self {
|
Self {
|
||||||
file_cache: HashMap::new(),
|
file_cache: HashMap::new(),
|
||||||
processed_files: HashSet::new(),
|
processed_files: HashSet::new(),
|
||||||
@ -22,6 +25,7 @@ impl FileProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn is_url(inp: Option<&str>) -> bool {
|
fn is_url(inp: Option<&str>) -> bool {
|
||||||
|
trace!("FileProcessor::is_url({:?})", inp);
|
||||||
#[cfg(feature = "http")]
|
#[cfg(feature = "http")]
|
||||||
if let Some(inp) = inp {
|
if let Some(inp) = inp {
|
||||||
if inp.starts_with("http://") || inp.starts_with("https://") {
|
if inp.starts_with("http://") || inp.starts_with("https://") {
|
||||||
@ -32,13 +36,14 @@ impl FileProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_path(input: &str, context: Option<&str>) -> String {
|
fn resolve_path(input: &str, context: Option<&str>) -> String {
|
||||||
|
trace!("FileProcessor::resolve_path({}, {:?})", input, context);
|
||||||
let mut input = input.to_string();
|
let mut input = input.to_string();
|
||||||
#[cfg(feature = "http")]
|
#[cfg(feature = "http")]
|
||||||
if cfg!(feature = "http") && (Self::is_url(Some(&input)) || Self::is_url(context)) {
|
if cfg!(feature = "http") && (Self::is_url(Some(&input)) || Self::is_url(context)) {
|
||||||
if Self::is_url(Some(&input)) {
|
if Self::is_url(Some(&input)) {
|
||||||
input.to_string()
|
input.to_string()
|
||||||
} else {
|
} else {
|
||||||
let mut url = Url::parse(context.unwrap()).unwrap();
|
let url = Url::parse(context.unwrap()).unwrap();
|
||||||
if !input.ends_with(".jrpc") {
|
if !input.ends_with(".jrpc") {
|
||||||
input = format!("{}.jrpc", input);
|
input = format!("{}.jrpc", input);
|
||||||
}
|
}
|
||||||
@ -50,6 +55,7 @@ impl FileProcessor {
|
|||||||
}
|
}
|
||||||
if let Some(context) = context {
|
if let Some(context) = context {
|
||||||
let mut path = PathBuf::from(context);
|
let mut path = PathBuf::from(context);
|
||||||
|
path.pop();
|
||||||
path = path.join(input);
|
path = path.join(input);
|
||||||
path.to_str().unwrap().to_string()
|
path.to_str().unwrap().to_string()
|
||||||
} else {
|
} else {
|
||||||
@ -59,6 +65,7 @@ impl FileProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_file_url(&mut self, url: &str) -> Result<String> {
|
fn get_file_url(&mut self, url: &str) -> Result<String> {
|
||||||
|
trace!("FileProcessor::get_file_url({})", url);
|
||||||
let resp = reqwest::blocking::get(url)?;
|
let resp = reqwest::blocking::get(url)?;
|
||||||
let body = resp.text()?;
|
let body = resp.text()?;
|
||||||
self.file_cache.insert(url.to_string(), body.clone());
|
self.file_cache.insert(url.to_string(), body.clone());
|
||||||
@ -66,12 +73,14 @@ impl FileProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_file_path(&mut self, path: &str) -> Result<String> {
|
fn get_file_path(&mut self, path: &str) -> Result<String> {
|
||||||
|
trace!("FileProcessor::get_file_path({})", path);
|
||||||
let body = std::fs::read_to_string(path)?;
|
let body = std::fs::read_to_string(path)?;
|
||||||
self.file_cache.insert(path.to_string(), body.clone());
|
self.file_cache.insert(path.to_string(), body.clone());
|
||||||
Ok(body)
|
Ok(body)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_file(&mut self, name: &str) -> Result<String> {
|
fn get_file(&mut self, name: &str) -> Result<String> {
|
||||||
|
trace!("FileProcessor::get_file({})", name);
|
||||||
if self.file_cache.contains_key(name) {
|
if self.file_cache.contains_key(name) {
|
||||||
return Ok(self.file_cache.get(name).unwrap().clone());
|
return Ok(self.file_cache.get(name).unwrap().clone());
|
||||||
} else {
|
} 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<Vec<RootNode>> {
|
||||||
|
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<IR> {
|
pub fn start_compile(&mut self, file: &str) -> Result<IR> {
|
||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,3 +5,24 @@ pub enum Keywords {
|
|||||||
Service,
|
Service,
|
||||||
Define,
|
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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
1
crates/libjrpc/src/targets/mod.rs
Normal file
1
crates/libjrpc/src/targets/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
mod rust;
|
214
crates/libjrpc/src/targets/rust.rs
Normal file
214
crates/libjrpc/src/targets/rust.rs
Normal file
@ -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<u8>".to_string(),
|
||||||
|
Type::Custom(name) => name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_dependencies(
|
||||||
|
&mut self,
|
||||||
|
file: &mut FileGenerator,
|
||||||
|
depends: &HashSet<Type>,
|
||||||
|
) -> 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<String, String>,
|
||||||
|
) -> 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!()
|
||||||
|
}
|
||||||
|
}
|
10
crates/libjrpc/templates/CSharp/CSharp.csproj
Normal file
10
crates/libjrpc/templates/CSharp/CSharp.csproj
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<LangVersion>10.0</LangVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
170
crates/libjrpc/templates/CSharp/JRpcClient.cs
Normal file
170
crates/libjrpc/templates/CSharp/JRpcClient.cs
Normal file
@ -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<string, TaskCompletionSource<JsonNode?>> Requests;
|
||||||
|
private int? Timeout = null;
|
||||||
|
|
||||||
|
public JRpcClient(JRpcTransport transport, int? timeout = null)
|
||||||
|
{
|
||||||
|
this.Transport = transport;
|
||||||
|
this.Timeout = timeout;
|
||||||
|
this.Requests = new Dictionary<string, TaskCompletionSource<JsonNode?>>();
|
||||||
|
|
||||||
|
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<JRpcError>();
|
||||||
|
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<JsonNode?> 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<JsonNode?>();
|
||||||
|
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<TResult?> SendRequest<TResult>(string method, JsonArray param)
|
||||||
|
{
|
||||||
|
var result = await this.SendRequestRaw(method, param);
|
||||||
|
return result.Deserialize<TResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
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") { }
|
||||||
|
}
|
199
crates/libjrpc/templates/CSharp/JRpcServer.cs
Normal file
199
crates/libjrpc/templates/CSharp/JRpcServer.cs
Normal file
@ -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<TContext>
|
||||||
|
{
|
||||||
|
public IDictionary<string, JRpcService<TContext>> Services;
|
||||||
|
|
||||||
|
public JRpcServer()
|
||||||
|
{
|
||||||
|
this.Services = new Dictionary<string, JRpcService<TContext>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddService(JRpcService<TContext> service)
|
||||||
|
{
|
||||||
|
this.Services.Add(service.Name, service);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JRpcServerSession<TContext> GetSession(JRpcTransport transport, TContext context)
|
||||||
|
{
|
||||||
|
return new JRpcServerSession<TContext>(this, transport, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class JRpcServerSession<TContext>
|
||||||
|
{
|
||||||
|
public JRpcServer<TContext> Server { get; private set; }
|
||||||
|
public JRpcTransport Transport { get; private set; }
|
||||||
|
public TContext Context { get; private set; }
|
||||||
|
|
||||||
|
public JRpcServerSession(JRpcServer<TContext> 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<TContext>
|
||||||
|
{
|
||||||
|
public abstract string Name { get; }
|
||||||
|
|
||||||
|
public HashSet<string> Functions = new HashSet<string>();
|
||||||
|
|
||||||
|
|
||||||
|
protected void RegisterFunction(string name)
|
||||||
|
{
|
||||||
|
this.Functions.Add(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract Task<JsonNode?> HandleRequest(string function, JsonNode param, TContext context);
|
||||||
|
// public abstract Task HandleNotification(string notification, JsonNode param);
|
||||||
|
}
|
15
crates/libjrpc/templates/CSharp/JRpcTransport.cs
Normal file
15
crates/libjrpc/templates/CSharp/JRpcTransport.cs
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
30
crates/libjrpc/templates/Dart/analysis_options.yaml
Normal file
30
crates/libjrpc/templates/Dart/analysis_options.yaml
Normal file
@ -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
|
52
crates/libjrpc/templates/Dart/base.dart
Normal file
52
crates/libjrpc/templates/Dart/base.dart
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
class JRPCError extends Error {
|
||||||
|
String message;
|
||||||
|
JRPCError(this.message);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => message;
|
||||||
|
}
|
||||||
|
|
||||||
|
int? int_fromJson(dynamic val) {
|
||||||
|
if (val == null) {
|
||||||
|
return null;
|
||||||
|
} else if (val is int) {
|
||||||
|
return val;
|
||||||
|
} else if (val is double) {
|
||||||
|
return val.toInt();
|
||||||
|
} else {
|
||||||
|
throw JRPCError("Not a number!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double? double_fromJson(dynamic val) {
|
||||||
|
if (val == null) {
|
||||||
|
return null;
|
||||||
|
} else if (val is int) {
|
||||||
|
return val.toDouble();
|
||||||
|
} else if (val is double) {
|
||||||
|
return val;
|
||||||
|
} else {
|
||||||
|
throw JRPCError("Not a number!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool? bool_fromJson(dynamic val) {
|
||||||
|
if (val == null) {
|
||||||
|
return null;
|
||||||
|
} else if (val is bool) {
|
||||||
|
return val;
|
||||||
|
} else {
|
||||||
|
throw JRPCError("Not a bool!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String? String_fromJson(dynamic val) {
|
||||||
|
if (val == null) {
|
||||||
|
return null;
|
||||||
|
} else if (val is String) {
|
||||||
|
return val;
|
||||||
|
} else {
|
||||||
|
return val.toString(); // TODO: Either this or error
|
||||||
|
// throw JRPCError("Not a string!");
|
||||||
|
}
|
||||||
|
}
|
10
crates/libjrpc/templates/Dart/pubspec.yaml
Normal file
10
crates/libjrpc/templates/Dart/pubspec.yaml
Normal file
@ -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
|
86
crates/libjrpc/templates/Dart/service_client.dart
Normal file
86
crates/libjrpc/templates/Dart/service_client.dart
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import "dart:async";
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import "./base.dart";
|
||||||
|
|
||||||
|
abstract class Service {
|
||||||
|
String name;
|
||||||
|
ServiceProvider provider;
|
||||||
|
|
||||||
|
Service(this.provider, this.name) {
|
||||||
|
provider._services[name] = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServiceProvider {
|
||||||
|
final Map<String, Service> _services = {};
|
||||||
|
final Map<String, Completer<dynamic>> _requests = {};
|
||||||
|
|
||||||
|
StreamController<Map<String, dynamic>> output = StreamController();
|
||||||
|
late StreamSubscription s;
|
||||||
|
|
||||||
|
ServiceProvider(Stream<Map<String, dynamic>> input) {
|
||||||
|
s = input.listen(onMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onMessage(Map<String, dynamic> msg) {
|
||||||
|
// print("Working on message");
|
||||||
|
if (msg.containsKey("method")) {
|
||||||
|
if (msg.containsKey("id")) {
|
||||||
|
print("Message is request");
|
||||||
|
// Request, not supported!
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// print("Message is notification");
|
||||||
|
// Notification
|
||||||
|
// TODO: Implement
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// print("Message is response");
|
||||||
|
// Response
|
||||||
|
var req = _requests[msg["id"]];
|
||||||
|
|
||||||
|
if (req == null) {
|
||||||
|
// print("Could not find related request. Ignoring");
|
||||||
|
return; // Irrelevant response
|
||||||
|
}
|
||||||
|
if (msg.containsKey("error")) {
|
||||||
|
//TODO:
|
||||||
|
req.completeError(JRPCError(msg["error"]["message"]));
|
||||||
|
} else {
|
||||||
|
req.complete(msg["result"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<dynamic> sendRequest(String method, dynamic params) {
|
||||||
|
var id = nanoid(10);
|
||||||
|
var req = {"jsonrpc": "2.0", "id": id, "method": method, "params": params};
|
||||||
|
|
||||||
|
var completer = Completer<dynamic>();
|
||||||
|
|
||||||
|
output.add(req);
|
||||||
|
_requests[id] = completer;
|
||||||
|
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendNotification(String method, dynamic params) {
|
||||||
|
var req = {"jsonrpc": "2.0", "method": method, "params": params};
|
||||||
|
output.add(req);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copied from: https://github.com/pd4d10/nanoid-dart (MIT License)
|
||||||
|
final _random = Random.secure();
|
||||||
|
const urlAlphabet =
|
||||||
|
'ModuleSymbhasOwnPr-0123456789ABCDEFGHNRVfgctiUvz_KqYTJkLxpZXIjQW';
|
||||||
|
|
||||||
|
String nanoid([int size = 21]) {
|
||||||
|
final len = urlAlphabet.length;
|
||||||
|
String id = '';
|
||||||
|
while (0 < size--) {
|
||||||
|
id += urlAlphabet[_random.nextInt(len)];
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
2
crates/libjrpc/templates/Rust/.editorconfig
Normal file
2
crates/libjrpc/templates/Rust/.editorconfig
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
[*.rs]
|
||||||
|
indent_size = 4
|
2
crates/libjrpc/templates/Rust/.gitignore
vendored
Normal file
2
crates/libjrpc/templates/Rust/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
target
|
||||||
|
Cargo.lock
|
15
crates/libjrpc/templates/Rust/Cargo.toml
Normal file
15
crates/libjrpc/templates/Rust/Cargo.toml
Normal file
@ -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"
|
226
crates/libjrpc/templates/Rust/src/lib.rs
Normal file
226
crates/libjrpc/templates/Rust/src/lib.rs
Normal file
@ -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<T> = std::result::Result<T, Box<dyn Error + Send + Sync>>;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
pub struct JRPCRequest {
|
||||||
|
pub jsonrpc: String,
|
||||||
|
pub id: Option<String>,
|
||||||
|
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<Value>,
|
||||||
|
pub error: Option<JRPCError>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct JRPCClient {
|
||||||
|
message_sender: Sender<JRPCRequest>,
|
||||||
|
requests: Arc<Mutex<HashMap<String, Sender<JRPCResult>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JRPCClient {
|
||||||
|
pub fn new(sender: Sender<JRPCRequest>) -> JRPCClient {
|
||||||
|
JRPCClient {
|
||||||
|
message_sender: sender,
|
||||||
|
requests: Arc::new(Mutex::new(HashMap::new())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_request(&self, mut request: JRPCRequest) -> Result<Value> {
|
||||||
|
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<dyn JRPCServerService>;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct JRPCSession {
|
||||||
|
server: JRPCServer,
|
||||||
|
message_sender: Sender<JRPCResult>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JRPCSession {
|
||||||
|
pub fn new(server: JRPCServer, sender: Sender<JRPCResult>) -> 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<String, JRPCServiceHandle>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<JRPCResult>) -> JRPCSession {
|
||||||
|
JRPCSession::new(self.clone(), sender)
|
||||||
|
}
|
||||||
|
}
|
44
crates/libjrpc/templates/TypeScript/ts_base.ts
Normal file
44
crates/libjrpc/templates/TypeScript/ts_base.ts
Normal file
@ -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) { }
|
30
crates/libjrpc/templates/TypeScript/ts_service_base.ts
Normal file
30
crates/libjrpc/templates/TypeScript/ts_service_base.ts
Normal file
@ -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;
|
||||||
|
}
|
105
crates/libjrpc/templates/TypeScript/ts_service_client.ts
Normal file
105
crates/libjrpc/templates/TypeScript/ts_service_client.ts
Normal file
@ -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<string, Service>();
|
||||||
|
requests = new Map<string, ResponseListener |undefined>();
|
||||||
|
|
||||||
|
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));
|
||||||
|
};
|
130
crates/libjrpc/templates/TypeScript/ts_service_server.ts
Normal file
130
crates/libjrpc/templates/TypeScript/ts_service_server.ts
Normal file
@ -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<T> {
|
||||||
|
public name: string = null as any;
|
||||||
|
public functions = new Set<string>();
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
}
|
||||||
|
|
||||||
|
type ISendMessageCB = (data: any, catchedErr?: Error) => void;
|
||||||
|
|
||||||
|
export class ServiceProvider<T = any> {
|
||||||
|
services = new Map<string, Service<T>>();
|
||||||
|
addService(service: Service<T>) {
|
||||||
|
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<T>): Session<T> {
|
||||||
|
return new Session(this, send, ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Session<T> {
|
||||||
|
ctx: Partial<T>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private provider: ServiceProvider,
|
||||||
|
private _send: ISendMessageCB,
|
||||||
|
ctx?: Partial<T>
|
||||||
|
) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
// Test
|
// Test
|
||||||
// import "./import";
|
import "./import";
|
||||||
|
|
||||||
define csharp_namespace Example;
|
define csharp_namespace Example;
|
||||||
define rust_crate example;
|
define rust_crate example;
|
||||||
|
Reference in New Issue
Block a user