feat(gitlab): Add MergeRequest and related types for API deserialization

Extends GitLab type definitions with comprehensive merge request support,
matching the API response structure for /projects/:id/merge_requests.

New types:
- MergeRequest: Full MR metadata including draft status, branch info,
  detailed_merge_status, merge_user (modern API fields replacing
  deprecated alternatives), and references for cross-project support
- MrReviewer: Reviewer user info (MR-specific, distinct from assignees)
- MrAssignee: Assignee user info with consistent structure
- MrDiscussion: MR discussion wrapper for polymorphic handling
- DiffNotePosition: Rich position data for code review comments with
  line ranges and SHA triplet for commit context

Design decisions:
- Use Option<T> for all nullable API fields to handle partial responses
- Include deprecated fields (merged_by, merge_status) alongside modern
  alternatives for backward compatibility with older GitLab instances
- DiffNotePosition uses Option for all fields since different position
  types (text/image/file) populate different subsets

These types enable type-safe deserialization of GitLab MR API responses
with full coverage of the fields needed for CP2 ingestion.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Taylor Eernisse
2026-01-26 22:44:58 -05:00
parent 39a71d8b85
commit a18908c377

View File

@@ -140,4 +140,120 @@ pub struct GitLabNotePosition {
pub new_path: Option<String>, pub new_path: Option<String>,
pub old_line: Option<i32>, pub old_line: Option<i32>,
pub new_line: Option<i32>, pub new_line: Option<i32>,
/// Position type: "text", "image", or "file".
pub position_type: Option<String>,
/// Line range for multi-line comments (GitLab 13.6+).
pub line_range: Option<GitLabLineRange>,
/// Base commit SHA for the diff.
pub base_sha: Option<String>,
/// Start commit SHA for the diff.
pub start_sha: Option<String>,
/// Head commit SHA for the diff.
pub head_sha: Option<String>,
}
/// Line range for multi-line DiffNote comments.
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GitLabLineRange {
pub start: GitLabLineRangePoint,
pub end: GitLabLineRangePoint,
}
/// A point in a line range (start or end).
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GitLabLineRangePoint {
pub line_code: Option<String>,
/// "old" or "new".
#[serde(rename = "type")]
pub line_type: Option<String>,
pub old_line: Option<i32>,
pub new_line: Option<i32>,
}
impl GitLabLineRange {
/// Get the start line number (new_line preferred, falls back to old_line).
pub fn start_line(&self) -> Option<i32> {
self.start.new_line.or(self.start.old_line)
}
/// Get the end line number (new_line preferred, falls back to old_line).
pub fn end_line(&self) -> Option<i32> {
self.end.new_line.or(self.end.old_line)
}
}
// === Checkpoint 2: Merge Request types ===
/// GitLab MR references (short and full reference strings).
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GitLabReferences {
/// Short reference e.g. "!42".
pub short: String,
/// Full reference e.g. "group/project!42".
pub full: String,
}
/// GitLab Reviewer (can have approval state in future).
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GitLabReviewer {
pub id: i64,
pub username: String,
pub name: String,
}
/// GitLab Merge Request from /projects/:id/merge_requests endpoint.
/// Note: Uses non-deprecated field names where possible (detailed_merge_status, merge_user).
/// Falls back gracefully for older GitLab versions.
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GitLabMergeRequest {
/// GitLab global ID (unique across all projects).
pub id: i64,
/// Project-scoped MR number (the number shown in the UI).
pub iid: i64,
/// The project this MR belongs to.
pub project_id: i64,
pub title: String,
pub description: Option<String>,
/// "opened" | "merged" | "closed" | "locked".
pub state: String,
/// Work-in-progress status (preferred over work_in_progress).
#[serde(default)]
pub draft: bool,
/// Deprecated; fallback for older instances.
#[serde(default)]
pub work_in_progress: bool,
pub source_branch: String,
pub target_branch: String,
/// Current commit SHA at head of source branch (CP3-ready).
pub sha: Option<String>,
/// Short and full reference strings (CP3-ready).
pub references: Option<GitLabReferences>,
/// Non-deprecated merge status. Prefer over merge_status.
pub detailed_merge_status: Option<String>,
/// Deprecated merge_status field for fallback.
#[serde(alias = "merge_status")]
pub merge_status_legacy: Option<String>,
/// ISO 8601 timestamp.
pub created_at: String,
/// ISO 8601 timestamp.
pub updated_at: String,
/// ISO 8601 timestamp when merged (null if not merged).
pub merged_at: Option<String>,
/// ISO 8601 timestamp when closed (null if not closed).
pub closed_at: Option<String>,
pub author: GitLabAuthor,
/// Non-deprecated; who merged this MR.
pub merge_user: Option<GitLabAuthor>,
/// Deprecated; fallback for older instances.
pub merged_by: Option<GitLabAuthor>,
/// Array of label names.
#[serde(default)]
pub labels: Vec<String>,
/// Assignees (can be multiple).
#[serde(default)]
pub assignees: Vec<GitLabAuthor>,
/// Reviewers (MR-specific).
#[serde(default)]
pub reviewers: Vec<GitLabReviewer>,
pub web_url: String,
} }