vein-adapter 0.3.0

Shared storage adapters for Vein services
Documentation
use std::{collections::BTreeSet, convert::TryFrom};

use anyhow::{Context, Result};
use serde::de::DeserializeOwned;
use serde_json::Value as JsonValue;

use super::models::DbGemMetadataRow;
use super::types::{GemDependency, GemMetadata};

pub struct PreparedMetadataStrings {
    pub licenses_json: String,
    pub authors_json: String,
    pub emails_json: String,
    pub dependencies_json: String,
    pub executables_json: String,
    pub extensions_json: String,
    pub native_languages_json: String,
    pub metadata_json: Option<String>,
    pub size_bytes: i64,
    pub sbom_json: Option<String>,
}

pub fn prepare_metadata_strings(metadata: &GemMetadata) -> Result<PreparedMetadataStrings> {
    let licenses_json =
        serde_json::to_string(&metadata.licenses).context("serializing licenses")?;
    let authors_json = serde_json::to_string(&metadata.authors).context("serializing authors")?;
    let emails_json = serde_json::to_string(&metadata.emails).context("serializing emails")?;
    let dependencies_json =
        serde_json::to_string(&metadata.dependencies).context("serializing dependencies")?;
    let executables_json =
        serde_json::to_string(&metadata.executables).context("serializing executables")?;
    let extensions_json =
        serde_json::to_string(&metadata.extensions).context("serializing extensions")?;
    let native_languages_json = serde_json::to_string(&metadata.native_languages)
        .context("serializing native language list")?;
    let metadata_json = if metadata.metadata.is_null() {
        None
    } else {
        Some(serde_json::to_string(&metadata.metadata).context("serializing metadata json")?)
    };
    let sbom_json = match &metadata.sbom {
        Some(value) => Some(serde_json::to_string(value).context("serializing sbom json")?),
        None => None,
    };
    let size_bytes = i64::try_from(metadata.size_bytes).unwrap_or(i64::MAX);

    Ok(PreparedMetadataStrings {
        licenses_json,
        authors_json,
        emails_json,
        dependencies_json,
        executables_json,
        extensions_json,
        native_languages_json,
        metadata_json,
        size_bytes,
        sbom_json,
    })
}

pub fn parse_language_rows(rows: Vec<Option<String>>) -> Result<Vec<String>> {
    let mut languages = BTreeSet::new();
    for row in rows.into_iter().flatten() {
        let items: Vec<String> =
            serde_json::from_str(&row).context("parsing native language list from database")?;
        languages.extend(items);
    }
    Ok(languages.into_iter().collect())
}

pub fn parse_json_array<T>(field: &str, raw: Option<String>) -> Result<Vec<T>>
where
    T: DeserializeOwned,
{
    match raw {
        Some(value) => {
            serde_json::from_str(&value).with_context(|| format!("parsing {field} json array"))
        }
        None => Ok(Vec::new()),
    }
}

pub fn parse_required_json_array<T>(field: &str, raw: String) -> Result<Vec<T>>
where
    T: DeserializeOwned,
{
    parse_json_array(field, Some(raw))
}

pub fn parse_json_value(field: &str, raw: Option<String>) -> Result<JsonValue> {
    match raw {
        Some(value) => {
            serde_json::from_str(&value).with_context(|| format!("parsing {field} json value"))
        }
        None => Ok(JsonValue::Null),
    }
}

pub fn hydrate_metadata_row(row: DbGemMetadataRow) -> Result<GemMetadata> {
    let licenses: Vec<String> = parse_required_json_array("licenses", row.licenses)?;
    let authors: Vec<String> = parse_required_json_array("authors", row.authors)?;
    let emails: Vec<String> = parse_required_json_array("emails", row.emails)?;
    let dependencies: Vec<GemDependency> =
        parse_required_json_array("dependencies", row.dependencies_json)?;
    let executables: Vec<String> = parse_json_array("executables", row.executables_json)?;
    let extensions: Vec<String> = parse_json_array("extensions", row.extensions_json)?;
    let native_languages: Vec<String> =
        parse_json_array("native_languages", row.native_languages_json)?;
    let metadata = parse_json_value("metadata", row.metadata_json)?;
    let sbom = match row.sbom_json {
        Some(raw) => {
            let value: JsonValue = serde_json::from_str(&raw).context("parsing sbom json value")?;
            (!value.is_null()).then_some(value)
        }
        None => None,
    };

    Ok(GemMetadata {
        name: row.name,
        version: row.version,
        platform: row.platform,
        summary: row.summary,
        description: row.description,
        licenses,
        authors,
        emails,
        homepage: row.homepage,
        documentation_url: row.documentation_url,
        changelog_url: row.changelog_url,
        source_code_url: row.source_code_url,
        bug_tracker_url: row.bug_tracker_url,
        wiki_url: row.wiki_url,
        funding_url: row.funding_url,
        metadata,
        dependencies,
        executables,
        extensions,
        native_languages,
        has_native_extensions: row.has_native_extensions,
        has_embedded_binaries: row.has_embedded_binaries,
        required_ruby_version: row.required_ruby_version,
        required_rubygems_version: row.required_rubygems_version,
        rubygems_version: row.rubygems_version,
        specification_version: row.specification_version,
        built_at: row.built_at,
        size_bytes: row.size_bytes.max(0) as u64,
        sha256: row.sha256,
        sbom,
    })
}