1
This commit is contained in:
@@ -31,9 +31,10 @@ use shared_kernel::{build_prefixed_uuid_id, format_timestamp_micros};
|
||||
use spacetime_client::{
|
||||
BigFishAgentMessageRecord, BigFishAnchorItemRecord, BigFishAnchorPackRecord,
|
||||
BigFishAssetCoverageRecord, BigFishAssetGenerateRecordInput, BigFishAssetSlotRecord,
|
||||
BigFishBackgroundBlueprintRecord, BigFishGameDraftRecord, BigFishLevelBlueprintRecord,
|
||||
BigFishMessageSubmitRecordInput, BigFishRuntimeParamsRecord, BigFishSessionCreateRecordInput,
|
||||
BigFishSessionRecord, BigFishWorkSummaryRecord, SpacetimeClientError,
|
||||
BigFishBackgroundBlueprintRecord, BigFishDraftCompileRecordInput, BigFishGameDraftRecord,
|
||||
BigFishLevelBlueprintRecord, BigFishMessageSubmitRecordInput, BigFishRuntimeParamsRecord,
|
||||
BigFishSessionCreateRecordInput, BigFishSessionRecord, BigFishWorkSummaryRecord,
|
||||
SpacetimeClientError,
|
||||
};
|
||||
use tokio::time::sleep;
|
||||
|
||||
@@ -41,6 +42,12 @@ use crate::big_fish_agent_turn::{
|
||||
BigFishAgentTurnRequest, build_failed_finalize_record_input, build_finalize_record_input,
|
||||
run_big_fish_agent_turn,
|
||||
};
|
||||
use crate::big_fish_draft_compiler::compile_big_fish_draft_with_fallback;
|
||||
use crate::prompt::big_fish::{
|
||||
BIG_FISH_DEFAULT_NEGATIVE_PROMPT, BIG_FISH_TRANSPARENT_ASSET_NEGATIVE_PROMPT,
|
||||
build_big_fish_level_main_image_prompt, build_big_fish_level_motion_prompt,
|
||||
build_big_fish_stage_background_prompt,
|
||||
};
|
||||
use crate::{
|
||||
ai_generation_drafts::{
|
||||
AiGenerationDraftContext, AiGenerationDraftSink, AiGenerationDraftWriter,
|
||||
@@ -48,6 +55,7 @@ use crate::{
|
||||
api_response::json_success_body,
|
||||
asset_billing::{consume_asset_operation_points, refund_asset_operation_points},
|
||||
auth::AuthenticatedAccessToken,
|
||||
character_visual_assets::try_apply_background_alpha_to_png,
|
||||
http_error::AppError,
|
||||
request_context::RequestContext,
|
||||
state::AppState,
|
||||
@@ -101,13 +109,13 @@ pub async fn get_big_fish_session(
|
||||
) -> Result<Json<Value>, Response> {
|
||||
ensure_non_empty(&request_context, &session_id, "sessionId")?;
|
||||
|
||||
let session = state
|
||||
.spacetime_client()
|
||||
.get_big_fish_session(session_id, authenticated.claims().user_id().to_string())
|
||||
.await
|
||||
.map_err(|error| {
|
||||
big_fish_error_response(&request_context, map_big_fish_client_error(error))
|
||||
})?;
|
||||
let session = load_big_fish_session_with_retry(
|
||||
&state,
|
||||
session_id,
|
||||
authenticated.claims().user_id().to_string(),
|
||||
)
|
||||
.await
|
||||
.map_err(|error| big_fish_error_response(&request_context, map_big_fish_client_error(error)))?;
|
||||
|
||||
Ok(json_success_body(
|
||||
Some(&request_context),
|
||||
@@ -145,13 +153,22 @@ pub async fn list_big_fish_gallery(
|
||||
State(state): State<AppState>,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
) -> Result<Json<Value>, Response> {
|
||||
let items = state
|
||||
.spacetime_client()
|
||||
.list_big_fish_gallery()
|
||||
.await
|
||||
.map_err(|error| {
|
||||
big_fish_error_response(&request_context, map_big_fish_client_error(error))
|
||||
})?;
|
||||
let items = match state.spacetime_client().list_big_fish_gallery().await {
|
||||
Ok(items) => items,
|
||||
Err(error) if should_soft_fallback_big_fish_gallery(&error) => {
|
||||
tracing::warn!(
|
||||
error = %error,
|
||||
"大鱼吃小鱼公开广场读取失败,已按空广场降级返回"
|
||||
);
|
||||
Vec::new()
|
||||
}
|
||||
Err(error) => {
|
||||
return Err(big_fish_error_response(
|
||||
&request_context,
|
||||
map_big_fish_client_error(error),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(json_success_body(
|
||||
Some(&request_context),
|
||||
@@ -489,13 +506,8 @@ pub async fn execute_big_fish_action(
|
||||
|
||||
let session_result = match action.as_str() {
|
||||
"big_fish_compile_draft" => {
|
||||
compile_big_fish_draft_with_all_assets(
|
||||
&state,
|
||||
session_id.clone(),
|
||||
owner_user_id.clone(),
|
||||
now,
|
||||
)
|
||||
.await
|
||||
compile_big_fish_draft_only(&state, session_id.clone(), owner_user_id.clone(), now)
|
||||
.await
|
||||
}
|
||||
"big_fish_generate_level_main_image" => {
|
||||
let asset_url = generate_big_fish_formal_asset(
|
||||
@@ -734,9 +746,13 @@ fn map_big_fish_level_response(
|
||||
level: level.level,
|
||||
name: level.name,
|
||||
one_line_fantasy: level.one_line_fantasy,
|
||||
text_description: level.text_description,
|
||||
silhouette_direction: level.silhouette_direction,
|
||||
size_ratio: level.size_ratio,
|
||||
visual_description: level.visual_description,
|
||||
visual_prompt_seed: level.visual_prompt_seed,
|
||||
idle_motion_description: level.idle_motion_description,
|
||||
move_motion_description: level.move_motion_description,
|
||||
motion_prompt_seed: level.motion_prompt_seed,
|
||||
merge_source_level: level.merge_source_level,
|
||||
prey_window: level.prey_window,
|
||||
@@ -802,98 +818,88 @@ fn map_big_fish_asset_coverage_response(
|
||||
}
|
||||
}
|
||||
|
||||
async fn compile_big_fish_draft_with_all_assets(
|
||||
async fn compile_big_fish_draft_only(
|
||||
state: &AppState,
|
||||
session_id: String,
|
||||
owner_user_id: String,
|
||||
now: i64,
|
||||
) -> Result<BigFishSessionRecord, SpacetimeClientError> {
|
||||
let session = state
|
||||
.spacetime_client()
|
||||
.compile_big_fish_draft(session_id.clone(), owner_user_id.clone(), now)
|
||||
.await?;
|
||||
let draft = session
|
||||
.draft
|
||||
.clone()
|
||||
.ok_or_else(|| SpacetimeClientError::Runtime("大鱼吃小鱼玩法草稿尚未生成".to_string()))?;
|
||||
// 点击生成草稿时一次性生成所有首版玩法资产,前端只负责展示进度和最终 session。
|
||||
for level in &draft.levels {
|
||||
let asset_url = generate_big_fish_formal_asset(
|
||||
state,
|
||||
&owner_user_id,
|
||||
&session_id,
|
||||
"level_main_image",
|
||||
Some(level.level),
|
||||
None,
|
||||
current_utc_micros(),
|
||||
)
|
||||
.await
|
||||
.map_err(|error| SpacetimeClientError::Runtime(error.message().to_string()))?;
|
||||
state
|
||||
.spacetime_client()
|
||||
.generate_big_fish_asset(BigFishAssetGenerateRecordInput {
|
||||
session_id: session_id.clone(),
|
||||
owner_user_id: owner_user_id.clone(),
|
||||
asset_kind: "level_main_image".to_string(),
|
||||
level: Some(level.level),
|
||||
motion_key: None,
|
||||
asset_url: Some(asset_url),
|
||||
generated_at_micros: current_utc_micros(),
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
for level in &draft.levels {
|
||||
for motion_key in ["idle_float", "move_swim"] {
|
||||
let asset_url = generate_big_fish_formal_asset(
|
||||
state,
|
||||
&owner_user_id,
|
||||
&session_id,
|
||||
"level_motion",
|
||||
Some(level.level),
|
||||
Some(motion_key),
|
||||
current_utc_micros(),
|
||||
)
|
||||
.await
|
||||
.map_err(|error| SpacetimeClientError::Runtime(error.message().to_string()))?;
|
||||
state
|
||||
.spacetime_client()
|
||||
.generate_big_fish_asset(BigFishAssetGenerateRecordInput {
|
||||
session_id: session_id.clone(),
|
||||
owner_user_id: owner_user_id.clone(),
|
||||
asset_kind: "level_motion".to_string(),
|
||||
level: Some(level.level),
|
||||
motion_key: Some(motion_key.to_string()),
|
||||
asset_url: Some(asset_url),
|
||||
generated_at_micros: current_utc_micros(),
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
let asset_url = generate_big_fish_formal_asset(
|
||||
state,
|
||||
&owner_user_id,
|
||||
&session_id,
|
||||
"stage_background",
|
||||
None,
|
||||
None,
|
||||
current_utc_micros(),
|
||||
)
|
||||
.await
|
||||
.map_err(|error| SpacetimeClientError::Runtime(error.message().to_string()))?;
|
||||
// 中文注释:大鱼吃小鱼草稿阶段只负责编译结果页草稿,不在这一步串行生成主图、动作或背景。
|
||||
// 这些资产统一留在结果页工坊按需触发,避免 compile action 因长耗时资产任务卡在首步等待态。
|
||||
let session =
|
||||
load_big_fish_session_with_retry(state, session_id.clone(), owner_user_id.clone()).await?;
|
||||
let anchor_pack = map_record_anchor_pack_to_domain(&session.anchor_pack);
|
||||
let compiled_draft =
|
||||
compile_big_fish_draft_with_fallback(state.llm_client(), &anchor_pack).await;
|
||||
let draft_json = serde_json::to_string(&compiled_draft).ok();
|
||||
|
||||
state
|
||||
.spacetime_client()
|
||||
.generate_big_fish_asset(BigFishAssetGenerateRecordInput {
|
||||
.compile_big_fish_draft(BigFishDraftCompileRecordInput {
|
||||
session_id,
|
||||
owner_user_id,
|
||||
asset_kind: "stage_background".to_string(),
|
||||
level: None,
|
||||
motion_key: None,
|
||||
asset_url: Some(asset_url),
|
||||
generated_at_micros: current_utc_micros(),
|
||||
draft_json,
|
||||
compiled_at_micros: now,
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn load_big_fish_session_with_retry(
|
||||
state: &AppState,
|
||||
session_id: String,
|
||||
owner_user_id: String,
|
||||
) -> Result<BigFishSessionRecord, SpacetimeClientError> {
|
||||
let mut last_retryable_error = None;
|
||||
|
||||
for attempt in 0..2 {
|
||||
match state
|
||||
.spacetime_client()
|
||||
.get_big_fish_session(session_id.clone(), owner_user_id.clone())
|
||||
.await
|
||||
{
|
||||
Ok(session) => return Ok(session),
|
||||
Err(error @ SpacetimeClientError::Timeout)
|
||||
| Err(error @ SpacetimeClientError::ConnectDropped) => {
|
||||
last_retryable_error = Some(error);
|
||||
if attempt == 0 {
|
||||
sleep(Duration::from_millis(250)).await;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
}
|
||||
|
||||
Err(last_retryable_error.unwrap_or(SpacetimeClientError::Timeout))
|
||||
}
|
||||
|
||||
fn map_record_anchor_pack_to_domain(
|
||||
anchor_pack: &BigFishAnchorPackRecord,
|
||||
) -> module_big_fish::BigFishAnchorPack {
|
||||
module_big_fish::BigFishAnchorPack {
|
||||
gameplay_promise: map_record_anchor_item_to_domain(&anchor_pack.gameplay_promise),
|
||||
ecology_visual_theme: map_record_anchor_item_to_domain(&anchor_pack.ecology_visual_theme),
|
||||
growth_ladder: map_record_anchor_item_to_domain(&anchor_pack.growth_ladder),
|
||||
risk_tempo: map_record_anchor_item_to_domain(&anchor_pack.risk_tempo),
|
||||
}
|
||||
}
|
||||
|
||||
fn map_record_anchor_item_to_domain(
|
||||
anchor_item: &BigFishAnchorItemRecord,
|
||||
) -> module_big_fish::BigFishAnchorItem {
|
||||
module_big_fish::BigFishAnchorItem {
|
||||
key: anchor_item.key.clone(),
|
||||
label: anchor_item.label.clone(),
|
||||
value: anchor_item.value.clone(),
|
||||
status: match anchor_item.status.as_str() {
|
||||
"confirmed" => module_big_fish::BigFishAnchorStatus::Confirmed,
|
||||
"locked" => module_big_fish::BigFishAnchorStatus::Locked,
|
||||
"inferred" => module_big_fish::BigFishAnchorStatus::Inferred,
|
||||
_ => module_big_fish::BigFishAnchorStatus::Missing,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn map_big_fish_agent_message_response(
|
||||
message: BigFishAgentMessageRecord,
|
||||
) -> BigFishAgentMessageResponse {
|
||||
@@ -960,12 +966,11 @@ struct BigFishFormalAssetContext {
|
||||
asset_object_kind: String,
|
||||
binding_slot: String,
|
||||
path_segments: Vec<String>,
|
||||
apply_transparent_background_post_process: bool,
|
||||
}
|
||||
|
||||
const BIG_FISH_TEXT_TO_IMAGE_MODEL: &str = "wan2.2-t2i-flash";
|
||||
const BIG_FISH_ENTITY_KIND: &str = "big_fish_session";
|
||||
const BIG_FISH_DEFAULT_NEGATIVE_PROMPT: &str = "文字,水印,logo,UI界面,对话框,边框,多余肢体,畸形鱼体,低清晰度,模糊,压缩噪点,现代摄影棚,写实照片背景,复杂背景";
|
||||
const BIG_FISH_TRANSPARENT_ASSET_NEGATIVE_PROMPT: &str = "文字,水印,logo,UI界面,对话框,边框,多余肢体,畸形鱼体,低清晰度,模糊,压缩噪点,现代摄影棚,写实照片背景,场景背景,水草背景,气泡背景,多只主体,阴影地面";
|
||||
|
||||
async fn generate_big_fish_formal_asset(
|
||||
state: &AppState,
|
||||
@@ -1009,6 +1014,7 @@ async fn generate_big_fish_formal_asset(
|
||||
&http_client,
|
||||
generated.image_url.as_str(),
|
||||
"下载 Big Fish 正式图片失败",
|
||||
context.apply_transparent_background_post_process,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -1049,6 +1055,7 @@ fn build_big_fish_formal_asset_context(
|
||||
level_part,
|
||||
asset_id,
|
||||
],
|
||||
apply_transparent_background_post_process: true,
|
||||
})
|
||||
}
|
||||
"level_motion" => {
|
||||
@@ -1077,6 +1084,7 @@ fn build_big_fish_formal_asset_context(
|
||||
sanitize_big_fish_path_segment(motion_key, "motion"),
|
||||
asset_id,
|
||||
],
|
||||
apply_transparent_background_post_process: true,
|
||||
})
|
||||
}
|
||||
"stage_background" => Ok(BigFishFormalAssetContext {
|
||||
@@ -1091,6 +1099,7 @@ fn build_big_fish_formal_asset_context(
|
||||
"stage-background".to_string(),
|
||||
asset_id,
|
||||
],
|
||||
apply_transparent_background_post_process: false,
|
||||
}),
|
||||
_ => Err(
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
@@ -1123,79 +1132,6 @@ fn find_big_fish_level_blueprint(
|
||||
})
|
||||
}
|
||||
|
||||
fn build_big_fish_level_main_image_prompt(
|
||||
draft: &BigFishGameDraftRecord,
|
||||
level: &BigFishLevelBlueprintRecord,
|
||||
) -> String {
|
||||
vec![
|
||||
format!(
|
||||
"为竖屏移动游戏《{}》生成一张等级生物主图。",
|
||||
draft.title
|
||||
),
|
||||
format!(
|
||||
"生态主题:{}。核心乐趣:{}。",
|
||||
draft.ecology_theme, draft.core_fun
|
||||
),
|
||||
format!(
|
||||
"等级:Lv.{},名称:{},幻想描述:{}。",
|
||||
level.level, level.name, level.one_line_fantasy
|
||||
),
|
||||
format!("轮廓方向:{}。", level.silhouette_direction),
|
||||
format!("视觉提示词种子:{}。", level.visual_prompt_seed),
|
||||
"画面要求:按 RPG 角色资产口径生成,单体鱼形游戏生物完整入镜,轮廓清晰,中心构图,2D 高完成度游戏插画,深海发光质感。".to_string(),
|
||||
"背景要求:透明背景 PNG 风格,不出现任何场景、水草、气泡、阴影地面、UI、文字、logo、水印、对话框或边框;不要出现多只主体。".to_string(),
|
||||
]
|
||||
.join("")
|
||||
}
|
||||
|
||||
fn build_big_fish_level_motion_prompt(
|
||||
draft: &BigFishGameDraftRecord,
|
||||
level: &BigFishLevelBlueprintRecord,
|
||||
motion_key: &str,
|
||||
) -> String {
|
||||
let motion_text = match motion_key {
|
||||
"move_swim" => "向右游动的关键帧预览,身体与尾鳍有清晰推进姿态,带轻微水流拖尾。",
|
||||
_ => "待机漂浮的关键帧预览,身体轻微摆动,姿态稳定,适合作为 idle 状态。",
|
||||
};
|
||||
vec![
|
||||
format!(
|
||||
"为竖屏移动游戏《{}》生成一张等级生物动作关键帧静态预览图。",
|
||||
draft.title
|
||||
),
|
||||
format!("生态主题:{}。", draft.ecology_theme),
|
||||
format!(
|
||||
"等级:Lv.{},名称:{},幻想描述:{}。",
|
||||
level.level, level.name, level.one_line_fantasy
|
||||
),
|
||||
format!("动作提示词种子:{}。", level.motion_prompt_seed),
|
||||
format!("动作要求:{motion_text}"),
|
||||
"画面要求:按 RPG 角色动画资产口径生成,单体鱼形生物完整入镜,轮廓清晰,动作方向明确,2D 高完成度游戏插画,适合作为 Big Fish 动作槽位的静态 keyframe。".to_string(),
|
||||
"背景要求:透明背景 PNG 风格,不出现任何场景、水草、气泡、阴影地面、UI、文字、logo、水印、对话框或边框;不要生成序列帧拼图,不要出现多只主体。".to_string(),
|
||||
]
|
||||
.join("")
|
||||
}
|
||||
|
||||
fn build_big_fish_stage_background_prompt(draft: &BigFishGameDraftRecord) -> String {
|
||||
let background = &draft.background;
|
||||
vec![
|
||||
format!(
|
||||
"为竖屏移动游戏《{}》生成一张 9:16 全屏活动区域背景。",
|
||||
draft.title
|
||||
),
|
||||
format!("生态主题:{}。", draft.ecology_theme),
|
||||
format!("背景主题:{}。色彩氛围:{}。", background.theme, background.color_mood),
|
||||
format!("前景提示:{}。", background.foreground_hints),
|
||||
format!("中景构图:{}。", background.midground_composition),
|
||||
format!("背景纵深:{}。", background.background_depth),
|
||||
format!("安全操作区:{}。", background.safe_play_area_hint),
|
||||
format!("出生边缘:{}。", background.spawn_edge_hint),
|
||||
format!("背景提示词种子:{}。", background.background_prompt_seed),
|
||||
"画面要求:竖屏 9:16,大场地,全屏运行态背景,中央 80% 保持开阔清爽,边缘只保留少量出生区环境提示。".to_string(),
|
||||
"元素要求:整体元素少,不出现大型主体、密集装饰、鱼群主角、UI、文字、logo、水印、对话框或边框;不要把中央操作区画得过暗或过复杂。".to_string(),
|
||||
]
|
||||
.join("")
|
||||
}
|
||||
|
||||
fn require_big_fish_dashscope_settings(
|
||||
state: &AppState,
|
||||
) -> Result<BigFishDashScopeSettings, AppError> {
|
||||
@@ -1372,6 +1308,7 @@ async fn download_big_fish_remote_image(
|
||||
http_client: &reqwest::Client,
|
||||
image_url: &str,
|
||||
fallback_message: &str,
|
||||
apply_transparent_background_post_process: bool,
|
||||
) -> Result<BigFishDownloadedImage, AppError> {
|
||||
let response = http_client.get(image_url).send().await.map_err(|error| {
|
||||
map_big_fish_dashscope_request_error(format!("{fallback_message}:{error}"))
|
||||
@@ -1397,10 +1334,25 @@ async fn download_big_fish_remote_image(
|
||||
}
|
||||
|
||||
let mime_type = normalize_big_fish_downloaded_image_mime_type(content_type.as_str());
|
||||
let mut normalized_bytes = bytes.to_vec();
|
||||
let mut normalized_mime_type = mime_type;
|
||||
let mut extension = big_fish_mime_to_extension(normalized_mime_type.as_str()).to_string();
|
||||
|
||||
// 中文注释:Big Fish 的等级主图与动作关键帧要和 RPG 角色主图保持同一后处理口径。
|
||||
// 因此在上游已经输出 PNG 时,统一补一层透明背景 alpha 清理,避免只靠 prompt 约束导致残留底色。
|
||||
if apply_transparent_background_post_process
|
||||
&& normalized_mime_type == "image/png"
|
||||
&& let Some(optimized) = try_apply_background_alpha_to_png(normalized_bytes.as_slice())
|
||||
{
|
||||
normalized_bytes = optimized;
|
||||
normalized_mime_type = "image/png".to_string();
|
||||
extension = "png".to_string();
|
||||
}
|
||||
|
||||
Ok(BigFishDownloadedImage {
|
||||
extension: big_fish_mime_to_extension(mime_type.as_str()).to_string(),
|
||||
mime_type,
|
||||
bytes: bytes.to_vec(),
|
||||
extension,
|
||||
mime_type: normalized_mime_type,
|
||||
bytes: normalized_bytes,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1735,15 +1687,37 @@ fn map_big_fish_client_error(error: SpacetimeClientError) -> AppError {
|
||||
StatusCode::BAD_REQUEST
|
||||
}
|
||||
SpacetimeClientError::Runtime(_) => StatusCode::BAD_REQUEST,
|
||||
SpacetimeClientError::Timeout => StatusCode::GATEWAY_TIMEOUT,
|
||||
_ => StatusCode::BAD_GATEWAY,
|
||||
};
|
||||
|
||||
let message = match &error {
|
||||
SpacetimeClientError::Timeout => "SpacetimeDB 会话读取超时,请稍后重试。".to_string(),
|
||||
SpacetimeClientError::ConnectDropped => {
|
||||
"SpacetimeDB 会话连接已断开,请稍后重试。".to_string()
|
||||
}
|
||||
_ => error.to_string(),
|
||||
};
|
||||
|
||||
AppError::from_status(status).with_details(json!({
|
||||
"provider": "spacetimedb",
|
||||
"message": error.to_string(),
|
||||
"message": message,
|
||||
}))
|
||||
}
|
||||
|
||||
fn should_soft_fallback_big_fish_gallery(error: &SpacetimeClientError) -> bool {
|
||||
match error {
|
||||
// 公开广场是首页可选数据,SpacetimeDB procedure 运行态短暂失败时不应阻断平台首屏。
|
||||
SpacetimeClientError::Runtime(_) | SpacetimeClientError::ConnectDropped => true,
|
||||
SpacetimeClientError::Procedure(message) => {
|
||||
message.contains("list_big_fish_works")
|
||||
|| message.contains("procedure")
|
||||
|| message.contains("No such procedure")
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn big_fish_error_response(request_context: &RequestContext, error: AppError) -> Response {
|
||||
error.into_response_with_context(Some(request_context))
|
||||
}
|
||||
@@ -1758,3 +1732,28 @@ fn current_utc_micros() -> i64 {
|
||||
fn current_timestamp_micros_to_string(value: i64) -> String {
|
||||
format_timestamp_micros(value)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn big_fish_gallery_soft_fallbacks_for_runtime_errors() {
|
||||
assert!(should_soft_fallback_big_fish_gallery(
|
||||
&SpacetimeClientError::Runtime("procedure runtime error".to_string())
|
||||
));
|
||||
assert!(should_soft_fallback_big_fish_gallery(
|
||||
&SpacetimeClientError::ConnectDropped
|
||||
));
|
||||
assert!(should_soft_fallback_big_fish_gallery(
|
||||
&SpacetimeClientError::Procedure("No such procedure: list_big_fish_works".to_string(),)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn big_fish_gallery_keeps_timeout_errors_visible() {
|
||||
assert!(!should_soft_fallback_big_fish_gallery(
|
||||
&SpacetimeClientError::Timeout
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user