grpc_schemas_example 0.1.0

Example for generating gRPC code for Rust from .proto files
Documentation
use std::io::Write;
use walkdir::WalkDir;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let file_paths = get_proto_file_paths()?;

    compile_protobuf(std::env::current_dir()?.display().to_string(), file_paths)?;

    let build_path = std::env::var_os("OUT_DIR").unwrap();
    let publish_under_package = "com.oliverhines.friendbook.grpc.services.";

    let code = generate_module_code(build_path.to_str().unwrap(), publish_under_package);

    let mut file =
        std::fs::File::create(vec![build_path.to_str().unwrap(), "services.rs"].join("/"))?;
    file.write_all(code?.as_bytes())?;

    Ok(())
}

/// Compiles given set of `.proto` files to Rust using tonic_build crate.
/// * `file_path_root` should be absolute path that `file_paths` are given relative to
/// * `file_paths` set of relative paths from the absolute `file_paths`
fn compile_protobuf(file_path_root: String, file_paths: Vec<String>) -> std::io::Result<()> {
    let proto_path_arg: Vec<String> = vec![String::from("--proto_path="), file_path_root];

    return tonic_build::configure()
        .protoc_arg(proto_path_arg.join(""))
        .build_server(true)
        .compile(&file_paths, &[]);
}

/// Returns the relative paths for every `.proto` file under "proto" directory
fn get_proto_file_paths() -> std::io::Result<Vec<String>> {
    let mut file_paths = Vec::new();
    for entry in WalkDir::new("proto") {
        let entry = entry?;
        let file_name = entry.path().display().to_string();

        if entry.file_type().is_file() && file_name.ends_with(".proto") {
            file_paths.push(file_name);
            println!("{}", entry.path().display().to_string())
        }
    }

    return Ok(file_paths);
}

fn generate_module_code(build_path: &str, publish_under_package: &str) -> std::io::Result<String> {
    let mut scope = codegen::Scope::new();
    let top_level_mod = scope.new_module("services");
    top_level_mod.vis("pub");

    for entry in WalkDir::new(&build_path) {
        let entry = entry?;
        let file_name = entry.file_name().to_str().unwrap();

        if entry.file_type().is_file()
            && str::starts_with(file_name, publish_under_package)
            && str::ends_with(file_name, ".rs")
        {
            let (_, remaining) = str::split_at(file_name, publish_under_package.len());
            let (remaining, _) = str::split_at(remaining, remaining.len() - 3);

            let package_name_parts: Vec<&str> = remaining.split(".").collect();

            let mut current_mod = &mut *top_level_mod;

            for i in 0..package_name_parts.len() {
                current_mod = current_mod.get_or_new_module(package_name_parts[i]);
                current_mod.vis("pub");
            }

            current_mod.new_struct(file_name);
        }
    }

    let mut scope_to_change = scope.to_string().replace(
        "struct ",
        "include!(concat!(
        env!(\"OUT_DIR\"),
        \"/",
    );

    scope_to_change = scope_to_change.replace(";", "\"));");

    return Ok(scope_to_change);
}