gitbay/Issues/IssueCreateViewModel.swift

v1.2.0
gitbay-ios/gitbay/Issues/IssueCreateViewModel.swift history · blame · raw

62 lines · 2041 bytes

 1import Foundation
 2import Observation
 3
 4/// One of `issue templates <repo>` — a body the compose sheet can start from.
 5nonisolated struct IssueTemplate: Decodable, Sendable, Hashable, Identifiable {
 6    let name: String
 7    let body: String
 8    var id: String { name }
 9}
10
11/// `issue create <repo> --title <t> --file -`, and the repo's templates.
12@Observable
13@MainActor
14final class IssueCreateViewModel {
15
16    private(set) var working = false
17    private(set) var errorMessage: String?
18    private(set) var templates: [IssueTemplate] = []
19    var format: String?
20
21    private let client: GitbayClient
22    let repoPath: String
23
24    init(client: GitbayClient, repoPath: String) {
25        self.client = client
26        self.repoPath = repoPath
27    }
28
29    nonisolated private struct Created: Decodable, Sendable {
30        let number: Int64
31    }
32
33    /// `issue templates <repo>`. Templates are a convenience, so a failed
34    /// read leaves the sheet without them rather than surfacing an error.
35    func loadTemplates() async {
36        templates = (try? await client.readList(
37            ["issue", "templates", repoPath], of: IssueTemplate.self)) ?? []
38    }
39
40    /// Returns the new issue's number, or nil with `errorMessage` set.
41    func create(title: String, body: String) async -> Int64? {
42        working = true
43        errorMessage = nil
44        defer { working = false }
45        do {
46            var argv = ["issue", "create", repoPath, "--title", title]
47            var stdin: String?
48            if !body.isEmpty {
49                argv.append(contentsOf: ["--file", "-"])
50                stdin = body
51            }
52            if let format { argv.append(contentsOf: ["--format", format]) }
53            let created = try await client.run(argv, stdin: stdin, as: Created.self)
54            return created?.number
55        } catch let error as GitbayError {
56            errorMessage = error.userFacingMessage
57        } catch {
58            errorMessage = GitbayError.transport(error).userFacingMessage
59        }
60        return nil
61    }
62}