ArtCraft 共享 API 契约层 artcraft_api_defs:服务器与客户端共用的 HTTP 类型定义规范
人工智能AI 应用媒体生成桌面应用【免费下载链接】artcraftArtCraft is an intentional crafting engine for artists, designers, and filmmakers项目地址https://gitcode.com/GitHub_Trending/ar/artcraft点击查看免费下载artcraft_api_defs是 ArtCraft 仓库中专门承载 HTTP API 共享类型定义的 crate其职责是让服务器端文档中记为storyteller_web与各类客户端尤其是 artcraft_client使用同一套 wire 类型完成序列化与反序列化避免两端各自维护一套易漂移的 DTO。本文以该 crate 的规范文档 AGENTS.md 为骨架结合仓库源码逐一拆解其目录组织、Request/Response/Path/Query 四条类型约定、PathInfo后缀规则、孤儿规则Orphan Rule边界与最小依赖治理读者读完后能够直接依据本文在 ArtCraft 中新增一个 API 端点类型并理解共享类型被artcraft_client消费的完整链路。一、定位为什么需要独立的共享类型 crate在 ArtCraft 的工程布局中artcraft_api_defs 处于 API 层的中枢位置它同时被服务器端storyteller_web与客户端 crate 依赖而不是把请求/响应类型塞进任何一侧。这样做的收益体现在三个方面单一事实来源同一份struct既用于服务端反序列化请求、序列化响应也用于客户端序列化请求、反序列化响应字段命名、可选性、枚举取值天然一致版本演进可控类型变更必须发生在共享层两端同步升级不会出现服务端已改、客户端仍发旧字段的隐性漂移与框架解耦该 crate 刻意不依赖 actix-web详见下文依赖治理一节因此类型定义可以被任何 HTTP 客户端栈复用。从根 Cargo.toml 的 workspace members 可以看到crates/api_clients/artcraft/artcraft_api_defs是仓库的一等成员并被声明为可被各 crate 通过artcraft_api_defs.workspace true引用的内部依赖artcraft_client/Cargo.toml 即如此引用。二、目录结构按业务功能feature组织lib.rs 的模块声明直接反映了文档所述的Organized by feature原则。整个 crate 目前按功能域划分了 24 个顶层模块users登录、改密码、改邮箱、改用户名、会话信息与登录质询users/mod.rsmedia_file媒体文件的删除、按 job 列举、按会话/类型列举、项目文件上传2D 项目、mood board、3D 场景、视频时间线等jobs会话任务列表moderation风控与运营侧类型覆盖告警、调试日志、Top 消费用户、钱包与账本、用户 Stripe 数据等generate按生成类型拆分的请求/响应例如cost_estimate/、image/、video/、object/、splat/等子目录omni_api与omni_gen面向第三方/统一入口的 Omni API 与 Omni Gen 生成、成本与任务状态类型其余功能域analytics、api_keys、characters、credits、folders、internal、mcp_sessions、prompts、stripe_artcraft、subscriptions、tags、user_referral_codes、video_info、web_referrals、utils、common。common是一个特殊功能域只存放跨功能复用的响应类型详见第四节。这种按功能、再按子功能的层级组织让一个端点的全部类型请求、响应、路径参数、查询参数、枚举都收拢在同一文件里查找与评审都极为高效。三、四条核心类型约定让 wire 形状可预测AGENTS.md 用四条规则规定了每种类型的 trait 组合与命名源码中处处可见其落地3.1 请求类型#[derive(Deserialize, ToSchema)]请求体只负责接收因此只派生反序列化与 OpenAPI 模式生成。典型示例是 users/login.rs 中的登录请求#[derive(Deserialize, ToSchema)] pub struct LoginRequest { pub username_or_email: String, pub password: String, }ToSchema来自utoipa用于生成 OpenAPI 文档Deserialize使服务端可以从 JSON 请求体直接构造该类型。3.2 响应类型#[derive(Serialize, ToSchema)]响应只负责发送因此派生序列化。同一个登录端点的成功响应体现了这一点同时展示了共享层如何承载业务语义注释——signed_session的字段注释解释了它的设计动机绕过 Cookie、适配禁用跨域 Cookie 的浏览器#[derive(Serialize, ToSchema)] pub struct LoginSuccessResponse { pub success: bool, /// A signed session that can be sent as a header, bypassing cookies. /// This is useful for API clients that dont support cookies or Google /// browsers killing cross-domain cookies. pub signed_session: String, }此外同一个文件里的LoginErrorType枚举AccountNeedsPassword、InvalidCredentials、ServerError展示了另一条隐含约定错误分类的枚举值也可以放在共享层只要它们不需要actix-web的ResponseError实现见第五节。3.3 路径参数Deserialize ToSchema且以PathInfo为后缀URL 路径中的参数类型以PathInfo结尾这是共享层最具辨识度的命名约定。media_file/delete_media_file.rs 给出了教科书式示例/// For the URL PathInfo #[derive(Serialize, Deserialize, ToSchema)] pub struct DeleteMediaFilePathInfo { pub token: MediaFileToken, }注意这里同时派生Serialize与Deserialize——路径参数既要由服务端从路径中反序列化出来也要能序列化例如客户端在拼接 URL 或做调试输出时需要。token字段使用了 tokens crate 中的MediaFileToken这也是共享层大量复用内部 schema crate 的证据。顺着PathInfo后缀可以快速在代码库中定位所有带路径参数的端点。3.4 查询参数#[derive(Deserialize, IntoParams)]查询字符串参数类型单独使用IntoParams同样是 utoipa 的 trait用于把结构体展开为 OpenAPI 的 query 参数且通常不需要ToSchema。jobs/list_session_jobs.rs 是文档约定的直接落地并注释了参数的逗号分隔取值语义#[derive(Deserialize, ToSchema, IntoParams)] pub struct ListSessionJobsQueryParams { /// NB: This can be one (or more comma-separated values) from JobStatusPlus. /// ?include_statespending or ?include_statespending,started,complete_success (etc.) pub include_states: OptionString, /// The opposite of include_states, this filters out states from view. pub exclude_states: OptionString, }另一个更纯粹的查询参数示例是 media_file/list/by_type/list_session_common.rs它只派生Deserialize, IntoParams承载游标分页所需的四个参数sort_ascending、page_size、cursor、cursor_is_reversed注释明确Pagination only — the media class is fixed by the endpoint。3.5 常量约定URL 路径以const形式与类型同文件源码中还呈现出一条 AGENTS.md 未显式写出、但普遍遵守的约定端点 URL 路径作为pub const ..._URL_PATH与类型放在同一文件。例如 jobs/list_session_jobs.rs 的LIST_SESSION_JOBS_URL_PATH: str /v1/jobs/session、media_file/list/by_type/list_session_mesh_media_files.rs 的LIST_SESSION_MESH_MEDIA_FILES_URL_PATH以及 analytics/log_active_user.rs 中按客户端版本区分的LOG_ACTIVE_USER_V1_PATH与LOG_ACTIVE_USER_V2_PATH。路径常量与类型定义同处一个文件客户端只需use该文件即可同时获得路径与类型见第七节不会出现路径字符串散落各处的维护问题。四、跨功能共享响应类型common/responses文档将common单列为功能域之一。它的实际内容是 common/responses/mod.rs 导出的七类被多个功能域复用的响应形状类型文件用途MediaLinks/VideoPreviewsmedia_links.rs媒体文件的 CDN 链接、缩略图模板与视频预览PaginationCursorspagination_cursors.rs无限滚动式游标分页JobDetailsLivePortraitRequest/JobDetailsLipsyncRequestjob_details.rs直播人像与口型同步任务的请求细节MediaFileCoverImageDetailsmedia_file_cover_image_details.rs封面图细节CoverImageLinkscover_image_links.rs封面图链接SimpleEntityStatssimple_entity_stats.rs实体统计SimpleGenericJsonSuccesssimple_generic_json_success.rs通用 JSON 成功响应UserDetailsLightuser_details_light.rs轻量用户信息以游标分页为例pagination_cursors.rs 的注释揭示了设计动机——不暴露总页数从而避免投资者与竞品通过接口推断数据库记录规模同时说明游标是不透明的通常加密的句柄#[derive(Serialize, Deserialize, ToSchema, Debug)] pub struct PaginationCursors { /// The next cursor. /// This is an opaque (typically even encrypted) handle. pub maybe_next: OptionString, /// The previous cursor. /// This is an opaque (typically even encrypted) handle. pub maybe_previous: OptionString, /// Details whether were walking forward or backward. pub cursor_is_reversed: bool, }MediaLinks则展示了 URL 类型直接进入 wire 结构cdn_url: Url来自urlcratemaybe_thumbnail_template用{WIDTH}占位符让客户端自行拼出不同尺寸的缩略图视频文件另有maybe_video_previews: OptionVideoPreviews内含静态帧、动图及各自的缩略图模板。这些共享类型被具体端点复用例如 jobs/list_session_jobs.rs 的ListSessionJobsItem就嵌入了MediaLinks、JobDetailsLivePortraitRequest、JobDetailsLipsyncRequest并组合了DateTimeUtcchrono与多个tokens类型list_session_mesh_media_files.rs 则把SessionMediaFileInfo与PaginationCursors组合成网格媒体文件列表响应。五、孤儿规则Orphan Rule共享层的边界在哪里AGENTS.md 明确划定了共享层的职责边界需要ResponseError实现actix-web的类型不能放在本 crate。原因是 Rust 的孤儿规则——impl ResponseError for MyType要求 trait 或类型至少有一个定义在本地 crate而ResponseError属于 actix-web共享层又禁止依赖 actix-web因此这类实现只能放在服务器端storyteller_web。这条规则的实际效果是错误响应类型留在服务端、纯数据类型下沉到共享层。仓库源码中的多处注释印证了同一原则在更细粒度上的应用omni_api/omni_upload_video.rs 指出上传接口的请求体是multipart/form-data因为 multipart 表单类型绑定于 Web 框架所以请求类型在storyteller_web中与 handler 一起声明共享层只放成功响应类型folders/common.rs 指出folder 的列表项形状因嵌入MediaFileCoverImageDetails等依赖请求上下文MediaDomainServerEnvironment的类型也保留在storyteller_web共享层只提供不依赖上下文的FolderInfo与FolderThumbnail。因此判断一个类型该不该进共享层可以套用两条检验标准它是否依赖 actix-web 或请求上下文MediaDomain、ServerEnvironment、multipart 绑定等——是则留在服务端它是否同时被服务端与客户端读写——是则应下沉到共享层。从源码结构看folders/common.rs还展示了共享层在被服务端使用时的另一重职责为FolderInfo这类跨端点复用的形状提供单一定义单文件夹 GET、创建响应、列表行、子文件夹列表行共用一个结构并保持与MediaLinks类似的视频预览镜像结构独立演进FolderThumbnailVideoPreviews的注释明确Mirrors the shape ofMediaLinks video previews, but is its own type。六、依赖治理刻意保持的最小依赖面Cargo.toml 完整呈现了 AGENTS.md 所述的依赖清单且是刻意精简的# Internal enums.workspace true tokens.workspace true # External chrono.workspace true url.workspace true # Serialization serde.workspace true serde_derive.workspace true serde_json.workspace true utoipa.workspace true [dev-dependencies] anyhow.workspace true要点解读外部依赖仅五个serde/serde_derive序列化、utoipaOpenAPI 模式、chronoDateTimeUtc时间戳、urlUrl类型的 CDN 链接serde_json主要用于测试与序列化工具内部 schema 依赖两个enums 与 tokens。enums提供跨端共享的枚举例如 common_video_model.rs 中#[serde(rename_all snake_case)]的CommonVideoModel被 omni_api/generate_requests/omni_api_video_generate_request.rs 直接引用为请求字段model: OptionCommonVideoModeltokens提供MediaFileToken、UserToken、FolderToken、ApiKeyToken、InferenceJobToken、BatchGenerationToken、PromptToken等强类型标识符没有 actix-web 依赖这是第五节的孤儿规则在依赖层面的对应——共享层保持纯类型 纯序列化的定位所有 Web 框架绑定都留在服务端开发依赖仅anyhow共享层自身测试极少引入重型依赖进一步印证其薄的特质。enumscrate 的测试也保证了这些共享枚举的序列化契约稳定common_video_model.rs内置了test_serialization、test_deserialization、test_round_trip等测试如assert_serialization(CommonVideoModel::Kling21Pro, kling_2p1_pro)确保 wire 上的字符串取值与枚举变体一一对应、双向往返无损。七、客户端消费链路artcraft_client 如何复用共享类型共享类型最终要被真实客户端使用才产生价值。artcraft_client 是消费方之一其 jobs/list_session_jobs.rs 展示了标准用法use artcraft_api_defs::jobs::list_session_jobs::{ListSessionJobsSuccessResponse, LIST_SESSION_JOBS_URL_PATH};客户端从共享层一次性引入响应类型 URL 路径常量再结合本地定义的请求状态枚举States::All / Include / Exclude拼出查询串最终通过basic_json_get_request发起请求并把响应反序列化为共享的ListSessionJobsSuccessResponsepub async fn list_session_jobs( api_host: ApiHost, maybe_creds: OptionStorytellerCredentialSet, job_states: States, ) - ResultListSessionJobsSuccessResponse, StorytellerError { let url match job_states { States::All LIST_SESSION_JOBS_URL_PATH.to_string(), States::Include(states) format!({}?include_states{}, LIST_SESSION_JOBS_URL_PATH, states_string(states)), States::Exclude(states) format!({}?exclude_states{}, LIST_SESSION_JOBS_URL_PATH, states_string(states)), }; Ok(basic_json_get_request(api_host, url, maybe_creds).await?) }客户端只负责如何请求认证、Host、HTTP 动词、查询串拼接而请求/响应长什么样完全由共享层裁决。这与服务端反序列化同一组请求类型形成闭环——两端各自use artcraft_api_defs::...即可保证契约一致这正是该 crate 存在的全部意义。八、实践小结新增一个端点类型时的检查清单综合 AGENTS.md 的约定与仓库源码的实际落地在artcraft_api_defs中新增端点类型时可依次自检目录放入对应 feature 子目录若为新功能则创建新mod.rs并在 lib.rs 注册请求体#[derive(Deserialize, ToSchema)]字段按需加Option与 serde 属性如skip_serializing_if见 analytics/log_active_user.rs响应体#[derive(Serialize, ToSchema)]可复用的形状优先引用common::responses路径参数#[derive(Serialize, Deserialize, ToSchema)]并以PathInfo后缀命名查询参数#[derive(Deserialize, IntoParams)]路径常量pub const XXX_URL_PATH与类型同文件边界检查若类型需要ResponseError、multipart 表单或请求上下文MediaDomain/ServerEnvironment则留在storyteller_web而非共享层依赖检查只用 serde / utoipa / chrono / url 与enums、tokens不得引入 actix-web。以上约定配合 AGENTS.md 与同目录的 CLAUDE.md内容与 AGENTS.md 完全一致构成了 ArtCraft 共享 API 契约层的完整开发规范也是后续参与该仓库 API 层开发时的首要阅读材料。赞分享人工智能AI 应用媒体生成桌面应用【免费下载链接】artcraftArtCraft is an intentional crafting engine for artists, designers, and filmmakers项目地址https://gitcode.com/GitHub_Trending/ar/artcraft点击查看免费下载相关推荐Gitpod 协议层深度解析gitpod-protocol 共享类型定义与跨服务通信契约Gitpod 协议层深度解析gitpod protocol 共享类型定义与跨服务通信契约 Gitpod Protocol components/gitpod开发工具后端云原生Civitai 的 civitai/buzz 包Buzz 服务类型化 HTTP 客户端与共享货币化规则解析Civitai 的 civitai/buzz 包Buzz 服务类型化 HTTP 客户端与共享货币化规则解析 导读 civitai/buzz 是 Civit后端前端AI 应用Cherry Studio 共享数据类型层src/shared/data完全指南Data API、Cache、Preference 与 BootConfig 的类型契约Cherry Studio 共享数据类型层src/shared/data完全指南Data API、Cache、Preference 与 BootConfi人工智能大模型AI 应用交互助手本地部署上一篇Apache Beam Twitter 流式接入实战Splittable DoFn、单例连接管理与按时间/条数终止的无界管道示例下一篇你的菜单栏是否已成为效率黑洞用Ice重新定义macOS界面管理创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考