prisma-client-rust 0.1.0

A prisma client for Rust
Documentation
pub mod platform;

use std::io;
use std::io::prelude::*;
use std::path::{Path, PathBuf};
use std::fs::{File, metadata, create_dir_all, copy};
use std::process::Command;
use directories::{ BaseDirs };
use reqwest::blocking as reqwest;
use flate2::read::GzDecoder;
use http::StatusCode;
use std::time::{SystemTime};

pub static PRISMA_VERSION: &str = "2.23.0";
pub static ENGINE_VERSION: &str = "adf5e8cba3daf12d456d911d72b6e9418681b28b";

pub static BASE_DIR_NAME: &str = "prisma/binaries";

pub struct Engine<'a> {
    pub name: &'a str,
    pub env: &'a str,
}

pub const ENGINES: [Engine; 4] =[
    Engine {
        name: "query-engine",
        env: "PRISMA_QUERY_ENGINE_BINARY"
    },
    Engine {
        name: "migration-engine",
        env: "PRISMA_MIGRATION_ENGINE_BINARY"
    },
    Engine {
        name: "introspection-engine",
        env: "PRISMA_INTROSPECTION_ENGINE_BINARY"
    },
    Engine {
        name: "prisma-fmt",
        env: "PRISMA_FMT_BINARY"
    },
];

pub fn prisma_cli_name() -> String {
    let variation = platform::name();

    format!("prisma-cli-{}", variation)
}

pub fn global_temp_dir () -> PathBuf {
    let temp = std::env::temp_dir();

    temp.join(BASE_DIR_NAME).join("engines").join(ENGINE_VERSION)
}

pub fn global_unpack_dir() -> PathBuf {
    global_temp_dir().join("unpacked")
}

pub fn global_cache_dir() -> PathBuf {
    let base_dirs = BaseDirs::new().unwrap();
    let cache_dir = base_dirs.cache_dir();

    cache_dir.join(BASE_DIR_NAME).join("cli").join(PRISMA_VERSION)
}

pub fn fetch_engine(to_dir: PathBuf, engine_name: String, binary_platform_name: String) -> Result<(), ()> {
    let to = platform::check_for_extension(
        binary_platform_name.clone(),
        to_dir
            .join(ENGINE_VERSION)
            .join(format!("prisma-{}-{}", engine_name, binary_platform_name))
            .into_os_string().into_string().unwrap()
    );

    let binary_platform_remote_name = match binary_platform_name.as_str() {
        "linux" => "linux-musl",
        name => name
    };

    let url = platform::check_for_extension(
        binary_platform_name.clone(),
        format!(
            "https://binaries.prisma.sh/all_commits/{}/{}/{}.gz",
            ENGINE_VERSION,
            binary_platform_remote_name,
            engine_name
        )
    );

    match metadata(&to) {
        Err(_) => (),
        Ok(_) => {
            println!("{} is cached", to.to_string());
            return Ok(());
        }
    };

    download(url.clone(), to.clone()).expect(&format!("could not download {} to {}", &url, &to));

    Ok(())
}

pub fn fetch_native(to_dir: PathBuf) -> Result<(), ()> {
    if !to_dir.is_absolute() {
        panic!("to_dir must be absolute")
    }

    download_cli(to_dir.clone()).expect("could not download engines");

    for e in &ENGINES {
        download_engine(
            e.name.to_string(),
            to_dir.clone()
        ).expect("could not download engines");
    };

    Ok(())
}

fn download_cli(to_dir: PathBuf) -> Result<(), ()> {
    let cli = prisma_cli_name();

    let to = platform::check_for_extension(
        platform::name(),
        to_dir.join(cli).into_os_string().into_string().unwrap()
    );

    let url = platform::check_for_extension(
        platform::name(),
        format!("https://prisma-photongo.s3-eu-west-1.amazonaws.com/{}-{}-{}.gz",
                "prisma-cli",
                PRISMA_VERSION,
                platform::name()
        )
    );

    match metadata(&to) {
        Err(_) => (),
        Ok(_) => {
            println!("{} is cached", to.to_string());
            return Ok(());
        }
    };

    download(url.clone(), to.clone()).expect(&format!("could not download {} to {}", url, to));

    Ok(())
}

fn download_engine(name: String, to_dir: PathBuf) -> Result<String, ()> {
    let binary_name = platform::binary_platform_name();

    let to = platform::check_for_extension(
        binary_name.to_string(),
        to_dir
            .join(ENGINE_VERSION)
            .join(format!("prisma-{}-{}", name, binary_name))
            .into_os_string().into_string().unwrap()
    );

    let url = platform::check_for_extension(
        binary_name.to_string(),
        format!("https://binaries.prisma.sh/all_commits/{}/{}/{}.gz", ENGINE_VERSION, name, &binary_name)
    );

    match metadata(&to) {
        Err(_) => {},
        Ok(_) => {
            println!("{} is cached", to.to_string());
            return Ok(to);
        }
    };

    let start_download = SystemTime::now();

    download(url.clone(), to.clone()).expect(&format!("could not download {} to {}", url, to));

    println!(
        "download() took {}",
        SystemTime::now().duration_since(start_download).unwrap().as_millis()
    );

    Ok(to.to_string())
}

fn download(url: String, to: String) -> Result<(), ()> {
    create_dir_all(Path::new(&to).parent().unwrap()).unwrap();

    let tmp = &(to.clone() + ".tmp");

    let resp = reqwest::get(&url).unwrap();

    if resp.status() != StatusCode::OK {
        panic!("received code {} from {}", resp.status(), &url);
    };

    let mut tmp_file = File::create(tmp).expect(&format!("could not create {}", tmp));

    if !cfg!(target_os = "windows") {
        Command::new("chmod")
            .arg("+x")
            .arg(tmp)
            .output()
            .expect("failed to make file executable");
    }

    let mut buffer = Vec::new();
    io::BufReader::new(
        GzDecoder::new(resp)
    ).read_to_end(&mut buffer).unwrap();

    tmp_file.write(buffer.as_slice()).expect("could not write to .tmp file");

    copy(tmp, to).expect(&format!("could not copy file {}", url));

    Ok(())
}