gitbay/MRs/MRModels.swift

v1.0.6
gitbay-ios/gitbay/MRs/MRModels.swift history · blame · raw

216 lines · 8346 bytes

  1import Foundation
  2
  3/// The MR below or above a stack member: `mr show` sends `{number, title}`.
  4nonisolated struct StackRef: Decodable, Sendable, Hashable, Identifiable {
  5    let number: Int64
  6    let title: String
  7
  8    var id: Int64 { number }
  9}
 10
 11/// One row of `mr list`, and the header half of `mr show`.
 12nonisolated struct MergeRequest: Decodable, Sendable, Hashable, Identifiable {
 13    let number: Int64
 14    let title: String
 15    let state: String
 16    let author: String
 17    /// "branch" for a same-repo MR, "owner/name:branch" cross-repo,
 18    /// "" when the source is gone.
 19    let source: String
 20    let targetRef: String
 21    let headSHA: String
 22    let body: String?
 23    let createdAt: Date
 24    /// Open but not asking: does not merge, does not appear in a review queue.
 25    let draft: Bool
 26    let stackedOn: StackRef?
 27
 28    enum CodingKeys: String, CodingKey {
 29        case number, title, state, author, source, body, draft
 30        case targetRef = "target_ref"
 31        case headSHA = "head_sha"
 32        case createdAt = "created_at"
 33        case stackedOn = "stacked_on"
 34    }
 35
 36    init(from decoder: Decoder) throws {
 37        let container = try decoder.container(keyedBy: CodingKeys.self)
 38        number = try container.decode(Int64.self, forKey: .number)
 39        title = try container.decode(String.self, forKey: .title)
 40        state = try container.decode(String.self, forKey: .state)
 41        author = try container.decode(String.self, forKey: .author)
 42        source = try container.decode(String.self, forKey: .source)
 43        targetRef = try container.decode(String.self, forKey: .targetRef)
 44        headSHA = try container.decode(String.self, forKey: .headSHA)
 45        body = try container.decodeIfPresent(String.self, forKey: .body)
 46        createdAt = try container.decode(Date.self, forKey: .createdAt)
 47        draft = try container.decodeIfPresent(Bool.self, forKey: .draft) ?? false
 48        stackedOn = try container.decodeIfPresent(StackRef.self, forKey: .stackedOn)
 49    }
 50
 51    var id: Int64 { number }
 52    var isOpen: Bool { state == "open" }
 53}
 54
 55/// The whole of `mr show`.
 56nonisolated struct MRDetail: Decodable, Sendable, Hashable {
 57    let number: Int64
 58    let title: String
 59    let state: String
 60    let author: String
 61    let source: String
 62    let targetRef: String
 63    let headSHA: String
 64    let body: String?
 65    let bodyFormat: String?
 66    let milestone: String?
 67    let createdAt: Date
 68    /// Set once the merge request is resolved. The actor is absent when
 69    /// the account is gone, or the row was imported.
 70    let mergedAt: Date?
 71    let mergedBy: String?
 72    let closedAt: Date?
 73    let closedBy: String?
 74    let checks: [Check]?
 75    let checksCombined: String?
 76    let unresolvedThreads: Int?
 77    let commits: [MRCommit]?
 78    let comments: [MRComment]?
 79    let reviews: [Review]?
 80    /// Open but not asking: does not merge, does not appear in a review queue.
 81    let draft: Bool
 82    let reviewRequests: [String]
 83    let stackedOn: StackRef?
 84    let stacked: [StackRef]
 85
 86    enum CodingKeys: String, CodingKey {
 87        case number, title, state, author, source, body, milestone, checks, commits, comments, reviews, draft, stacked
 88        case bodyFormat = "body_format"
 89        case targetRef = "target_ref"
 90        case headSHA = "head_sha"
 91        case createdAt = "created_at"
 92        case mergedAt = "merged_at"
 93        case mergedBy = "merged_by"
 94        case closedAt = "closed_at"
 95        case closedBy = "closed_by"
 96        case checksCombined = "checks_combined"
 97        case unresolvedThreads = "unresolved_threads"
 98        case reviewRequests = "review_requests"
 99        case stackedOn = "stacked_on"
100    }
101
102    init(from decoder: Decoder) throws {
103        let container = try decoder.container(keyedBy: CodingKeys.self)
104        number = try container.decode(Int64.self, forKey: .number)
105        title = try container.decode(String.self, forKey: .title)
106        state = try container.decode(String.self, forKey: .state)
107        author = try container.decode(String.self, forKey: .author)
108        source = try container.decode(String.self, forKey: .source)
109        targetRef = try container.decode(String.self, forKey: .targetRef)
110        headSHA = try container.decode(String.self, forKey: .headSHA)
111        body = try container.decodeIfPresent(String.self, forKey: .body)
112        bodyFormat = try container.decodeIfPresent(String.self, forKey: .bodyFormat)
113        milestone = try container.decodeIfPresent(String.self, forKey: .milestone)
114        createdAt = try container.decode(Date.self, forKey: .createdAt)
115        mergedAt = try container.decodeIfPresent(Date.self, forKey: .mergedAt)
116        mergedBy = try container.decodeIfPresent(String.self, forKey: .mergedBy)
117        closedAt = try container.decodeIfPresent(Date.self, forKey: .closedAt)
118        closedBy = try container.decodeIfPresent(String.self, forKey: .closedBy)
119        checks = try container.decodeIfPresent([Check].self, forKey: .checks)
120        checksCombined = try container.decodeIfPresent(String.self, forKey: .checksCombined)
121        unresolvedThreads = try container.decodeIfPresent(Int.self, forKey: .unresolvedThreads)
122        commits = try container.decodeIfPresent([MRCommit].self, forKey: .commits)
123        comments = try container.decodeIfPresent([MRComment].self, forKey: .comments)
124        reviews = try container.decodeIfPresent([Review].self, forKey: .reviews)
125        draft = try container.decodeIfPresent(Bool.self, forKey: .draft) ?? false
126        reviewRequests = try container.decodeIfPresent([String].self, forKey: .reviewRequests) ?? []
127        stackedOn = try container.decodeIfPresent(StackRef.self, forKey: .stackedOn)
128        stacked = try container.decodeIfPresent([StackRef].self, forKey: .stacked) ?? []
129    }
130
131    var isOpen: Bool { state == "open" }
132
133    nonisolated struct Check: Decodable, Sendable, Hashable {
134        let context: String
135        let state: String
136        let url: String?
137        /// When the check last changed state.
138        let updatedAt: Date?
139        /// How long the build behind the check ran, as the server
140        /// formats it ("1m12s"). Only CI checks have a build to time.
141        let duration: String?
142
143        enum CodingKeys: String, CodingKey {
144            case context, state, url, duration
145            case updatedAt = "updated_at"
146        }
147    }
148
149    nonisolated struct MRCommit: Decodable, Sendable, Hashable, Identifiable {
150        let sha: String
151        let subject: String
152        var id: String { sha }
153        var shortSHA: String { String(sha.prefix(10)) }
154    }
155
156    nonisolated struct MRComment: Decodable, Sendable, Hashable, Identifiable {
157        let author: String
158        let body: String
159        let bodyFormat: String?
160        let createdAt: Date
161
162        enum CodingKeys: String, CodingKey {
163            case author, body
164            case bodyFormat = "body_format"
165            case createdAt = "created_at"
166        }
167
168        var id: String { author + createdAt.timeIntervalSince1970.description + body }
169    }
170
171    nonisolated struct Review: Decodable, Sendable, Hashable, Identifiable {
172        let reviewer: String
173        let verdict: String
174        /// The review predates the current head — it approved older code.
175        let stale: Bool
176        let createdAt: Date?
177
178        enum CodingKeys: String, CodingKey {
179            case reviewer, verdict, stale
180            case createdAt = "created_at"
181        }
182
183        var id: String { reviewer }
184    }
185}
186
187/// One review thread of `mr threads`.
188nonisolated struct ReviewThread: Decodable, Sendable, Hashable, Identifiable {
189    let id: Int64
190    let path: String
191    let side: String // new | old
192    let line: Int64
193    /// Anchored to an older head; the diff has moved under it.
194    let stale: Bool
195    let resolvedBy: String?
196    let comments: [Comment]
197
198    enum CodingKeys: String, CodingKey {
199        case id, path, side, line, stale, comments
200        case resolvedBy = "resolved_by"
201    }
202
203    var isResolved: Bool { resolvedBy != nil }
204
205    nonisolated struct Comment: Decodable, Sendable, Hashable, Identifiable {
206        let id: Int64
207        let author: String
208        let body: String
209        let createdAt: Date
210
211        enum CodingKeys: String, CodingKey {
212            case id, author, body
213            case createdAt = "created_at"
214        }
215    }
216}