pulls.list_pulls | 前端 api.ts:79-91 发 state/draft/author/base/head/search/sort/order/limit/offset;后端 ListPullQuery(lib/service/pulls/src/services/pulls.rs:99-120,:100 #[serde(deny_unknown_fields)])字段逐一对应,无多无少。实证 200:curl 'http://localhost:7051/api/v1/repos/demo/demorepos/pulls?state=all&draft=false&sort=created&order=desc&limit=20&offset=0' → {"code":200,...,"limit":20,"offset":0}。另实证前端 state:'draft'→'open' 的映射是必需的:?state=draft → 400 {"code":400,"message":"unknown state \draft`",...,"error_code":"bad_request"}` |
pulls.list_pulls 未知字段 | 实证 400 原文点名允许集,证明“未知 query 是 400 不是忽略”:{"code":400,"message":"invalid query parameters","data":null,"error":"Query deserialize error: unknown field `bogus`, expected one of `state`, `author`, `base`, `head`, `draft`, `search`, `sort`, `order`, `limit`, `offset`","error_code":null} |
pulls.list_pulls 的 label/milestone | 该端点不接受标签与里程碑过滤,实证:?label=bug → 400 Query deserialize error: unknown field 'label'。前端当前未发这两项(若后续加筛选必须改后端)。sort 只接受 number|created|updated(?sort=bogus → 400 unknown variant 'bogus', expected one of 'number','created','updated'),与生成类型 PullSort 一致 |
pulls.list_pulls 分页边界 | limit=0 → 400 limit must be between 1 and 100;offset=5001 → 400 offset must not exceed 5000。前端 pulls-tab.tsx:14 PAGE_SIZE = 20、limit: 50(收件箱)均在 1..=100 内 |
pulls.list_my_pulls 路径与 query | 路径 /pulls/@me/{scope}(lib/service/pulls/src/routes.rs:118)与生成 URL 一致;scope 取值 created|review-requested|reviewed 与 InboxPath 文档 + :41-49 的 match 分支完全一致;query 仅 state/limit/offset,与 InboxQuery(services/inbox.rs:22-32)一致 |
pulls.list_pull_comments / list_pull_events 的 query | 后端均用 PageQuery(lib/infra/api/src/pagination.rs:10-15,#[serde(deny_unknown_fields)],仅 limit/offset),与生成 ListPullCommentsParams/ListPullEventsParams 一致。实证:…/pulls/1/comments?bogus=1 → 400 Query deserialize error: unknown field \bogus`, expected `limit` or `offset`;不带未知字段则越过 query 校验进入业务(…?limit=1→ 404pull request not found`,因 demo 仓库无 PR) |
pulls.create_pull | api.ts:118-125 发 title/head_branch/base_branch/body/draft/issue_id;CreatePullCmd(services/pulls.rs:122-132)字段名与类型一致,issue_id:null→None 合法 |
pulls.update_pull(PATCH 三态) | api.ts:130 直传 PullPatchCmd;UpdatePullCmd(services/pulls.rs:134-149)对 body/auto_merge/issue_id 用 deserialize_opt_opt,缺省=不变、null=清空、值=设置。前端 pull-detail.tsx:638 的 body 恒为字符串(清空发 ""),title: undefined 会被 JSON 丢弃 → 均落在预期语义 |
pulls.close_pull / reopen_pull | 后端 POST…/close、POST…/reopen 返回 PullDto(close_pull.rs:18、reopen_pull.rs:18),与前端 oneOf<Pull> 一致 |
pulls.merge_pull | 路径 {ITEM}/merge + POST;body MergePullCmd(lib/service/pulls/src/services/merge.rs:14-30)6 个字段与前端 MergeCmd 一致;method 枚举 MergeMethod = "merge"|"squash"|"rebase"(snake_case),与 models::pull_request::pull_requests::MergeMethod 的序列化一致;响应 MergePullDto{pull,result,summary} 与前端 MergeOutcome 一致 |
pulls.lock_pull / unlock_pull | PUT …/lock 与 DELETE …/lock(routes.rs:60),两个生成客户端 URL 相同、方法不同,OPS 表分别映射正确;响应被 ignore() 丢弃,不受形状影响 |
pulls.create_pull_comment(行内评论) | api.ts:194-205 发 body/parent_comment_id/path/line/side/commit_sha/review_id;CreatePullCommentCmd(lib/service/pulls/src/services/comments.rs:25-41)7 个字段名与类型逐一对应,line: i32 vs 前端 number、side: Option<String> 自由值 left|right 一致。files-tab.tsx:88 的 commit_sha: headSha 取自 pull-detail.tsx:234 headSha={pull.head.sha}(是 sha 不是 branch/label),未误用 |
pulls.update_pull_comment / delete / resolve / unresolve | 路径 {ITEM}/comments/{comment_id}(PATCH/DELETE)与 …/resolve(PUT/DELETE);CommentPath(handlers/comments/update_pull_comment.rs:11-17)段序 space_name/repo_name/pull_ref/comment_id 与前端 {...pathOf(ref), comment_id} 一致;UpdatePullCommentCmd{body:String} 与前端 {body} 一致 |
pulls.list_pull_comment_tree | 返回 Vec<PullCommentThreadDto>(list_pull_comment_tree.rs:18),前端按数组解包(listOf),PullCommentThread = {root, replies},conversation.tsx:64/218/282 正好读 root/replies |
pulls.list_pull_commits / get_pull_files | 均为数组(Vec<MergeCommitDto> / Vec<ChangedPathDiffDto>),前端 listOf;commits-tab.tsx:34-58 读 sha/subject/author_name/author_email/author_at/parents 与 MergeCommitDto 一致;model/meta.ts:137-145 正确把 file.path.new_path/old_path 与 lines_added/removed 映射出来(未把 path 对象当字符串用) |
pulls.get_pull_checks / get_pull_gate | PullChecksDto{head_sha,statuses}、PullGateDto{head_sha,mergeable,conflicts,gate,pending_review_requests};pull-detail.tsx:458/469 读 g.required_approvals/g.approvals,二者确实在 MergeGateDto(lib/service/pulls/src/services/approvals.rs:17-26)内 |
pulls.refresh_pull_summary | 后端 POST {ITEM}/summary 返回 PullSummaryDto(refresh_pull_summary.rs:19),前端 oneOf<PullSummary> 对应 EnvelopePullSummaryDto['data'] |
pulls.list_pull_events 响应 | 后端 Page<PullEventDto>(services/events.rs:37-47),前端手写的 PullEvent(pull.ts:72-80)字段与 PullEventDto 一一对应(id/pull_request_id/event_type/actor/commit_sha/content/created_at) |
pulls.list_pull_assignees / get_pull 等单对象端点 | 返回 PullDto/Vec<PullAssigneeDto>,前端 oneOf/listOf 分支正确;pageOf 只用于真分页端点(list_pulls/list_my_pulls/comments/events) |
pulls.add_pull_assignee / remove_pull_assignee | 路径 {ITEM}/assignees/{user_ref}(add_pull_assignee.rs:11-17,user_ref: String 接受用户名或 UUID),POST/DELETE 双方法;前端 pathOf+user_ref 段序一致 |
pulls.attach_pull_label / detach_pull_label / list_pull_labels / list_label_options | AttachLabelCmd{label:String}(packages/apiclient/pulls/attach-pull-label.schemas.ts)与前端 {label} 一致;label_ref:String(detach_pull_label.rs:10-16);list_label_options 路由注册在 {ITEM} 之前(routes.rs:55-57),实证 GET /api/v1/repos/demo/demorepos/pulls/label-options → 200(未被 {pull_ref} 吞掉) |
pulls.link_issue_pull / unlink_issue_pull / list_issue_pulls | 路径段顺序 issues/{space_name}/boards/{board_id}/issues/{issue_id}/pulls[/{relation_id}] 与生成 URL 完全一致;LinkIssueCmd{space_name,repo_name,pull_ref} 与前端 data 一致;board_id/relation_id 为 Uuid,前端传字符串 UUID |
pulls.get_pull_patch | 前端不走 Apis,api.ts:163-164 手工拼 /api/v1/repos/{s}/{r}/pulls/{pull}/patch,与 routes.rs:58 的 {ITEM}/patch 一致(浏览器直连下载,不涉及信封) |
reviews.submit_review / publish_review | body 均为 SubmitReviewCmd(services/reviews.rs:49-56):state: ReviewIntent(approve|request_changes|comment|pending,#[serde(rename_all="snake_case")] 见 :21-28)、body、comments: Vec<InlineDraft>;InlineDraft{path,line: i32,side,body} 与前端 {path,line,side,body} 一致。PATCH 语义不适用(POST 整体提交) |
reviews.get_review / list_reviews | GET {ITEM}/reviews/{review_id} 单对象、GET {ITEM}/reviews 数组(list_reviews.rs:18),前端分别 oneOf/listOf;PullReviewDto 的正文叫 content(:65),前端 conversation.tsx:332 body: review.content ?? null 用的是正确字段名;ReviewState 取值 approved|changes_requested|commented|pending|dismissed 与 conversation.tsx:370/397 的判断一致 |
reviews.dismiss_review | PUT {ITEM}/reviews/{review_id}/dismiss,ReviewPath{space_name,repo_name,pull_ref,review_id:Uuid} 段序与前端 pathOf+review_id 一致,无 body |
reviews.request_reviewers | RequestReviewersCmd(services/reviewers.rs:52-58):reviewers: Vec<String>、required: bool 且 #[serde(default = "default_required")] 默认 true。前端 api.ts:249 发 required: required ?? undefined,但唯一调用点 pull-detail.tsx:718/735 的 useState(true) 恒传布尔 → 与后端默认一致,未踩“缺省即计入必需审批”的坑 |
reviews.list_requested_reviewers / delete_requested_reviewer | 均返回/作用于 Vec<PullReviewerDto>;路径 …/requested-reviewers/{user_id},ReviewerPath.user_id: i64,前端传 number(:253-254)一致 |
protection.* 5 个操作(分支策略 CRUD) | 生成 URL /api/v1/repos/{s}/{r}/branch-protection[/{policy_id}](packages/apiclient/protection/*.ts)与 lib/service/git/src/routes.rs:291-299 完全一致(注意:openapi tag 名是 protection,路径前缀是 branch-protection,两者不冲突);body 15 个字段名与 CreateBranchPolicyCmd(services/protection.rs:101-131)、UpdateBranchPolicyCmd(:157-188)逐一对应,required_approvals 前后端同名(不存在 required_reviewers),生成类型 CreateBranchPolicyCmd/UpdateBranchPolicyCmd(packages/apiclient/protection/*.schemas.ts)亦一致;list_policies 返回数组(EnvelopeVecBranchPolicyDto)、get/create/update 返回单对象,前端 listPolicies 用数组分支、其余 oneOf,正确(未实证:require_repo_admin,匿名 401) |
branch_protection.* 3 个操作 | 路径 /branch-protection/{policy_id}/whitelists[/{whitelist_id}] 与 routes.rs:301-307 一致;AddBranchWhitelistCmd{kind, user_id: Option<i64>, group_id: Option<Uuid>}(services/whitelists.rs:49-53)与前端 data 一致,缺省字段反序列化为 None(serde 对 Option 缺省即 None),前端显式补 null 同样安全;WhitelistKind 取值 Push|Merge|Approval PascalCase(services/whitelists.rs:12-16,无 rename_all),前端 KIND_LABEL 与 useState<WhitelistKind>('Push')(protection-settings.tsx:28-36/292)完全吻合——虽与“枚举一律 snake_case”的约定不符,但前后端内部自洽,不构成错配 |
tag_protection.* 3 个操作(whitelist) | 路径 /tag-protection/{policy_id}/whitelists[/{user_id}] 与 routes.rs:321-326 一致;AddTagWhitelistCmd{user_id: i64}(services/whitelists.rs:63-66)与前端 {user_id: number} 一致,user_id 来自 Number(...) 而非字符串(protection-settings.tsx:430),不会触发 i64 反序列化失败 |