later 0.0.16

Distributed Background jobs manager and runner for Rust
Documentation
#[cfg(feature = "sqlite")]
use std::{
    env,
    ffi::{CStr, CString},
    fs,
    path::{Path, PathBuf},
};

#[cfg(feature = "sqlite")]
fn migration_files(directory: &Path) -> Result<Vec<PathBuf>, String> {
    let entries = fs::read_dir(directory)
        .map_err(|error| format!("could not read {}: {error}", directory.display()))?;
    let mut files = Vec::new();
    for entry in entries {
        let path = entry
            .map_err(|error| format!("could not read migration entry: {error}"))?
            .path();
        if path.extension().and_then(|extension| extension.to_str()) == Some("sql") {
            files.push(path);
        }
    }
    files.sort();
    Ok(files)
}

fn main() {
    if let Err(error) = generate_schema_snapshots() {
        panic!("could not generate schema snapshots: {error}");
    }
}

#[cfg(not(feature = "sqlite"))]
fn generate_schema_snapshots() -> Result<(), String> {
    Ok(())
}

#[cfg(feature = "sqlite")]
fn generate_schema_snapshots() -> Result<(), String> {
    let manifest_dir = PathBuf::from(
        env::var("CARGO_MANIFEST_DIR")
            .map_err(|error| format!("missing manifest directory: {error}"))?,
    );
    let sqlite_migrations = manifest_dir.join("migrations/sqlite");
    let sqlite_snapshot = manifest_dir.join("schema.sqlite");

    if sqlite_snapshot.exists() {
        fs::remove_file(&sqlite_snapshot)
            .map_err(|error| format!("could not replace {}: {error}", sqlite_snapshot.display()))?;
    }
    let sqlite_path = CString::new(sqlite_snapshot.to_string_lossy().as_bytes())
        .map_err(|error| format!("invalid SQLite snapshot path: {error}"))?;
    let mut connection = std::ptr::null_mut();
    let open_status =
        unsafe { libsqlite3_sys::sqlite3_open(sqlite_path.as_ptr(), &mut connection) };
    if open_status != libsqlite3_sys::SQLITE_OK {
        return Err(format!("could not create {}", sqlite_snapshot.display()));
    }
    for path in migration_files(&sqlite_migrations)? {
        println!("cargo:rerun-if-changed={}", path.display());
        let migration = fs::read_to_string(&path)
            .map_err(|error| format!("could not read {}: {error}", path.display()))?;
        let migration = CString::new(migration)
            .map_err(|error| format!("invalid SQL in {}: {error}", path.display()))?;
        let mut error_message = std::ptr::null_mut();
        let status = unsafe {
            libsqlite3_sys::sqlite3_exec(
                connection,
                migration.as_ptr(),
                None,
                std::ptr::null_mut(),
                &mut error_message,
            )
        };
        if status != libsqlite3_sys::SQLITE_OK {
            let message = if error_message.is_null() {
                "unknown SQLite error".to_owned()
            } else {
                unsafe { CStr::from_ptr(error_message) }
                    .to_string_lossy()
                    .into_owned()
            };
            if !error_message.is_null() {
                unsafe { libsqlite3_sys::sqlite3_free(error_message.cast()) };
            }
            unsafe { libsqlite3_sys::sqlite3_close(connection) };
            return Err(format!("could not apply {}: {message}", path.display()));
        }
    }
    unsafe { libsqlite3_sys::sqlite3_close(connection) };
    Ok(())
}