161 lines
5.9 KiB
Rust
161 lines
5.9 KiB
Rust
use std::collections::BTreeMap;
|
|
|
|
use platform_oss::{LegacyAssetPrefix, OssObjectAccess, OssPutObjectRequest};
|
|
|
|
use super::helpers::{
|
|
GeneratedImageAssetDataUrl, GeneratedImageAssetHelperError, GeneratedImageAssetImageFormat,
|
|
GeneratedImageAssetStoragePaths, build_generated_image_asset_metadata,
|
|
build_generated_image_asset_storage_paths, merge_generated_image_asset_metadata,
|
|
};
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub struct GeneratedImageAssetAdapterBoundary;
|
|
|
|
impl GeneratedImageAssetAdapterBoundary {
|
|
pub const BILLING_BOUNDARY_COMMENT: &'static str = "generated_image_assets adapter only prepares asset payloads; callers own billing before or after persistence.";
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub struct GeneratedImageAssetAdapter;
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub struct GeneratedImageAssetPersistInput {
|
|
pub prefix: LegacyAssetPrefix,
|
|
pub path_segments: Vec<String>,
|
|
pub file_stem: String,
|
|
pub image: GeneratedImageAssetDataUrl,
|
|
pub access: OssObjectAccess,
|
|
pub metadata: GeneratedImageAssetAdapterMetadata,
|
|
pub extra_metadata: BTreeMap<String, String>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
|
pub struct GeneratedImageAssetAdapterMetadata {
|
|
pub asset_kind: Option<String>,
|
|
pub owner_user_id: Option<String>,
|
|
pub entity_kind: Option<String>,
|
|
pub entity_id: Option<String>,
|
|
pub slot: Option<String>,
|
|
pub provider: Option<String>,
|
|
pub task_id: Option<String>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub struct GeneratedImageAssetPreparedPut {
|
|
pub request: OssPutObjectRequest,
|
|
pub storage_paths: GeneratedImageAssetStoragePaths,
|
|
pub format: GeneratedImageAssetImageFormat,
|
|
}
|
|
|
|
impl GeneratedImageAssetAdapter {
|
|
pub fn prepare_put_object(
|
|
input: GeneratedImageAssetPersistInput,
|
|
) -> Result<GeneratedImageAssetPreparedPut, GeneratedImageAssetHelperError> {
|
|
let file_name = format!(
|
|
"{}.{}",
|
|
input.file_stem.trim(),
|
|
input.image.format.extension
|
|
);
|
|
let storage_paths = build_generated_image_asset_storage_paths(
|
|
input.prefix,
|
|
&input.path_segments,
|
|
file_name.as_str(),
|
|
)?;
|
|
let metadata = merge_generated_image_asset_metadata(
|
|
build_generated_image_asset_metadata(input.metadata.into()),
|
|
input.extra_metadata,
|
|
);
|
|
let format = input.image.format.clone();
|
|
|
|
Ok(GeneratedImageAssetPreparedPut {
|
|
request: OssPutObjectRequest {
|
|
prefix: input.prefix,
|
|
path_segments: input.path_segments,
|
|
file_name,
|
|
content_type: Some(format.mime_type.clone()),
|
|
access: input.access,
|
|
metadata,
|
|
body: input.image.bytes,
|
|
},
|
|
storage_paths,
|
|
format,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl From<GeneratedImageAssetAdapterMetadata> for super::helpers::GeneratedImageAssetMetadataInput {
|
|
fn from(value: GeneratedImageAssetAdapterMetadata) -> Self {
|
|
Self {
|
|
asset_kind: value.asset_kind,
|
|
owner_user_id: value.owner_user_id,
|
|
entity_kind: value.entity_kind,
|
|
entity_id: value.entity_id,
|
|
slot: value.slot,
|
|
provider: value.provider,
|
|
task_id: value.task_id,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod generated_image_assets_adapter_tests {
|
|
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STANDARD};
|
|
|
|
use super::*;
|
|
use crate::generated_assets::helpers::decode_generated_image_asset_data_url;
|
|
|
|
#[test]
|
|
fn generated_image_assets_adapter_prepares_put_without_billing_side_effects() {
|
|
let image = decode_generated_image_asset_data_url(&format!(
|
|
"data:image/png;base64,{}",
|
|
BASE64_STANDARD.encode(b"png bytes")
|
|
))
|
|
.expect("image should decode");
|
|
|
|
let prepared =
|
|
GeneratedImageAssetAdapter::prepare_put_object(GeneratedImageAssetPersistInput {
|
|
prefix: LegacyAssetPrefix::SquareHoleAssets,
|
|
path_segments: vec!["work/1".to_string(), "cover".to_string()],
|
|
file_stem: "image".to_string(),
|
|
image,
|
|
access: OssObjectAccess::Private,
|
|
metadata: GeneratedImageAssetAdapterMetadata {
|
|
asset_kind: Some("square-hole-cover".to_string()),
|
|
owner_user_id: Some("user-1".to_string()),
|
|
entity_kind: Some("work".to_string()),
|
|
entity_id: Some("work-1".to_string()),
|
|
slot: Some("cover".to_string()),
|
|
provider: Some("dashscope".to_string()),
|
|
task_id: Some("task-1".to_string()),
|
|
},
|
|
extra_metadata: BTreeMap::from([("caller".to_string(), "unit-test".to_string())]),
|
|
})
|
|
.expect("put object should be prepared");
|
|
|
|
assert_eq!(
|
|
GeneratedImageAssetAdapterBoundary::BILLING_BOUNDARY_COMMENT,
|
|
"generated_image_assets adapter only prepares asset payloads; callers own billing before or after persistence."
|
|
);
|
|
assert_eq!(prepared.request.prefix, LegacyAssetPrefix::SquareHoleAssets);
|
|
assert_eq!(prepared.request.file_name, "image.png");
|
|
assert_eq!(prepared.request.content_type, Some("image/png".to_string()));
|
|
assert_eq!(prepared.request.body, b"png bytes");
|
|
assert_eq!(
|
|
prepared.storage_paths.object_key,
|
|
"generated-square-hole-assets/work-1/cover/image.png"
|
|
);
|
|
assert_eq!(
|
|
prepared.storage_paths.legacy_public_path,
|
|
"/generated-square-hole-assets/work-1/cover/image.png"
|
|
);
|
|
assert_eq!(
|
|
prepared.request.metadata.get("asset_kind"),
|
|
Some(&"square-hole-cover".to_string())
|
|
);
|
|
assert_eq!(
|
|
prepared.request.metadata.get("caller"),
|
|
Some(&"unit-test".to_string())
|
|
);
|
|
}
|
|
}
|