Restructure and start working on CLI

This commit is contained in:
Fabian Stamm
2025-05-26 16:43:40 +02:00
parent 883b6da7eb
commit b61518de00
38 changed files with 134 additions and 8 deletions

1
libjrpc/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

20
libjrpc/Cargo.toml Normal file
View File

@ -0,0 +1,20 @@
[package]
name = "libjrpc"
version = "0.1.0"
edition = "2021"
[features]
default = ["http"]
http = ["dep:reqwest", "dep:url"]
[dependencies]
anyhow = "1.0.89"
lazy_static = "1.5.0"
log = "0.4.22"
regex = "1.10.6"
reqwest = { version = "0.12.7", optional = true, features = ["blocking"] }
url = { version = "2.5.2", optional = true }
[dev-dependencies]
env_logger = "0.11.5"
ctor = "*"

7
libjrpc/import.jrpc Normal file
View File

@ -0,0 +1,7 @@
import "./import.jrpc";
type TestAtom {
val_number?: float;
val_boolean?: boolean;
val_string?: string;
}

111
libjrpc/src/compile.rs Normal file
View File

@ -0,0 +1,111 @@
use std::{collections::HashMap, path::PathBuf};
use anyhow::{Context, Result};
use crate::{
ir::{EnumDefinition, ServiceDefinition, TypeDefinition},
IR,
};
pub trait Compile {
fn new(options: &HashMap<String, String>) -> Result<Self>
where
Self: Sized;
fn name(&self) -> String;
fn start(&mut self, ctx: &mut CompileContext) -> 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, ir: &IR) -> 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: Vec<String>,
}
impl FileGenerator {
pub fn new() -> Self {
FileGenerator {
content: Vec::new(),
}
}
pub fn a<T: ToString>(&mut self, indent: usize, content: T) {
let line = " ".repeat(indent) + &content.to_string();
self.content.push(line);
}
pub fn a0<T: ToString>(&mut self, content: T) {
self.a(0, content);
}
pub fn a1<T: ToString>(&mut self, content: T) {
self.a(1, content);
}
pub fn a2<T: ToString>(&mut self, content: T) {
self.a(2, content);
}
pub fn a3<T: ToString>(&mut self, content: T) {
self.a(3, content);
}
pub fn a4<T: ToString>(&mut self, content: T) {
self.a(4, content);
}
pub fn a5<T: ToString>(&mut self, content: T) {
self.a(5, content);
}
pub fn a6<T: ToString>(&mut self, content: T) {
self.a(6, content);
}
pub fn add_line(&mut self, line: &str) {
self.content.push(line.to_string());
}
pub fn get_content(&self) -> String {
self.content.join("\n")
}
pub fn into_content(self) -> String {
self.get_content()
}
}

490
libjrpc/src/ir.rs Normal file
View File

@ -0,0 +1,490 @@
use std::{
collections::{HashMap, HashSet},
error::Error,
fmt::Display,
hash::{Hash, Hasher},
};
use anyhow::Result;
use crate::parser::{
EnumStatement, Node, ParserPosition, RootNode, ServiceStatement, TypeStatement,
};
static BUILT_INS: [&str; 6] = ["int", "float", "string", "boolean", "bytes", "void"];
pub trait Definition {
fn get_position(&self) -> ParserPosition;
}
#[derive(Debug, Clone)]
pub struct IR {
pub options: HashMap<String, String>,
pub steps: Vec<Step>,
}
#[derive(Debug, Clone)]
pub enum Step {
Type(TypeDefinition),
Enum(EnumDefinition),
Service(ServiceDefinition),
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum Type {
Int,
Float,
String,
Bool,
Bytes,
Void,
Custom(String),
}
impl ToString for Type {
fn to_string(&self) -> String {
match self {
Type::Int => "int".to_string(),
Type::Float => "float".to_string(),
Type::String => "string".to_string(),
Type::Bool => "bool".to_string(),
Type::Bytes => "bytes".to_string(),
Type::Void => "void".to_string(),
Type::Custom(name) => name.clone(),
}
}
}
impl Hash for Type {
fn hash<H: Hasher>(&self, state: &mut H) {
self.to_string().hash(state);
}
}
impl From<&String> for Type {
fn from(value: &String) -> Self {
Self::from(value.as_str())
}
}
impl From<String> for Type {
fn from(value: String) -> Self {
Self::from(value.as_str())
}
}
impl From<&str> for Type {
fn from(s: &str) -> Self {
match s {
"int" => Type::Int,
"float" => Type::Float,
"string" => Type::String,
"bool" => Type::Bool,
"boolean" => Type::Bool,
"bytes" => Type::Bytes,
"void" => Type::Void,
_ => Type::Custom(s.to_string()),
}
}
}
#[derive(Debug, Clone)]
pub struct TypeDefinition {
pub name: String,
pub depends: HashSet<Type>,
pub fields: Vec<Field>,
pub position: ParserPosition,
}
impl Definition for TypeDefinition {
fn get_position(&self) -> ParserPosition {
self.position.clone()
}
}
#[derive(Debug, Clone)]
pub struct Field {
pub name: String,
pub typ: Type,
pub array: bool,
pub optional: bool,
pub map: Option<Type>,
}
#[derive(Debug, Clone)]
pub struct EnumDefinition {
pub name: String,
pub values: Vec<EnumField>,
pub position: ParserPosition,
}
impl Definition for EnumDefinition {
fn get_position(&self) -> ParserPosition {
self.position.clone()
}
}
#[derive(Debug, Clone)]
pub struct EnumField {
pub name: String,
pub value: i32,
}
#[derive(Debug, Clone)]
pub struct ServiceDefinition {
pub name: String,
pub depends: HashSet<Type>,
pub methods: Vec<Method>,
pub position: ParserPosition,
}
impl Definition for ServiceDefinition {
fn get_position(&self) -> ParserPosition {
self.position.clone()
}
}
#[derive(Debug, Clone)]
pub struct Method {
pub name: String,
pub inputs: Vec<MethodInput>,
pub output: Option<MethodOutput>,
pub decorators: MethodDecorators,
}
#[derive(Debug, Clone)]
pub struct MethodInput {
pub name: String,
pub typ: Type,
pub array: bool,
pub optional: bool,
}
#[derive(Debug, Clone)]
pub struct MethodOutput {
pub typ: Type,
pub array: bool,
}
#[derive(Debug, Clone)]
pub struct MethodDecorators {
pub description: Option<String>,
pub parameter_descriptions: HashMap<String, String>,
pub return_description: Option<String>,
}
fn build_type(stmt: &TypeStatement) -> Result<TypeDefinition> {
let mut typedef = TypeDefinition {
position: stmt.position.clone(),
name: stmt.name.clone(),
depends: HashSet::new(),
fields: Vec::new(),
};
for field in &stmt.fields {
let typ = Type::from(&field.fieldtype);
typedef.depends.insert(typ.clone());
if let Some(maptype) = &field.map {
if maptype != "string" && maptype != "int" {
return Err(IRError::new("Map type must be string or int", field).into());
}
}
typedef.fields.push(Field {
name: field.name.clone(),
typ: typ.clone(),
array: field.array,
optional: field.optional,
map: field.map.as_ref().map(|s| Type::from(s)),
});
}
Ok(typedef)
}
fn build_enum(stmt: &EnumStatement) -> Result<EnumDefinition> {
let mut enumdef = EnumDefinition {
position: stmt.position.clone(),
name: stmt.name.clone(),
values: Vec::new(),
};
let mut last = -1 as i32;
for field in &stmt.values {
let value = if let Some(value) = field.value {
if value > std::i32::MAX as i64 {
return Err(IRError::new("Enum value too large", field).into());
}
let value = value as i32;
if value <= last {
return Err(IRError::new("Enum values must be increasing", field).into());
}
last = value;
value
} else {
last = last + 1;
last
};
enumdef.values.push(EnumField {
name: field.name.clone(),
value,
});
}
Ok(enumdef)
}
fn build_service(stmt: &ServiceStatement) -> Result<ServiceDefinition> {
let mut servdef = ServiceDefinition {
position: stmt.position.clone(),
name: stmt.name.clone(),
depends: HashSet::new(),
methods: Vec::new(),
};
for method in &stmt.methods {
let mut methoddef = Method {
name: method.name.clone(),
inputs: Vec::new(),
output: method.return_type.as_ref().map(|rt| {
let typ = Type::from(&rt.fieldtype);
if typ != Type::Void {
servdef.depends.insert(typ.clone());
}
MethodOutput {
typ,
array: rt.array,
}
}),
decorators: MethodDecorators {
description: None,
parameter_descriptions: HashMap::new(),
return_description: None,
},
};
let mut optional_starts = false;
for inp in &method.inputs {
let typ = Type::from(&inp.fieldtype);
servdef.depends.insert(typ.clone());
if optional_starts && !inp.optional {
return Err(
IRError::new("Optional fields must come after required fields", inp).into(),
);
}
if inp.optional {
optional_starts = true;
}
methoddef.inputs.push(MethodInput {
name: inp.name.clone(),
typ,
array: inp.array,
optional: inp.optional,
});
}
for decorator in &method.decorators {
match decorator.name.as_str() {
"Description" => {
if methoddef.decorators.description.is_some() {
return Err(IRError::new("Duplicate description", decorator).into());
}
if decorator.args.len() != 1 {
return Err(IRError::new(
"Description must have exactly one argument",
decorator,
)
.into());
}
methoddef.decorators.description = Some(decorator.args[0].clone());
}
"Returns" => {
if methoddef.decorators.return_description.is_some() {
return Err(IRError::new("Duplicate return description", decorator).into());
}
if decorator.args.len() != 1 {
return Err(IRError::new(
"Returns must have exactly one argument",
decorator,
)
.into());
}
methoddef.decorators.return_description = Some(decorator.args[0].clone());
}
"Param" => {
if decorator.args.len() != 2 {
return Err(IRError::new(
"Param must have exactly two arguments",
decorator,
)
.into());
}
let name = decorator.args[0].clone();
let description = decorator.args[1].clone();
if methoddef
.decorators
.parameter_descriptions
.contains_key(&name)
{
return Err(
IRError::new("Duplicate parameter description", decorator).into()
);
}
if methoddef.inputs.iter().find(|i| i.name == name).is_none() {
return Err(IRError::new("Parameter not found", decorator).into());
}
methoddef
.decorators
.parameter_descriptions
.insert(name, description);
}
_ => {
return Err(IRError::new("Unknown decorator", decorator).into());
}
}
}
servdef.methods.push(methoddef);
}
Ok(servdef)
}
pub fn build_ir(root: &Vec<RootNode>) -> Result<IR> {
let mut options = HashMap::<String, String>::new();
let mut steps = Vec::new();
for node in root {
match node {
RootNode::Type(stmt) => steps.push(Step::Type(build_type(stmt)?)),
RootNode::Enum(stmt) => steps.push(Step::Enum(build_enum(stmt)?)),
RootNode::Service(stmt) => steps.push(Step::Service(build_service(stmt)?)),
RootNode::Define(stmt) => {
if options.contains_key(&stmt.key) {
return Err(IRError::new("Duplicate define", stmt).into());
}
if (stmt.key == "use_messagepack" || stmt.key == "allow_bytes")
&& stmt.value == "true"
{
options.insert("allow_bytes".to_owned(), "true".to_owned());
}
options.insert(stmt.key.clone(), stmt.value.clone());
}
RootNode::Import(_) => {
panic!("Import not supported at this stage!");
}
}
}
let mut all_types = HashSet::<String>::new();
let mut serv_types = HashSet::<String>::new();
for bi in &BUILT_INS {
all_types.insert(bi.to_string());
}
for step in &steps {
match step {
Step::Type(typedef) => {
if all_types.contains(&typedef.name) {
return Err(IRError::new_from_def("Duplicate type", typedef).into());
}
all_types.insert(typedef.name.clone());
}
Step::Enum(enumdef) => {
if all_types.contains(&enumdef.name) {
return Err(IRError::new_from_def("Duplicate type", enumdef).into());
}
all_types.insert(enumdef.name.clone());
}
Step::Service(servdef) => {
if serv_types.contains(&servdef.name) {
return Err(IRError::new_from_def("Duplicate type", servdef).into());
}
serv_types.insert(servdef.name.clone());
}
}
}
// Verify dependencies
for step in &steps {
match step {
Step::Type(typedef) => {
for dep in &typedef.depends {
if let Type::Custom(dep) = dep {
if !all_types.contains(dep) {
return Err(IRError::new_from_def(
&format!("Type {} depends on unknown type {}", typedef.name, dep),
typedef,
)
.into());
}
}
}
}
Step::Service(servdef) => {
for dep in &servdef.depends {
if let Type::Custom(dep) = dep {
if !all_types.contains(dep) {
return Err(IRError::new_from_def(
&format!(
"Service {} depends on unknown type {}",
servdef.name, dep
),
servdef,
)
.into());
}
}
}
}
_ => {}
}
}
Ok(IR { options, steps })
}
#[derive(Debug, Clone)]
pub struct IRError {
pub message: String,
pub position: ParserPosition,
}
impl IRError {
fn new(msg: &str, node: &impl Node) -> IRError {
IRError {
message: format!("{}: {}", msg, node.get_name()),
position: node.get_position(),
}
}
fn new_from_def(msg: &str, def: &impl Definition) -> IRError {
IRError {
message: msg.to_string(),
position: def.get_position(),
}
}
}
impl Error for IRError {}
impl Display for IRError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "ParserError: {} at {:?}", self.message, self.position)
}
}

43
libjrpc/src/lib.rs Normal file
View File

@ -0,0 +1,43 @@
mod compile;
mod ir;
mod parser;
mod process;
mod shared;
pub 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 crate::{
compile::{Compile, CompileContext},
targets::{self, rust::RustCompiler},
};
#[cfg(test)]
#[ctor::ctor]
fn init() {
env_logger::init();
}
#[test]
pub fn parse_jrpc() {
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();
println!("{:?}", ir);
}
#[test]
pub fn generate_rust() {
let mut fp = crate::process::FileProcessor::new();
let ir = fp.start_compile("./test.jrpc").unwrap();
targets::compile::<RustCompiler>(ir, "./output/rust").unwrap();
}
}

683
libjrpc/src/parser.rs Normal file
View File

@ -0,0 +1,683 @@
use anyhow::Result;
use log::{debug, trace};
use std::{collections::HashMap, error::Error, fmt::Display, sync::Arc};
use crate::{Token, TokenPosition, TokenType};
pub type PResult<T> = Result<T, ParserError>;
#[derive(Debug, Clone)]
pub struct ParserPosition {
path: Arc<String>,
position: usize,
token_positions: Vec<TokenPosition>,
}
impl ParserPosition {
pub fn from_token(token: &Token) -> Self {
Self {
path: token.2.path.clone(),
position: token.2.start,
token_positions: vec![token.2.clone()],
}
}
}
pub trait Node {
fn get_position(&self) -> ParserPosition;
fn get_name(&self) -> String {
String::from("")
}
}
#[derive(Debug, Clone)]
pub enum RootNode {
Define(DefineStatement),
Import(ImportStatement),
Service(ServiceStatement),
Type(TypeStatement),
Enum(EnumStatement),
}
impl Node for RootNode {
fn get_position(&self) -> ParserPosition {
match self {
RootNode::Define(stmt) => stmt.get_position(),
RootNode::Import(stmt) => stmt.get_position(),
RootNode::Service(stmt) => stmt.get_position(),
RootNode::Type(stmt) => stmt.get_position(),
RootNode::Enum(stmt) => stmt.get_position(),
}
}
fn get_name(&self) -> String {
match self {
RootNode::Define(_) => String::from("define"),
RootNode::Import(_) => String::from("import"),
RootNode::Service(_) => String::from("service"),
RootNode::Type(_) => String::from("type"),
RootNode::Enum(_) => String::from("enum"),
}
}
}
#[derive(Debug, Clone)]
pub struct DefineStatement {
pub position: ParserPosition,
pub key: String,
pub value: String,
}
impl Node for DefineStatement {
fn get_position(&self) -> ParserPosition {
self.position.clone()
}
fn get_name(&self) -> String {
format!("define {}", self.key)
}
}
#[derive(Debug, Clone)]
pub struct ImportStatement {
pub position: ParserPosition,
pub path: String,
}
impl Node for ImportStatement {
fn get_position(&self) -> ParserPosition {
self.position.clone()
}
fn get_name(&self) -> String {
format!("import {}", self.path)
}
}
#[derive(Debug, Clone)]
pub struct TypeStatement {
pub position: ParserPosition,
pub name: String,
pub fields: Vec<TypeFieldNode>,
}
impl Node for TypeStatement {
fn get_position(&self) -> ParserPosition {
self.position.clone()
}
fn get_name(&self) -> String {
format!("type {}", self.name)
}
}
#[derive(Debug, Clone)]
pub struct TypeFieldNode {
pub position: ParserPosition,
pub name: String,
pub optional: bool,
pub fieldtype: String,
pub array: bool,
pub map: Option<String>,
}
impl Node for TypeFieldNode {
fn get_position(&self) -> ParserPosition {
self.position.clone()
}
fn get_name(&self) -> String {
format!("type_field {}", self.name)
}
}
#[derive(Debug, Clone)]
pub struct EnumStatement {
pub position: ParserPosition,
pub name: String,
pub values: Vec<EnumValueNode>,
}
impl Node for EnumStatement {
fn get_position(&self) -> ParserPosition {
self.position.clone()
}
fn get_name(&self) -> String {
format!("enum {}", self.name)
}
}
#[derive(Debug, Clone)]
pub struct EnumValueNode {
pub position: ParserPosition,
pub name: String,
pub value: Option<i64>,
}
impl Node for EnumValueNode {
fn get_position(&self) -> ParserPosition {
self.position.clone()
}
fn get_name(&self) -> String {
format!("enum_field {}", self.name)
}
}
#[derive(Debug, Clone)]
pub struct ServiceStatement {
pub position: ParserPosition,
pub name: String,
pub methods: Vec<ServiceMethodNode>,
}
impl Node for ServiceStatement {
fn get_position(&self) -> ParserPosition {
self.position.clone()
}
fn get_name(&self) -> String {
format!("service {}", self.name)
}
}
#[derive(Debug, Clone)]
pub struct ServiceMethodNode {
pub position: ParserPosition,
pub name: String,
pub inputs: Vec<ServiceMethodInputNode>,
pub return_type: Option<ServiceMethodReturnNode>,
pub decorators: Vec<Decorator>,
}
impl Node for ServiceMethodNode {
fn get_position(&self) -> ParserPosition {
self.position.clone()
}
fn get_name(&self) -> String {
format!("method {}", self.name)
}
}
#[derive(Debug, Clone)]
pub struct Decorator {
pub position: ParserPosition,
pub name: String,
pub args: Vec<String>,
}
impl Node for Decorator {
fn get_position(&self) -> ParserPosition {
self.position.clone()
}
fn get_name(&self) -> String {
format!("decorator {}", self.name)
}
}
#[derive(Debug, Clone)]
pub struct ServiceMethodInputNode {
pub position: ParserPosition,
pub name: String,
pub fieldtype: String,
pub optional: bool,
pub array: bool,
}
impl Node for ServiceMethodInputNode {
fn get_position(&self) -> ParserPosition {
self.position.clone()
}
fn get_name(&self) -> String {
format!("method_arg {}", self.name)
}
}
#[derive(Debug, Clone)]
pub struct ServiceMethodReturnNode {
pub position: ParserPosition,
pub fieldtype: String,
pub array: bool,
}
impl Node for ServiceMethodReturnNode {
fn get_position(&self) -> ParserPosition {
self.position.clone()
}
}
#[derive(Debug, Clone)]
pub struct Parser {
tokens: Vec<Token>,
position: usize,
}
impl Parser {
pub fn new(tokens: Vec<Token>) -> Parser {
// Remove all comments and spaces at this stage
let tokens = tokens
.into_iter()
.filter(|e| e.0 != TokenType::Comment && e.0 != TokenType::Space)
.collect();
Parser {
tokens,
position: 0,
}
}
fn has_current_token(&self) -> bool {
self.position < self.tokens.len()
}
fn current_token(&mut self) -> &Token {
&self.tokens[self.position]
}
fn assert_types(&mut self, token_types: &[TokenType]) -> PResult<()> {
if token_types.iter().any(|t| *t == self.current_token().0) {
Ok(())
} else {
Err(ParserError::new(
&format!("Unexpected token. Expected on of {:?}", token_types),
self.current_token(),
))
}
}
fn eat_token(&mut self) -> PResult<(String, ParserPosition)> {
debug!("Parser::eat_token()");
let pos = ParserPosition::from_token(self.current_token());
let value = self.current_token().1.clone();
self.position += 1;
Ok((value, pos))
}
fn eat_token_value(&mut self, value: &str) -> PResult<(String, ParserPosition)> {
debug!("Parser::eat_token_value({})", value);
let (val, pos) = self.eat_token()?;
if val != value {
return Err(ParserError::new(
&format!("Expected token of value {}", value),
self.current_token(),
));
}
Ok((val, pos))
}
fn eat_token_type(&mut self, token_type: TokenType) -> PResult<(String, ParserPosition)> {
debug!("Parser::eat_token_type({:?})", token_type);
if self.current_token().0 != token_type {
return Err(ParserError::new(
&format!("Expected token of type {:?}", token_type),
self.current_token(),
));
}
self.eat_token()
}
fn eat_text(&mut self) -> PResult<(String, ParserPosition)> {
debug!("Parser::eat_text()");
self.eat_token_type(TokenType::Text)
}
fn eat_text_with_keywords(&mut self) -> PResult<(String, ParserPosition)> {
debug!("Parser::eat_text_with_keywords()");
self.assert_types(&[TokenType::Text, TokenType::Keyword])?;
self.eat_token()
}
fn eat_string_or_text_as_raw(&mut self) -> PResult<String> {
debug!("Parser::eat_string_or_text_as_raw()");
self.assert_types(&[TokenType::String, TokenType::Text])?;
let token = self.current_token();
if token.0 == TokenType::String {
let (value, _) = self.eat_token()?;
let count = value.chars().count();
Ok(value.chars().skip(1).take(count - 2).collect())
} else {
let (value, _) = self.eat_token()?;
Ok(value)
}
}
fn eat_number(&mut self) -> PResult<i64> {
debug!("Parser::eat_number()");
self.assert_types(&[TokenType::Number])?;
let (value, _) = self.eat_token()?;
value
.parse()
.map_err(|_| ParserError::new("Invalid number", self.current_token()))
}
fn parse_define(&mut self) -> PResult<DefineStatement> {
debug!("Parser::parse_define()");
let (_, position) = self.eat_token_value("define")?;
let (key, _) = self.eat_token()?;
trace!("Parser::parse_define()::key = {}", key);
let value = self.eat_string_or_text_as_raw()?;
self.eat_token_type(TokenType::Semicolon)?;
Ok(DefineStatement {
position,
key,
value,
})
}
fn parse_import(&mut self) -> PResult<ImportStatement> {
debug!("Parser::parse_import()");
let (_, pos) = self.eat_token_value("import")?;
let path = self.eat_string_or_text_as_raw()?;
self.eat_token_type(TokenType::Semicolon)?;
Ok(ImportStatement {
position: pos,
path,
})
}
fn parse_enum_value(&mut self) -> PResult<EnumValueNode> {
debug!("Parser::parse_enum_value()");
let (name, pos) = self.eat_token()?;
let value = if self.current_token().0 == TokenType::Equals {
self.eat_token()?;
Some(self.eat_number()?)
} else {
None
};
Ok(EnumValueNode {
position: pos,
name,
value,
})
}
fn parse_enum(&mut self) -> PResult<EnumStatement> {
debug!("Parser::parse_enum()");
let (_, pos) = self.eat_token_value("enum")?;
let (name, _) = self.eat_token()?;
trace!("Parser::parse_enum()::name = {}", name);
self.eat_token_type(TokenType::CurlyOpen)?;
let mut values: Vec<EnumValueNode> = Vec::new();
while self.current_token().0 == TokenType::Text {
values.push(self.parse_enum_value()?);
if self.current_token().0 == TokenType::Comma {
//TODO: Maybe use a next flag or break or something with the commas
self.eat_token()?;
}
}
self.eat_token_type(TokenType::CurlyClose)?;
Ok(EnumStatement {
position: pos,
name,
values,
})
}
fn parse_type_field(&mut self) -> PResult<TypeFieldNode> {
debug!("Parser::parse_type_field()");
let (name, pos) = self.eat_text_with_keywords()?;
trace!("Parser::parse_type_field()::name = {}", name);
let mut optional = false;
let mut array = false;
let mut map = None;
if self.current_token().0 == TokenType::Questionmark {
optional = true;
self.eat_token()?;
}
_ = self.eat_token_type(TokenType::Colon);
let fieldtype = if self.current_token().0 == TokenType::CurlyOpen {
self.eat_token()?;
let (key_type, _) = self.eat_text()?;
self.eat_token_type(TokenType::Comma)?;
let (value_type, _) = self.eat_text()?;
self.eat_token_type(TokenType::CurlyClose)?;
map = Some(key_type);
value_type
} else {
let (type_name, _) = self.eat_text()?;
if self.current_token().0 == TokenType::Array {
array = true;
self.eat_token()?;
}
type_name
};
self.eat_token_type(TokenType::Semicolon)?;
Ok(TypeFieldNode {
position: pos,
name,
optional,
fieldtype,
map,
array,
})
}
fn parse_type(&mut self) -> PResult<TypeStatement> {
debug!("Parser::parse_type()");
let (_, pos) = self.eat_token_value("type")?;
let (name, _) = self.eat_text()?;
trace!("Parser::prase_type()::name = {}", name);
self.eat_token_type(TokenType::CurlyOpen)?;
let mut fields = Vec::new();
while self.current_token().0 == TokenType::Text
|| self.current_token().0 == TokenType::Keyword
{
fields.push(self.parse_type_field()?);
}
self.eat_token_type(TokenType::CurlyClose)?;
Ok(TypeStatement {
position: pos,
name,
fields,
})
}
fn parse_decorator(&mut self) -> PResult<Decorator> {
debug!("Parser::parse_decorator()");
let (_, position) = self.eat_token_type(TokenType::At)?;
let (decorator, _) = self.eat_text()?;
trace!("Parser::parse_decorator()::name = {}", decorator);
self.eat_token_type(TokenType::BracketOpen)?;
let mut args = Vec::new();
let mut first = true;
while self.current_token().0 != TokenType::BracketClose {
if first {
first = false
} else {
self.eat_token_type(TokenType::Comma)?;
}
args.push(self.eat_string_or_text_as_raw()?);
}
self.eat_token_type(TokenType::BracketClose)?;
Ok(Decorator {
name: decorator,
args,
position,
})
}
fn parse_method(
&mut self,
decorators: Vec<Decorator>,
notification: bool,
) -> PResult<ServiceMethodNode> {
debug!(
"Parser::parse_method({}, {})",
decorators.len(),
notification
);
let (name, pos) = self.eat_text()?;
trace!("Parser::parse_method()::name = {}", name);
self.eat_token_type(TokenType::BracketOpen)?;
let mut inputs = Vec::new();
while self.current_token().0 != TokenType::BracketClose {
let (name, position) = self.eat_text_with_keywords()?;
let mut optional = false;
if self.current_token().0 == TokenType::Questionmark {
optional = true;
self.eat_token()?;
}
self.eat_token_type(TokenType::Colon)?;
let (fieldtype, _) = self.eat_text()?;
let mut array = false;
if self.current_token().0 == TokenType::Array {
array = true;
self.eat_token()?;
}
inputs.push(ServiceMethodInputNode {
name,
fieldtype,
array,
optional,
position,
});
if self.current_token().0 == TokenType::Comma {
self.eat_token()?;
}
trace!(
"Parser::parse_method()::params_next_token: {}",
self.current_token().1
);
}
self.eat_token_type(TokenType::BracketClose)?;
let mut return_type = None;
if !notification {
self.eat_token_type(TokenType::Colon)?;
let (fieldtype, position) = self.eat_text()?;
let mut array = false;
if self.current_token().0 == TokenType::Array {
array = true;
self.eat_token()?;
}
return_type = Some(ServiceMethodReturnNode {
position,
fieldtype,
array,
});
}
self.eat_token_type(TokenType::Semicolon)?;
Ok(ServiceMethodNode {
position: pos,
name,
inputs,
return_type,
decorators,
})
}
fn parse_service(&mut self) -> PResult<ServiceStatement> {
debug!("Parser::parse_service()");
let (_, pos) = self.eat_token_value("service")?;
let (name, _) = self.eat_text()?;
trace!("Parser::parse_service()::name = {}", name);
self.eat_token_type(TokenType::CurlyOpen)?;
let mut methods = Vec::new();
while self.current_token().0 != TokenType::CurlyClose {
let mut decorators = Vec::new();
while self.current_token().0 == TokenType::At {
decorators.push(self.parse_decorator()?);
}
let mut notification = false;
if self.current_token().1 == "notification" {
self.eat_token()?;
notification = true;
}
methods.push(self.parse_method(decorators, notification)?);
}
self.eat_token_type(TokenType::CurlyClose)?;
Ok(ServiceStatement {
position: pos,
name,
methods,
})
}
fn parse_statement(&mut self) -> PResult<RootNode> {
debug!("Parser::parse_statement()");
let token = self.current_token();
if let TokenType::Keyword = token.0 {
trace!("Parser::parse_statement()::type = {}", token.1);
return match token.1.as_str() {
"define" => Ok(RootNode::Define(self.parse_define()?)),
"import" => Ok(RootNode::Import(self.parse_import()?)),
"type" => Ok(RootNode::Type(self.parse_type()?)),
"service" => Ok(RootNode::Service(self.parse_service()?)),
"enum" => Ok(RootNode::Enum(self.parse_enum()?)),
_ => Err(ParserError::new("Unknown keyword", token)),
};
} else {
Err(ParserError::new("Expected keyword", token))
}
}
pub fn parse(&mut self) -> PResult<Vec<RootNode>> {
debug!("Parser::parse()");
let mut nodes = Vec::new();
while self.has_current_token() {
nodes.push(self.parse_statement()?);
}
Ok(nodes)
}
}
#[derive(Debug, Clone)]
pub struct ParserError {
pub message: String,
pub token: Token,
}
impl ParserError {
fn new(msg: &str, token: &Token) -> ParserError {
ParserError {
message: format!("{}: {}", msg, token.1),
token: token.clone(),
}
}
}
impl Error for ParserError {}
impl Display for ParserError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "ParserError: {} at {:?}", self.message, self.token)
}
}

135
libjrpc/src/process.rs Normal file
View File

@ -0,0 +1,135 @@
use std::{
collections::{HashMap, HashSet},
path::PathBuf,
sync::Arc,
};
use anyhow::Result;
use log::trace;
use url::Url;
use crate::{ir::IR, parser::RootNode};
pub struct FileProcessor {
file_cache: HashMap<String, String>,
processed_files: HashSet<String>,
}
impl FileProcessor {
pub fn new() -> Self {
trace!("FileProcessor::new()");
Self {
file_cache: HashMap::new(),
processed_files: HashSet::new(),
}
}
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://") {
return true;
}
}
false
}
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 url = Url::parse(context.unwrap()).unwrap();
if !input.ends_with(".jrpc") {
input = format!("{}.jrpc", input);
}
url.join(&input).unwrap().to_string()
}
} else {
if !input.ends_with(".jrpc") {
input = format!("{}.jrpc", input);
}
if let Some(context) = context {
let mut path = PathBuf::from(context);
path.pop();
path = path.join(input);
path.to_str().unwrap().to_string()
} else {
input
}
}
}
fn get_file_url(&mut self, url: &str) -> Result<String> {
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());
Ok(body)
}
fn get_file_path(&mut self, path: &str) -> Result<String> {
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<String> {
trace!("FileProcessor::get_file({})", name);
if self.file_cache.contains_key(name) {
return Ok(self.file_cache.get(name).unwrap().clone());
} else {
#[cfg(feature = "http")]
{
if name.starts_with("http://") || name.starts_with("https://") {
return self.get_file_url(name);
} else {
return self.get_file_path(name);
}
}
#[cfg(not(feature = "http"))]
{
return self.get_file_path(name);
}
}
}
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> {
trace!("FileProcessor::start_compile({})", file);
let parsed = self.process_file(file, true)?;
let ir = crate::ir::build_ir(&parsed)?;
Ok(ir)
}
}

28
libjrpc/src/shared.rs Normal file
View File

@ -0,0 +1,28 @@
pub enum Keywords {
Type,
Enum,
Import,
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(),
}
}
}

View File

@ -0,0 +1,28 @@
use anyhow::Result;
use crate::{
compile::{Compile, CompileContext},
IR,
};
pub mod rust;
pub fn compile<T: Compile>(ir: IR, output: &str) -> Result<()> {
let mut ctx = CompileContext::new(output);
let mut compiler = T::new(&ir.options)?;
compiler.start(&mut ctx)?;
for step in ir.steps.iter() {
match step {
crate::ir::Step::Type(definition) => compiler.generate_type(&mut ctx, &definition)?,
crate::ir::Step::Enum(definition) => compiler.generate_enum(&mut ctx, &definition)?,
crate::ir::Step::Service(definition) => {
compiler.generate_service(&mut ctx, &definition)?
}
}
}
compiler.finalize(&mut ctx, &ir)?;
Ok(())
}

568
libjrpc/src/targets/rust.rs Normal file
View File

@ -0,0 +1,568 @@
use anyhow::Result;
use log::warn;
use std::collections::{HashMap, HashSet};
use crate::compile::{Compile, CompileContext, FileGenerator};
use crate::ir::{EnumDefinition, ServiceDefinition, Step, Type, TypeDefinition};
use crate::shared::Keywords;
use crate::IR;
pub struct RustCompiler {
crate_name: String,
}
static RUST_KEYWORDS: [&'static str; 6] = ["type", "return", "static", "pub", "enum", "self"];
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::Void => "()".to_string(),
Type::Custom(name) => name.clone(),
}
}
fn type_to_rust_ext(typ: &Type, optional: bool, array: bool) -> String {
let mut result = Self::type_to_rust(typ);
if optional {
result = format!("Option<{}>", result);
}
if array {
result = format!("Vec<{}>", result);
}
result
}
fn add_dependencies(
&mut self,
file: &mut FileGenerator,
depends: &HashSet<Type>,
) -> Result<()> {
for dep in depends {
match dep {
Type::Custom(name) => {
file.a0(&format!("use crate::{};", name));
}
_ => {}
}
}
file.a0("");
file.a0("");
Ok(())
}
fn fix_keyword_name(name: &str) -> String {
if RUST_KEYWORDS.contains(&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
}
fn generate_service_lib(ctx: &CompileContext, ir: &IR) -> Result<()> {
let mut f = FileGenerator::new();
let mut fc = FileGenerator::new();
let mut fs = FileGenerator::new();
f.a0("pub mod base_lib;");
f.a0("pub use base_lib::{JRPCServer, JRPCClient, Result};");
for step in ir.steps.iter() {
match step {
Step::Type(def) => {
f.a0(format!("mod {};", Self::to_snake(&def.name)));
f.a(
0,
format!("pub use {}::{};", Self::to_snake(&def.name), def.name),
);
}
Step::Enum(def) => {
f.a0(format!("mod {};", Self::to_snake(&def.name)));
f.a(
0,
format!("pub use {}::{};", Self::to_snake(&def.name), def.name),
);
}
Step::Service(def) => {
fs.a0(format!("mod {};", Self::to_snake(&def.name)));
fs.a0(format!(
"pub use {}::{{ {}, {}Handler }};",
Self::to_snake(&def.name),
def.name,
def.name
));
fc.a0(format!("mod {};", Self::to_snake(&def.name)));
fc.a0(format!(
"pub use {}::{};",
Self::to_snake(&def.name),
def.name,
));
}
}
}
f.a0("pub mod server;");
f.a0("pub mod client;");
ctx.write_file("src/lib.rs", f.get_content())?;
ctx.write_file("src/server/mod.rs", fs.get_content())?;
ctx.write_file("src/client/mod.rs", fc.get_content())?;
Ok(())
}
fn generate_service_server(
&mut self,
ctx: &mut CompileContext,
definition: &ServiceDefinition,
) -> anyhow::Result<()> {
let mut f = FileGenerator::new();
self.add_dependencies(&mut f, &definition.depends)?;
f.a0("use crate::base_lib::{JRPCServerService, JRPCRequest, Result};");
f.a0("use serde_json::Value;");
f.a0("use std::sync::Arc;");
f.a0("use async_trait::async_trait;");
f.a0("#[async_trait]");
f.a0(format!("pub trait {} {{", definition.name));
for method in definition.methods.iter() {
let params = method
.inputs
.iter()
.map(|arg| {
format!(
"{}: {}",
Self::fix_keyword_name(&arg.name),
Self::type_to_rust_ext(&arg.typ, arg.optional, arg.array)
)
})
.collect::<Vec<String>>()
.join(", ");
let ret = method.output.as_ref().map_or_else(
|| "()".to_owned(),
|r| Self::type_to_rust_ext(&r.typ, false, r.array),
);
f.a1("#[allow(non_snake_case)]");
f.a(
1,
format!(
"async fn {}(&self, {}) -> Result<{}>;",
Self::fix_keyword_name(&method.name),
params,
ret
),
);
}
f.a0("}");
f.a0("");
f.a0(format!("pub struct {}Handler {{", definition.name));
f.a1(format!(
"implementation: Box<dyn {} + Sync + Send + 'static>,",
definition.name
));
f.a0("}");
f.a0("");
f.a0(format!("impl {}Handler {{", definition.name));
f.a1(format!(
"pub fn new(implementation: Box<dyn {} + Sync + Send + 'static>) -> Arc<Self> {{",
definition.name,
));
f.a2("Arc::from(Self { implementation })");
f.a1("}");
f.a0("}");
f.a0("");
f.a0("#[async_trait]");
f.a0(format!(
"impl JRPCServerService for {}Handler {{",
definition.name
));
f.a1(format!(
"fn get_id(&self) -> String {{ \"{}\".to_owned() }} ",
definition.name
));
f.a1("");
f.a1("#[allow(non_snake_case)]");
f.a1(
"async fn handle(&self, msg: &JRPCRequest, function: &str) -> Result<(bool, Value)> {",
);
f.a2("match function {");
// TODO: Implement optional arguments!
for method in &definition.methods {
f.a3(format!(
"\"{}\" => {{",
Self::fix_keyword_name(&method.name)
));
if method.inputs.len() < 1 {
f.a5(format!(
"let res = self.implementation.{}().await?;",
method.name
));
f.a5("Ok((true, serde_json::to_value(res)?))");
} else {
f.a4("if msg.params.is_array() {");
if method.inputs.len() > 0 {
f.a5(
"let arr = msg.params.as_array().unwrap(); //TODO: Check if this can fail.",
);
}
f.a5(format!("let res = self.implementation.{}(", method.name));
for (i, arg) in method.inputs.iter().enumerate() {
f.a6(format!("serde_json::from_value(arr[{}].clone())", i));
f.a(
7,
format!(
".map_err(|_| \"Parameter for field '{}' should be of type '{}'!\")?{}",
arg.name,
arg.typ.to_string(),
if i < method.inputs.len() - 1 { "," } else { "" }
),
);
}
f.a5(").await?;");
if let Some(_output) = &method.output {
f.a5("Ok((true, serde_json::to_value(res)?))");
} else {
f.a5("_ = res;");
f.a5("Ok((true, Value::Null))");
}
f.a4("} else if msg.params.is_object() {");
f.a5("let obj = msg.params.as_object().unwrap(); //TODO: Check if this can fail.");
f.a5(format!("let res = self.implementation.{}(", method.name));
for (i, arg) in method.inputs.iter().enumerate() {
f.a6(format!(
"serde_json::from_value(obj.get(\"{}\").ok_or(\"Parameter of field '{}' missing!\")?.clone())",
arg.name,
arg.name
));
f.a(
7,
format!(
".map_err(|_| \"Parameter for field {} should be of type '{}'!\")?{}",
arg.name,
arg.typ.to_string(),
if i < method.inputs.len() - 1 { "," } else { "" }
),
);
}
f.a5(").await?;");
if let Some(_output) = &method.output {
f.a5("Ok((true, serde_json::to_value(res)?))");
} else {
f.a5("Ok((false, Value::Null))");
}
f.a4("} else {");
f.a5("Err(Box::from(\"Invalid parameters??\".to_owned()))");
f.a4("}");
}
f.a3("}");
}
f.a3("_ => { Err(Box::from(format!(\"Invalid function {}\", function).to_owned())) },");
f.a2("}");
f.a1("}");
f.a0("}");
ctx.write_file(
&format!("src/server/{}.rs", Self::to_snake(&definition.name)),
f.into_content(),
)?;
Ok(())
}
fn generate_service_client(
&mut self,
ctx: &mut CompileContext,
definition: &ServiceDefinition,
) -> anyhow::Result<()> {
let mut f = FileGenerator::new();
self.add_dependencies(&mut f, &definition.depends)?;
f.a0("use crate::base_lib::{JRPCClient, JRPCRequest, Result};");
f.a0("use serde_json::json;");
f.a0("");
f.a0(format!("pub struct {} {{", definition.name));
f.a1("client: JRPCClient,");
f.a0("}");
f.a0("");
f.a0(format!("impl {} {{", definition.name));
f.a1("pub fn new(client: JRPCClient) -> Self {");
f.a2(format!("Self {{ client }}"));
f.a1("}");
f.a0("");
for method in &definition.methods {
let params = method
.inputs
.iter()
.map(|arg| {
format!(
"{}: {}",
Self::fix_keyword_name(&arg.name),
Self::type_to_rust_ext(&arg.typ, arg.optional, arg.array)
)
})
.collect::<Vec<String>>()
.join(", ");
let ret = method.output.as_ref().map_or("()".to_string(), |output| {
Self::type_to_rust_ext(&output.typ, false, output.array)
});
f.a1("#[allow(non_snake_case)]");
f.a1(format!(
"pub async fn {}(&self, {}) -> Result<{}> {{",
method.name, params, ret
));
f.a2("let l_req = JRPCRequest {");
f.a3("jsonrpc: \"2.0\".to_owned(),");
f.a3("id: None, // 'id' will be set by the send_request function");
f.a3(format!(
"method: \"{}.{}\".to_owned(),",
definition.name, method.name
));
f.a3(format!(
"params: json!([{}])",
method
.inputs
.iter()
.map(|e| Self::fix_keyword_name(&e.name))
.collect::<Vec<String>>()
.join(", ")
));
f.a2("};");
if let Some(output) = &method.output {
f.a2("let l_res = self.client.send_request(l_req).await;");
f.a2("match l_res {");
f.a3("Err(e) => Err(e),");
if output.typ == Type::Void {
f.a3("Ok(_) => Ok(())");
} else {
f.a3("Ok(o) => serde_json::from_value(o).map_err(|e| Box::from(e))");
}
f.a2("}");
} else {
f.a2("self.client.send_notification(l_req).await;");
f.a2("Ok(())");
}
f.a1("}");
}
f.a0("}");
ctx.write_file(
&format!("src/client/{}.rs", Self::to_snake(&definition.name)),
f.into_content(),
)?;
Ok(())
}
}
impl Compile for RustCompiler {
fn new(options: &HashMap<String, String>) -> anyhow::Result<Self> {
let crate_name = if let Some(crate_name) = options.get("rust_crate") {
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");
}
}
Ok(RustCompiler { crate_name })
}
fn name(&self) -> String {
"rust".to_string()
}
fn start(&mut self, ctx: &mut CompileContext) -> anyhow::Result<()> {
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(),
)?;
Ok(())
}
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.a0("use std::collections::hash_map::HashMap;")
}
f.a0("use serde::{Deserialize, Serialize};");
self.add_dependencies(&mut f, &definition.depends)?;
f.a0("#[derive(Clone, Debug, Serialize, Deserialize)]");
f.a0(format!("pub struct {} {{", definition.name));
for field in definition.fields.iter() {
f.a(1, "#[allow(non_snake_case)]");
let 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, format!("#[serde(rename = \"{}\")]", field.name));
}
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.a0("}");
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<()> {
let mut f = FileGenerator::new();
f.a0("use int_enum::IntEnum;");
// f.a0("use serde::{Deserialize, Serialize};");
f.a0("");
f.a0("");
f.a0("#[repr(i64)]");
f.a0("#[derive(Clone, Copy, Debug, Eq, PartialEq, IntEnum)]");
f.a0(format!("pub enum {} {{", definition.name));
for val in definition.values.iter() {
f.a(1, format!("{} = {},", val.name, val.value));
}
f.a0("}");
ctx.write_file(
&format!("src/{}.rs", Self::to_snake(&definition.name)),
f.into_content(),
)?;
Ok(())
}
fn generate_service(
&mut self,
ctx: &mut CompileContext,
definition: &ServiceDefinition,
) -> anyhow::Result<()> {
self.generate_service_client(ctx, definition)?;
self.generate_service_server(ctx, definition)?;
Ok(())
}
fn finalize(&mut self, ctx: &mut CompileContext, ir: &IR) -> anyhow::Result<()> {
Self::generate_service_lib(ctx, ir)?;
Ok(())
}
}

197
libjrpc/src/tokenizer.rs Normal file
View File

@ -0,0 +1,197 @@
use anyhow::Result;
use lazy_static::lazy_static;
use regex::Regex;
use std::{error::Error, fmt::Display, sync::Arc};
fn match_regex(regex: &Regex, input: &str, index: usize) -> Option<String> {
// println!("Matching regex {:?} at index {}", regex, index);
let (_, b) = input.split_at(index);
let Some(caps) = regex.captures(b) else {
return None;
};
let capture = caps.get(0).unwrap();
if capture.start() != 0 {
return None;
}
Some(capture.as_str().to_owned())
}
struct TokenizerRegex(Regex, TokenType);
lazy_static! {
static ref REGEXES: Vec<TokenizerRegex> = {
let mut regexes = Vec::new();
regexes.push(TokenizerRegex(
Regex::new(r#"^\s+"#).unwrap(),
TokenType::Space,
));
regexes.push(TokenizerRegex( //FIXME: This regex is not working
Regex::new(r#"(?s)^(\/\*)(.|\s)*?(\*\/)/"#).unwrap(),
TokenType::Comment,
));
regexes.push(TokenizerRegex(
Regex::new(r#"^\/\/.+"#).unwrap(),
TokenType::Comment,
));
regexes.push(TokenizerRegex(
Regex::new(r#"^#.+"#).unwrap(),
TokenType::Comment,
));
regexes.push(TokenizerRegex(
Regex::new(r#"^".*?""#).unwrap(),
TokenType::String,
));
regexes.push(TokenizerRegex(
Regex::new(r#"^(type|enum|import|service|define)\b"#).unwrap(),
TokenType::Keyword,
));
regexes.push(TokenizerRegex(Regex::new(r#"^\@"#).unwrap(), TokenType::At));
regexes.push(TokenizerRegex(
Regex::new(r#"^\:"#).unwrap(),
TokenType::Colon,
));
regexes.push(TokenizerRegex(
Regex::new(r#"^\;"#).unwrap(),
TokenType::Semicolon,
));
regexes.push(TokenizerRegex(
Regex::new(r#"^\,"#).unwrap(),
TokenType::Comma,
));
regexes.push(TokenizerRegex(
Regex::new(r#"^\="#).unwrap(),
TokenType::Equals,
));
regexes.push(TokenizerRegex(
Regex::new(r#"^\{"#).unwrap(),
TokenType::CurlyOpen,
));
regexes.push(TokenizerRegex(
Regex::new(r#"^\}"#).unwrap(),
TokenType::CurlyClose,
));
regexes.push(TokenizerRegex(
Regex::new(r#"^\("#).unwrap(),
TokenType::BracketOpen,
));
regexes.push(TokenizerRegex(
Regex::new(r#"^\)"#).unwrap(),
TokenType::BracketClose,
));
regexes.push(TokenizerRegex(
Regex::new(r#"^\[\]"#).unwrap(),
TokenType::Array,
));
regexes.push(TokenizerRegex(
Regex::new(r#"^\?"#).unwrap(),
TokenType::Questionmark,
));
regexes.push(TokenizerRegex(
Regex::new(r#"^[\.0-9]+"#).unwrap(),
TokenType::Number,
));
regexes.push(TokenizerRegex(
Regex::new(r#"^[a-zA-Z_]([a-zA-Z0-9_]?)+"#).unwrap(),
TokenType::Text,
));
regexes
};
}
#[derive(Debug, Clone)]
pub struct TokenPosition {
pub path: Arc<String>,
pub start: usize,
pub end: usize,
}
impl TokenPosition {
pub fn new(path: Arc<String>, start: usize, end: usize) -> Self {
Self { path, start, end }
}
}
impl Display for TokenPosition {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
//TODO: Map index to line and column
write!(f, "{}:{}:{}", self.path, self.start, self.end)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum TokenType {
Space,
Comment,
String,
Keyword,
At,
Colon,
Semicolon,
Comma,
Equals,
CurlyOpen,
CurlyClose,
BracketOpen,
BracketClose,
Array,
Questionmark,
Number,
Text,
}
#[derive(Debug, Clone)]
pub struct TokenError {
pub message: String,
pub position: TokenPosition,
}
impl Error for TokenError {}
impl Display for TokenError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "TokenError: {} at {:?}", self.message, self.position)
}
}
#[derive(Debug, Clone)]
pub struct Token(pub TokenType, pub String, pub TokenPosition);
pub fn tokenize(path: Arc<String>, input: String) -> Result<Vec<Token>> {
let mut tokens = Vec::new();
let mut index = 0;
while index < input.len() {
let res = REGEXES
.iter()
.map(|regex| (match_regex(&regex.0, &input, index), regex.1))
.find(|x| x.0.is_some());
if let Some(mat) = res {
let value = mat.0.unwrap();
let value_len = value.len();
tokens.push(Token(
mat.1,
value,
TokenPosition::new(path.clone(), index, index + value_len),
));
index += value_len;
} else {
return Err(TokenError {
message: format!(
"Unexpected character: {}",
input.chars().skip(index).take(4).collect::<String>()
),
position: TokenPosition::new(path.clone(), index, index + 1),
}
.into());
}
}
// println!("{:?}", tokens);
Ok(tokens)
}

View 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>

View 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") { }
}

View 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);
}

View 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);
}
}

View 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

View 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!");
}
}

View 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

View 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;
}

View File

@ -0,0 +1,2 @@
[*.rs]
indent_size = 4

2
libjrpc/templates/Rust/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
target
Cargo.lock

View 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"

View 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)
}
}

View 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) { }

View 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;
}

View 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));
};

View 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
}
}
}

75
libjrpc/test.jrpc Normal file
View File

@ -0,0 +1,75 @@
// Test
import "./import";
define csharp_namespace Example;
define rust_crate example;
define dart_library_name example;
enum TestEnum {
VAL1,
VAL2,
VAL10 = 10,
VAL11,
VAL12
}
type Test {
atom: TestAtom;
array: TestAtom[];
enumValue: TestEnum;
map: {int, TestAtom};
}
type AddValueRequest {
value1: float;
value2: float;
}
type AddValueResponse {
value: float;
}
service TestService {
@Description("Add two numbers")
@Param("request", "Parameter containing the two numbers")
@Returns("")
AddValuesSingleParam(request: AddValueRequest): AddValueResponse;
@Description("Add two numbers")
@Param("value1", "The first value")
@Param("value2", "The second value")
AddValuesMultipleParams(value1: float, value2: float): float;
@Description("Does literaly nothing")
@Param("param1", "Some number")
ReturningVoid(param1: float): void;
@Description("Just sends an Event with a String")
@Param("param1", "Parameter with some string for event")
notification OnEvent(param1: string);
ThrowingError(): void;
FunctionWithArrayAsParamAndReturn(values1: float[], values2: float[]): float[];
FunctionWithKeywords(type: float, static: float, event: float): float;
}
type Test2 {
name: string;
age: int;
}
type TestKeywords {
type: float;
static: float;
event: float;
}
service SimpleTestService {
@Description("asdasdasd")
GetTest(name: string, age: int): Test2;
notification TestNot();
}