新增编辑器业务引用窄查询
分别按项目资源 ID 与素材 ID 主键查询并在双表冲突时失败关闭 校验资源归属、对象存储位置与 asset object 所有权 补齐 Spacetime 客户端映射、facade、生成 bindings 与回归测试
This commit is contained in:
@@ -57,7 +57,8 @@ pub use self::editor_project::{
|
||||
EditorProjectLayoutSaveV2RecordInput, EditorProjectRecord, EditorProjectRenameRecordInput,
|
||||
EditorProjectResourceCreateRecordInput, EditorProjectResourceMediaRepairRecordInput,
|
||||
EditorProjectResourcePublicShowcaseListRecordInput, EditorProjectResourceRecord,
|
||||
EditorProjectResourceShowcaseUpdateRecordInput, EditorShowcaseAssetAdminListRecordInput,
|
||||
EditorProjectResourceShowcaseUpdateRecordInput, EditorReferenceRecord,
|
||||
EditorReferenceResolveRecordInput, EditorShowcaseAssetAdminListRecordInput,
|
||||
EditorShowcaseAssetAdminReviewRecordInput, EditorShowcaseAssetDisplayUpdateRecordInput,
|
||||
EditorShowcaseAssetLikeToggleRecordInput, EditorShowcaseAssetPublicListRecordInput,
|
||||
EditorShowcaseAssetRecord, EditorShowcaseAssetRefundMarkRecordInput,
|
||||
@@ -108,8 +109,8 @@ pub(crate) use self::editor_project::{
|
||||
map_editor_project_layout_save_v2_procedure_result, map_editor_project_list_procedure_result,
|
||||
map_editor_project_optional_procedure_result, map_editor_project_required_procedure_result,
|
||||
map_editor_project_resource_list_procedure_result,
|
||||
map_editor_project_resource_procedure_result, map_editor_showcase_asset_list_procedure_result,
|
||||
map_editor_showcase_asset_procedure_result,
|
||||
map_editor_project_resource_procedure_result, map_editor_reference_procedure_result,
|
||||
map_editor_showcase_asset_list_procedure_result, map_editor_showcase_asset_procedure_result,
|
||||
map_editor_showcase_campaign_config_procedure_result,
|
||||
map_optional_editor_asset_procedure_result,
|
||||
};
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
use super::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct EditorReferenceResolveRecordInput {
|
||||
pub owner_user_id: String,
|
||||
pub bucket: String,
|
||||
pub reference_id: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum EditorReferenceRecord {
|
||||
ProjectResource(EditorProjectResourceRecord),
|
||||
Asset(EditorAssetRecord),
|
||||
}
|
||||
|
||||
impl From<EditorReferenceResolveRecordInput>
|
||||
for crate::module_bindings::EditorReferenceResolveInput
|
||||
{
|
||||
fn from(input: EditorReferenceResolveRecordInput) -> Self {
|
||||
Self {
|
||||
owner_user_id: input.owner_user_id,
|
||||
bucket: input.bucket,
|
||||
reference_id: input.reference_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct EditorCanvasViewportRecord {
|
||||
pub x: f64,
|
||||
@@ -1061,6 +1086,25 @@ pub(crate) fn map_editor_project_resource_procedure_result(
|
||||
.ok_or_else(|| SpacetimeClientError::missing_snapshot("图片画布资源快照"))
|
||||
}
|
||||
|
||||
pub(crate) fn map_editor_reference_procedure_result(
|
||||
result: EditorReferenceProcedureResult,
|
||||
) -> Result<EditorReferenceRecord, SpacetimeClientError> {
|
||||
if !result.ok {
|
||||
return Err(SpacetimeClientError::procedure_failed(result.error_message));
|
||||
}
|
||||
match (result.resource, result.asset) {
|
||||
(Some(resource), None) => map_editor_project_resource_snapshot(resource)
|
||||
.map(EditorReferenceRecord::ProjectResource),
|
||||
(None, Some(asset)) => map_editor_asset_snapshot(asset).map(EditorReferenceRecord::Asset),
|
||||
(None, None) => Err(SpacetimeClientError::missing_snapshot(
|
||||
"编辑器引用资源或素材快照",
|
||||
)),
|
||||
(Some(_), Some(_)) => Err(SpacetimeClientError::procedure_failed(Some(
|
||||
"SpacetimeDB procedure 同时返回了编辑器资源与素材快照".to_string(),
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn map_editor_project_resource_list_procedure_result(
|
||||
result: EditorProjectResourceListProcedureResult,
|
||||
) -> Result<Vec<EditorProjectResourceRecord>, SpacetimeClientError> {
|
||||
|
||||
@@ -163,6 +163,27 @@ impl From<EditorSpritesheetSliceBatchPersistRecordInput>
|
||||
}
|
||||
|
||||
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_pixel_art_result(
|
||||
&self,
|
||||
input: EditorPixelArtResultPreflightRecordInput,
|
||||
|
||||
@@ -50,7 +50,8 @@ pub use mapper::{
|
||||
EditorProjectLayoutSaveV2RecordInput, EditorProjectRecord, EditorProjectRenameRecordInput,
|
||||
EditorProjectResourceCreateRecordInput, EditorProjectResourceMediaRepairRecordInput,
|
||||
EditorProjectResourcePublicShowcaseListRecordInput, EditorProjectResourceRecord,
|
||||
EditorProjectResourceShowcaseUpdateRecordInput, EditorShowcaseAssetAdminListRecordInput,
|
||||
EditorProjectResourceShowcaseUpdateRecordInput, EditorReferenceRecord,
|
||||
EditorReferenceResolveRecordInput, EditorShowcaseAssetAdminListRecordInput,
|
||||
EditorShowcaseAssetAdminReviewRecordInput, EditorShowcaseAssetDisplayUpdateRecordInput,
|
||||
EditorShowcaseAssetLikeToggleRecordInput, EditorShowcaseAssetPublicListRecordInput,
|
||||
EditorShowcaseAssetRecord, EditorShowcaseAssetRefundMarkRecordInput,
|
||||
|
||||
@@ -108,7 +108,8 @@ pub use self::editor_project::{
|
||||
EditorShowcaseAssetLikeToggleRecordInput, EditorShowcaseAssetPublicListRecordInput,
|
||||
EditorShowcaseAssetRecord, EditorShowcaseAssetRefundMarkRecordInput,
|
||||
EditorShowcaseAssetSubmitRecordInput, EditorShowcaseCampaignConfigGetRecordInput,
|
||||
EditorShowcaseCampaignConfigRecord, EditorShowcaseCampaignConfigUpsertRecordInput,
|
||||
EditorReferenceRecord, EditorReferenceResolveRecordInput, EditorShowcaseCampaignConfigRecord,
|
||||
EditorShowcaseCampaignConfigUpsertRecordInput,
|
||||
};
|
||||
pub use self::external_api_key::{
|
||||
ExternalApiKeyAuthenticateRecordInput, ExternalApiKeyCreateRecordInput, ExternalApiKeyRecord,
|
||||
@@ -260,7 +261,8 @@ pub(crate) use self::editor_project::{
|
||||
map_editor_project_layout_save_v2_procedure_result, map_editor_project_list_procedure_result,
|
||||
map_editor_project_optional_procedure_result, map_editor_project_required_procedure_result,
|
||||
map_editor_project_resource_list_procedure_result,
|
||||
map_editor_project_resource_procedure_result, map_editor_showcase_asset_list_procedure_result,
|
||||
map_editor_project_resource_procedure_result, map_editor_reference_procedure_result,
|
||||
map_editor_showcase_asset_list_procedure_result,
|
||||
map_editor_showcase_asset_procedure_result,
|
||||
map_editor_showcase_campaign_config_procedure_result,
|
||||
map_optional_editor_asset_procedure_result,
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
use super::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct EditorReferenceResolveRecordInput {
|
||||
pub owner_user_id: String,
|
||||
pub bucket: String,
|
||||
pub reference_id: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum EditorReferenceRecord {
|
||||
ProjectResource(EditorProjectResourceRecord),
|
||||
Asset(EditorAssetRecord),
|
||||
}
|
||||
|
||||
impl From<EditorReferenceResolveRecordInput> for crate::module_bindings::EditorReferenceResolveInput
|
||||
{
|
||||
fn from(input: EditorReferenceResolveRecordInput) -> Self {
|
||||
Self {
|
||||
owner_user_id: input.owner_user_id,
|
||||
bucket: input.bucket,
|
||||
reference_id: input.reference_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct EditorCanvasViewportRecord {
|
||||
pub x: f64,
|
||||
@@ -1061,6 +1085,25 @@ pub(crate) fn map_editor_project_resource_procedure_result(
|
||||
.ok_or_else(|| SpacetimeClientError::missing_snapshot("图片画布资源快照"))
|
||||
}
|
||||
|
||||
pub(crate) fn map_editor_reference_procedure_result(
|
||||
result: EditorReferenceProcedureResult,
|
||||
) -> Result<EditorReferenceRecord, SpacetimeClientError> {
|
||||
if !result.ok {
|
||||
return Err(SpacetimeClientError::procedure_failed(result.error_message));
|
||||
}
|
||||
match (result.resource, result.asset) {
|
||||
(Some(resource), None) => map_editor_project_resource_snapshot(resource)
|
||||
.map(EditorReferenceRecord::ProjectResource),
|
||||
(None, Some(asset)) => map_editor_asset_snapshot(asset).map(EditorReferenceRecord::Asset),
|
||||
(None, None) => Err(SpacetimeClientError::missing_snapshot(
|
||||
"编辑器引用资源或素材快照",
|
||||
)),
|
||||
(Some(_), Some(_)) => Err(SpacetimeClientError::procedure_failed(Some(
|
||||
"SpacetimeDB procedure 同时返回了编辑器资源与素材快照".to_string(),
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn map_editor_project_resource_list_procedure_result(
|
||||
result: EditorProjectResourceListProcedureResult,
|
||||
) -> Result<Vec<EditorProjectResourceRecord>, SpacetimeClientError> {
|
||||
|
||||
@@ -334,6 +334,8 @@ pub mod editor_project_snapshot_type;
|
||||
pub mod editor_project_table;
|
||||
pub mod editor_project_type;
|
||||
pub mod editor_project_viewport_snapshot_type;
|
||||
pub mod editor_reference_procedure_result_type;
|
||||
pub mod editor_reference_resolve_input_type;
|
||||
pub mod editor_showcase_asset_admin_list_input_type;
|
||||
pub mod editor_showcase_asset_admin_review_input_type;
|
||||
pub mod editor_showcase_asset_display_update_input_type;
|
||||
@@ -620,6 +622,7 @@ pub mod renew_external_generation_job_lease_and_return_procedure;
|
||||
pub mod repair_editor_asset_media_and_return_procedure;
|
||||
pub mod repair_editor_canvas_resources_and_return_procedure;
|
||||
pub mod repair_editor_project_resource_media_and_return_procedure;
|
||||
pub mod resolve_editor_reference_and_return_procedure;
|
||||
pub mod resolve_profile_recharge_refund_manual_review_and_return_procedure;
|
||||
pub mod revoke_database_migration_operator_procedure;
|
||||
pub mod revoke_external_api_key_and_return_procedure;
|
||||
@@ -1169,6 +1172,8 @@ pub use editor_project_snapshot_type::EditorProjectSnapshot;
|
||||
pub use editor_project_table::*;
|
||||
pub use editor_project_type::EditorProject;
|
||||
pub use editor_project_viewport_snapshot_type::EditorProjectViewportSnapshot;
|
||||
pub use editor_reference_procedure_result_type::EditorReferenceProcedureResult;
|
||||
pub use editor_reference_resolve_input_type::EditorReferenceResolveInput;
|
||||
pub use editor_showcase_asset_admin_list_input_type::EditorShowcaseAssetAdminListInput;
|
||||
pub use editor_showcase_asset_admin_review_input_type::EditorShowcaseAssetAdminReviewInput;
|
||||
pub use editor_showcase_asset_display_update_input_type::EditorShowcaseAssetDisplayUpdateInput;
|
||||
@@ -1455,6 +1460,7 @@ pub use renew_external_generation_job_lease_and_return_procedure::renew_external
|
||||
pub use repair_editor_asset_media_and_return_procedure::repair_editor_asset_media_and_return;
|
||||
pub use repair_editor_canvas_resources_and_return_procedure::repair_editor_canvas_resources_and_return;
|
||||
pub use repair_editor_project_resource_media_and_return_procedure::repair_editor_project_resource_media_and_return;
|
||||
pub use resolve_editor_reference_and_return_procedure::resolve_editor_reference_and_return;
|
||||
pub use resolve_profile_recharge_refund_manual_review_and_return_procedure::resolve_profile_recharge_refund_manual_review_and_return;
|
||||
pub use revoke_database_migration_operator_procedure::revoke_database_migration_operator;
|
||||
pub use revoke_external_api_key_and_return_procedure::revoke_external_api_key_and_return;
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::editor_asset_snapshot_type::EditorAssetSnapshot;
|
||||
use super::editor_project_resource_snapshot_type::EditorProjectResourceSnapshot;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub struct EditorReferenceProcedureResult {
|
||||
pub ok: bool,
|
||||
pub resource: Option<EditorProjectResourceSnapshot>,
|
||||
pub asset: Option<EditorAssetSnapshot>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for EditorReferenceProcedureResult {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub struct EditorReferenceResolveInput {
|
||||
pub owner_user_id: String,
|
||||
pub bucket: String,
|
||||
pub reference_id: String,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for EditorReferenceResolveInput {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::editor_reference_procedure_result_type::EditorReferenceProcedureResult;
|
||||
use super::editor_reference_resolve_input_type::EditorReferenceResolveInput;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
struct ResolveEditorReferenceAndReturnArgs {
|
||||
pub input: EditorReferenceResolveInput,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for ResolveEditorReferenceAndReturnArgs {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the procedure `resolve_editor_reference_and_return`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteProcedures`].
|
||||
pub trait resolve_editor_reference_and_return {
|
||||
fn resolve_editor_reference_and_return(&self, input: EditorReferenceResolveInput) {
|
||||
self.resolve_editor_reference_and_return_then(input, |_, _| {});
|
||||
}
|
||||
|
||||
fn resolve_editor_reference_and_return_then(
|
||||
&self,
|
||||
input: EditorReferenceResolveInput,
|
||||
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<EditorReferenceProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
);
|
||||
}
|
||||
|
||||
impl resolve_editor_reference_and_return for super::RemoteProcedures {
|
||||
fn resolve_editor_reference_and_return_then(
|
||||
&self,
|
||||
input: EditorReferenceResolveInput,
|
||||
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<EditorReferenceProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) {
|
||||
self.imp
|
||||
.invoke_procedure_with_callback::<_, EditorReferenceProcedureResult>(
|
||||
"resolve_editor_reference_and_return",
|
||||
ResolveEditorReferenceAndReturnArgs { input },
|
||||
__callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -663,6 +663,13 @@ pub struct EditorProjectResourceMediaRepairInput {
|
||||
pub updated_at_micros: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)]
|
||||
pub struct EditorReferenceResolveInput {
|
||||
pub owner_user_id: String,
|
||||
pub bucket: String,
|
||||
pub reference_id: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)]
|
||||
pub struct EditorProjectResourceShowcaseUpdateInput {
|
||||
pub resource_id: String,
|
||||
@@ -1226,6 +1233,14 @@ pub struct EditorProjectResourceProcedureResult {
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)]
|
||||
pub struct EditorReferenceProcedureResult {
|
||||
pub ok: bool,
|
||||
pub resource: Option<EditorProjectResourceSnapshot>,
|
||||
pub asset: Option<EditorAssetSnapshot>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, SpacetimeType)]
|
||||
pub struct EditorProjectResourceListProcedureResult {
|
||||
pub ok: bool,
|
||||
@@ -1527,6 +1542,37 @@ pub fn create_editor_project_resource_and_return(
|
||||
}
|
||||
}
|
||||
|
||||
#[spacetimedb::procedure]
|
||||
pub fn resolve_editor_reference_and_return(
|
||||
ctx: &mut ProcedureContext,
|
||||
input: EditorReferenceResolveInput,
|
||||
) -> EditorReferenceProcedureResult {
|
||||
let caller = ctx.sender();
|
||||
match ctx.try_with_tx(|tx| {
|
||||
require_editor_generation_runtime_service_identity(tx, caller)?;
|
||||
resolve_editor_reference(tx, &input)
|
||||
}) {
|
||||
Ok(ResolvedEditorReference::ProjectResource(resource)) => EditorReferenceProcedureResult {
|
||||
ok: true,
|
||||
resource: Some(resource),
|
||||
asset: None,
|
||||
error_message: None,
|
||||
},
|
||||
Ok(ResolvedEditorReference::Asset(asset)) => EditorReferenceProcedureResult {
|
||||
ok: true,
|
||||
resource: None,
|
||||
asset: Some(asset),
|
||||
error_message: None,
|
||||
},
|
||||
Err(message) => EditorReferenceProcedureResult {
|
||||
ok: false,
|
||||
resource: None,
|
||||
asset: None,
|
||||
error_message: Some(message),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[spacetimedb::procedure]
|
||||
pub fn repair_editor_project_resource_media_and_return(
|
||||
ctx: &mut ProcedureContext,
|
||||
@@ -6264,6 +6310,117 @@ fn require_editor_generation_pricing_bootstrap(
|
||||
.map_err(|_| "当前 identity 无权初始化模型定价服务身份".to_string())
|
||||
}
|
||||
|
||||
enum ResolvedEditorReference {
|
||||
ProjectResource(EditorProjectResourceSnapshot),
|
||||
Asset(EditorAssetSnapshot),
|
||||
}
|
||||
|
||||
fn resolve_editor_reference(
|
||||
ctx: &ReducerContext,
|
||||
input: &EditorReferenceResolveInput,
|
||||
) -> Result<ResolvedEditorReference, String> {
|
||||
let owner_user_id = input.owner_user_id.trim();
|
||||
let bucket = input.bucket.trim();
|
||||
if owner_user_id.is_empty() || bucket.is_empty() {
|
||||
return Err("编辑器引用的 owner_user_id 与 bucket 不能为空".to_string());
|
||||
}
|
||||
let reference_id = input.reference_id.trim();
|
||||
if reference_id.is_empty() {
|
||||
return Err("编辑器引用的 reference_id 不能为空".to_string());
|
||||
}
|
||||
let resource = ctx
|
||||
.db
|
||||
.editor_project_resource()
|
||||
.resource_id()
|
||||
.find(&reference_id.to_string());
|
||||
let asset = ctx
|
||||
.db
|
||||
.editor_asset()
|
||||
.asset_id()
|
||||
.find(&reference_id.to_string());
|
||||
match (resource, asset) {
|
||||
(Some(_), Some(_)) => Err("编辑器引用 ID 同时命中项目资源与素材,无法唯一解析".to_string()),
|
||||
(Some(resource), None) => {
|
||||
resolve_editor_reference_resource(ctx, bucket, owner_user_id, &resource)
|
||||
}
|
||||
(None, Some(asset)) => resolve_editor_reference_asset(ctx, bucket, owner_user_id, asset),
|
||||
(None, None) => Err("编辑器引用不存在".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_editor_reference_resource(
|
||||
ctx: &ReducerContext,
|
||||
bucket: &str,
|
||||
owner_user_id: &str,
|
||||
resource: &EditorProjectResource,
|
||||
) -> Result<ResolvedEditorReference, String> {
|
||||
let object_key = validate_editor_reference_record_fields(
|
||||
"资源",
|
||||
owner_user_id,
|
||||
resource.owner_user_id.as_str(),
|
||||
resource.object_key.as_deref(),
|
||||
)?;
|
||||
verify_editor_reference_asset_object(ctx, bucket, owner_user_id, object_key.as_str())?;
|
||||
let mut snapshot = resource_snapshot_from_row(resource.clone());
|
||||
snapshot.object_key = Some(object_key);
|
||||
Ok(ResolvedEditorReference::ProjectResource(snapshot))
|
||||
}
|
||||
|
||||
fn resolve_editor_reference_asset(
|
||||
ctx: &ReducerContext,
|
||||
bucket: &str,
|
||||
owner_user_id: &str,
|
||||
asset: EditorAsset,
|
||||
) -> Result<ResolvedEditorReference, String> {
|
||||
let object_key = validate_editor_reference_record_fields(
|
||||
"素材",
|
||||
owner_user_id,
|
||||
asset.owner_user_id.as_str(),
|
||||
asset.object_key.as_deref(),
|
||||
)?;
|
||||
verify_editor_reference_asset_object(ctx, bucket, owner_user_id, object_key.as_str())?;
|
||||
let mut snapshot = asset_snapshot_from_row(ctx, asset);
|
||||
snapshot.object_key = Some(object_key);
|
||||
Ok(ResolvedEditorReference::Asset(snapshot))
|
||||
}
|
||||
|
||||
fn validate_editor_reference_record_fields(
|
||||
record_label: &str,
|
||||
expected_owner_user_id: &str,
|
||||
actual_owner_user_id: &str,
|
||||
object_key: Option<&str>,
|
||||
) -> Result<String, String> {
|
||||
if actual_owner_user_id.trim() != expected_owner_user_id {
|
||||
return Err(format!("编辑器{record_label}引用不属于当前用户"));
|
||||
}
|
||||
object_key
|
||||
.map(str::trim)
|
||||
.map(|value| value.trim_start_matches('/'))
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(ToOwned::to_owned)
|
||||
.ok_or_else(|| format!("编辑器{record_label}引用缺少已登记 object_key"))
|
||||
}
|
||||
|
||||
fn verify_editor_reference_asset_object(
|
||||
ctx: &ReducerContext,
|
||||
bucket: &str,
|
||||
owner_user_id: &str,
|
||||
object_key: &str,
|
||||
) -> Result<(), String> {
|
||||
let asset_object = crate::find_asset_object_by_location(
|
||||
ctx,
|
||||
&AssetObjectLocationInput {
|
||||
bucket: bucket.to_string(),
|
||||
object_key: object_key.to_string(),
|
||||
},
|
||||
)?
|
||||
.ok_or_else(|| "编辑器引用对应的 asset_object 不存在".to_string())?;
|
||||
if asset_object.owner_user_id.as_deref().map(str::trim) != Some(owner_user_id) {
|
||||
return Err("编辑器引用对应的 asset_object 不属于当前用户".to_string());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn is_editor_generation_runtime_service_identity(
|
||||
ctx: &ReducerContext,
|
||||
candidate: Identity,
|
||||
@@ -16389,4 +16546,59 @@ mod tests {
|
||||
Some("character-animation")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn editor_reference_record_validation_rejects_owner_and_object_key_failures() {
|
||||
assert!(
|
||||
validate_editor_reference_record_fields(
|
||||
"资源",
|
||||
"owner-1",
|
||||
"owner-2",
|
||||
Some("generated/icon-spec.png"),
|
||||
)
|
||||
.unwrap_err()
|
||||
.contains("不属于当前用户")
|
||||
);
|
||||
assert!(
|
||||
validate_editor_reference_record_fields("素材", "owner-1", "owner-1", None)
|
||||
.unwrap_err()
|
||||
.contains("object_key")
|
||||
);
|
||||
assert_eq!(
|
||||
validate_editor_reference_record_fields(
|
||||
"素材",
|
||||
"owner-1",
|
||||
"owner-1",
|
||||
Some("/generated/icon-spec.png"),
|
||||
)
|
||||
.unwrap(),
|
||||
"generated/icon-spec.png"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn editor_reference_resolution_uses_both_primary_keys_and_fails_on_collision() {
|
||||
let source = include_str!("editor_project_storage.rs");
|
||||
let start = source
|
||||
.find("fn resolve_editor_reference(\n")
|
||||
.expect("reference resolver");
|
||||
let end = source[start..]
|
||||
.find("fn resolve_editor_reference_resource(\n")
|
||||
.map(|offset| start + offset)
|
||||
.expect("resource resolver boundary");
|
||||
let resolver = &source[start..end];
|
||||
for required in [
|
||||
".editor_project_resource()\n .resource_id()\n .find(",
|
||||
".editor_asset()\n .asset_id()\n .find(",
|
||||
"(Some(_), Some(_))",
|
||||
"同时命中项目资源与素材",
|
||||
] {
|
||||
assert!(
|
||||
resolver.contains(required),
|
||||
"resolver must retain {required}"
|
||||
);
|
||||
}
|
||||
assert!(!resolver.contains(".iter()"));
|
||||
assert!(!resolver.contains(".filter("));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user