diff --git a/Cargo.toml b/Cargo.toml index f48eb0d..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.0.3" +endpoint-libs-requirement = ">=1.1.2" [dependencies] # Internal dependencies -endpoint-libs = { git = "https://github.com/pathscale/endpoint-libs.git", tag = "v1.0.3" } +endpoint-libs = "1.1.2" 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..b60b9c1 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: ["UserRole::Superadmin", "UserRole::Support"], ), EndpointSchema( name: "UserGetEvent1", @@ -75,6 +76,7 @@ Config( stream_response: None, description: "", json_schema: (), + roles: ["UserRole::Superadmin", "UserRole::Support"], ), EndpointSchema( name: "UserGetDebugEvent1", @@ -99,6 +101,7 @@ Config( stream_response: None, description: "", json_schema: (), + roles: ["UserRole::Superadmin", "UserRole::Support"], ), EndpointSchema( name: "UserGetOrder1", @@ -129,6 +132,7 @@ Config( stream_response: None, description: "", json_schema: (), + roles: ["UserRole::Superadmin", "UserRole::Support"], ), EndpointSchema( name: "UserGetSignal1", @@ -158,6 +162,7 @@ Config( stream_response: None, description: "", json_schema: (), + roles: ["UserRole::Superadmin", "UserRole::Support"], ), EndpointSchema( name: "UserGetBalance1", @@ -175,6 +180,7 @@ Config( stream_response: None, description: "", json_schema: (), + roles: ["UserRole::Superadmin", "UserRole::Support"], ), EndpointSchema( name: "UserGetLivePosition1", @@ -203,6 +209,7 @@ Config( stream_response: None, description: "", json_schema: (), + roles: ["UserRole::Superadmin", "UserRole::Support"], ), EndpointSchema( name: "UserSubLivePosition1", @@ -245,6 +252,7 @@ Config( )), description: "", json_schema: (), + roles: ["UserRole::Superadmin", "UserRole::Support"], ), EndpointSchema( name: "UserGetS1Ledger", @@ -280,6 +288,7 @@ Config( stream_response: None, description: "", json_schema: (), + roles: ["UserRole::Superadmin", "UserRole::Support"], ), EndpointSchema( name: "UserCancelOrClosePosition1", @@ -294,7 +303,8 @@ Config( stream_response: None, description: "", json_schema: (), + roles: ["UserRole::Superadmin", "UserRole::Support"], ), ] ) -) \ 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..9377fe7 100644 --- a/examples/minimal_project/config/test_schema.ron +++ b/examples/minimal_project/config/test_schema.ron @@ -17,7 +17,8 @@ Config( stream_response: None, description: "", json_schema: (), + roles: ["UserRole::Superadmin", "UserRole::Support"], ), ], ), -) \ No newline at end of file +) diff --git a/src/docs.rs b/src/docs.rs index 77b29ff..77a8e85 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,9 +92,10 @@ 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) + .truncate(true) .open(&def_filename)?; } @@ -102,7 +103,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 +111,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 f1c9dda..9d3ae9d 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)] @@ -72,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 " @@ -128,17 +129,20 @@ 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), Definition::EnumList(enum_types) => enums.extend(enum_types), + Definition::RoleList(role_types) => { + enums.extend(role_types); + } } } @@ -191,10 +195,8 @@ fn read_version_file(path: &Path) -> eyre::Result { Ok(version_config) } -fn check_compatibility( - version_config: VersionConfig -) -> eyre::Result<()> { - let current_crate_version = Version::parse(&get_crate_version()).unwrap(); +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(); @@ -206,8 +208,8 @@ fn check_compatibility( 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)); @@ -234,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 e86afd4..4b62290 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; @@ -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) @@ -285,12 +285,18 @@ impl From for ErrorCode {{ for s in &data.services { for endpoint in &s.endpoints { + let roles_list = resolve_roles_ids(&endpoint.roles, &data.enums) + .into_iter() + .map(|x| x.to_string()) + .join(", "); + write!( &mut f, " impl WsRequest for {end_name2}Request {{ type Response = {end_name2}Response; const METHOD_ID: u32 = {code}; + const ROLES: &[u32] = &[{roles_list}]; const SCHEMA: &'static str = r#\"{schema}\"#; }} impl WsResponse for {end_name2}Response {{ @@ -310,6 +316,47 @@ 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(("", 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 '{role_variant_name}' not found in enum '{role_enum_name}'" + ); + } + } else { + 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}"); + } + } + + roles_ids_set.into_iter().collect() +} + pub fn rustfmt(f: &Path) -> eyre::Result<()> { let exit = Command::new("rustfmt") .arg("--edition") @@ -358,6 +405,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(),