k2rbac-api-server 0.1.0

Axum server scaffold for the standalone k2rbac/auth Rust service
// SPDX-FileCopyrightText: 2026 Alexander R. Croft
// SPDX-License-Identifier: GPL-3.0-or-later

use std::path::{Path, PathBuf};
use std::process::Command;

fn main() {
    println!("cargo:rerun-if-changed=crisp.typed.manifest");
    println!("cargo:rerun-if-changed=crisp.types");

    let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").expect("manifest dir"));
    let out_dir = PathBuf::from(std::env::var("OUT_DIR").expect("out dir"));
    std::fs::create_dir_all(&out_dir).expect("create out dir");

    compile_templates(&manifest_dir, &out_dir);
}

fn compile_templates(manifest_dir: &Path, output_dir: &Path) {
    let typed_out_dir = output_dir.join("crisp_typed");
    std::fs::create_dir_all(&typed_out_dir).expect("create typed out dir");

    compile_batch(
        manifest_dir,
        &manifest_dir.join("crisp.typed.manifest"),
        &manifest_dir.join("crisp.types"),
        &typed_out_dir,
        &output_dir.join("crisp.typed.cargo-deps"),
    );
}

fn compile_batch(
    manifest_dir: &Path,
    manifest_path: &Path,
    typed_manifest: &Path,
    output_dir: &Path,
    cargo_deps_path: &Path,
) {
    let output = invoke_crispc(manifest_dir, manifest_path, typed_manifest, output_dir, cargo_deps_path)
        .expect("failed to invoke crispc");

    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        let stdout = String::from_utf8_lossy(&output.stdout);
        panic!(
            "template compilation failed for manifest {}\n{}\n{}",
            manifest_path.display(),
            stderr.trim(),
            stdout.trim()
        );
    }

    let deps = std::fs::read_to_string(cargo_deps_path).unwrap_or_else(|err| {
        panic!(
            "template compilation succeeded but dependency file '{}' could not be read: {err}",
            cargo_deps_path.display()
        )
    });
    for line in deps.lines().map(str::trim).filter(|line| !line.is_empty()) {
        println!("cargo:rerun-if-changed={line}");
    }
}

fn invoke_crispc(
    manifest_dir: &Path,
    manifest_path: &Path,
    typed_manifest: &Path,
    output_dir: &Path,
    cargo_deps_path: &Path,
) -> std::io::Result<std::process::Output> {

    let crisp_binary = resolve_crispc_binary(manifest_dir).ok_or_else(|| {
        std::io::Error::new(
            std::io::ErrorKind::NotFound,
            "could not find crispc; set CRISPC_BIN or vendor ext/crisp/bin/crispc",
        )
    })?;

    let mut command = Command::new(crisp_binary);
    command.args([
        "--manifest",
        &manifest_path.display().to_string(),
        "--dialect",
        "rust",
    ]);
    command.args([
        "--typed-context-manifest",
        &typed_manifest.display().to_string(),
    ]);
    command.args([
        "--cargo-deps",
        &cargo_deps_path.display().to_string(),
        "--out",
        &output_dir.display().to_string(),
    ]);
    command.output()
}

fn resolve_crispc_binary(manifest_dir: &Path) -> Option<PathBuf> {
    if let Ok(path) = std::env::var("CRISPC_BIN") {
        let candidate = PathBuf::from(path);
        if candidate.is_file() {
            return Some(candidate);
        }
    }

    for ancestor in manifest_dir.ancestors() {
        for candidate in [
            ancestor.join("ext/crisp/dist/bin/crispc"),
            ancestor.join("ext/crisp/bin/crispc"),
            ancestor.join("dist/bin/crispc"),
            ancestor.join("bin/crispc"),
        ] {
            if candidate.is_file() {
                return Some(candidate);
            }
        }
    }

    None
}