lenso-contract-codegen 0.6.2

Code generator for portable Lenso Capability contracts.
Documentation
use std::{env, path::Path, process::ExitCode};

use lenso_contract_codegen::{
    ProjectionLanguage, check_generated, check_projection, lint_compatibility, write_generated,
    write_projection,
};

fn usage() -> &'static str {
    "usage:\n  lenso-contract-codegen generate <descriptor> <rust-output> <typescript-output>\n  lenso-contract-codegen generate <descriptor> --rust <output>\n  lenso-contract-codegen generate <descriptor> --rust-runtime <output>\n  lenso-contract-codegen generate <descriptor> --typescript <output>\n  lenso-contract-codegen generate <descriptor> --wit <output>\n  lenso-contract-codegen check <descriptor> <rust-output> <typescript-output>\n  lenso-contract-codegen check <descriptor> --rust <output>\n  lenso-contract-codegen check <descriptor> --rust-runtime <output>\n  lenso-contract-codegen check <descriptor> --typescript <output>\n  lenso-contract-codegen check <descriptor> --wit <output>\n  lenso-contract-codegen lint <old-descriptor> <new-descriptor>"
}

fn selected_projection(arguments: &[String]) -> Option<(ProjectionLanguage, &Path)> {
    if arguments.len() != 4 {
        return None;
    }
    let language = match arguments[2].as_str() {
        "--rust" => ProjectionLanguage::Rust,
        "--rust-runtime" => ProjectionLanguage::RustRuntime,
        "--typescript" => ProjectionLanguage::TypeScript,
        "--wit" => ProjectionLanguage::Wit,
        _ => return None,
    };
    Some((language, Path::new(&arguments[3])))
}

fn run(arguments: &[String]) -> Result<(), String> {
    let Some(command) = arguments.first().map(String::as_str) else {
        return Err(usage().to_owned());
    };
    match command {
        "generate" => match selected_projection(arguments) {
            Some((language, output)) => {
                write_projection(Path::new(&arguments[1]), language, output)
                    .map_err(|error| error.to_string())
            }
            None if arguments.len() == 4 => write_generated(
                Path::new(&arguments[1]),
                Path::new(&arguments[2]),
                Path::new(&arguments[3]),
            )
            .map_err(|error| error.to_string()),
            None => Err(usage().to_owned()),
        },
        "check" => match selected_projection(arguments) {
            Some((language, output)) => {
                check_projection(Path::new(&arguments[1]), language, output)
                    .map_err(|error| error.to_string())
            }
            None if arguments.len() == 4 => check_generated(
                Path::new(&arguments[1]),
                Path::new(&arguments[2]),
                Path::new(&arguments[3]),
            )
            .map_err(|error| error.to_string()),
            None => Err(usage().to_owned()),
        },
        "lint" if arguments.len() == 3 => {
            lint_compatibility(Path::new(&arguments[1]), Path::new(&arguments[2]))
                .map(|_| ())
                .map_err(|error| error.to_string())
        }
        _ => Err(usage().to_owned()),
    }
}

fn main() -> ExitCode {
    match run(&env::args().skip(1).collect::<Vec<_>>()) {
        Ok(()) => ExitCode::SUCCESS,
        Err(error) => {
            eprintln!("{error}");
            ExitCode::FAILURE
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    const FIXTURE: &str = "tests/fixtures/profile/capability.json";

    #[test]
    fn one_language_projection_does_not_require_a_peer_output() {
        let root = std::env::temp_dir().join(format!(
            "lenso-contract-codegen-cli-projection-{}",
            std::process::id()
        ));
        std::fs::create_dir_all(&root).expect("temporary directory should be created");
        let output = root.join("bindings.ts");
        let output = output.to_string_lossy().into_owned();

        run(&[
            "generate".to_owned(),
            FIXTURE.to_owned(),
            "--typescript".to_owned(),
            output.clone(),
        ])
        .expect("TypeScript projection should generate independently");
        run(&[
            "check".to_owned(),
            FIXTURE.to_owned(),
            "--typescript".to_owned(),
            output,
        ])
        .expect("TypeScript projection should check independently");

        assert!(!root.join("bindings.rs").exists());
        std::fs::remove_dir_all(root).expect("temporary directory should be removable");
    }

    #[test]
    fn runtime_codec_projection_is_selectable() {
        let root = std::env::temp_dir().join(format!(
            "lenso-contract-codegen-cli-runtime-{}",
            std::process::id()
        ));
        std::fs::create_dir_all(&root).expect("temporary directory should be created");
        let output = root.join("runtime.rs").to_string_lossy().into_owned();

        run(&[
            "generate".to_owned(),
            FIXTURE.to_owned(),
            "--rust-runtime".to_owned(),
            output.clone(),
        ])
        .expect("runtime codec projection should generate independently");
        run(&[
            "check".to_owned(),
            FIXTURE.to_owned(),
            "--rust-runtime".to_owned(),
            output,
        ])
        .expect("runtime codec projection should check independently");

        std::fs::remove_dir_all(root).expect("temporary directory should be removable");
    }
}