use tonic::Status;
use crate::proto::udb::core::storage::entity::v1 as storage_entity_pb;
use super::errors::storage_field_violation;
pub(crate) fn normalize_etag(etag: &str) -> String {
etag.trim()
.trim_start_matches("W/")
.trim_matches('"')
.to_ascii_lowercase()
}
fn file_type_from_db(value: &str) -> i32 {
use storage_entity_pb::FileType as T;
match value {
"IMAGE" | "FILE_TYPE_IMAGE" => T::Image as i32,
"VIDEO" | "FILE_TYPE_VIDEO" => T::Video as i32,
"AUDIO" | "FILE_TYPE_AUDIO" => T::Audio as i32,
"PDF" | "FILE_TYPE_PDF" => T::Pdf as i32,
"DOCUMENT" | "FILE_TYPE_DOCUMENT" => T::Document as i32,
"ARCHIVE" | "FILE_TYPE_ARCHIVE" => T::Archive as i32,
"OTHER" | "FILE_TYPE_OTHER" => T::Other as i32,
_ => T::Unspecified as i32,
}
}
fn file_status_from_db(value: &str) -> i32 {
use storage_entity_pb::FileStatus as S;
match value {
"PENDING" | "FILE_STATUS_PENDING" => S::Pending as i32,
"ACTIVE" | "FILE_STATUS_ACTIVE" => S::Active as i32,
"DELETED" | "FILE_STATUS_DELETED" => S::Deleted as i32,
_ => S::Unspecified as i32,
}
}
pub(crate) fn file_type_to_db(value: &str, default: &str) -> Result<String, Status> {
let v = value.trim();
if v.is_empty() {
return Ok(default.to_string());
}
let short = match v.to_ascii_uppercase().as_str() {
"IMAGE" | "FILE_TYPE_IMAGE" => "IMAGE",
"VIDEO" | "FILE_TYPE_VIDEO" => "VIDEO",
"AUDIO" | "FILE_TYPE_AUDIO" => "AUDIO",
"PDF" | "FILE_TYPE_PDF" => "PDF",
"DOCUMENT" | "FILE_TYPE_DOCUMENT" => "DOCUMENT",
"ARCHIVE" | "FILE_TYPE_ARCHIVE" => "ARCHIVE",
"OTHER" | "FILE_TYPE_OTHER" => "OTHER",
other => {
return Err(storage_field_violation(
"file_type",
"must be a supported FileType enum value",
format!("unknown file type: {other}"),
));
}
};
Ok(short.to_string())
}
#[allow(dead_code)]
pub(crate) fn file_status_to_db(value: &str, default: &str) -> Result<String, Status> {
let v = value.trim();
if v.is_empty() {
return Ok(default.to_string());
}
let short = match v.to_ascii_uppercase().as_str() {
"PENDING" | "FILE_STATUS_PENDING" => "PENDING",
"ACTIVE" | "FILE_STATUS_ACTIVE" => "ACTIVE",
"DELETED" | "FILE_STATUS_DELETED" => "DELETED",
other => {
return Err(storage_field_violation(
"status",
"must be a supported FileStatus enum value",
format!("unknown file status: {other}"),
));
}
};
Ok(short.to_string())
}
pub(crate) fn register_is_public_bind(requested: Option<bool>) -> bool {
requested.unwrap_or(false)
}
pub(crate) fn file_type_to_short(value: i32) -> &'static str {
use storage_entity_pb::FileType as T;
match T::try_from(value).unwrap_or(T::Unspecified) {
T::Image => "IMAGE",
T::Video => "VIDEO",
T::Audio => "AUDIO",
T::Pdf => "PDF",
T::Document => "DOCUMENT",
T::Archive => "ARCHIVE",
T::Other => "OTHER",
T::Unspecified => "",
}
}
pub(crate) fn file_status_to_short(value: i32) -> &'static str {
use storage_entity_pb::FileStatus as S;
match S::try_from(value).unwrap_or(S::Unspecified) {
S::Pending => "PENDING",
S::Active => "ACTIVE",
S::Deleted => "DELETED",
S::Unspecified => "",
}
}
fn file_json_object(row: &serde_json::Value) -> &serde_json::Map<String, serde_json::Value> {
row.get("n")
.and_then(serde_json::Value::as_object)
.or_else(|| row.as_object())
.unwrap_or_else(|| {
static EMPTY: std::sync::OnceLock<serde_json::Map<String, serde_json::Value>> =
std::sync::OnceLock::new();
EMPTY.get_or_init(serde_json::Map::new)
})
}
fn json_string(row: &serde_json::Map<String, serde_json::Value>, key: &str) -> String {
row.get(key)
.and_then(|value| match value {
serde_json::Value::String(value) => Some(value.clone()),
serde_json::Value::Number(value) => Some(value.to_string()),
serde_json::Value::Bool(value) => Some(value.to_string()),
serde_json::Value::Null => None,
other => Some(other.to_string()),
})
.unwrap_or_default()
}
fn json_i64(row: &serde_json::Map<String, serde_json::Value>, key: &str) -> i64 {
row.get(key)
.and_then(|value| match value {
serde_json::Value::Number(value) => value.as_i64(),
serde_json::Value::String(value) => value.trim().parse::<i64>().ok(),
_ => None,
})
.unwrap_or(0)
}
fn json_bool(row: &serde_json::Map<String, serde_json::Value>, key: &str) -> bool {
row.get(key)
.and_then(|value| match value {
serde_json::Value::Bool(value) => Some(*value),
serde_json::Value::Number(value) => value.as_i64().map(|n| n != 0),
serde_json::Value::String(value) => match value.trim().to_ascii_lowercase().as_str() {
"true" | "t" | "1" | "yes" => Some(true),
"false" | "f" | "0" | "no" => Some(false),
_ => None,
},
_ => None,
})
.unwrap_or(false)
}
pub(crate) fn file_from_json(row: &serde_json::Value) -> storage_entity_pb::File {
let row = file_json_object(row);
storage_entity_pb::File {
file_id: json_string(row, "file_id"),
tenant_id: json_string(row, "tenant_id"),
project_id: json_string(row, "project_id"),
filename: json_string(row, "filename"),
content_type: json_string(row, "content_type"),
size_bytes: json_i64(row, "size_bytes"),
backend: json_string(row, "backend"),
bucket: json_string(row, "bucket"),
object_key: json_string(row, "object_key"),
url: json_string(row, "url"),
cdn_url: json_string(row, "cdn_url"),
file_type: file_type_from_db(&json_string(row, "file_type")),
reference_id: json_string(row, "reference_id"),
reference_type: json_string(row, "reference_type"),
is_public: json_bool(row, "is_public"),
status: file_status_from_db(&json_string(row, "status")),
checksum: json_string(row, "checksum"),
uploaded_by: json_string(row, "uploaded_by"),
deleted_by: json_string(row, "deleted_by"),
..Default::default()
}
}