#![allow(clippy::all)]
#![allow(warnings)]
#![allow(unused_imports)]
#![allow(unused_variables)]
#![allow(clippy::needless_borrows_for_generic_args)]
#![allow(clippy::assertions_on_constants)]
#![cfg(feature = "codegen")]
use rpcnet::codegen::{CodeGenerator, ServiceDefinition};
use std::fs;
use std::path::PathBuf;
use tempfile::TempDir;
#[test]
fn test_parse_simple_service() {
let input = r#"
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
pub struct AddRequest {
pub a: i32,
pub b: i32,
}
#[derive(Serialize, Deserialize)]
pub struct AddResponse {
pub result: i32,
}
#[derive(Serialize, Deserialize)]
pub enum MathError {
Overflow,
}
#[service]
pub trait Calculator {
async fn add(&self, request: AddRequest) -> Result<AddResponse, MathError>;
}
"#;
let definition = ServiceDefinition::parse(input).expect("Failed to parse");
assert_eq!(definition.service_name().to_string(), "Calculator");
let methods = definition.methods();
assert_eq!(methods.len(), 1);
assert_eq!(methods[0].sig.ident.to_string(), "add");
}
#[test]
fn test_reject_non_async_methods() {
let input = r#"
#[service]
pub trait BadService {
fn sync_method(&self) -> String;
}
"#;
let result = ServiceDefinition::parse(input);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.to_string().contains("must be async"));
}
#[test]
fn test_reject_non_result_return() {
let input = r#"
#[service]
pub trait BadService {
async fn bad_method(&self) -> String;
}
"#;
let result = ServiceDefinition::parse(input);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.to_string().contains("must return Result"));
}
#[test]
fn test_generate_server_code() {
let input = r#"
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
pub struct EchoRequest {
pub message: String,
}
#[derive(Serialize, Deserialize)]
pub struct EchoResponse {
pub message: String,
}
#[service]
pub trait EchoService {
async fn echo(&self, request: EchoRequest) -> Result<EchoResponse, String>;
}
"#;
let definition = ServiceDefinition::parse(input).expect("Failed to parse");
let generator = CodeGenerator::new(definition);
let server_code = generator.generate_server();
let server_str = server_code.to_string();
assert!(server_str.contains("EchoServiceHandler"));
assert!(server_str.contains("EchoServiceServer"));
assert!(server_str.contains("async fn echo"));
assert!(server_str.contains("register_all"));
assert!(server_str.contains("serve"));
}
#[test]
fn test_generate_client_code() {
let input = r#"
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
pub struct PingRequest {
pub id: u64,
}
#[derive(Serialize, Deserialize)]
pub struct PingResponse {
pub id: u64,
pub timestamp: u64,
}
#[service]
pub trait PingService {
async fn ping(&self, request: PingRequest) -> Result<PingResponse, String>;
}
"#;
let definition = ServiceDefinition::parse(input).expect("Failed to parse");
let generator = CodeGenerator::new(definition);
let client_code = generator.generate_client();
let client_str = client_code.to_string();
assert!(client_str.contains("PingServiceClient"));
assert!(client_str.contains("async fn connect"));
assert!(client_str.contains("async fn ping"));
assert!(
client_str.contains("self . inner . call") || client_str.contains("self.inner.call"),
"Generated code should contain a call to the inner RPC client"
);
}
#[test]
fn test_builder_api() {
let temp_dir = TempDir::new().unwrap();
let input_file = temp_dir.path().join("test_service.rpc.rs");
let output_dir = temp_dir.path().join("generated");
let service_def = r#"
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
pub struct TestRequest {
pub value: String,
}
#[derive(Serialize, Deserialize)]
pub struct TestResponse {
pub result: String,
}
#[service]
pub trait TestService {
async fn test_method(&self, request: TestRequest) -> Result<TestResponse, String>;
}
"#;
fs::write(&input_file, service_def).unwrap();
rpcnet::codegen::Builder::new()
.input(&input_file)
.output(&output_dir)
.build()
.expect("Failed to generate code");
let service_dir = output_dir.join("test_service");
assert!(service_dir.exists());
assert!(service_dir.join("mod.rs").exists());
assert!(service_dir.join("types.rs").exists());
assert!(service_dir.join("server.rs").exists());
assert!(service_dir.join("client.rs").exists());
let mod_content = fs::read_to_string(service_dir.join("mod.rs")).unwrap();
assert!(mod_content.contains("pub mod types"));
assert!(mod_content.contains("pub mod server"));
assert!(mod_content.contains("pub mod client"));
let types_content = fs::read_to_string(service_dir.join("types.rs")).unwrap();
assert!(types_content.contains("struct TestRequest"));
assert!(types_content.contains("struct TestResponse"));
}
#[test]
fn test_parse_calculator_example() {
let calculator_path = PathBuf::from("examples/calculator/calculator.rpc.rs");
if calculator_path.exists() {
let content = fs::read_to_string(calculator_path).unwrap();
let definition =
ServiceDefinition::parse(&content).expect("Failed to parse calculator.rpc.rs");
assert_eq!(definition.service_name().to_string(), "Calculator");
let methods = definition.methods();
assert_eq!(methods.len(), 4);
let method_names: Vec<String> = methods.iter().map(|m| m.sig.ident.to_string()).collect();
assert!(method_names.contains(&"add".to_string()));
assert!(method_names.contains(&"subtract".to_string()));
assert!(method_names.contains(&"multiply".to_string()));
assert!(method_names.contains(&"divide".to_string()));
}
}
#[test]
fn test_generated_code_is_valid_rust() {
let input = r#"
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
pub struct Request {
pub data: Vec<u8>,
}
#[derive(Serialize, Deserialize)]
pub struct Response {
pub result: Vec<u8>,
}
#[service]
pub trait DataService {
async fn process(&self, request: Request) -> Result<Response, String>;
}
"#;
let definition = ServiceDefinition::parse(input).expect("Failed to parse");
let generator = CodeGenerator::new(definition);
let server_code = generator.generate_server();
let client_code = generator.generate_client();
let types_code = generator.generate_types();
syn::parse2::<syn::File>(server_code).expect("Generated server code is not valid Rust");
syn::parse2::<syn::File>(client_code).expect("Generated client code is not valid Rust");
syn::parse2::<syn::File>(types_code).expect("Generated types code is not valid Rust");
}
#[test]
fn test_multiple_methods() {
let input = r#"
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
pub struct GetRequest { pub key: String }
#[derive(Serialize, Deserialize)]
pub struct GetResponse { pub value: String }
#[derive(Serialize, Deserialize)]
pub struct SetRequest { pub key: String, pub value: String }
#[derive(Serialize, Deserialize)]
pub struct SetResponse { pub success: bool }
#[derive(Serialize, Deserialize)]
pub struct DeleteRequest { pub key: String }
#[derive(Serialize, Deserialize)]
pub struct DeleteResponse { pub success: bool }
#[service]
pub trait KVStore {
async fn get(&self, request: GetRequest) -> Result<GetResponse, String>;
async fn set(&self, request: SetRequest) -> Result<SetResponse, String>;
async fn delete(&self, request: DeleteRequest) -> Result<DeleteResponse, String>;
}
"#;
let definition = ServiceDefinition::parse(input).expect("Failed to parse");
assert_eq!(definition.methods().len(), 3);
let generator = CodeGenerator::new(definition);
let server_code = generator.generate_server();
let server_str = server_code.to_string();
assert!(server_str.contains("async fn get"));
assert!(server_str.contains("async fn set"));
assert!(server_str.contains("async fn delete"));
assert!(server_str.contains("KVStore.get"));
assert!(server_str.contains("KVStore.set"));
assert!(server_str.contains("KVStore.delete"));
}