feat(types): Add GitLab Resource Event serde types with deserialization tests
Adds six new types for deserializing responses from GitLab's three Resource Events API endpoints (state, label, milestone): - GitLabStateEvent: State transitions with optional user, source_commit, and source_merge_request reference - GitLabLabelEvent: Label add/remove events with nested GitLabLabelRef - GitLabMilestoneEvent: Milestone assignment changes with nested GitLabMilestoneRef - GitLabMergeRequestRef: Lightweight MR reference (iid, title, web_url) - GitLabLabelRef: Label metadata (id, name, color, description) - GitLabMilestoneRef: Milestone metadata (id, iid, title) All types derive Deserialize + Serialize and use Option<T> for nullable fields (user, source_commit, color, description) to match GitLab's API contract where these fields may be null. Includes 8 new test cases covering: - State events with/without user, with/without source_merge_request - Label events for add and remove actions, including null color handling - Milestone event deserialization - Standalone ref type deserialization (MR, label, milestone) Uses r##"..."## raw string delimiters where JSON contains hex color codes (#FF0000) that would conflict with r#"..."# delimiters. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ pub use transformers::{
|
||||
transform_discussion, transform_issue, transform_notes,
|
||||
};
|
||||
pub use types::{
|
||||
GitLabAuthor, GitLabDiscussion, GitLabIssue, GitLabNote, GitLabNotePosition, GitLabProject,
|
||||
GitLabUser, GitLabVersion,
|
||||
GitLabAuthor, GitLabDiscussion, GitLabIssue, GitLabLabelEvent, GitLabLabelRef,
|
||||
GitLabMergeRequestRef, GitLabMilestoneEvent, GitLabMilestoneRef, GitLabNote,
|
||||
GitLabNotePosition, GitLabProject, GitLabStateEvent, GitLabUser, GitLabVersion,
|
||||
};
|
||||
|
||||
@@ -182,6 +182,70 @@ impl GitLabLineRange {
|
||||
}
|
||||
}
|
||||
|
||||
// === Resource Event types (Phase B - Gate 1) ===
|
||||
|
||||
/// Reference to an MR in state event's source_merge_request field.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct GitLabMergeRequestRef {
|
||||
pub iid: i64,
|
||||
pub title: Option<String>,
|
||||
pub web_url: Option<String>,
|
||||
}
|
||||
|
||||
/// Reference to a label in label event's label field.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct GitLabLabelRef {
|
||||
pub id: i64,
|
||||
pub name: String,
|
||||
pub color: Option<String>,
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
/// Reference to a milestone in milestone event's milestone field.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct GitLabMilestoneRef {
|
||||
pub id: i64,
|
||||
pub iid: i64,
|
||||
pub title: String,
|
||||
}
|
||||
|
||||
/// State change event from the Resource State Events API.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct GitLabStateEvent {
|
||||
pub id: i64,
|
||||
pub user: Option<GitLabAuthor>,
|
||||
pub created_at: String,
|
||||
pub resource_type: String,
|
||||
pub resource_id: i64,
|
||||
pub state: String,
|
||||
pub source_commit: Option<String>,
|
||||
pub source_merge_request: Option<GitLabMergeRequestRef>,
|
||||
}
|
||||
|
||||
/// Label change event from the Resource Label Events API.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct GitLabLabelEvent {
|
||||
pub id: i64,
|
||||
pub user: Option<GitLabAuthor>,
|
||||
pub created_at: String,
|
||||
pub resource_type: String,
|
||||
pub resource_id: i64,
|
||||
pub label: GitLabLabelRef,
|
||||
pub action: String,
|
||||
}
|
||||
|
||||
/// Milestone change event from the Resource Milestone Events API.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct GitLabMilestoneEvent {
|
||||
pub id: i64,
|
||||
pub user: Option<GitLabAuthor>,
|
||||
pub created_at: String,
|
||||
pub resource_type: String,
|
||||
pub resource_id: i64,
|
||||
pub milestone: GitLabMilestoneRef,
|
||||
pub action: String,
|
||||
}
|
||||
|
||||
// === Checkpoint 2: Merge Request types ===
|
||||
|
||||
/// GitLab MR references (short and full reference strings).
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
//! Tests for GitLab API response type deserialization.
|
||||
|
||||
use lore::gitlab::types::{
|
||||
GitLabAuthor, GitLabDiscussion, GitLabIssue, GitLabMergeRequest, GitLabMilestone, GitLabNote,
|
||||
GitLabNotePosition, GitLabReferences, GitLabReviewer,
|
||||
GitLabAuthor, GitLabDiscussion, GitLabIssue, GitLabLabelEvent, GitLabLabelRef,
|
||||
GitLabMergeRequest, GitLabMergeRequestRef, GitLabMilestone, GitLabMilestoneEvent,
|
||||
GitLabMilestoneRef, GitLabNote, GitLabNotePosition, GitLabReferences, GitLabReviewer,
|
||||
GitLabStateEvent,
|
||||
};
|
||||
|
||||
#[test]
|
||||
@@ -637,3 +639,209 @@ fn deserializes_diffnote_position_with_line_range() {
|
||||
assert_eq!(range.start_line(), Some(10));
|
||||
assert_eq!(range.end_line(), Some(15));
|
||||
}
|
||||
|
||||
// === Resource Event type tests ===
|
||||
|
||||
#[test]
|
||||
fn deserializes_state_event_closed_by_mr() {
|
||||
let json = r#"{
|
||||
"id": 1001,
|
||||
"user": {
|
||||
"id": 42,
|
||||
"username": "developer",
|
||||
"name": "Dev User"
|
||||
},
|
||||
"created_at": "2024-03-15T10:30:00.000Z",
|
||||
"resource_type": "Issue",
|
||||
"resource_id": 555,
|
||||
"state": "closed",
|
||||
"source_commit": null,
|
||||
"source_merge_request": {
|
||||
"iid": 99,
|
||||
"title": "Fix the bug",
|
||||
"web_url": "https://gitlab.example.com/group/project/-/merge_requests/99"
|
||||
}
|
||||
}"#;
|
||||
|
||||
let event: GitLabStateEvent =
|
||||
serde_json::from_str(json).expect("Failed to deserialize state event");
|
||||
|
||||
assert_eq!(event.id, 1001);
|
||||
assert!(event.user.is_some());
|
||||
assert_eq!(event.user.as_ref().unwrap().username, "developer");
|
||||
assert_eq!(event.resource_type, "Issue");
|
||||
assert_eq!(event.resource_id, 555);
|
||||
assert_eq!(event.state, "closed");
|
||||
assert!(event.source_commit.is_none());
|
||||
assert!(event.source_merge_request.is_some());
|
||||
let mr_ref = event.source_merge_request.unwrap();
|
||||
assert_eq!(mr_ref.iid, 99);
|
||||
assert_eq!(mr_ref.title, Some("Fix the bug".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserializes_state_event_simple_no_user() {
|
||||
let json = r#"{
|
||||
"id": 1002,
|
||||
"user": null,
|
||||
"created_at": "2024-03-15T10:30:00.000Z",
|
||||
"resource_type": "MergeRequest",
|
||||
"resource_id": 777,
|
||||
"state": "merged",
|
||||
"source_commit": "abc123def456",
|
||||
"source_merge_request": null
|
||||
}"#;
|
||||
|
||||
let event: GitLabStateEvent =
|
||||
serde_json::from_str(json).expect("Failed to deserialize state event without user");
|
||||
|
||||
assert_eq!(event.id, 1002);
|
||||
assert!(event.user.is_none());
|
||||
assert_eq!(event.resource_type, "MergeRequest");
|
||||
assert_eq!(event.state, "merged");
|
||||
assert_eq!(event.source_commit, Some("abc123def456".to_string()));
|
||||
assert!(event.source_merge_request.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserializes_label_event_add() {
|
||||
let json = r##"{
|
||||
"id": 2001,
|
||||
"user": {
|
||||
"id": 42,
|
||||
"username": "developer",
|
||||
"name": "Dev User"
|
||||
},
|
||||
"created_at": "2024-03-15T10:30:00.000Z",
|
||||
"resource_type": "Issue",
|
||||
"resource_id": 555,
|
||||
"label": {
|
||||
"id": 100,
|
||||
"name": "bug",
|
||||
"color": "#FF0000",
|
||||
"description": "Bug label"
|
||||
},
|
||||
"action": "add"
|
||||
}"##;
|
||||
|
||||
let event: GitLabLabelEvent =
|
||||
serde_json::from_str(json).expect("Failed to deserialize label event");
|
||||
|
||||
assert_eq!(event.id, 2001);
|
||||
assert_eq!(event.action, "add");
|
||||
assert_eq!(event.label.id, 100);
|
||||
assert_eq!(event.label.name, "bug");
|
||||
assert_eq!(event.label.color, Some("#FF0000".to_string()));
|
||||
assert_eq!(event.label.description, Some("Bug label".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserializes_label_event_remove_null_color() {
|
||||
let json = r#"{
|
||||
"id": 2002,
|
||||
"user": {
|
||||
"id": 42,
|
||||
"username": "developer",
|
||||
"name": "Dev User"
|
||||
},
|
||||
"created_at": "2024-03-15T10:30:00.000Z",
|
||||
"resource_type": "MergeRequest",
|
||||
"resource_id": 777,
|
||||
"label": {
|
||||
"id": 101,
|
||||
"name": "needs-review",
|
||||
"color": null,
|
||||
"description": null
|
||||
},
|
||||
"action": "remove"
|
||||
}"#;
|
||||
|
||||
let event: GitLabLabelEvent =
|
||||
serde_json::from_str(json).expect("Failed to deserialize label remove event");
|
||||
|
||||
assert_eq!(event.action, "remove");
|
||||
assert!(event.label.color.is_none());
|
||||
assert!(event.label.description.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserializes_milestone_event() {
|
||||
let json = r#"{
|
||||
"id": 3001,
|
||||
"user": {
|
||||
"id": 42,
|
||||
"username": "developer",
|
||||
"name": "Dev User"
|
||||
},
|
||||
"created_at": "2024-03-15T10:30:00.000Z",
|
||||
"resource_type": "Issue",
|
||||
"resource_id": 555,
|
||||
"milestone": {
|
||||
"id": 200,
|
||||
"iid": 5,
|
||||
"title": "v1.0"
|
||||
},
|
||||
"action": "add"
|
||||
}"#;
|
||||
|
||||
let event: GitLabMilestoneEvent =
|
||||
serde_json::from_str(json).expect("Failed to deserialize milestone event");
|
||||
|
||||
assert_eq!(event.id, 3001);
|
||||
assert_eq!(event.action, "add");
|
||||
assert_eq!(event.milestone.id, 200);
|
||||
assert_eq!(event.milestone.iid, 5);
|
||||
assert_eq!(event.milestone.title, "v1.0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserializes_merge_request_ref() {
|
||||
let json = r#"{
|
||||
"iid": 42,
|
||||
"title": "Feature branch",
|
||||
"web_url": "https://gitlab.example.com/group/project/-/merge_requests/42"
|
||||
}"#;
|
||||
|
||||
let mr_ref: GitLabMergeRequestRef =
|
||||
serde_json::from_str(json).expect("Failed to deserialize MR ref");
|
||||
|
||||
assert_eq!(mr_ref.iid, 42);
|
||||
assert_eq!(mr_ref.title, Some("Feature branch".to_string()));
|
||||
assert_eq!(
|
||||
mr_ref.web_url,
|
||||
Some("https://gitlab.example.com/group/project/-/merge_requests/42".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserializes_label_ref() {
|
||||
let json = r##"{
|
||||
"id": 100,
|
||||
"name": "bug",
|
||||
"color": "#FF0000",
|
||||
"description": "Bug label"
|
||||
}"##;
|
||||
|
||||
let label_ref: GitLabLabelRef =
|
||||
serde_json::from_str(json).expect("Failed to deserialize label ref");
|
||||
|
||||
assert_eq!(label_ref.id, 100);
|
||||
assert_eq!(label_ref.name, "bug");
|
||||
assert_eq!(label_ref.color, Some("#FF0000".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserializes_milestone_ref() {
|
||||
let json = r#"{
|
||||
"id": 200,
|
||||
"iid": 5,
|
||||
"title": "v1.0"
|
||||
}"#;
|
||||
|
||||
let ms_ref: GitLabMilestoneRef =
|
||||
serde_json::from_str(json).expect("Failed to deserialize milestone ref");
|
||||
|
||||
assert_eq!(ms_ref.id, 200);
|
||||
assert_eq!(ms_ref.iid, 5);
|
||||
assert_eq!(ms_ref.title, "v1.0");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user