mod seaorm;
mod selection;
mod sqlx;
use alibi_schema_registry::FieldDef;
use clap::{Parser, Subcommand, ValueEnum};
use seaorm::seaorm_schema;
use selection::{list_plugins, select};
use sqlx::sqlx_schema;
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::ExitCode;
#[derive(Parser)]
#[command(name = "alibi", about = "CLI tools for Alibi")]
struct Cli {
#[command(subcommand)]
command: Command,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, ValueEnum)]
enum Backend {
#[default]
Sqlx,
Seaorm,
}
#[derive(Subcommand)]
enum Command {
Generate {
#[arg(short, long, value_enum, default_value_t)]
backend: Backend,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(
short,
long,
value_delimiter = ',',
value_parser = clap::builder::PossibleValuesParser::new(
list_plugins().into_iter().chain(["all"])
)
)]
plugins: Vec<String>,
},
}
fn main() -> ExitCode {
let Command::Generate {
backend,
output,
plugins,
} = Cli::parse().command;
let plugins = if plugins.iter().any(|p| p == "all") {
list_plugins().into_iter().map(String::from).collect()
} else {
plugins
};
let schema = generate_schema(&plugins, backend);
match write_schema(output.as_deref(), &schema) {
Ok(()) => ExitCode::SUCCESS,
Err(message) => {
_ = writeln!(std::io::stderr().lock(), "{message}");
ExitCode::FAILURE
}
}
}
fn write_schema(output: Option<&Path>, schema: &str) -> Result<(), String> {
let Some(path) = output else {
return std::io::stdout()
.lock()
.write_all(schema.as_bytes())
.map_err(|error| format!("failed to write stdout: {error}"));
};
if let Some(parent) = path.parent()
&& !parent.exists()
{
fs::create_dir_all(parent)
.map_err(|error| format!("failed to create directory {}: {error}", parent.display()))?;
}
fs::write(path, schema)
.map_err(|error| format!("failed to write {}: {error}", path.display()))?;
_ = writeln!(
std::io::stderr().lock(),
"wrote auth schema to {}",
path.display()
);
Ok(())
}
fn generate_schema(plugins: &[String], backend: Backend) -> String {
let selection = select(plugins);
let tokens = match backend {
Backend::Sqlx => sqlx_schema(&selection),
Backend::Seaorm => seaorm_schema(&selection),
};
#[expect(
clippy::expect_used,
reason = "generated from hardcoded registry; parse failure is a bug"
)]
let file = syn::parse2(tokens).expect("generated code should be valid syntax");
prettyplease::unparse(&file)
}
#[expect(
clippy::panic,
reason = "type strings come from hardcoded registry; parse failure is a bug"
)]
fn parse_type(field: &FieldDef, ty: &str) -> syn::Type {
syn::parse_str(ty)
.unwrap_or_else(|e| panic!("invalid type `{ty}` for field `{}`: {e}", field.name))
}