Files
Genarrative/server-rs/crates/spacetime-client/src/editor_project.rs
T
k88936 8abeb333bb
Project CI / Repository checks (push) Successful in 56s
Project CI / Frontend tests (push) Successful in 4m1s
Project CI / Backend tests (push) Successful in 4m26s
Project CI / Native shell tests (push) Successful in 15m12s
统一画布快速编辑白名单与入口行为 (#140)
- 使用白名单控制快速编辑功能的显示

- 同步浮动工具栏、右键菜单、打开入口和提交门禁
- 禁用角色动作序列帧(整体)、音频和单个图标(explain: backend reject)的快速编辑

- 把快速编辑api改为只接受resource Id | asset Id,删除 image_src 参数, 以保证校验

其中包含生产数据迁移。迁移用于清理历史普通图片错误持久化的 assetKind="image",覆盖 asset → project-resource → showcase → canvas 四个作用域,并包含 operator 鉴权、分批 dry-run、批次 SHA-256 绑定 apply、active 结构化画
  > 布迁移摘要同步及最终零残留复核。

---------

Co-authored-by: kdletters <kdletters@qq.com>
Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/140
Co-authored-by: 王德宇 <kvtodev@outlook.com>
Co-committed-by: 王德宇 <kvtodev@outlook.com>
2026-08-12 14:46:24 +08:00

1873 lines
69 KiB
Rust

use super::*;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EditorGenerationTargetPreflightRecordInput {
pub owner_user_id: String,
pub project_id: Option<String>,
pub asset_folder_id: Option<String>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct EditorPixelArtCanvasPlaceholderRecordInput {
pub x: f64,
pub y: f64,
pub width: f64,
pub height: f64,
pub original_width: f64,
pub original_height: f64,
}
#[derive(Clone, Debug, PartialEq)]
pub struct EditorPixelArtCanvasCompletionRecordInput {
pub dialog_id: String,
pub title: String,
pub placeholder: EditorPixelArtCanvasPlaceholderRecordInput,
}
#[derive(Clone, Debug, PartialEq)]
pub struct EditorPixelArtResultPreflightRecordInput {
pub owner_user_id: String,
pub project_id: String,
pub asset_folder_id: String,
pub project_resource: EditorProjectResourceCreateRecordInput,
pub canvas_completion: EditorPixelArtCanvasCompletionRecordInput,
}
#[derive(Clone, Debug, PartialEq)]
pub struct EditorPixelArtResultPersistRecordInput {
pub owner_user_id: String,
pub project_id: String,
pub operation_id: String,
pub operation_fingerprint: String,
pub asset_object: module_assets::AssetObjectUpsertInput,
pub project_resource: EditorProjectResourceCreateRecordInput,
pub asset: EditorAssetCreateRecordInput,
pub canvas_completion: EditorPixelArtCanvasCompletionRecordInput,
pub completed_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq)]
pub struct EditorPixelArtResultPersistRecord {
pub asset_object: module_assets::AssetObjectUpsertSnapshot,
pub project_resource: EditorProjectResourceRecord,
pub asset: EditorAssetRecord,
pub project: Option<EditorProjectRecord>,
}
impl From<EditorPixelArtCanvasPlaceholderRecordInput>
for crate::module_bindings::EditorPixelArtCanvasPlaceholderInput
{
fn from(input: EditorPixelArtCanvasPlaceholderRecordInput) -> Self {
Self {
x: input.x,
y: input.y,
width: input.width,
height: input.height,
original_width: input.original_width,
original_height: input.original_height,
}
}
}
impl From<EditorGenerationTargetPreflightRecordInput>
for crate::module_bindings::EditorGenerationTargetPreflightInput
{
fn from(input: EditorGenerationTargetPreflightRecordInput) -> Self {
Self {
owner_user_id: input.owner_user_id,
project_id: input.project_id,
asset_folder_id: input.asset_folder_id,
}
}
}
impl From<EditorPixelArtCanvasCompletionRecordInput>
for crate::module_bindings::EditorPixelArtCanvasCompletionInput
{
fn from(input: EditorPixelArtCanvasCompletionRecordInput) -> Self {
Self {
dialog_id: input.dialog_id,
title: input.title,
placeholder: input.placeholder.into(),
}
}
}
impl From<EditorPixelArtResultPreflightRecordInput>
for crate::module_bindings::EditorPixelArtResultPreflightInput
{
fn from(input: EditorPixelArtResultPreflightRecordInput) -> Self {
Self {
owner_user_id: input.owner_user_id,
project_id: input.project_id,
asset_folder_id: input.asset_folder_id,
project_resource: input.project_resource.into(),
canvas_completion: input.canvas_completion.into(),
}
}
}
impl From<EditorPixelArtResultPersistRecordInput>
for crate::module_bindings::EditorPixelArtResultPersistInput
{
fn from(input: EditorPixelArtResultPersistRecordInput) -> Self {
Self {
owner_user_id: input.owner_user_id,
project_id: input.project_id,
operation_id: input.operation_id,
operation_fingerprint: input.operation_fingerprint,
asset_object: input.asset_object.into(),
project_resource: input.project_resource.into(),
asset: input.asset.into(),
canvas_completion: input.canvas_completion.into(),
completed_at_micros: input.completed_at_micros,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EditorSpritesheetSlicePersistItemRecordInput {
pub asset_object: module_assets::AssetObjectUpsertInput,
pub project_resource: Option<EditorProjectResourceCreateRecordInput>,
pub asset: Option<EditorAssetCreateRecordInput>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EditorSpritesheetSliceBatchPersistRecordInput {
pub owner_user_id: String,
pub task_id: String,
pub group_task_id: Option<String>,
pub expected_asset_count: u32,
pub items: Vec<EditorSpritesheetSlicePersistItemRecordInput>,
pub completed_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EditorSpritesheetSlicePersistedItemRecord {
pub asset_object: module_assets::AssetObjectUpsertSnapshot,
pub project_resource: Option<EditorProjectResourceRecord>,
pub asset: Option<EditorAssetRecord>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EditorSpritesheetSliceBatchPersistRecord {
pub items: Vec<EditorSpritesheetSlicePersistedItemRecord>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EditorGenerationResultPersistItemRecordInput {
pub slot: String,
pub asset_object: Option<module_assets::AssetObjectUpsertInput>,
pub project_resource: Option<EditorProjectResourceCreateRecordInput>,
pub asset: Option<EditorAssetCreateRecordInput>,
pub binding: Option<module_assets::AssetEntityBindingInput>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct EditorGenerationResultPersistRecordInput {
pub owner_user_id: String,
pub operation_kind: String,
pub operation_id: String,
pub operation_fingerprint: String,
pub items: Vec<EditorGenerationResultPersistItemRecordInput>,
pub canvas_layout: Option<EditorProjectLayoutSaveV2RecordInput>,
pub job_completion: Option<ExternalGenerationJobCompleteRecordInput>,
pub completed_at_micros: i64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum EditorGenerationResultPersistStatusRecord {
Applied,
AlreadyApplied,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EditorGenerationResultPersistedItemRecord {
pub slot: String,
pub asset_object: Option<module_assets::AssetObjectUpsertSnapshot>,
pub project_resource: Option<EditorProjectResourceRecord>,
pub asset: Option<EditorAssetRecord>,
pub binding: Option<module_assets::AssetEntityBindingSnapshot>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct EditorGenerationResultPersistRecord {
pub status: EditorGenerationResultPersistStatusRecord,
pub items: Vec<EditorGenerationResultPersistedItemRecord>,
pub project: Option<EditorProjectRecord>,
pub job: Option<ExternalGenerationJobRecord>,
}
impl From<EditorSpritesheetSlicePersistItemRecordInput>
for crate::module_bindings::EditorSpritesheetSlicePersistItemInput
{
fn from(input: EditorSpritesheetSlicePersistItemRecordInput) -> Self {
Self {
asset_object: input.asset_object.into(),
project_resource: input.project_resource.map(Into::into),
asset: input.asset.map(Into::into),
}
}
}
impl From<EditorSpritesheetSliceBatchPersistRecordInput>
for crate::module_bindings::EditorSpritesheetSliceBatchPersistInput
{
fn from(input: EditorSpritesheetSliceBatchPersistRecordInput) -> Self {
Self {
owner_user_id: input.owner_user_id,
task_id: input.task_id,
group_task_id: input.group_task_id,
expected_asset_count: input.expected_asset_count,
items: input.items.into_iter().map(Into::into).collect(),
completed_at_micros: input.completed_at_micros,
}
}
}
impl From<EditorGenerationResultPersistItemRecordInput>
for crate::module_bindings::EditorGenerationResultPersistItemInput
{
fn from(input: EditorGenerationResultPersistItemRecordInput) -> Self {
Self {
slot: input.slot,
asset_object: input.asset_object.map(Into::into),
project_resource: input.project_resource.map(Into::into),
asset: input.asset.map(Into::into),
binding: input.binding.map(Into::into),
}
}
}
impl From<EditorGenerationResultPersistRecordInput>
for crate::module_bindings::EditorGenerationResultPersistInput
{
fn from(input: EditorGenerationResultPersistRecordInput) -> Self {
Self {
owner_user_id: input.owner_user_id,
operation_kind: input.operation_kind,
operation_id: input.operation_id,
operation_fingerprint: input.operation_fingerprint,
items: input.items.into_iter().map(Into::into).collect(),
canvas_layout: input.canvas_layout.map(Into::into),
job_completion: input.job_completion.map(Into::into),
completed_at_micros: input.completed_at_micros,
}
}
}
impl SpacetimeClient {
pub async fn resolve_editor_reference(
&self,
input: EditorReferenceResolveRecordInput,
) -> Result<EditorReferenceRecord, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"resolve_editor_reference_and_return",
move |connection, sender| {
connection
.procedures()
.resolve_editor_reference_and_return_then(procedure_input, move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_reference_procedure_result);
send_once(&sender, mapped);
});
},
)
.await
}
pub async fn preflight_editor_generation_target(
&self,
input: EditorGenerationTargetPreflightRecordInput,
) -> Result<(), SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"preflight_editor_generation_target_and_return",
move |connection, sender| {
connection
.procedures()
.preflight_editor_generation_target_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_generation_target_preflight_result);
send_once(&sender, mapped);
},
);
},
)
.await
}
pub async fn preflight_editor_pixel_art_result(
&self,
input: EditorPixelArtResultPreflightRecordInput,
) -> Result<(), SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"preflight_editor_pixel_art_result_and_return",
move |connection, sender| {
connection
.procedures()
.preflight_editor_pixel_art_result_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_pixel_art_result_preflight_result);
send_once(&sender, mapped);
},
);
},
)
.await
}
pub async fn persist_editor_pixel_art_result(
&self,
input: EditorPixelArtResultPersistRecordInput,
) -> Result<EditorPixelArtResultPersistRecord, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"persist_editor_pixel_art_result_and_return",
move |connection, sender| {
connection
.procedures()
.persist_editor_pixel_art_result_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_pixel_art_result_persist_result);
send_once(&sender, mapped);
},
);
},
)
.await
}
pub async fn persist_editor_spritesheet_slice_batch(
&self,
input: EditorSpritesheetSliceBatchPersistRecordInput,
) -> Result<EditorSpritesheetSliceBatchPersistRecord, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"persist_editor_spritesheet_slice_batch_and_return",
move |connection, sender| {
connection
.procedures()
.persist_editor_spritesheet_slice_batch_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_spritesheet_slice_batch_persist_result);
send_once(&sender, mapped);
},
);
},
)
.await
}
pub async fn persist_editor_generation_result(
&self,
input: EditorGenerationResultPersistRecordInput,
) -> Result<EditorGenerationResultPersistRecord, SpacetimeClientError> {
self.persist_editor_generation_result_with_dispatch(input, || {})
.await
}
pub async fn persist_editor_generation_result_with_dispatch<F>(
&self,
input: EditorGenerationResultPersistRecordInput,
on_dispatch: F,
) -> Result<EditorGenerationResultPersistRecord, SpacetimeClientError>
where
F: FnOnce() + Send + 'static,
{
let procedure_input = input.into();
self.call_after_connect(
"persist_editor_generation_result_and_return",
move |connection, sender| {
on_dispatch();
connection
.procedures()
.persist_editor_generation_result_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_generation_result_persist_result);
send_once(&sender, mapped);
},
);
},
)
.await
}
pub async fn create_editor_project(
&self,
input: EditorProjectCreateRecordInput,
) -> Result<EditorProjectRecord, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"create_editor_project_and_return",
move |connection, sender| {
connection
.procedures()
.create_editor_project_and_return_then(procedure_input, move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_project_required_procedure_result);
send_once(&sender, mapped);
});
},
)
.await
}
pub async fn get_recent_editor_project(
&self,
owner_user_id: String,
) -> Result<Option<EditorProjectRecord>, SpacetimeClientError> {
let procedure_input = EditorProjectGetRecentInput { owner_user_id };
self.call_after_connect(
"get_recent_editor_project_and_return",
move |connection, sender| {
connection
.procedures()
.get_recent_editor_project_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_project_optional_procedure_result);
send_once(&sender, mapped);
},
);
},
)
.await
}
pub async fn get_editor_project(
&self,
input: EditorProjectGetRecordInput,
) -> Result<EditorProjectRecord, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"get_editor_project_and_return",
move |connection, sender| {
connection.procedures().get_editor_project_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_project_required_procedure_result);
send_once(&sender, mapped);
},
);
},
)
.await
}
pub async fn list_editor_projects(
&self,
owner_user_id: String,
) -> Result<Vec<EditorProjectRecord>, SpacetimeClientError> {
let procedure_input = EditorProjectListInput { owner_user_id };
self.call_after_connect(
"list_editor_projects_and_return",
move |connection, sender| {
connection
.procedures()
.list_editor_projects_and_return_then(procedure_input, move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_project_list_procedure_result);
send_once(&sender, mapped);
});
},
)
.await
}
pub async fn rename_editor_project(
&self,
input: EditorProjectRenameRecordInput,
) -> Result<EditorProjectRecord, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"rename_editor_project_and_return",
move |connection, sender| {
connection
.procedures()
.rename_editor_project_and_return_then(procedure_input, move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_project_required_procedure_result);
send_once(&sender, mapped);
});
},
)
.await
}
pub async fn delete_editor_project(
&self,
input: EditorProjectDeleteRecordInput,
) -> Result<String, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"delete_editor_project_and_return",
move |connection, sender| {
connection
.procedures()
.delete_editor_project_and_return_then(procedure_input, move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_project_delete_procedure_result);
send_once(&sender, mapped);
});
},
)
.await
}
pub async fn save_editor_project_layout(
&self,
input: EditorProjectLayoutSaveRecordInput,
) -> Result<EditorProjectRecord, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"save_editor_project_layout_and_return",
move |connection, sender| {
connection
.procedures()
.save_editor_project_layout_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_project_required_procedure_result);
send_once(&sender, mapped);
},
);
},
)
.await
}
pub async fn save_editor_project_layout_ack(
&self,
input: EditorProjectLayoutSaveRecordInput,
) -> Result<EditorProjectLayoutSaveAckRecord, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"save_editor_project_layout_ack",
move |connection, sender| {
connection.procedures().save_editor_project_layout_ack_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_project_layout_save_procedure_result);
send_once(&sender, mapped);
},
);
},
)
.await
}
pub async fn save_editor_project_layout_v2_ack(
&self,
input: EditorProjectLayoutSaveV2RecordInput,
) -> Result<EditorProjectLayoutSaveV2AckRecord, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"save_editor_project_layout_v_2_ack",
move |connection, sender| {
connection
.procedures()
.save_editor_project_layout_v_2_ack_then(procedure_input, move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_project_layout_save_v2_procedure_result);
send_once(&sender, mapped);
});
},
)
.await
}
pub async fn save_editor_project_layout_v2(
&self,
input: EditorProjectLayoutSaveV2RecordInput,
) -> Result<EditorProjectRecord, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"save_editor_project_layout_v_2_and_return",
move |connection, sender| {
connection
.procedures()
.save_editor_project_layout_v_2_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_project_required_procedure_result);
send_once(&sender, mapped);
},
);
},
)
.await
}
pub async fn create_editor_project_resource(
&self,
input: EditorProjectResourceCreateRecordInput,
) -> Result<EditorProjectResourceRecord, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"create_editor_project_resource_and_return",
move |connection, sender| {
connection
.procedures()
.create_editor_project_resource_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_project_resource_procedure_result);
send_once(&sender, mapped);
},
);
},
)
.await
}
pub async fn repair_editor_project_resource_media(
&self,
input: EditorProjectResourceMediaRepairRecordInput,
) -> Result<EditorProjectResourceRecord, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"repair_editor_project_resource_media_and_return",
move |connection, sender| {
connection
.procedures()
.repair_editor_project_resource_media_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_project_resource_procedure_result);
send_once(&sender, mapped);
},
);
},
)
.await
}
pub async fn update_editor_project_resource_showcase(
&self,
input: EditorProjectResourceShowcaseUpdateRecordInput,
) -> Result<EditorProjectResourceRecord, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"update_editor_project_resource_showcase_and_return",
move |connection, sender| {
connection
.procedures()
.update_editor_project_resource_showcase_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_project_resource_procedure_result);
send_once(&sender, mapped);
},
);
},
)
.await
}
pub async fn list_public_editor_project_resources(
&self,
input: EditorProjectResourcePublicShowcaseListRecordInput,
) -> Result<Vec<EditorProjectResourceRecord>, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"list_public_editor_project_resources_and_return",
move |connection, sender| {
connection
.procedures()
.list_public_editor_project_resources_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_project_resource_list_procedure_result);
send_once(&sender, mapped);
},
);
},
)
.await
}
pub async fn get_editor_asset_library(
&self,
owner_user_id: String,
now_micros: i64,
) -> Result<EditorAssetLibraryRecord, SpacetimeClientError> {
let procedure_input = EditorAssetLibraryGetInput {
owner_user_id,
now_micros,
};
self.call_after_connect(
"get_editor_asset_library_and_return",
move |connection, sender| {
connection
.procedures()
.get_editor_asset_library_and_return_then(procedure_input, move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_asset_library_procedure_result);
send_once(&sender, mapped);
});
},
)
.await
}
pub async fn admin_list_editor_assets(
&self,
input: AdminEditorAssetListRecordInput,
) -> Result<Vec<AdminEditorAssetRecord>, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"admin_list_editor_assets_and_return",
move |connection, sender| {
connection
.procedures()
.admin_list_editor_assets_and_return_then(procedure_input, move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_admin_editor_asset_list_procedure_result);
send_once(&sender, mapped);
});
},
)
.await
}
pub async fn find_editor_asset_group_source(
&self,
input: EditorAssetGroupSourceLookupRecordInput,
) -> Result<Option<EditorAssetRecord>, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"find_editor_asset_group_source_and_return",
move |connection, sender| {
connection
.procedures()
.find_editor_asset_group_source_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_optional_editor_asset_procedure_result);
send_once(&sender, mapped);
},
);
},
)
.await
}
pub async fn create_editor_asset_folder(
&self,
input: EditorAssetFolderCreateRecordInput,
) -> Result<EditorAssetFolderRecord, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"create_editor_asset_folder_and_return",
move |connection, sender| {
connection
.procedures()
.create_editor_asset_folder_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_asset_folder_procedure_result);
send_once(&sender, mapped);
},
);
},
)
.await
}
pub async fn update_editor_asset_folder(
&self,
input: EditorAssetFolderUpdateRecordInput,
) -> Result<EditorAssetFolderRecord, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"update_editor_asset_folder_and_return",
move |connection, sender| {
connection
.procedures()
.update_editor_asset_folder_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_asset_folder_procedure_result);
send_once(&sender, mapped);
},
);
},
)
.await
}
pub async fn delete_editor_asset_folder(
&self,
input: EditorAssetFolderDeleteRecordInput,
) -> Result<EditorAssetLibraryRecord, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"delete_editor_asset_folder_and_return",
move |connection, sender| {
connection
.procedures()
.delete_editor_asset_folder_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_asset_folder_library_procedure_result);
send_once(&sender, mapped);
},
);
},
)
.await
}
pub async fn create_editor_asset(
&self,
input: EditorAssetCreateRecordInput,
) -> Result<EditorAssetRecord, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"create_editor_asset_and_return",
move |connection, sender| {
connection.procedures().create_editor_asset_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_asset_procedure_result);
send_once(&sender, mapped);
},
);
},
)
.await
}
pub async fn complete_editor_asset_group_cohort(
&self,
input: EditorAssetGroupCohortCompleteRecordInput,
) -> Result<EditorAssetRecord, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"complete_editor_asset_group_cohort_and_return",
move |connection, sender| {
connection
.procedures()
.complete_editor_asset_group_cohort_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_asset_procedure_result);
send_once(&sender, mapped);
},
);
},
)
.await
}
pub async fn update_editor_asset(
&self,
input: EditorAssetUpdateRecordInput,
) -> Result<EditorAssetRecord, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"update_editor_asset_and_return",
move |connection, sender| {
connection.procedures().update_editor_asset_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_asset_procedure_result);
send_once(&sender, mapped);
},
);
},
)
.await
}
pub async fn repair_editor_asset_media(
&self,
input: EditorAssetMediaRepairRecordInput,
) -> Result<EditorAssetRecord, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"repair_editor_asset_media_and_return",
move |connection, sender| {
connection
.procedures()
.repair_editor_asset_media_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_asset_procedure_result);
send_once(&sender, mapped);
},
);
},
)
.await
}
pub async fn delete_editor_asset(
&self,
input: EditorAssetDeleteRecordInput,
) -> Result<EditorAssetRecord, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"delete_editor_asset_and_return",
move |connection, sender| {
connection.procedures().delete_editor_asset_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_asset_procedure_result);
send_once(&sender, mapped);
},
);
},
)
.await
}
pub async fn submit_editor_showcase_asset(
&self,
input: EditorShowcaseAssetSubmitRecordInput,
) -> Result<EditorShowcaseAssetRecord, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"submit_editor_showcase_asset_and_return",
move |connection, sender| {
connection
.procedures()
.submit_editor_showcase_asset_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_showcase_asset_procedure_result);
send_once(&sender, mapped);
},
);
},
)
.await
}
pub async fn list_public_editor_showcase_assets(
&self,
input: EditorShowcaseAssetPublicListRecordInput,
) -> Result<Vec<EditorShowcaseAssetRecord>, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"list_public_editor_showcase_assets_and_return",
move |connection, sender| {
connection
.procedures()
.list_public_editor_showcase_assets_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_showcase_asset_list_procedure_result);
send_once(&sender, mapped);
},
);
},
)
.await
}
pub async fn list_public_editor_showcase_assets_for_viewer(
&self,
input: EditorShowcaseAssetViewerListRecordInput,
) -> Result<Vec<EditorShowcaseAssetViewerRecord>, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"list_public_editor_showcase_assets_for_viewer_and_return",
move |connection, sender| {
connection
.procedures()
.list_public_editor_showcase_assets_for_viewer_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_showcase_asset_viewer_list_procedure_result);
send_once(&sender, mapped);
},
);
},
)
.await
}
pub async fn admin_list_editor_showcase_assets(
&self,
input: EditorShowcaseAssetAdminListRecordInput,
) -> Result<Vec<EditorShowcaseAssetRecord>, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"admin_list_editor_showcase_assets_and_return",
move |connection, sender| {
connection
.procedures()
.admin_list_editor_showcase_assets_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_showcase_asset_list_procedure_result);
send_once(&sender, mapped);
},
);
},
)
.await
}
pub async fn admin_review_editor_showcase_asset(
&self,
input: EditorShowcaseAssetAdminReviewRecordInput,
) -> Result<EditorShowcaseAssetRecord, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"admin_review_editor_showcase_asset_and_return",
move |connection, sender| {
connection
.procedures()
.admin_review_editor_showcase_asset_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_showcase_asset_procedure_result);
send_once(&sender, mapped);
},
);
},
)
.await
}
pub async fn update_editor_showcase_asset_display(
&self,
input: EditorShowcaseAssetDisplayUpdateRecordInput,
) -> Result<EditorShowcaseAssetRecord, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"update_editor_showcase_asset_display_and_return",
move |connection, sender| {
connection
.procedures()
.update_editor_showcase_asset_display_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_showcase_asset_procedure_result);
send_once(&sender, mapped);
},
);
},
)
.await
}
pub async fn mark_editor_showcase_asset_refunded(
&self,
input: EditorShowcaseAssetRefundMarkRecordInput,
) -> Result<EditorShowcaseAssetRecord, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"mark_editor_showcase_asset_refunded_and_return",
move |connection, sender| {
connection
.procedures()
.mark_editor_showcase_asset_refunded_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_showcase_asset_procedure_result);
send_once(&sender, mapped);
},
);
},
)
.await
}
pub async fn set_editor_showcase_asset_like_for_viewer(
&self,
input: EditorShowcaseAssetLikeToggleRecordInput,
) -> Result<EditorShowcaseAssetViewerRecord, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"set_editor_showcase_asset_like_for_viewer_and_return",
move |connection, sender| {
connection
.procedures()
.set_editor_showcase_asset_like_for_viewer_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_showcase_asset_viewer_procedure_result);
send_once(&sender, mapped);
},
);
},
)
.await
}
pub async fn get_editor_showcase_campaign_config(
&self,
input: EditorShowcaseCampaignConfigGetRecordInput,
) -> Result<Option<EditorShowcaseCampaignConfigRecord>, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"get_editor_showcase_campaign_config_and_return",
move |connection, sender| {
connection
.procedures()
.get_editor_showcase_campaign_config_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_showcase_campaign_config_procedure_result);
send_once(&sender, mapped);
},
);
},
)
.await
}
pub async fn upsert_editor_showcase_campaign_config(
&self,
input: EditorShowcaseCampaignConfigUpsertRecordInput,
) -> Result<Option<EditorShowcaseCampaignConfigRecord>, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"upsert_editor_showcase_campaign_config_and_return",
move |connection, sender| {
connection
.procedures()
.upsert_editor_showcase_campaign_config_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(map_editor_showcase_campaign_config_procedure_result);
send_once(&sender, mapped);
},
);
},
)
.await
}
pub async fn get_editor_generation_pricing_config(
&self,
) -> Result<Option<EditorGenerationPricingConfigRecord>, SpacetimeClientError> {
self.call_after_connect(
"get_editor_generation_pricing_config_and_return",
move |connection, sender| {
connection
.procedures()
.get_editor_generation_pricing_config_and_return_then(move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(
map_editor_generation_pricing_config_optional_procedure_result,
);
send_once(&sender, mapped);
});
},
)
.await
}
pub async fn upsert_editor_generation_pricing_config(
&self,
input: EditorGenerationPricingConfigUpsertRecordInput,
) -> Result<EditorGenerationPricingConfigRecord, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"upsert_editor_generation_pricing_config_and_return",
move |connection, sender| {
connection
.procedures()
.upsert_editor_generation_pricing_config_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(
map_editor_generation_pricing_config_required_procedure_result,
);
send_once(&sender, mapped);
},
);
},
)
.await
}
pub async fn initialize_editor_generation_pricing_config_if_missing(
&self,
input: EditorGenerationPricingConfigUpsertRecordInput,
) -> Result<EditorGenerationPricingConfigRecord, SpacetimeClientError> {
let procedure_input = input.into();
self.call_after_connect(
"initialize_editor_generation_pricing_config_if_missing_and_return",
move |connection, sender| {
connection
.procedures()
.initialize_editor_generation_pricing_config_if_missing_and_return_then(
procedure_input,
move |_, result| {
let mapped = result
.map_err(SpacetimeClientError::from_sdk_error)
.and_then(
map_editor_generation_pricing_config_required_procedure_result,
);
send_once(&sender, mapped);
},
);
},
)
.await
}
}
fn map_editor_pixel_art_result_preflight_result(
result: crate::module_bindings::EditorPixelArtResultPreflightResult,
) -> Result<(), SpacetimeClientError> {
if result.ok {
return Ok(());
}
Err(SpacetimeClientError::procedure_failed(result.error_message))
}
fn map_editor_generation_target_preflight_result(
result: crate::module_bindings::EditorGenerationTargetPreflightResult,
) -> Result<(), SpacetimeClientError> {
if result.ok {
return Ok(());
}
Err(SpacetimeClientError::procedure_failed(result.error_message))
}
fn map_editor_pixel_art_result_persist_result(
result: crate::module_bindings::EditorPixelArtResultPersistResult,
) -> Result<EditorPixelArtResultPersistRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
// 中文注释:三种落库状态(Applied / DialogMissing / AlreadyApplied)目前没有任何
// 上层消费方,不再向上透传;但状态缺失说明 procedure 返回体不完整,仍要拦在这里。
if result.status.is_none() {
return Err(SpacetimeClientError::validation_failed(
"完美像素持久化结果缺少状态",
));
}
let asset_object = result.asset_object.ok_or_else(|| {
SpacetimeClientError::validation_failed("完美像素持久化结果缺少 asset object")
})?;
let project_resource = result
.project_resource
.ok_or_else(|| SpacetimeClientError::validation_failed("完美像素持久化结果缺少项目资源"))?;
let asset = result
.asset
.ok_or_else(|| SpacetimeClientError::validation_failed("完美像素持久化结果缺少账号素材"))?;
let project = result
.project
.map(|project| {
map_editor_project_optional_procedure_result(
crate::module_bindings::EditorProjectProcedureResult {
ok: true,
project: Some(project),
error_message: None,
},
)
})
.transpose()?
.flatten();
Ok(EditorPixelArtResultPersistRecord {
asset_object: map_editor_spritesheet_asset_object_snapshot(asset_object),
project_resource: map_editor_spritesheet_project_resource_snapshot(project_resource)?,
asset: map_editor_spritesheet_asset_snapshot(asset)?,
project,
})
}
fn map_editor_spritesheet_slice_batch_persist_result(
result: crate::module_bindings::EditorSpritesheetSliceBatchPersistResult,
) -> Result<EditorSpritesheetSliceBatchPersistRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let items = result
.items
.into_iter()
.map(|item| {
Ok(EditorSpritesheetSlicePersistedItemRecord {
asset_object: map_editor_spritesheet_asset_object_snapshot(item.asset_object),
project_resource: item
.project_resource
.map(map_editor_spritesheet_project_resource_snapshot)
.transpose()?,
asset: item
.asset
.map(map_editor_spritesheet_asset_snapshot)
.transpose()?,
})
})
.collect::<Result<Vec<_>, SpacetimeClientError>>()?;
Ok(EditorSpritesheetSliceBatchPersistRecord { items })
}
fn map_editor_generation_result_persist_result(
result: crate::module_bindings::EditorGenerationResultPersistResult,
) -> Result<EditorGenerationResultPersistRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let status = match result
.status
.ok_or_else(|| SpacetimeClientError::validation_failed("编辑器生成结果原子提交缺少状态"))?
{
crate::module_bindings::EditorGenerationResultPersistStatus::Applied => {
EditorGenerationResultPersistStatusRecord::Applied
}
crate::module_bindings::EditorGenerationResultPersistStatus::AlreadyApplied => {
EditorGenerationResultPersistStatusRecord::AlreadyApplied
}
};
let items = result
.items
.into_iter()
.map(|item| {
Ok(EditorGenerationResultPersistedItemRecord {
slot: item.slot,
asset_object: item
.asset_object
.map(map_editor_spritesheet_asset_object_snapshot),
project_resource: item
.project_resource
.map(map_editor_spritesheet_project_resource_snapshot)
.transpose()?,
asset: item
.asset
.map(map_editor_spritesheet_asset_snapshot)
.transpose()?,
binding: item.binding.map(map_editor_generation_binding_snapshot),
})
})
.collect::<Result<Vec<_>, SpacetimeClientError>>()?;
let project = result
.project
.map(|project| {
map_editor_project_optional_procedure_result(
crate::module_bindings::EditorProjectProcedureResult {
ok: true,
project: Some(project),
error_message: None,
},
)
})
.transpose()?
.flatten();
let job = result.job.map(map_external_generation_job_snapshot);
Ok(EditorGenerationResultPersistRecord {
status,
items,
project,
job,
})
}
fn map_editor_generation_binding_snapshot(
snapshot: crate::module_bindings::AssetEntityBindingSnapshot,
) -> module_assets::AssetEntityBindingSnapshot {
module_assets::AssetEntityBindingSnapshot {
binding_id: snapshot.binding_id,
asset_object_id: snapshot.asset_object_id,
entity_kind: snapshot.entity_kind,
entity_id: snapshot.entity_id,
slot: snapshot.slot,
asset_kind: snapshot.asset_kind,
owner_user_id: snapshot.owner_user_id,
profile_id: snapshot.profile_id,
created_at_micros: snapshot.created_at_micros,
updated_at_micros: snapshot.updated_at_micros,
}
}
fn map_editor_spritesheet_asset_object_snapshot(
snapshot: crate::module_bindings::AssetObjectUpsertSnapshot,
) -> module_assets::AssetObjectUpsertSnapshot {
module_assets::AssetObjectUpsertSnapshot {
asset_object_id: snapshot.asset_object_id,
bucket: snapshot.bucket,
object_key: snapshot.object_key,
access_policy: match snapshot.access_policy {
crate::module_bindings::AssetObjectAccessPolicy::Private => {
module_assets::AssetObjectAccessPolicy::Private
}
crate::module_bindings::AssetObjectAccessPolicy::PublicRead => {
module_assets::AssetObjectAccessPolicy::PublicRead
}
},
content_type: snapshot.content_type,
content_length: snapshot.content_length,
content_hash: snapshot.content_hash,
version: snapshot.version,
source_job_id: snapshot.source_job_id,
owner_user_id: snapshot.owner_user_id,
profile_id: snapshot.profile_id,
entity_id: snapshot.entity_id,
asset_kind: snapshot.asset_kind,
created_at_micros: snapshot.created_at_micros,
updated_at_micros: snapshot.updated_at_micros,
}
}
fn map_editor_spritesheet_project_resource_snapshot(
snapshot: crate::module_bindings::EditorProjectResourceSnapshot,
) -> Result<EditorProjectResourceRecord, SpacetimeClientError> {
Ok(EditorProjectResourceRecord {
resource_id: snapshot.resource_id,
project_id: snapshot.project_id,
owner_user_id: snapshot.owner_user_id,
asset_object_id: snapshot.asset_object_id,
image_src: snapshot.image_src,
object_key: snapshot.object_key,
width: snapshot.width,
height: snapshot.height,
source_type: snapshot.source_type,
prompt: snapshot.prompt,
actual_prompt: snapshot.actual_prompt,
model: snapshot.model,
provider: snapshot.provider,
task_id: snapshot.task_id,
source_resource_id: snapshot.source_resource_id,
asset_kind: snapshot.asset_kind,
generation_inputs: parse_editor_spritesheet_generation_inputs(
snapshot.generation_inputs_json,
"图片画布图集切片资源生成输入 JSON",
)?,
image_sequence_frames: parse_editor_spritesheet_generation_inputs(
snapshot.image_sequence_frames_json,
"图片画布图集切片资源序列帧 JSON",
)?,
image_sequence_duration_ms: snapshot.image_sequence_duration_ms,
public_showcase_enabled: snapshot.public_showcase_enabled,
created_at: format_timestamp_micros(snapshot.created_at_micros),
updated_at: format_timestamp_micros(snapshot.updated_at_micros),
})
}
fn map_editor_spritesheet_asset_snapshot(
snapshot: crate::module_bindings::EditorAssetSnapshot,
) -> Result<EditorAssetRecord, SpacetimeClientError> {
Ok(EditorAssetRecord {
asset_id: snapshot.asset_id,
folder_id: snapshot.folder_id,
label: snapshot.label,
asset_object_id: snapshot.asset_object_id,
image_src: snapshot.image_src,
object_key: snapshot.object_key,
width: snapshot.width,
height: snapshot.height,
source_type: snapshot.source_type,
prompt: snapshot.prompt,
actual_prompt: snapshot.actual_prompt,
model: snapshot.model,
provider: snapshot.provider,
task_id: snapshot.task_id,
asset_kind: snapshot.asset_kind,
generation_inputs: parse_editor_spritesheet_generation_inputs(
snapshot.generation_inputs_json,
"图片画布图集切片素材生成输入 JSON",
)?,
image_sequence_frames: parse_editor_spritesheet_generation_inputs(
snapshot.image_sequence_frames_json,
"图片画布图集切片素材序列帧 JSON",
)?,
image_sequence_duration_ms: snapshot.image_sequence_duration_ms,
source_resource_id: snapshot.source_resource_id,
public_showcase_enabled: snapshot.public_showcase_enabled,
thumbnail_src: snapshot.thumbnail_src,
generation_cost_mud_points: snapshot.generation_cost_mud_points,
showcase_id: snapshot.showcase_id,
showcase_review_status: snapshot.showcase_review_status,
showcase_display_enabled: snapshot.showcase_display_enabled,
showcase_like_count: snapshot.showcase_like_count,
created_at: format_timestamp_micros(snapshot.created_at_micros),
updated_at: format_timestamp_micros(snapshot.updated_at_micros),
})
}
fn parse_editor_spritesheet_generation_inputs(
raw: Option<String>,
label: &str,
) -> Result<Option<serde_json::Value>, SpacetimeClientError> {
raw.map(|value| {
serde_json::from_str(&value).map_err(|error| {
SpacetimeClientError::validation_failed(format!("{label} 无法解析:{error}"))
})
})
.transpose()
}
#[cfg(test)]
mod editor_generation_result_persist_mapper_tests {
use super::*;
fn empty_result(ok: bool) -> crate::module_bindings::EditorGenerationResultPersistResult {
crate::module_bindings::EditorGenerationResultPersistResult {
ok,
status: None,
items: Vec::new(),
project: None,
job: None,
error_message: None,
}
}
#[test]
fn editor_generation_result_mapper_preserves_procedure_failure() {
let mut result = empty_result(false);
result.error_message = Some("事务提交失败".to_string());
let error = map_editor_generation_result_persist_result(result)
.expect_err("ok=false 必须保留 procedure 失败");
assert!(matches!(
error,
SpacetimeClientError::Procedure(message) if message == "事务提交失败"
));
}
#[test]
fn editor_generation_result_mapper_requires_status() {
let error = map_editor_generation_result_persist_result(empty_result(true))
.expect_err("成功结果缺少状态时必须拒绝");
assert!(matches!(
error,
SpacetimeClientError::Runtime(message)
if message == "编辑器生成结果原子提交缺少状态"
));
}
#[test]
fn editor_generation_result_mapper_preserves_apply_status() {
let cases = [
(
crate::module_bindings::EditorGenerationResultPersistStatus::Applied,
EditorGenerationResultPersistStatusRecord::Applied,
),
(
crate::module_bindings::EditorGenerationResultPersistStatus::AlreadyApplied,
EditorGenerationResultPersistStatusRecord::AlreadyApplied,
),
];
for (binding_status, expected) in cases {
let mut result = empty_result(true);
result.status = Some(binding_status);
let mapped =
map_editor_generation_result_persist_result(result).expect("完整状态应映射成功");
assert_eq!(mapped.status, expected);
assert!(mapped.items.is_empty());
assert!(mapped.project.is_none());
assert!(mapped.job.is_none());
}
}
}
#[cfg(test)]
mod pixel_art_persist_mapper_tests {
use super::*;
fn empty_pixel_art_persist_result(
ok: bool,
) -> crate::module_bindings::EditorPixelArtResultPersistResult {
crate::module_bindings::EditorPixelArtResultPersistResult {
ok,
status: None,
asset_object: None,
project_resource: None,
asset: None,
project: None,
error_message: None,
}
}
fn pixel_art_asset_object_snapshot() -> crate::module_bindings::AssetObjectUpsertSnapshot {
crate::module_bindings::AssetObjectUpsertSnapshot {
asset_object_id: "assetobj_pixel".to_string(),
bucket: "private".to_string(),
object_key: "editor/pixel.png".to_string(),
access_policy: crate::module_bindings::AssetObjectAccessPolicy::Private,
content_type: Some("image/png".to_string()),
content_length: 16,
content_hash: Some("output-sha256".to_string()),
version: 1,
source_job_id: Some("pixel-art-snap-dialog-1".to_string()),
owner_user_id: Some("user-1".to_string()),
profile_id: None,
entity_id: Some("project-1".to_string()),
asset_kind: "editor_pixel_art_snap".to_string(),
created_at_micros: 10,
updated_at_micros: 11,
}
}
fn pixel_art_project_resource_snapshot() -> crate::module_bindings::EditorProjectResourceSnapshot
{
crate::module_bindings::EditorProjectResourceSnapshot {
resource_id: "editor-resource-pixel".to_string(),
project_id: "project-1".to_string(),
owner_user_id: "user-1".to_string(),
asset_object_id: Some("assetobj_pixel".to_string()),
image_src: "/api/assets/assetobj_pixel/content".to_string(),
object_key: Some("editor/pixel.png".to_string()),
width: 32,
height: 32,
source_type: "perfect-pixel".to_string(),
prompt: None,
actual_prompt: None,
model: None,
provider: None,
task_id: Some("pixel-art-snap-dialog-1".to_string()),
source_resource_id: Some("source-resource".to_string()),
asset_kind: None,
generation_inputs_json: Some(r#"{"source":"canvas"}"#.to_string()),
public_showcase_enabled: false,
created_at_micros: 10,
updated_at_micros: 11,
image_sequence_frames_json: None,
image_sequence_duration_ms: None,
}
}
fn pixel_art_asset_snapshot() -> crate::module_bindings::EditorAssetSnapshot {
crate::module_bindings::EditorAssetSnapshot {
asset_id: "editor-asset-pixel".to_string(),
folder_id: "folder-1".to_string(),
label: "Pixel result".to_string(),
asset_object_id: Some("assetobj_pixel".to_string()),
image_src: "/api/assets/assetobj_pixel/content".to_string(),
object_key: Some("editor/pixel.png".to_string()),
width: 32,
height: 32,
source_type: "perfect-pixel".to_string(),
prompt: None,
actual_prompt: None,
model: None,
provider: None,
task_id: Some("pixel-art-snap-dialog-1".to_string()),
asset_kind: None,
generation_inputs_json: Some(r#"{"source":"canvas"}"#.to_string()),
source_resource_id: Some("source-resource".to_string()),
public_showcase_enabled: Some(false),
created_at_micros: 10,
updated_at_micros: 11,
thumbnail_src: None,
generation_cost_mud_points: 0,
showcase_id: None,
showcase_review_status: None,
showcase_display_enabled: None,
showcase_like_count: None,
group_task_id: None,
image_sequence_frames_json: None,
image_sequence_duration_ms: None,
}
}
fn successful_pixel_art_persist_result(
status: crate::module_bindings::EditorPixelArtResultPersistStatus,
) -> crate::module_bindings::EditorPixelArtResultPersistResult {
crate::module_bindings::EditorPixelArtResultPersistResult {
ok: true,
status: Some(status),
asset_object: Some(pixel_art_asset_object_snapshot()),
project_resource: Some(pixel_art_project_resource_snapshot()),
asset: Some(pixel_art_asset_snapshot()),
project: None,
error_message: None,
}
}
#[test]
fn editor_pixel_art_persist_mapper_rejects_procedure_failure_before_snapshots() {
let mut result = empty_pixel_art_persist_result(false);
result.error_message = Some("事务提交失败".to_string());
let error = map_editor_pixel_art_result_persist_result(result)
.expect_err("ok=false 必须保留 procedure 失败");
assert!(matches!(
error,
SpacetimeClientError::Procedure(message) if message == "事务提交失败"
));
}
#[test]
fn editor_pixel_art_persist_mapper_rejects_missing_required_snapshot() {
let mut result = empty_pixel_art_persist_result(true);
result.status = Some(crate::module_bindings::EditorPixelArtResultPersistStatus::Applied);
let error = map_editor_pixel_art_result_persist_result(result)
.expect_err("成功结果缺少 asset object 时必须拒绝");
assert!(matches!(
error,
SpacetimeClientError::Runtime(message)
if message == "完美像素持久化结果缺少 asset object"
));
}
#[test]
fn editor_pixel_art_persist_mapper_rejects_missing_status() {
let mut result = successful_pixel_art_persist_result(
crate::module_bindings::EditorPixelArtResultPersistStatus::Applied,
);
result.status = None;
let error = map_editor_pixel_art_result_persist_result(result)
.expect_err("成功结果缺少状态时必须拒绝");
assert!(matches!(
error,
SpacetimeClientError::Runtime(message)
if message == "完美像素持久化结果缺少状态"
));
}
#[test]
fn editor_pixel_art_persist_mapper_accepts_every_status_and_missing_project() {
let cases = [
crate::module_bindings::EditorPixelArtResultPersistStatus::Applied,
crate::module_bindings::EditorPixelArtResultPersistStatus::DialogMissing,
crate::module_bindings::EditorPixelArtResultPersistStatus::AlreadyApplied,
];
for binding_status in cases {
let mapped = map_editor_pixel_art_result_persist_result(
successful_pixel_art_persist_result(binding_status),
)
.expect("project=None 仍应映射成功");
assert_eq!(mapped.asset_object.asset_object_id, "assetobj_pixel");
assert_eq!(mapped.project_resource.resource_id, "editor-resource-pixel");
assert_eq!(mapped.asset.asset_id, "editor-asset-pixel");
assert!(mapped.project.is_none());
}
}
}