Some improvements to the type system
This commit is contained in:
@ -67,6 +67,24 @@ impl CompileContext {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn strip_template_ignores(content: &str) -> String {
|
||||||
|
let mut result = String::new();
|
||||||
|
let mut ignore = false;
|
||||||
|
for line in content.lines() {
|
||||||
|
if ignore {
|
||||||
|
ignore = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if line.trim().contains("@template-ignore") {
|
||||||
|
ignore = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
result.push_str(line);
|
||||||
|
result.push_str("\n");
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
pub fn write_file(&self, filename: &str, content: &str) -> Result<()> {
|
pub fn write_file(&self, filename: &str, content: &str) -> Result<()> {
|
||||||
let res_path = self.output_folder.clone().join(filename);
|
let res_path = self.output_folder.clone().join(filename);
|
||||||
let res_dir = res_path.parent().context("Path has no parent!")?;
|
let res_dir = res_path.parent().context("Path has no parent!")?;
|
||||||
|
@ -31,7 +31,7 @@ pub enum Step {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub enum Type {
|
pub enum BaseType {
|
||||||
Int,
|
Int,
|
||||||
Float,
|
Float,
|
||||||
String,
|
String,
|
||||||
@ -41,57 +41,143 @@ pub enum Type {
|
|||||||
Custom(String),
|
Custom(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToString for Type {
|
impl ToString for BaseType {
|
||||||
fn to_string(&self) -> String {
|
fn to_string(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
Type::Int => "int".to_string(),
|
BaseType::Int => "int".to_string(),
|
||||||
Type::Float => "float".to_string(),
|
BaseType::Float => "float".to_string(),
|
||||||
Type::String => "string".to_string(),
|
BaseType::String => "string".to_string(),
|
||||||
Type::Bool => "bool".to_string(),
|
BaseType::Bool => "bool".to_string(),
|
||||||
Type::Bytes => "bytes".to_string(),
|
BaseType::Bytes => "bytes".to_string(),
|
||||||
Type::Void => "void".to_string(),
|
BaseType::Void => "void".to_string(),
|
||||||
Type::Custom(name) => name.clone(),
|
BaseType::Custom(name) => name.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for Type {
|
impl Hash for BaseType {
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
self.to_string().hash(state);
|
self.to_string().hash(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&String> for Type {
|
impl From<&String> for BaseType {
|
||||||
fn from(value: &String) -> Self {
|
fn from(value: &String) -> Self {
|
||||||
Self::from(value.as_str())
|
Self::from(value.as_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<String> for Type {
|
impl From<String> for BaseType {
|
||||||
fn from(value: String) -> Self {
|
fn from(value: String) -> Self {
|
||||||
Self::from(value.as_str())
|
Self::from(value.as_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&str> for Type {
|
impl From<&str> for BaseType {
|
||||||
fn from(s: &str) -> Self {
|
fn from(s: &str) -> Self {
|
||||||
match s {
|
match s {
|
||||||
"int" => Type::Int,
|
"int" => BaseType::Int,
|
||||||
"float" => Type::Float,
|
"float" => BaseType::Float,
|
||||||
"string" => Type::String,
|
"string" => BaseType::String,
|
||||||
"bool" => Type::Bool,
|
"bool" => BaseType::Bool,
|
||||||
"boolean" => Type::Bool,
|
"boolean" => BaseType::Bool,
|
||||||
"bytes" => Type::Bytes,
|
"bytes" => BaseType::Bytes,
|
||||||
"void" => Type::Void,
|
"void" => BaseType::Void,
|
||||||
_ => Type::Custom(s.to_string()),
|
_ => BaseType::Custom(s.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub enum TypeModifier {
|
||||||
|
None,
|
||||||
|
Array,
|
||||||
|
Optional,
|
||||||
|
Map(BaseType),
|
||||||
|
OptionalArray,
|
||||||
|
OptionalMap(BaseType),
|
||||||
|
OptionalMapArray(BaseType),
|
||||||
|
MapArray(BaseType),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TypeModifier {
|
||||||
|
pub fn from_flags(optional: bool, array: bool, map: Option<BaseType>) -> Self {
|
||||||
|
match (optional, array, map) {
|
||||||
|
(false, false, None) => Self::None,
|
||||||
|
(false, true, None) => Self::Array,
|
||||||
|
(true, false, None) => Self::Optional,
|
||||||
|
(true, true, None) => Self::OptionalArray,
|
||||||
|
(false, false, Some(map)) => Self::Map(map),
|
||||||
|
(false, true, Some(map)) => Self::MapArray(map),
|
||||||
|
(true, false, Some(map)) => Self::OptionalMap(map),
|
||||||
|
(true, true, Some(map)) => Self::OptionalMapArray(map),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_flags(&self) -> (bool, bool, Option<BaseType>) {
|
||||||
|
match self.clone() {
|
||||||
|
Self::None => (false, false, None),
|
||||||
|
Self::Array => (false, true, None),
|
||||||
|
Self::Optional => (true, false, None),
|
||||||
|
Self::OptionalArray => (true, true, None),
|
||||||
|
Self::Map(map) => (false, false, Some(map)),
|
||||||
|
Self::MapArray(map) => (false, true, Some(map)),
|
||||||
|
Self::OptionalMap(map) => (true, false, Some(map)),
|
||||||
|
Self::OptionalMapArray(map) => (true, true, Some(map)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Type(pub BaseType, pub TypeModifier);
|
||||||
|
|
||||||
|
impl Type {
|
||||||
|
pub fn from_flags(base: BaseType, optional: bool, array: bool, map: Option<BaseType>) -> Self {
|
||||||
|
Self(base, TypeModifier::from_flags(optional, array, map))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns flags in the order of: (OPTIONAL, ARRAY, MAP)
|
||||||
|
pub fn into_flags(self) -> (BaseType, bool, bool, Option<BaseType>) {
|
||||||
|
let b = self.0.clone();
|
||||||
|
match self.1 {
|
||||||
|
TypeModifier::None => (b, false, false, None),
|
||||||
|
TypeModifier::Array => (b, false, true, None),
|
||||||
|
TypeModifier::Optional => (b, true, false, None),
|
||||||
|
TypeModifier::OptionalArray => (b, true, true, None),
|
||||||
|
TypeModifier::Map(map) => (b, false, false, Some(map)),
|
||||||
|
TypeModifier::MapArray(map) => (b, false, true, Some(map)),
|
||||||
|
TypeModifier::OptionalMap(map) => (b, true, false, Some(map)),
|
||||||
|
TypeModifier::OptionalMapArray(map) => (b, true, true, Some(map)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_optional(&self) -> bool {
|
||||||
|
self.1.get_flags().0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_array(&self) -> bool {
|
||||||
|
self.1.get_flags().1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_map(&self) -> bool {
|
||||||
|
self.1.get_flags().2.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_map(&self) -> Option<BaseType> {
|
||||||
|
self.1.get_flags().2.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// {
|
||||||
|
// pub base: BaseType,
|
||||||
|
// pub array: bool,
|
||||||
|
// pub optional: bool,
|
||||||
|
// pub map: Option<BaseType>,
|
||||||
|
// }
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct TypeDefinition {
|
pub struct TypeDefinition {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub depends: HashSet<Type>,
|
pub depends: HashSet<BaseType>,
|
||||||
pub fields: Vec<Field>,
|
pub fields: Vec<Field>,
|
||||||
pub position: ParserPosition,
|
pub position: ParserPosition,
|
||||||
}
|
}
|
||||||
@ -106,9 +192,6 @@ impl Definition for TypeDefinition {
|
|||||||
pub struct Field {
|
pub struct Field {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub typ: Type,
|
pub typ: Type,
|
||||||
pub array: bool,
|
|
||||||
pub optional: bool,
|
|
||||||
pub map: Option<Type>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -133,7 +216,7 @@ pub struct EnumField {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ServiceDefinition {
|
pub struct ServiceDefinition {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub depends: HashSet<Type>,
|
pub depends: HashSet<BaseType>,
|
||||||
pub methods: Vec<Method>,
|
pub methods: Vec<Method>,
|
||||||
pub position: ParserPosition,
|
pub position: ParserPosition,
|
||||||
}
|
}
|
||||||
@ -156,14 +239,11 @@ pub struct Method {
|
|||||||
pub struct MethodInput {
|
pub struct MethodInput {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub typ: Type,
|
pub typ: Type,
|
||||||
pub array: bool,
|
|
||||||
pub optional: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct MethodOutput {
|
pub struct MethodOutput {
|
||||||
pub typ: Type,
|
pub typ: Type,
|
||||||
pub array: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -182,7 +262,7 @@ fn build_type(stmt: &TypeStatement) -> Result<TypeDefinition> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
for field in &stmt.fields {
|
for field in &stmt.fields {
|
||||||
let typ = Type::from(&field.fieldtype);
|
let typ = BaseType::from(&field.fieldtype);
|
||||||
typedef.depends.insert(typ.clone());
|
typedef.depends.insert(typ.clone());
|
||||||
|
|
||||||
if let Some(maptype) = &field.map {
|
if let Some(maptype) = &field.map {
|
||||||
@ -193,10 +273,12 @@ fn build_type(stmt: &TypeStatement) -> Result<TypeDefinition> {
|
|||||||
|
|
||||||
typedef.fields.push(Field {
|
typedef.fields.push(Field {
|
||||||
name: field.name.clone(),
|
name: field.name.clone(),
|
||||||
typ: typ.clone(),
|
typ: Type::from_flags(
|
||||||
array: field.array,
|
typ.clone(),
|
||||||
optional: field.optional,
|
field.optional,
|
||||||
map: field.map.as_ref().map(|s| Type::from(s)),
|
field.array,
|
||||||
|
field.map.as_ref().map(|s| BaseType::from(s)),
|
||||||
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,14 +329,11 @@ fn build_service(stmt: &ServiceStatement) -> Result<ServiceDefinition> {
|
|||||||
name: method.name.clone(),
|
name: method.name.clone(),
|
||||||
inputs: Vec::new(),
|
inputs: Vec::new(),
|
||||||
output: method.return_type.as_ref().map(|rt| {
|
output: method.return_type.as_ref().map(|rt| {
|
||||||
let typ = Type::from(&rt.fieldtype);
|
let typ = Type::from_flags(BaseType::from(&rt.fieldtype), false, rt.array, None);
|
||||||
if typ != Type::Void {
|
if typ.0 != BaseType::Void {
|
||||||
servdef.depends.insert(typ.clone());
|
servdef.depends.insert(typ.0.clone());
|
||||||
}
|
|
||||||
MethodOutput {
|
|
||||||
typ,
|
|
||||||
array: rt.array,
|
|
||||||
}
|
}
|
||||||
|
MethodOutput { typ }
|
||||||
}),
|
}),
|
||||||
decorators: MethodDecorators {
|
decorators: MethodDecorators {
|
||||||
description: None,
|
description: None,
|
||||||
@ -265,7 +344,7 @@ fn build_service(stmt: &ServiceStatement) -> Result<ServiceDefinition> {
|
|||||||
|
|
||||||
let mut optional_starts = false;
|
let mut optional_starts = false;
|
||||||
for inp in &method.inputs {
|
for inp in &method.inputs {
|
||||||
let typ = Type::from(&inp.fieldtype);
|
let typ = BaseType::from(&inp.fieldtype);
|
||||||
servdef.depends.insert(typ.clone());
|
servdef.depends.insert(typ.clone());
|
||||||
|
|
||||||
if optional_starts && !inp.optional {
|
if optional_starts && !inp.optional {
|
||||||
@ -280,9 +359,7 @@ fn build_service(stmt: &ServiceStatement) -> Result<ServiceDefinition> {
|
|||||||
|
|
||||||
methoddef.inputs.push(MethodInput {
|
methoddef.inputs.push(MethodInput {
|
||||||
name: inp.name.clone(),
|
name: inp.name.clone(),
|
||||||
typ,
|
typ: Type::from_flags(typ.clone(), inp.optional, inp.array, None),
|
||||||
array: inp.array,
|
|
||||||
optional: inp.optional,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -426,7 +503,7 @@ pub fn build_ir(root: &Vec<RootNode>) -> Result<IR> {
|
|||||||
match step {
|
match step {
|
||||||
Step::Type(typedef) => {
|
Step::Type(typedef) => {
|
||||||
for dep in &typedef.depends {
|
for dep in &typedef.depends {
|
||||||
if let Type::Custom(dep) = dep {
|
if let BaseType::Custom(dep) = dep {
|
||||||
if !all_types.contains(dep) {
|
if !all_types.contains(dep) {
|
||||||
return Err(IRError::new_from_def(
|
return Err(IRError::new_from_def(
|
||||||
&format!("Type {} depends on unknown type {}", typedef.name, dep),
|
&format!("Type {} depends on unknown type {}", typedef.name, dep),
|
||||||
@ -439,7 +516,7 @@ pub fn build_ir(root: &Vec<RootNode>) -> Result<IR> {
|
|||||||
}
|
}
|
||||||
Step::Service(servdef) => {
|
Step::Service(servdef) => {
|
||||||
for dep in &servdef.depends {
|
for dep in &servdef.depends {
|
||||||
if let Type::Custom(dep) = dep {
|
if let BaseType::Custom(dep) = dep {
|
||||||
if !all_types.contains(dep) {
|
if !all_types.contains(dep) {
|
||||||
return Err(IRError::new_from_def(
|
return Err(IRError::new_from_def(
|
||||||
&format!(
|
&format!(
|
||||||
|
@ -3,7 +3,9 @@ use log::warn;
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use crate::compile::{Compile, CompileContext, FileGenerator};
|
use crate::compile::{Compile, CompileContext, FileGenerator};
|
||||||
use crate::ir::{EnumDefinition, ServiceDefinition, Step, Type, TypeDefinition};
|
use crate::ir::{
|
||||||
|
BaseType, EnumDefinition, ServiceDefinition, Step, Type, TypeDefinition, TypeModifier,
|
||||||
|
};
|
||||||
use crate::shared::Keywords;
|
use crate::shared::Keywords;
|
||||||
use crate::IR;
|
use crate::IR;
|
||||||
|
|
||||||
@ -14,37 +16,43 @@ pub struct RustCompiler {
|
|||||||
static RUST_KEYWORDS: [&'static str; 6] = ["type", "return", "static", "pub", "enum", "self"];
|
static RUST_KEYWORDS: [&'static str; 6] = ["type", "return", "static", "pub", "enum", "self"];
|
||||||
|
|
||||||
impl RustCompiler {
|
impl RustCompiler {
|
||||||
fn type_to_rust(typ: &Type) -> String {
|
fn type_to_rust(typ: &BaseType) -> String {
|
||||||
match typ {
|
match typ {
|
||||||
Type::String => "String".to_string(),
|
BaseType::String => "String".to_string(),
|
||||||
Type::Int => "i64".to_string(),
|
BaseType::Int => "i64".to_string(),
|
||||||
Type::Float => "f64".to_string(),
|
BaseType::Float => "f64".to_string(),
|
||||||
Type::Bool => "bool".to_string(),
|
BaseType::Bool => "bool".to_string(),
|
||||||
Type::Bytes => "Vec<u8>".to_string(),
|
BaseType::Bytes => "Vec<u8>".to_string(),
|
||||||
Type::Void => "()".to_string(),
|
BaseType::Void => "()".to_string(),
|
||||||
Type::Custom(name) => name.clone(),
|
BaseType::Custom(name) => name.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn type_to_rust_ext(typ: &Type, optional: bool, array: bool) -> String {
|
fn type_to_rust_ext(typ: &Type) -> String {
|
||||||
let mut result = Self::type_to_rust(typ);
|
let mut result = Self::type_to_rust(&typ.0);
|
||||||
if optional {
|
|
||||||
result = format!("Option<{}>", result);
|
let (optional, array, map) = typ.1.get_flags();
|
||||||
}
|
|
||||||
if array {
|
if array {
|
||||||
result = format!("Vec<{}>", result);
|
result = format!("Vec<{}>", result);
|
||||||
}
|
}
|
||||||
|
if let Some(map) = &map {
|
||||||
|
result = format!("HashMap<{}, {}>", Self::type_to_rust(&map), result);
|
||||||
|
}
|
||||||
|
if optional {
|
||||||
|
result = format!("Option<{}>", result);
|
||||||
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_dependencies(
|
fn add_dependencies(
|
||||||
&mut self,
|
&mut self,
|
||||||
file: &mut FileGenerator,
|
file: &mut FileGenerator,
|
||||||
depends: &HashSet<Type>,
|
depends: &HashSet<BaseType>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
for dep in depends {
|
for dep in depends {
|
||||||
match dep {
|
match dep {
|
||||||
Type::Custom(name) => {
|
BaseType::Custom(name) => {
|
||||||
file.a0(&format!("use crate::{};", name));
|
file.a0(&format!("use crate::{};", name));
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@ -148,16 +156,16 @@ impl RustCompiler {
|
|||||||
format!(
|
format!(
|
||||||
"{}: {}",
|
"{}: {}",
|
||||||
Self::fix_keyword_name(&arg.name),
|
Self::fix_keyword_name(&arg.name),
|
||||||
Self::type_to_rust_ext(&arg.typ, arg.optional, arg.array)
|
Self::type_to_rust_ext(&arg.typ)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
.join(", ");
|
.join(", ");
|
||||||
|
|
||||||
let ret = method.output.as_ref().map_or_else(
|
let ret = method
|
||||||
|| "()".to_owned(),
|
.output
|
||||||
|r| Self::type_to_rust_ext(&r.typ, false, r.array),
|
.as_ref()
|
||||||
);
|
.map_or_else(|| "()".to_owned(), |r| Self::type_to_rust_ext(&r.typ));
|
||||||
|
|
||||||
f.a1("#[allow(non_snake_case)]");
|
f.a1("#[allow(non_snake_case)]");
|
||||||
f.a(
|
f.a(
|
||||||
@ -238,7 +246,7 @@ impl RustCompiler {
|
|||||||
format!(
|
format!(
|
||||||
".map_err(|_| \"Parameter for field '{}' should be of type '{}'!\")?{}",
|
".map_err(|_| \"Parameter for field '{}' should be of type '{}'!\")?{}",
|
||||||
arg.name,
|
arg.name,
|
||||||
arg.typ.to_string(),
|
arg.typ.0.to_string(),
|
||||||
if i < method.inputs.len() - 1 { "," } else { "" }
|
if i < method.inputs.len() - 1 { "," } else { "" }
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -266,7 +274,7 @@ impl RustCompiler {
|
|||||||
format!(
|
format!(
|
||||||
".map_err(|_| \"Parameter for field {} should be of type '{}'!\")?{}",
|
".map_err(|_| \"Parameter for field {} should be of type '{}'!\")?{}",
|
||||||
arg.name,
|
arg.name,
|
||||||
arg.typ.to_string(),
|
arg.typ.0.to_string(),
|
||||||
if i < method.inputs.len() - 1 { "," } else { "" }
|
if i < method.inputs.len() - 1 { "," } else { "" }
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -332,13 +340,13 @@ impl RustCompiler {
|
|||||||
format!(
|
format!(
|
||||||
"{}: {}",
|
"{}: {}",
|
||||||
Self::fix_keyword_name(&arg.name),
|
Self::fix_keyword_name(&arg.name),
|
||||||
Self::type_to_rust_ext(&arg.typ, arg.optional, arg.array)
|
Self::type_to_rust_ext(&arg.typ)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
.join(", ");
|
.join(", ");
|
||||||
let ret = method.output.as_ref().map_or("()".to_string(), |output| {
|
let ret = method.output.as_ref().map_or("()".to_string(), |output| {
|
||||||
Self::type_to_rust_ext(&output.typ, false, output.array)
|
Self::type_to_rust_ext(&output.typ)
|
||||||
});
|
});
|
||||||
|
|
||||||
f.a1("#[allow(non_snake_case)]");
|
f.a1("#[allow(non_snake_case)]");
|
||||||
@ -369,7 +377,7 @@ impl RustCompiler {
|
|||||||
f.a2("let l_res = self.client.send_request(l_req).await;");
|
f.a2("let l_res = self.client.send_request(l_req).await;");
|
||||||
f.a2("match l_res {");
|
f.a2("match l_res {");
|
||||||
f.a3("Err(e) => Err(e),");
|
f.a3("Err(e) => Err(e),");
|
||||||
if output.typ == Type::Void {
|
if output.typ.0 == BaseType::Void {
|
||||||
f.a3("Ok(_) => Ok(())");
|
f.a3("Ok(_) => Ok(())");
|
||||||
} else {
|
} else {
|
||||||
f.a3("Ok(o) => serde_json::from_value(o).map_err(|e| Box::from(e))");
|
f.a3("Ok(o) => serde_json::from_value(o).map_err(|e| Box::from(e))");
|
||||||
@ -440,7 +448,11 @@ impl Compile for RustCompiler {
|
|||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let mut f = FileGenerator::new();
|
let mut f = FileGenerator::new();
|
||||||
|
|
||||||
if definition.fields.iter().any(|e| e.map.is_some()) {
|
if definition
|
||||||
|
.fields
|
||||||
|
.iter()
|
||||||
|
.any(|e| e.typ.1.get_flags().2.is_some())
|
||||||
|
{
|
||||||
f.a0("use std::collections::hash_map::HashMap;")
|
f.a0("use std::collections::hash_map::HashMap;")
|
||||||
}
|
}
|
||||||
f.a0("use serde::{Deserialize, Serialize};");
|
f.a0("use serde::{Deserialize, Serialize};");
|
||||||
@ -451,7 +463,6 @@ impl Compile for RustCompiler {
|
|||||||
f.a0(format!("pub struct {} {{", definition.name));
|
f.a0(format!("pub struct {} {{", definition.name));
|
||||||
for field in definition.fields.iter() {
|
for field in definition.fields.iter() {
|
||||||
f.a(1, "#[allow(non_snake_case)]");
|
f.a(1, "#[allow(non_snake_case)]");
|
||||||
let func = format!("pub {}:", Self::fix_keyword_name(&field.name));
|
|
||||||
|
|
||||||
if Keywords::is_keyword(&field.name) {
|
if Keywords::is_keyword(&field.name) {
|
||||||
warn!(
|
warn!(
|
||||||
@ -462,49 +473,11 @@ impl Compile for RustCompiler {
|
|||||||
f.a(1, format!("#[serde(rename = \"{}\")]", field.name));
|
f.a(1, format!("#[serde(rename = \"{}\")]", field.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut opts = String::new();
|
f.a1(format!(
|
||||||
let mut opte = String::new();
|
"pub {}: {}",
|
||||||
|
Self::fix_keyword_name(&field.name),
|
||||||
if field.optional {
|
Self::type_to_rust_ext(&field.typ)
|
||||||
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("}");
|
f.a0("}");
|
||||||
|
@ -3,7 +3,7 @@ use log::{info, warn};
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use crate::compile::{Compile, CompileContext, FileGenerator};
|
use crate::compile::{Compile, CompileContext, FileGenerator};
|
||||||
use crate::ir::{EnumDefinition, ServiceDefinition, Step, Type, TypeDefinition};
|
use crate::ir::{BaseType, EnumDefinition, ServiceDefinition, Step, Type, TypeDefinition};
|
||||||
use crate::shared::Keywords;
|
use crate::shared::Keywords;
|
||||||
use crate::IR;
|
use crate::IR;
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ pub struct TypeScriptCompiler {
|
|||||||
flavour: Flavour,
|
flavour: Flavour,
|
||||||
}
|
}
|
||||||
|
|
||||||
static TS_KEYWORDS: [&'static str; 51] = [
|
static TS_KEYWORDS: [&'static str; 52] = [
|
||||||
"abstract",
|
"abstract",
|
||||||
"arguments",
|
"arguments",
|
||||||
"await",
|
"await",
|
||||||
@ -69,28 +69,25 @@ static TS_KEYWORDS: [&'static str; 51] = [
|
|||||||
"while",
|
"while",
|
||||||
"with",
|
"with",
|
||||||
"yield",
|
"yield",
|
||||||
|
"static",
|
||||||
];
|
];
|
||||||
|
|
||||||
impl TypeScriptCompiler {
|
impl TypeScriptCompiler {
|
||||||
fn type_to_typescript(typ: &Type) -> String {
|
fn type_to_typescript(typ: &BaseType) -> String {
|
||||||
match typ {
|
match typ {
|
||||||
Type::String => "string".to_string(),
|
BaseType::String => "string".to_string(),
|
||||||
Type::Int => "number".to_string(),
|
BaseType::Int => "number".to_string(),
|
||||||
Type::Float => "number".to_string(),
|
BaseType::Float => "number".to_string(),
|
||||||
Type::Bool => "boolean".to_string(),
|
BaseType::Bool => "boolean".to_string(),
|
||||||
Type::Bytes => "Uint8Array".to_string(),
|
BaseType::Bytes => "Uint8Array".to_string(),
|
||||||
Type::Void => "void".to_string(),
|
BaseType::Void => "void".to_string(),
|
||||||
Type::Custom(name) => name.clone(),
|
BaseType::Custom(name) => name.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn type_to_typescript_ext(
|
fn type_to_typescript_ext(typ: &Type) -> String {
|
||||||
typ: &Type,
|
let mut result = Self::type_to_typescript(&typ.0);
|
||||||
optional: bool,
|
let (optional, array, map) = typ.1.get_flags();
|
||||||
array: bool,
|
|
||||||
map: &Option<Type>,
|
|
||||||
) -> String {
|
|
||||||
let mut result = Self::type_to_typescript(typ);
|
|
||||||
if optional {
|
if optional {
|
||||||
result = format!("({} | undefined)", result);
|
result = format!("({} | undefined)", result);
|
||||||
}
|
}
|
||||||
@ -100,7 +97,7 @@ impl TypeScriptCompiler {
|
|||||||
if let Some(map) = map {
|
if let Some(map) = map {
|
||||||
result = format!(
|
result = format!(
|
||||||
"{{ [key: {} ]: {} }}",
|
"{{ [key: {} ]: {} }}",
|
||||||
Self::type_to_typescript(map),
|
Self::type_to_typescript(&map),
|
||||||
result
|
result
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -110,7 +107,7 @@ impl TypeScriptCompiler {
|
|||||||
fn add_dependencies(
|
fn add_dependencies(
|
||||||
&mut self,
|
&mut self,
|
||||||
file: &mut FileGenerator,
|
file: &mut FileGenerator,
|
||||||
depends: &HashSet<Type>,
|
depends: &HashSet<BaseType>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let esm = if self.flavour == Flavour::ESM {
|
let esm = if self.flavour == Flavour::ESM {
|
||||||
".js"
|
".js"
|
||||||
@ -121,7 +118,7 @@ impl TypeScriptCompiler {
|
|||||||
"import {{ VerificationError, apply_int, apply_float, apply_string, apply_boolean, apply_void }} from \"./ts_base{esm}\""));
|
"import {{ VerificationError, apply_int, apply_float, apply_string, apply_boolean, apply_void }} from \"./ts_base{esm}\""));
|
||||||
for dep in depends {
|
for dep in depends {
|
||||||
match dep {
|
match dep {
|
||||||
Type::Custom(name) => {
|
BaseType::Custom(name) => {
|
||||||
file.a0(&format!(
|
file.a0(&format!(
|
||||||
"import {name}, {{ apply_{name} }} from \"./{name}{esm}\";"
|
"import {name}, {{ apply_{name} }} from \"./{name}{esm}\";"
|
||||||
));
|
));
|
||||||
@ -155,8 +152,125 @@ impl TypeScriptCompiler {
|
|||||||
ctx: &mut CompileContext,
|
ctx: &mut CompileContext,
|
||||||
definition: &ServiceDefinition,
|
definition: &ServiceDefinition,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
|
let esm = if self.flavour == Flavour::ESM {
|
||||||
|
".js"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.write_file("ts_service_server.ts", &format!("
|
||||||
|
import {{ type RequestObject, type ResponseObject, ErrorCodes, Logging }} from \"./ts_service_base{esm}\";
|
||||||
|
import {{ VerificationError }} from \"./ts_base{esm}\";
|
||||||
|
|
||||||
|
{}
|
||||||
|
",
|
||||||
|
CompileContext::strip_template_ignores(include_str!("../../templates/TypeScript/ts_service_server.ts"))))?;
|
||||||
|
|
||||||
let mut f = FileGenerator::new();
|
let mut f = FileGenerator::new();
|
||||||
|
|
||||||
|
f.a0(format!(
|
||||||
|
"import {{ Service }} from \"./ts_service_server{esm}\";"
|
||||||
|
));
|
||||||
|
self.add_dependencies(&mut f, &definition.depends)?;
|
||||||
|
|
||||||
|
f.a0(format!(
|
||||||
|
"export abstract class {}<T> extends Service<T> {{",
|
||||||
|
definition.name
|
||||||
|
));
|
||||||
|
f.a1(format!("public name = \"{}\"", definition.name));
|
||||||
|
f.a1(format!("constructor() {{"));
|
||||||
|
f.a2(format!("super();"));
|
||||||
|
for func in &definition.methods {
|
||||||
|
f.a2(format!("this.functions.add(\"{}\");", func.name));
|
||||||
|
}
|
||||||
|
f.a1("}");
|
||||||
|
f.a0("");
|
||||||
|
|
||||||
|
for func in &definition.methods {
|
||||||
|
let mut params = func
|
||||||
|
.inputs
|
||||||
|
.iter()
|
||||||
|
.map(|p| format!("{}: {}", p.name, Self::type_to_typescript_ext(&p.typ)))
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
params.push("ctx: T".to_string());
|
||||||
|
let params = params.join(", ");
|
||||||
|
|
||||||
|
if let Some(output) = &func.output {
|
||||||
|
f.a1(format!(
|
||||||
|
"public abstract {}({}): Promise<{}>;",
|
||||||
|
func.name,
|
||||||
|
params,
|
||||||
|
Self::type_to_typescript_ext(&output.typ)
|
||||||
|
));
|
||||||
|
f.a1(format!(
|
||||||
|
"_{}(params: any[] | any, ctx: T): Promise<{}> {{",
|
||||||
|
func.name,
|
||||||
|
Self::type_to_typescript_ext(&output.typ)
|
||||||
|
));
|
||||||
|
let params_str_arr = func
|
||||||
|
.inputs
|
||||||
|
.iter()
|
||||||
|
.map(|e| format!("\"{}\"", e.name))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ");
|
||||||
|
f.a2(format!(
|
||||||
|
"let p = this._into_parameters([{}], params)",
|
||||||
|
params_str_arr
|
||||||
|
));
|
||||||
|
|
||||||
|
for (index, input) in func.inputs.iter().enumerate() {
|
||||||
|
f.a2(format!(
|
||||||
|
"if(p[{}] !== null && p[{}] !== undefined) {{",
|
||||||
|
index, index
|
||||||
|
));
|
||||||
|
if input.typ.array {
|
||||||
|
f.a2(format!("for (const elm of p[{}]) {{", index));
|
||||||
|
f.a3(format!("apply_{}(elm);", input.name));
|
||||||
|
f.a2(format!("}}"));
|
||||||
|
} else if let Some(_map) = &input.typ.map {
|
||||||
|
// TODO: Implement map type handling
|
||||||
|
panic!("Map in arguments is not allowed!");
|
||||||
|
} else {
|
||||||
|
f.a3(format!(
|
||||||
|
"apply_{}(p[{}]);",
|
||||||
|
input.typ.base.to_string(),
|
||||||
|
index
|
||||||
|
));
|
||||||
|
}
|
||||||
|
f.a2("}");
|
||||||
|
if !input.typ.optional {
|
||||||
|
f.a2(format!(
|
||||||
|
"else throw new Error(`Missing required parameter ${}`);",
|
||||||
|
input.name
|
||||||
|
));
|
||||||
|
}
|
||||||
|
f.a0("");
|
||||||
|
}
|
||||||
|
|
||||||
|
f.a2("p.push(ctx);");
|
||||||
|
|
||||||
|
let ret_apply = String::new();
|
||||||
|
if output.typ.
|
||||||
|
|
||||||
|
f.a2("//@ts-ignore This will cause a typescript error when strict checking, since p is not a tuple");
|
||||||
|
f.a2(format!(
|
||||||
|
"return this.{}.call(this, ...p).then(res=>{});",
|
||||||
|
func.name, ret_apply
|
||||||
|
));
|
||||||
|
|
||||||
|
f.a1("}");
|
||||||
|
} else {
|
||||||
|
f.a1(format!("public abstract {}({}): void;", func.name, params));
|
||||||
|
}
|
||||||
|
|
||||||
|
f.a0("");
|
||||||
|
}
|
||||||
|
// f.a2(format!("}}"));
|
||||||
|
// f.a0(format!(""));
|
||||||
|
f.a0("}");
|
||||||
|
|
||||||
|
ctx.write_file(&format!("{}_server.ts", definition.name), &f.into_content())?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,25 +285,31 @@ impl TypeScriptCompiler {
|
|||||||
""
|
""
|
||||||
};
|
};
|
||||||
|
|
||||||
ctx.write_file("ts_service_client.ts", &format!("
|
ctx.write_file(
|
||||||
import {{ type RequestObject, type ResponseObject, ErrorCodes, Logging }} from \"./service_base{esm}\";
|
"ts_service_base.ts",
|
||||||
import {{ VerificationError }} from \"./ts_base{esm}\";
|
include_str!("../../templates/TypeScript/ts_service_base.ts"),
|
||||||
|
)?;
|
||||||
|
|
||||||
{}
|
ctx.write_file("ts_service_client.ts", &format!("
|
||||||
", include_str!("../../templates/TypeScript/ts_service_client.ts")))?;
|
import {{ type RequestObject, type ResponseObject, ErrorCodes, Logging }} from \"./ts_service_base{esm}\";
|
||||||
|
import {{ VerificationError }} from \"./ts_base{esm}\";
|
||||||
|
|
||||||
|
{}
|
||||||
|
",
|
||||||
|
CompileContext::strip_template_ignores(include_str!("../../templates/TypeScript/ts_service_client.ts"))))?;
|
||||||
|
|
||||||
let mut f = FileGenerator::new();
|
let mut f = FileGenerator::new();
|
||||||
self.add_dependencies(&mut f, &definition.depends)?;
|
self.add_dependencies(&mut f, &definition.depends)?;
|
||||||
|
|
||||||
f.a0(format!(
|
f.a0(format!(
|
||||||
"import {{ Service, ServiceProvider, getRandomID }} from \"./service_client{esm}\""
|
"import {{ Service, ServiceProvider, getRandomID }} from \"./ts_service_client{esm}\""
|
||||||
));
|
));
|
||||||
|
|
||||||
f.a0("export type {");
|
// f.a0("export type {");
|
||||||
for dep in &definition.depends {
|
// for dep in &definition.depends {
|
||||||
f.a1(format!("{},", Self::type_to_typescript(&dep)));
|
// f.a1(format!("{},", Self::type_to_typescript(&dep)));
|
||||||
}
|
// }
|
||||||
f.a0("}");
|
// f.a0("}");
|
||||||
|
|
||||||
f.a0(format!(
|
f.a0(format!(
|
||||||
"export class {} extends Service {{",
|
"export class {} extends Service {{",
|
||||||
@ -199,7 +319,46 @@ impl TypeScriptCompiler {
|
|||||||
f.a2(format!("super(provider, \"{}\");", definition.name));
|
f.a2(format!("super(provider, \"{}\");", definition.name));
|
||||||
f.a1("}");
|
f.a1("}");
|
||||||
|
|
||||||
//TODO: Change the way methods are implemented in a way, that the jsonrpc, etc. fields are exportedf into the ServiceProvider class. This should make the actual function body a lot easier!
|
for fnc in &definition.methods {
|
||||||
|
let params = fnc
|
||||||
|
.inputs
|
||||||
|
.iter()
|
||||||
|
.map(|p| {
|
||||||
|
format!(
|
||||||
|
"{}: {}",
|
||||||
|
Self::fix_keyword_name(&p.name),
|
||||||
|
Self::type_to_typescript_ext(&p.typ)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ");
|
||||||
|
|
||||||
|
if let Some(output) = &fnc.output {
|
||||||
|
f.a1(format!(
|
||||||
|
"async {}({}): Promise<{}> {{",
|
||||||
|
fnc.name,
|
||||||
|
params,
|
||||||
|
Self::type_to_typescript_ext(&output.typ)
|
||||||
|
));
|
||||||
|
f.a2("return new Promise<any>((ok, err) => {");
|
||||||
|
f.a3(format!(
|
||||||
|
"return this._provider.sendRequest(\"{}.{}\", [...arguments], {{ok, err}});",
|
||||||
|
definition.name, fnc.name
|
||||||
|
));
|
||||||
|
f.a2("});");
|
||||||
|
f.a1("}");
|
||||||
|
} else {
|
||||||
|
f.a1(format!("{}({}): void {{", fnc.name, params,));
|
||||||
|
f.a2(format!(
|
||||||
|
"this._provider.sendNotification(\"{}.{}\", [...arguments]);",
|
||||||
|
definition.name, fnc.name
|
||||||
|
));
|
||||||
|
f.a1("}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.a0("}");
|
||||||
|
|
||||||
|
ctx.write_file(&format!("{}_client.ts", definition.name), &f.into_content())?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -244,8 +403,7 @@ impl Compile for TypeScriptCompiler {
|
|||||||
|
|
||||||
f.a0(format!("export default class {} {{", definition.name));
|
f.a0(format!("export default class {} {{", definition.name));
|
||||||
for field in definition.fields.iter() {
|
for field in definition.fields.iter() {
|
||||||
let typ =
|
let typ = Self::type_to_typescript_ext(&field.typ);
|
||||||
Self::type_to_typescript_ext(&field.typ, field.optional, field.array, &field.map);
|
|
||||||
|
|
||||||
f.a1(format!(
|
f.a1(format!(
|
||||||
"public {}: {};",
|
"public {}: {};",
|
||||||
@ -285,7 +443,7 @@ impl Compile for TypeScriptCompiler {
|
|||||||
f.a1(format!("let res = new {}() as any;", definition.name));
|
f.a1(format!("let res = new {}() as any;", definition.name));
|
||||||
|
|
||||||
for field in definition.fields.iter() {
|
for field in definition.fields.iter() {
|
||||||
if field.optional {
|
if field.typ.is_optional() {
|
||||||
f.a1(format!(
|
f.a1(format!(
|
||||||
"if (data.{} !== null && data.{} !== undefined ) {{",
|
"if (data.{} !== null && data.{} !== undefined ) {{",
|
||||||
field.name, field.name
|
field.name, field.name
|
||||||
@ -301,20 +459,7 @@ impl Compile for TypeScriptCompiler {
|
|||||||
f.a1("else {");
|
f.a1("else {");
|
||||||
}
|
}
|
||||||
|
|
||||||
if field.array {
|
if field.typ.is_map() {
|
||||||
f.a2(format!(
|
|
||||||
"if (!Array.isArray(data.{})) throw new VerificationError(\"array\", \"{}\", data.{});",
|
|
||||||
field.name,
|
|
||||||
definition.name,
|
|
||||||
field.name
|
|
||||||
));
|
|
||||||
f.a2(format!(
|
|
||||||
"res.{} = data.{}.map(elm => apply_{}(elm));",
|
|
||||||
field.name,
|
|
||||||
field.name,
|
|
||||||
Self::type_to_typescript(&field.typ),
|
|
||||||
));
|
|
||||||
} else if let Some(map) = &field.map {
|
|
||||||
f.a2(format!(
|
f.a2(format!(
|
||||||
"if (typeof data.{} != \"object\") throw new VerificationError(\"map\", \"{}\", data.{}); ",
|
"if (typeof data.{} != \"object\") throw new VerificationError(\"map\", \"{}\", data.{}); ",
|
||||||
field.name,
|
field.name,
|
||||||
@ -326,13 +471,36 @@ impl Compile for TypeScriptCompiler {
|
|||||||
"Object.entries(data.{}).forEach(([key, val]) => res.{}[key] = apply_{}(val));",
|
"Object.entries(data.{}).forEach(([key, val]) => res.{}[key] = apply_{}(val));",
|
||||||
field.name,
|
field.name,
|
||||||
field.name,
|
field.name,
|
||||||
Self::type_to_typescript(&field.typ),
|
Self::type_to_typescript(&field.typ.base),
|
||||||
));
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if field.typ.is_array() {
|
||||||
|
f.a2(format!(
|
||||||
|
"if (!Array.isArray(data.{})) throw new VerificationError(\"array\", \"{}\", data.{});",
|
||||||
|
field.name,
|
||||||
|
definition.name,
|
||||||
|
field.name
|
||||||
|
));
|
||||||
|
f.a2(format!(
|
||||||
|
"res.{} = data.{}.map(elm => apply_{}(elm));",
|
||||||
|
field.name,
|
||||||
|
field.name,
|
||||||
|
Self::type_to_typescript(&field.typ.base),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if field.typ.array {
|
||||||
|
|
||||||
|
} else if let Some(_map) = &field.typ.map {
|
||||||
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
f.a2(format!(
|
f.a2(format!(
|
||||||
"res.{} = apply_{}(data.{})",
|
"res.{} = apply_{}(data.{})",
|
||||||
field.name,
|
field.name,
|
||||||
Self::type_to_typescript(&field.typ),
|
Self::type_to_typescript(&field.typ.base),
|
||||||
field.name
|
field.name
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,30 @@
|
|||||||
//@template-ignore
|
//@template-ignore
|
||||||
import { VerificationError } from "./ts_base";
|
import { VerificationError } from "./ts_base";
|
||||||
//@template-ignore
|
//@template-ignore
|
||||||
import { type RequestObject, type ResponseObject, ErrorCodes, Logging } from "./ts_service_base";
|
import {
|
||||||
|
//@template-ignore
|
||||||
|
type RequestObject,
|
||||||
|
//@template-ignore
|
||||||
|
type ResponseObject,
|
||||||
|
//@template-ignore
|
||||||
|
Logging,
|
||||||
|
//@template-ignore
|
||||||
|
} from "./ts_service_base";
|
||||||
|
|
||||||
export type IMessageCallback = (data: any) => void;
|
export type IMessageCallback = (data: any) => void;
|
||||||
|
|
||||||
export type ResponseListener = {
|
export type ResponseListener = {
|
||||||
ok: (response:any)=>void;
|
ok: (response: any) => void;
|
||||||
err: (error: Error)=>void;
|
err: (error: Error) => void;
|
||||||
}
|
};
|
||||||
|
|
||||||
export class Service {
|
export class Service {
|
||||||
public _name: string = null as any;
|
public _name: string = null as any;
|
||||||
|
|
||||||
constructor(protected _provider: ServiceProvider, name: string) {
|
constructor(
|
||||||
|
protected _provider: ServiceProvider,
|
||||||
|
name: string,
|
||||||
|
) {
|
||||||
this._name = name;
|
this._name = name;
|
||||||
this._provider.services.set(name, this);
|
this._provider.services.set(name, this);
|
||||||
}
|
}
|
||||||
@ -22,14 +32,14 @@ export class Service {
|
|||||||
|
|
||||||
export class ServiceProvider {
|
export class ServiceProvider {
|
||||||
services = new Map<string, Service>();
|
services = new Map<string, Service>();
|
||||||
requests = new Map<string, ResponseListener |undefined>();
|
requests = new Map<string, ResponseListener | undefined>();
|
||||||
|
|
||||||
constructor(private sendPacket: IMessageCallback) {}
|
constructor(private sendPacket: IMessageCallback) {}
|
||||||
|
|
||||||
onPacket(msg: RequestObject | ResponseObject) {
|
onPacket(msg: RequestObject | ResponseObject) {
|
||||||
Logging.log("CLIENT: Received message:", msg);
|
Logging.log("CLIENT: Received message:", msg);
|
||||||
if("method" in msg) {
|
if ("method" in msg) {
|
||||||
if(msg.id){
|
if (msg.id) {
|
||||||
Logging.log("CLIENT: Determined type is Request");
|
Logging.log("CLIENT: Determined type is Request");
|
||||||
// Request, which are not supported by client, so ignore
|
// Request, which are not supported by client, so ignore
|
||||||
return;
|
return;
|
||||||
@ -37,9 +47,12 @@ export class ServiceProvider {
|
|||||||
Logging.log("CLIENT: Determined type is Notification");
|
Logging.log("CLIENT: Determined type is Notification");
|
||||||
//Notification. Send to Notification handler
|
//Notification. Send to Notification handler
|
||||||
const [srvName, fncName] = msg.method.split(".");
|
const [srvName, fncName] = msg.method.split(".");
|
||||||
let service = this.services.get(srvName)
|
let service = this.services.get(srvName);
|
||||||
if(!service) {
|
if (!service) {
|
||||||
Logging.log("CLIENT: Did not find Service wanted by Notification!", srvName);
|
Logging.log(
|
||||||
|
"CLIENT: Did not find Service wanted by Notification!",
|
||||||
|
srvName,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
//TODO: Implement Event thingy (or so :))
|
//TODO: Implement Event thingy (or so :))
|
||||||
}
|
}
|
||||||
@ -48,10 +61,16 @@ export class ServiceProvider {
|
|||||||
Logging.log("CLIENT: Determined type is Response");
|
Logging.log("CLIENT: Determined type is Response");
|
||||||
// Response
|
// Response
|
||||||
let resListener = this.requests.get(msg.id);
|
let resListener = this.requests.get(msg.id);
|
||||||
if(!resListener) return; // Ignore wrong responses
|
if (!resListener) return; // Ignore wrong responses
|
||||||
if(msg.error) {
|
if (msg.error) {
|
||||||
if(msg.error.data && msg.error.data.$ == "verification_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))
|
resListener.err(
|
||||||
|
new VerificationError(
|
||||||
|
msg.error.data.type,
|
||||||
|
msg.error.data.field,
|
||||||
|
msg.error.data.value,
|
||||||
|
),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
resListener.err(new Error(msg.error.message));
|
resListener.err(new Error(msg.error.message));
|
||||||
}
|
}
|
||||||
@ -59,24 +78,30 @@ export class ServiceProvider {
|
|||||||
resListener.ok(msg.result);
|
resListener.ok(msg.result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sendMessage(msg: RequestObject, res?: ResponseListener) {
|
sendNotification(method: string, params: any[]) {
|
||||||
Logging.log("CLIENT: Sending Messgage", msg);
|
Logging.log("CLIENT: Sending Notification", method, params);
|
||||||
if(msg.id) {
|
this.sendPacket({
|
||||||
this.requests.set(msg.id, res);
|
jsonrpc: "2.0",
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
this.sendPacket(msg)
|
|
||||||
|
sendRequest(method: string, params: any[], res?: ResponseListener) {
|
||||||
|
Logging.log("CLIENT: Sending Request", method, params);
|
||||||
|
const id = getRandomID(16);
|
||||||
|
this.requests.set(id, res);
|
||||||
|
this.sendPacket({
|
||||||
|
jsonrpc: "2.0",
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
id,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
declare var require: any;
|
declare var require: any;
|
||||||
export const getRandomBytes = (
|
export const getRandomBytes = (
|
||||||
typeof self !== "undefined" && (self.crypto || (self as any).msCrypto)
|
typeof self !== "undefined" && (self.crypto || (self as any).msCrypto)
|
||||||
@ -87,9 +112,7 @@ export const getRandomBytes = (
|
|||||||
return function (n: number) {
|
return function (n: number) {
|
||||||
var a = new Uint8Array(n);
|
var a = new Uint8Array(n);
|
||||||
for (var i = 0; i < n; i += QUOTA) {
|
for (var i = 0; i < n; i += QUOTA) {
|
||||||
crypto.getRandomValues(
|
crypto.getRandomValues(a.subarray(i, i + Math.min(n - i, QUOTA)));
|
||||||
a.subarray(i, i + Math.min(n - i, QUOTA))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return a;
|
return a;
|
||||||
};
|
};
|
||||||
|
@ -1,14 +1,42 @@
|
|||||||
//@template-ignore
|
//@template-ignore
|
||||||
import { VerificationError } from "./ts_base";
|
import { VerificationError } from "./ts_base";
|
||||||
//@template-ignore
|
//@template-ignore
|
||||||
import { type RequestObject, type ResponseObject, ErrorCodes, Logging } from "./ts_service_base";
|
import {
|
||||||
|
//@template-ignore
|
||||||
|
type RequestObject,
|
||||||
|
//@template-ignore
|
||||||
|
type ResponseObject,
|
||||||
|
//@template-ignore
|
||||||
|
ErrorCodes,
|
||||||
|
//@template-ignore
|
||||||
|
Logging,
|
||||||
|
//@template-ignore
|
||||||
|
} from "./ts_service_base";
|
||||||
|
|
||||||
export class Service<T> {
|
export class Service<T> {
|
||||||
public name: string = null as any;
|
public name: string = null as any;
|
||||||
public functions = new Set<string>();
|
public functions = new Set<string>();
|
||||||
|
|
||||||
constructor() { }
|
_into_parameters(params: string[], value: any[] | any): any[] {
|
||||||
|
let p: any[] = [];
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
p = params;
|
||||||
|
if (p.length > params.length) {
|
||||||
|
throw new VerificationError(`Too many parameters provided`);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (p.length < params.length) {
|
||||||
|
p.push(undefined);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
} else {
|
||||||
|
p = params.map((p) => value[p]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ISendMessageCB = (data: any, catchedErr?: Error) => void;
|
type ISendMessageCB = (data: any, catchedErr?: Error) => void;
|
||||||
@ -18,7 +46,7 @@ export class ServiceProvider<T = any> {
|
|||||||
addService(service: Service<T>) {
|
addService(service: Service<T>) {
|
||||||
this.services.set(service.name, service);
|
this.services.set(service.name, service);
|
||||||
Logging.log("SERVER: Adding Service to provider:", service.name);
|
Logging.log("SERVER: Adding Service to provider:", service.name);
|
||||||
Logging.log("SERVER: Service provides:", [...service.functions.keys()])
|
Logging.log("SERVER: Service provides:", [...service.functions.keys()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
getSession(send: ISendMessageCB, ctx?: Partial<T>): Session<T> {
|
getSession(send: ISendMessageCB, ctx?: Partial<T>): Session<T> {
|
||||||
@ -32,13 +60,13 @@ class Session<T> {
|
|||||||
constructor(
|
constructor(
|
||||||
private provider: ServiceProvider,
|
private provider: ServiceProvider,
|
||||||
private _send: ISendMessageCB,
|
private _send: ISendMessageCB,
|
||||||
ctx?: Partial<T>
|
ctx?: Partial<T>,
|
||||||
) {
|
) {
|
||||||
this.ctx = ctx || {};
|
this.ctx = ctx || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
send(data: any, catchedErr?: Error) {
|
send(data: any, catchedErr?: Error) {
|
||||||
Logging.log("SERVER: Sending Message", data)
|
Logging.log("SERVER: Sending Message", data);
|
||||||
this._send(data, catchedErr);
|
this._send(data, catchedErr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,7 +123,8 @@ class Session<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let result = await (service as any)["_" + fncName](data.params, this.ctx);
|
let result = await (service as any)["_" + fncName](data.params, this.ctx);
|
||||||
if (data.id) { //Request
|
if (data.id) {
|
||||||
|
//Request
|
||||||
this.send({
|
this.send({
|
||||||
jsonrpc: "2.0",
|
jsonrpc: "2.0",
|
||||||
id: data.id,
|
id: data.id,
|
||||||
@ -111,17 +140,20 @@ class Session<T> {
|
|||||||
error: {
|
error: {
|
||||||
code: ErrorCodes.InternalError,
|
code: ErrorCodes.InternalError,
|
||||||
message: err.message,
|
message: err.message,
|
||||||
data: err instanceof VerificationError ? {
|
data:
|
||||||
|
err instanceof VerificationError
|
||||||
|
? {
|
||||||
$: "verification_error",
|
$: "verification_error",
|
||||||
type: err.type,
|
type: err.type,
|
||||||
field: err.field,
|
field: err.field,
|
||||||
value: err.value
|
value: err.value,
|
||||||
} : {
|
}
|
||||||
$: "unknown_error"
|
: {
|
||||||
|
$: "unknown_error",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as ResponseObject,
|
} as ResponseObject,
|
||||||
err
|
err,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
//TODO: Think about else case
|
//TODO: Think about else case
|
||||||
|
Reference in New Issue
Block a user