59 lines
2.1 KiB
Swift
59 lines
2.1 KiB
Swift
import Foundation
|
|
|
|
/// Transport boundary. A future URLSession implementation must enforce a finite
|
|
/// request timeout and throw HTTP errors before decoding the response body.
|
|
@MainActor
|
|
protocol LeadRemoteService {
|
|
func fetchLeads(salesCode: String, query: LeadQuery) async throws -> [LeadDTO]
|
|
func fetchLead(id: String, salesCode: String) async throws -> LeadDTO
|
|
func setBookmark(_ bookmarked: Bool, id: String, salesCode: String) async throws -> LeadDTO
|
|
}
|
|
|
|
enum LeadRemoteError: Error {
|
|
case http(statusCode: Int)
|
|
case malformedResponse
|
|
}
|
|
|
|
/// Provisional transport shape, independent of the UI and domain model.
|
|
struct LeadDTO: Codable, Sendable {
|
|
let id: String
|
|
let companyName: String?
|
|
let contactTitle: String?
|
|
let firstName: String?
|
|
let lastName: String?
|
|
let street: String?
|
|
let postalCode: String
|
|
let city: String
|
|
let phoneNumber: String?
|
|
let createdAt: Date
|
|
let offerCount: Int
|
|
let unsignedContractCount: Int
|
|
var isBookmarked: Bool
|
|
|
|
func toDomain() throws -> Lead {
|
|
func clean(_ value: String?) -> String? {
|
|
guard let text = value?.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
!text.isEmpty else { return nil }
|
|
return text
|
|
}
|
|
let company = clean(companyName)
|
|
let first = clean(firstName)
|
|
let last = clean(lastName)
|
|
guard clean(id) != nil, company != nil || first != nil || last != nil,
|
|
clean(postalCode) != nil, clean(city) != nil,
|
|
offerCount >= 0, unsignedContractCount >= 0 else {
|
|
throw LeadError.invalidResponse
|
|
}
|
|
let contact = first != nil || last != nil
|
|
? LeadContact(title: clean(contactTitle), firstName: first ?? "", lastName: last ?? "")
|
|
: nil
|
|
return Lead(
|
|
id: id, companyName: company, contact: contact,
|
|
address: LeadAddress(street: clean(street), postalCode: postalCode, city: city),
|
|
phoneNumber: clean(phoneNumber), createdAt: createdAt,
|
|
offerCount: offerCount, unsignedContractCount: unsignedContractCount,
|
|
isBookmarked: isBookmarked
|
|
)
|
|
}
|
|
}
|