Files
Termind/TFI/Application/FeatureModels/MobileSecretDraft.swift
T

81 lines
3.3 KiB
Swift

import Foundation
import Observation
import TermindApplication
/// Plaintext lives only in an editor or a request. Previewing never writes the Keychain.
@MainActor @Observable
final class MobileSecretDraft {
enum Action: String, CaseIterable { case preserve, replace, clear }
private var originalReference = ""
private var originalSecret: String?
private var readError: String?
func begin(in ai: AIConfigurationModel, reference: String) {
originalReference = reference
if !reference.isEmpty {
originalSecret = ai.secret(for: reference)
readError = ai.secretStorageError
}
}
var action = Action.preserve
var text = ""
var isDirty: Bool { action != .preserve || !text.isEmpty }
var isValid: Bool { action != .replace || !text.isEmpty }
func effective(in ai: AIConfigurationModel, reference: String) throws -> String {
switch action {
case .clear: return ""
case .replace: return text
case .preserve:
guard !reference.isEmpty else { return "" }
let value = ai.secret(for: reference) ?? ""
if let error = ai.secretStorageError { throw MobileEditorError.message(error) }
return value
}
}
func providerEdit(reference: String, kind: ModelProviderCredentialKind) throws -> ModelProviderCredentialEdit? {
guard action != .preserve else { return nil }
if let readError { throw MobileEditorError.message(readError) }
guard reference == originalReference else { throw SettingsDraftMerge.Conflict.changed }
return .init(
kind: kind, reference: reference, previous: originalSecret, replacement: action == .clear ? nil : text)
}
func discardPlaintext() {
text = ""
originalSecret = nil
}
func commit(in ai: AIConfigurationModel, reference: String, kind: String = "key", save: (String) -> Bool) throws {
if action != .preserve {
if let readError { throw MobileEditorError.message(readError) }
guard reference == originalReference else { throw SettingsDraftMerge.Conflict.changed }
if !reference.isEmpty {
let current = ai.secret(for: reference)
if let error = ai.secretStorageError { throw MobileEditorError.message(error) }
guard current == originalSecret else { throw SettingsDraftMerge.Conflict.changed }
}
}
let ref = reference.isEmpty ? AIConfigurationModel.makeCredentialRef(kind) : reference
var previous: String?
if action != .preserve {
previous = ai.secret(for: ref)
if let error = ai.secretStorageError { throw MobileEditorError.message(error) }
ai.setSecret(action == .clear ? nil : text, for: ref)
if let error = ai.secretStorageError { throw MobileEditorError.message(error) }
}
let next = action == .preserve ? reference : action == .clear ? "" : ref
guard save(next) else {
var message = ai.storageError ?? String(localized: "Unable to Save")
if action != .preserve {
ai.setSecret(previous, for: ref)
if let error = ai.secretStorageError { message += "\n" + error }
}
throw MobileEditorError.message(message)
}
text = ""
}
}