From b27686a371042959b659ce49343e29dedb890466 Mon Sep 17 00:00:00 2001 From: apakhomov Date: Sat, 19 Jul 2025 16:22:42 +0300 Subject: [PATCH 1/7] Support ROLES generation --- Cargo.toml | 3 +- examples/minimal_project/config/roles.ron | 33 +++++++++++ .../minimal_project/config/s1_endpoints.ron | 3 +- .../minimal_project/config/test_schema.ron | 2 +- src/main.rs | 8 ++- src/rust.rs | 55 ++++++++++++++++++- 6 files changed, 97 insertions(+), 7 deletions(-) create mode 100644 examples/minimal_project/config/roles.ron diff --git a/Cargo.toml b/Cargo.toml index f48eb0d..bf0c7f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,8 @@ endpoint-libs-requirement = ">=1.0.3" [dependencies] # Internal dependencies -endpoint-libs = { git = "https://github.com/pathscale/endpoint-libs.git", tag = "v1.0.3" } +endpoint-libs = { git = "https://github.com/pathscale/endpoint-libs", rev = "refs/pull/6/head" } +#endpoint-libs = "1.0.6" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" eyre = "0.6" diff --git a/examples/minimal_project/config/roles.ron b/examples/minimal_project/config/roles.ron new file mode 100644 index 0000000..27ee985 --- /dev/null +++ b/examples/minimal_project/config/roles.ron @@ -0,0 +1,33 @@ +#![enable(unwrap_newtypes)] +#![enable(unwrap_variant_newtypes)] +Config( + definition: EnumList ( + [ + Enum( + name: "UserRole", + variants: [ + EnumVariant( + name: "Superadmin", + value: 1, + comment: "Superadmin can do literally everything. Very dangerous role.", + ), + EnumVariant( + name: "Support", + value: 2, + comment: "Support can view and manage some staff.", + ), + EnumVariant( + name: "Viewer", + value: 3, + comment: "Viewer can only view some data.", + ), + EnumVariant( + name: "Regular", + value: 4, + comment: "Viewer can only view some data.", + ), + ], + ), + ] + ) +) diff --git a/examples/minimal_project/config/s1_endpoints.ron b/examples/minimal_project/config/s1_endpoints.ron index 608cd77..acb410f 100644 --- a/examples/minimal_project/config/s1_endpoints.ron +++ b/examples/minimal_project/config/s1_endpoints.ron @@ -44,6 +44,7 @@ Config( stream_response: None, description: "", json_schema: (), + roles: Some(["UserRole::Superadmin", "UserRole::Support"]), ), EndpointSchema( name: "UserGetEvent1", @@ -297,4 +298,4 @@ Config( ), ] ) -) \ No newline at end of file +) diff --git a/examples/minimal_project/config/test_schema.ron b/examples/minimal_project/config/test_schema.ron index e2379e3..7ee49d3 100644 --- a/examples/minimal_project/config/test_schema.ron +++ b/examples/minimal_project/config/test_schema.ron @@ -20,4 +20,4 @@ Config( ), ], ), -) \ No newline at end of file +) diff --git a/src/main.rs b/src/main.rs index f1c9dda..060494b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -50,6 +50,7 @@ enum Definition { EndpointSchemaList(String, u16, Vec), Enum(Type), EnumList(Vec), + RoleList(Vec), } #[derive(Deserialize, Serialize)] @@ -139,6 +140,9 @@ fn build_object_lists(dir: PathBuf) -> eyre::Result { } Definition::Enum(enum_type) => enums.push(enum_type), Definition::EnumList(enum_types) => enums.extend(enum_types), + Definition::RoleList(role_types) => { + enums.extend(role_types); + } } } @@ -191,9 +195,7 @@ fn read_version_file(path: &Path) -> eyre::Result { Ok(version_config) } -fn check_compatibility( - version_config: VersionConfig -) -> eyre::Result<()> { +fn check_compatibility(version_config: VersionConfig) -> eyre::Result<()> { let current_crate_version = Version::parse(&get_crate_version()).unwrap(); let binary_version_req = VersionReq::parse(&version_config.binary.version).unwrap(); diff --git a/src/rust.rs b/src/rust.rs index e86afd4..3dbe524 100644 --- a/src/rust.rs +++ b/src/rust.rs @@ -3,7 +3,7 @@ use convert_case::{Case, Casing}; use endpoint_libs::model::{EnumVariant, Field, ProceduralFunction, Type}; use eyre::bail; use itertools::Itertools; -use std::collections::BTreeSet; +use std::collections::{BTreeSet, HashMap}; use std::fs::File; use std::io::Write; use std::path::Path; @@ -285,12 +285,23 @@ impl From for ErrorCode {{ for s in &data.services { for endpoint in &s.endpoints { + let roles_unique_ids = if let Some(roles) = &endpoint.roles { + let roles_list = resolve_roles_ids(roles, &data.enums) + .into_iter() + .map(|x| x.to_string()) + .join(", "); + format!("Some(&[{}])", roles_list) + } else { + "None".to_string() + }; + write!( &mut f, " impl WsRequest for {end_name2}Request {{ type Response = {end_name2}Response; const METHOD_ID: u32 = {code}; + const ROLES: Option<&[u32]> = {roles_unique_ids}; const SCHEMA: &'static str = r#\"{schema}\"#; }} impl WsResponse for {end_name2}Response {{ @@ -310,6 +321,48 @@ impl WsResponse for {end_name2}Response {{ Ok(()) } +/// Resolves the IDs of roles from a list of role names and a list of enum types. +/// endpoint_roles: vec!["Role1::Value1", "Role1::Value2"] +fn resolve_roles_ids(endpoint_roles: &Vec, all_enums: &Vec) -> Vec { + let mut all_enums_typed: HashMap> = HashMap::new(); + for e in all_enums { + if let Type::Enum { name, variants } = e { + all_enums_typed.insert(name.clone(), variants.clone()); + } + } + + let mut roles_ids = vec![]; + for role in endpoint_roles { + let (role_enum_name, role_variant_name) = + role.split_once("::").unwrap_or_else(|| ("", role.as_str())); + + if let Some(role_enum_variants) = all_enums_typed.get(role_enum_name) { + if let Some(role_variant_in_endpoint) = role_enum_variants + .iter() + .find(|v| v.name == role_variant_name) + { + roles_ids.push(role_variant_in_endpoint.value); + } else { + eprintln!( + "Warning: Role variant '{}' not found in enum '{}'", + role_variant_name, role_enum_name + ); + } + } else { + eprintln!("Warning: Role enum '{}' not found", role_enum_name); + } + } + // check there is not duplicate roles ids and print error if there are + let mut roles_ids_set: BTreeSet = BTreeSet::new(); + for id in &roles_ids { + if !roles_ids_set.insert(*id) { + eprintln!("Warning: Duplicate role ID found: {}", id); + } + } + + roles_ids_set.into_iter().collect() +} + pub fn rustfmt(f: &Path) -> eyre::Result<()> { let exit = Command::new("rustfmt") .arg("--edition") From 821c7e1d4e3fd9e3dd0afdcea56176c9c1fe6750 Mon Sep 17 00:00:00 2001 From: apakhomov Date: Mon, 21 Jul 2025 16:23:59 +0300 Subject: [PATCH 2/7] Clippy fix --- src/docs.rs | 12 ++++++------ src/main.rs | 14 +++++++------- src/rust.rs | 17 ++++++++--------- src/service.rs | 5 +---- src/sql.rs | 9 +++------ 5 files changed, 25 insertions(+), 32 deletions(-) diff --git a/src/docs.rs b/src/docs.rs index 77b29ff..de89419 100644 --- a/src/docs.rs +++ b/src/docs.rs @@ -70,12 +70,12 @@ pub fn gen_systemd_services(data: &Data, app_name: &str, user: &str) -> eyre::Re for srv in &data.services { let service_filename = data .project_root - .join(format!("etc")) - .join(format!("systemd")) + .join("etc") + .join("systemd") .join(format!("{}_{}.service", app_name, srv.name)); let mut service_file = File::create(&service_filename)?; let v = get_systemd_service(app_name, &srv.name, user); - write!(&mut service_file, "{}", v)?; + write!(&mut service_file, "{v}")?; } Ok(()) } @@ -92,7 +92,7 @@ pub fn get_error_messages(root: &Path) -> eyre::Result { } if !def_filename.exists() { - let mut file = OpenOptions::new() + let file = OpenOptions::new() .create(true) .write(true) .open(&def_filename)?; @@ -102,7 +102,7 @@ pub fn get_error_messages(root: &Path) -> eyre::Result { let def_file = std::fs::read(&def_filename)?; if def_file.is_empty() { - return Ok(ErrorMessages { + Ok(ErrorMessages { language: String::from("TODO"), codes: vec![ErrorMessage { code: 0, @@ -110,7 +110,7 @@ pub fn get_error_messages(root: &Path) -> eyre::Result { message: String::from("Please populate error_codes.json"), source: String::from("None"), }], - }); + }) } else { let definitions: ErrorMessages = serde_json::from_slice(&def_file)?; Ok(definitions) diff --git a/src/main.rs b/src/main.rs index 060494b..9d3ae9d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -73,7 +73,7 @@ fn process_file(file_path: &Path) -> eyre::Result { let file_string = std::fs::read_to_string(file_path)?; let config_file: Config = from_str(&file_string)?; - return Ok(config_file.definition); + Ok(config_file.definition) } _ => Err(eyre!( "Non RON file OR file without extension in config dir " @@ -129,13 +129,13 @@ fn build_object_lists(dir: PathBuf) -> eyre::Result { Definition::EndpointSchema(service_name, service_id, endpoint_schema) => { service_schema_map .entry((service_name, service_id)) - .or_insert(vec![]) + .or_default() .push(endpoint_schema) } Definition::EndpointSchemaList(service_name, service_id, endpoint_schemas) => { service_schema_map .entry((service_name, service_id)) - .or_insert(vec![]) + .or_default() .extend(endpoint_schemas) } Definition::Enum(enum_type) => enums.push(enum_type), @@ -196,7 +196,7 @@ fn read_version_file(path: &Path) -> eyre::Result { } fn check_compatibility(version_config: VersionConfig) -> eyre::Result<()> { - let current_crate_version = Version::parse(&get_crate_version()).unwrap(); + let current_crate_version = Version::parse(get_crate_version()).unwrap(); let binary_version_req = VersionReq::parse(&version_config.binary.version).unwrap(); @@ -208,8 +208,8 @@ fn check_compatibility(version_config: VersionConfig) -> eyre::Result<()> { let caller_libs_version = Version::parse(&version_config.libs.version).unwrap(); if !binary_version_req.matches(¤t_crate_version) { - return Err(eyre!("Binary version constraint not satisfied. Version: {} is specified in version.toml. Current binary version is: {}", - &version_config.binary.version, &get_crate_version())); + Err(eyre!("Binary version constraint not satisfied. Version: {} is specified in version.toml. Current binary version is: {}", + &version_config.binary.version, &get_crate_version())) } else if !libs_version_req.matches(&caller_libs_version) { return Err(eyre!("endpoint-libs version constraint not satisfied. Version: {} is specified in version.toml. This version of endpoint-gen requires: {}", caller_libs_version, libs_version_requirement)); @@ -236,7 +236,7 @@ fn main() -> Result<()> { let config_dir = { if let Some(config_dir) = &args.config_dir { - PathBuf::from_str(&config_dir)? + PathBuf::from_str(config_dir)? } else { env::current_dir()? } diff --git a/src/rust.rs b/src/rust.rs index 3dbe524..3d0828a 100644 --- a/src/rust.rs +++ b/src/rust.rs @@ -25,7 +25,7 @@ impl ToRust for Type { Type::Struct { name, .. } => name.clone(), Type::StructRef(name) => name.clone(), Type::Object => "serde_json::Value".to_owned(), - Type::DataTable { name, .. } => format!("Vec<{}>", name), + Type::DataTable { name, .. } => format!("Vec<{name}>"), Type::Vec(ele) => { format!("Vec<{}>", ele.to_rust_ref(serde_with)) } @@ -77,7 +77,7 @@ impl ToRust for Type { if serde_with_opt.is_empty() { "".to_string() } else { - format!("#[serde(with = \"{}\")]", serde_with_opt) + format!("#[serde(with = \"{serde_with_opt}\")]") }, x.name, x.ty.to_rust_ref(serde_with) @@ -290,7 +290,7 @@ impl From for ErrorCode {{ .into_iter() .map(|x| x.to_string()) .join(", "); - format!("Some(&[{}])", roles_list) + format!("Some(&[{roles_list}])") } else { "None".to_string() }; @@ -334,7 +334,7 @@ fn resolve_roles_ids(endpoint_roles: &Vec, all_enums: &Vec) -> Vec let mut roles_ids = vec![]; for role in endpoint_roles { let (role_enum_name, role_variant_name) = - role.split_once("::").unwrap_or_else(|| ("", role.as_str())); + role.split_once("::").unwrap_or(("", role.as_str())); if let Some(role_enum_variants) = all_enums_typed.get(role_enum_name) { if let Some(role_variant_in_endpoint) = role_enum_variants @@ -344,19 +344,18 @@ fn resolve_roles_ids(endpoint_roles: &Vec, all_enums: &Vec) -> Vec roles_ids.push(role_variant_in_endpoint.value); } else { eprintln!( - "Warning: Role variant '{}' not found in enum '{}'", - role_variant_name, role_enum_name + "Warning: Role variant '{role_variant_name}' not found in enum '{role_enum_name}'" ); } } else { - eprintln!("Warning: Role enum '{}' not found", role_enum_name); + eprintln!("Warning: Role enum '{role_enum_name}' not found"); } } // check there is not duplicate roles ids and print error if there are let mut roles_ids_set: BTreeSet = BTreeSet::new(); for id in &roles_ids { if !roles_ids_set.insert(*id) { - eprintln!("Warning: Duplicate role ID found: {}", id); + eprintln!("Warning: Duplicate role ID found: {id}"); } } @@ -411,6 +410,6 @@ pub fn dump_endpoint_schema(data: &Data, mut writer: impl Write) -> eyre::Result "#, cases = cases.join("\n") ); - writeln!(writer, "{}", code)?; + writeln!(writer, "{code}")?; Ok(()) } diff --git a/src/service.rs b/src/service.rs index 7b4ab29..d665d3a 100644 --- a/src/service.rs +++ b/src/service.rs @@ -19,9 +19,6 @@ StandardInput=null [Install] WantedBy=default.target -"#, - app_name = app_name, - service_name = service_name, - user = user +"# ) } diff --git a/src/sql.rs b/src/sql.rs index 4fbb20f..beeaf1e 100644 --- a/src/sql.rs +++ b/src/sql.rs @@ -1,8 +1,5 @@ -use crate::Data; use endpoint_libs::model::{ProceduralFunction, Type}; use itertools::Itertools; -use std::fs::File; -use std::io::Write; pub const PARAM_PREFIX: &str = "a_"; @@ -24,7 +21,7 @@ impl ToSql for Type { .map(|x| format!("\"{}\" {}", x.name, x.ty.to_sql())); format!( "table (\n{}\n)", - fields.map(|x| format!(" {}", x)).join(",\n") + fields.map(|x| format!(" {x}")).join(",\n") ) } Type::StructRef(_name) => "jsonb".to_owned(), @@ -42,8 +39,8 @@ impl ToSql for Type { Type::Bytea => "bytea".to_owned(), Type::UUID => "uuid".to_owned(), Type::Inet => "inet".to_owned(), - Type::Enum { name, .. } => format!("enum_{}", name), - Type::EnumRef(name) => format!("enum_{}", name), + Type::Enum { name, .. } => format!("enum_{name}"), + Type::EnumRef(name) => format!("enum_{name}"), // 38 digits in total, with 4-18 decimal digits. So to be exact we need 38+18 digits Type::BlockchainDecimal => "decimal(56, 18)".to_owned(), Type::BlockchainAddress => "varchar".to_owned(), From 033bf3b49b8dd032d7f1dfbd972ecda2efe4d623 Mon Sep 17 00:00:00 2001 From: apakhomov Date: Mon, 21 Jul 2025 16:24:59 +0300 Subject: [PATCH 3/7] Clippy fix 2 --- src/docs.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/docs.rs b/src/docs.rs index de89419..77a8e85 100644 --- a/src/docs.rs +++ b/src/docs.rs @@ -92,9 +92,10 @@ pub fn get_error_messages(root: &Path) -> eyre::Result { } if !def_filename.exists() { - let file = OpenOptions::new() + let _file = OpenOptions::new() .create(true) .write(true) + .truncate(true) .open(&def_filename)?; } From 67e38a60384153ac1e491967d8d8003779a6bc66 Mon Sep 17 00:00:00 2001 From: apakhomov Date: Tue, 22 Jul 2025 11:34:13 +0300 Subject: [PATCH 4/7] Use endpoint-libs 1.0.7 --- Cargo.toml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bf0c7f2..f6eac04 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,12 +9,11 @@ description = "Generate Rust code for websocket API endpoints" license = "MIT" [package.metadata] -endpoint-libs-requirement = ">=1.0.3" +endpoint-libs-requirement = ">=1.0.7" [dependencies] # Internal dependencies -endpoint-libs = { git = "https://github.com/pathscale/endpoint-libs", rev = "refs/pull/6/head" } -#endpoint-libs = "1.0.6" +endpoint-libs = "1.0.7" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" eyre = "0.6" From cc124227342512b81f8672c3b00d107c3f03de27 Mon Sep 17 00:00:00 2001 From: apakhomov Date: Wed, 23 Jul 2025 13:22:39 +0300 Subject: [PATCH 5/7] Roles now is not Option --- Cargo.toml | 4 ++-- src/rust.rs | 15 +++++---------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f6eac04..6d615b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,11 +9,11 @@ description = "Generate Rust code for websocket API endpoints" license = "MIT" [package.metadata] -endpoint-libs-requirement = ">=1.0.7" +endpoint-libs-requirement = ">=1.1.1" [dependencies] # Internal dependencies -endpoint-libs = "1.0.7" +endpoint-libs = "1.1.1" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" eyre = "0.6" diff --git a/src/rust.rs b/src/rust.rs index 3d0828a..4b62290 100644 --- a/src/rust.rs +++ b/src/rust.rs @@ -285,15 +285,10 @@ impl From for ErrorCode {{ for s in &data.services { for endpoint in &s.endpoints { - let roles_unique_ids = if let Some(roles) = &endpoint.roles { - let roles_list = resolve_roles_ids(roles, &data.enums) - .into_iter() - .map(|x| x.to_string()) - .join(", "); - format!("Some(&[{roles_list}])") - } else { - "None".to_string() - }; + let roles_list = resolve_roles_ids(&endpoint.roles, &data.enums) + .into_iter() + .map(|x| x.to_string()) + .join(", "); write!( &mut f, @@ -301,7 +296,7 @@ impl From for ErrorCode {{ impl WsRequest for {end_name2}Request {{ type Response = {end_name2}Response; const METHOD_ID: u32 = {code}; - const ROLES: Option<&[u32]> = {roles_unique_ids}; + const ROLES: &[u32] = &[{roles_list}]; const SCHEMA: &'static str = r#\"{schema}\"#; }} impl WsResponse for {end_name2}Response {{ From 38e6112b4398c39af0bf7c68b8e4de2fa2b25322 Mon Sep 17 00:00:00 2001 From: apakhomov Date: Wed, 23 Jul 2025 13:40:36 +0300 Subject: [PATCH 6/7] Add missing `roles` field to example --- examples/minimal_project/config/s1_endpoints.ron | 11 ++++++++++- examples/minimal_project/config/test_schema.ron | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/examples/minimal_project/config/s1_endpoints.ron b/examples/minimal_project/config/s1_endpoints.ron index acb410f..b60b9c1 100644 --- a/examples/minimal_project/config/s1_endpoints.ron +++ b/examples/minimal_project/config/s1_endpoints.ron @@ -44,7 +44,7 @@ Config( stream_response: None, description: "", json_schema: (), - roles: Some(["UserRole::Superadmin", "UserRole::Support"]), + roles: ["UserRole::Superadmin", "UserRole::Support"], ), EndpointSchema( name: "UserGetEvent1", @@ -76,6 +76,7 @@ Config( stream_response: None, description: "", json_schema: (), + roles: ["UserRole::Superadmin", "UserRole::Support"], ), EndpointSchema( name: "UserGetDebugEvent1", @@ -100,6 +101,7 @@ Config( stream_response: None, description: "", json_schema: (), + roles: ["UserRole::Superadmin", "UserRole::Support"], ), EndpointSchema( name: "UserGetOrder1", @@ -130,6 +132,7 @@ Config( stream_response: None, description: "", json_schema: (), + roles: ["UserRole::Superadmin", "UserRole::Support"], ), EndpointSchema( name: "UserGetSignal1", @@ -159,6 +162,7 @@ Config( stream_response: None, description: "", json_schema: (), + roles: ["UserRole::Superadmin", "UserRole::Support"], ), EndpointSchema( name: "UserGetBalance1", @@ -176,6 +180,7 @@ Config( stream_response: None, description: "", json_schema: (), + roles: ["UserRole::Superadmin", "UserRole::Support"], ), EndpointSchema( name: "UserGetLivePosition1", @@ -204,6 +209,7 @@ Config( stream_response: None, description: "", json_schema: (), + roles: ["UserRole::Superadmin", "UserRole::Support"], ), EndpointSchema( name: "UserSubLivePosition1", @@ -246,6 +252,7 @@ Config( )), description: "", json_schema: (), + roles: ["UserRole::Superadmin", "UserRole::Support"], ), EndpointSchema( name: "UserGetS1Ledger", @@ -281,6 +288,7 @@ Config( stream_response: None, description: "", json_schema: (), + roles: ["UserRole::Superadmin", "UserRole::Support"], ), EndpointSchema( name: "UserCancelOrClosePosition1", @@ -295,6 +303,7 @@ Config( stream_response: None, description: "", json_schema: (), + roles: ["UserRole::Superadmin", "UserRole::Support"], ), ] ) diff --git a/examples/minimal_project/config/test_schema.ron b/examples/minimal_project/config/test_schema.ron index 7ee49d3..9377fe7 100644 --- a/examples/minimal_project/config/test_schema.ron +++ b/examples/minimal_project/config/test_schema.ron @@ -17,6 +17,7 @@ Config( stream_response: None, description: "", json_schema: (), + roles: ["UserRole::Superadmin", "UserRole::Support"], ), ], ), From ff1ed5553ec0506f55f1d88df22aa4d4be75ba9b Mon Sep 17 00:00:00 2001 From: apakhomov Date: Wed, 23 Jul 2025 14:33:42 +0300 Subject: [PATCH 7/7] Bump --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6d615b2..dc0a55c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,11 +9,11 @@ description = "Generate Rust code for websocket API endpoints" license = "MIT" [package.metadata] -endpoint-libs-requirement = ">=1.1.1" +endpoint-libs-requirement = ">=1.1.2" [dependencies] # Internal dependencies -endpoint-libs = "1.1.1" +endpoint-libs = "1.1.2" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" eyre = "0.6"