commit 0ae444daa24d60a1813a45086dbbdc138944af7c
parent a4be87232936816d49be78f4be69dbe495a294be
Author: triesap <tyson@radroots.org>
Date: Fri, 19 Jun 2026 16:37:49 -0700
errors: adapt field app to typed runtime failures
- add a Swift adapter for generated Radroots runtime error categories.
- route app error displays through typed runtime messages.
- lock FFI generation to the typed field_lib source revision.
- keep generated bindings and frameworks in the existing ignored output paths.
Diffstat:
16 files changed, 93 insertions(+), 31 deletions(-)
diff --git a/Radroots.xcodeproj/project.pbxproj b/Radroots.xcodeproj/project.pbxproj
@@ -56,6 +56,7 @@
D62E9461833A0AA5E622A1E6 /* ToastModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 227028B4EBDC6703999FB9DA /* ToastModifier.swift */; };
D9BF5BE7E4AB5EACBF342539 /* FieldSecureIdentityStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8246B707FA9D218414EC4038 /* FieldSecureIdentityStore.swift */; };
DCE468F668A3C346E716B04C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CCF0F7B3C57D8D770F178329 /* Assets.xcassets */; };
+ DDDA83E35D868FE927D2ED37 /* FieldRuntimeError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32238D3DFB4465DEEDE6E003 /* FieldRuntimeError.swift */; };
E1EDAEE6B182025ACAF754A6 /* RadrootsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15DBA726450712D6DE88E951 /* RadrootsProvider.swift */; };
E3864E34D67BAD0744B93180 /* Bundle+Build.swift in Sources */ = {isa = PBXBuildFile; fileRef = 138AA7BAA021EE13E829390B /* Bundle+Build.swift */; };
E432FD39ECC8F03764EEED81 /* FieldDocumentInterchangeUITestProbe.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF05B944D348DAA4EF28B463 /* FieldDocumentInterchangeUITestProbe.swift */; };
@@ -77,6 +78,7 @@
26BAE32CD1D46033DDA1A5BB /* TradeListingDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TradeListingDetailView.swift; sourceTree = "<group>"; };
2818363B157125491FB84A1E /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = "<group>"; };
2F3541389124A31F5D701A45 /* FieldLocationCheckIn.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldLocationCheckIn.swift; sourceTree = "<group>"; };
+ 32238D3DFB4465DEEDE6E003 /* FieldRuntimeError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldRuntimeError.swift; sourceTree = "<group>"; };
3B4E53FD4C4AADF63855888A /* FieldTelemetry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldTelemetry.swift; sourceTree = "<group>"; };
3E6187FA7C4786EC662718B2 /* FieldExternalActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldExternalActions.swift; sourceTree = "<group>"; };
41A4289F43625DD65E6C4B25 /* CopyRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyRow.swift; sourceTree = "<group>"; };
@@ -218,6 +220,7 @@
CA942715DB13FFD494FD35A0 /* FieldIdentityPublicMetadataStore.swift */,
9EB0A2CFCBBFA9D204C6992B /* FieldLocalState.swift */,
2F3541389124A31F5D701A45 /* FieldLocationCheckIn.swift */,
+ 32238D3DFB4465DEEDE6E003 /* FieldRuntimeError.swift */,
E883FDB7004A210C9D7BE27A /* FieldRuntimeService.swift */,
8246B707FA9D218414EC4038 /* FieldSecureIdentityStore.swift */,
3B4E53FD4C4AADF63855888A /* FieldTelemetry.swift */,
@@ -459,6 +462,7 @@
D4CFDE54747B6D6957977025 /* FieldIdentityPublicMetadataStore.swift in Sources */,
6E15D30653861F26AC45B501 /* FieldLocalState.swift in Sources */,
1C6EA551530A46CA77BD9E1C /* FieldLocationCheckIn.swift in Sources */,
+ DDDA83E35D868FE927D2ED37 /* FieldRuntimeError.swift in Sources */,
D25C1E1DC99F5CF8E99AE970 /* FieldRuntimeService.swift in Sources */,
D9BF5BE7E4AB5EACBF342539 /* FieldSecureIdentityStore.swift in Sources */,
9346DA48630668A65D37E14A /* FieldTelemetry.swift in Sources */,
diff --git a/Radroots/App/AppState.swift b/Radroots/App/AppState.swift
@@ -185,7 +185,7 @@ public final class AppState: ObservableObject {
telemetryProbeTask = nil
await FieldBackgroundURLSessionEvents.shared.completePendingAfterStartupFailure()
backgroundExecution = nil
- let message = error.localizedDescription
+ let message = error.fieldRuntimeMessage
bootstrapPhase = .failed(message)
telemetry.appStartupFailed(error)
startTelemetryProbeRefreshForUITest()
@@ -541,7 +541,7 @@ public final class AppState: ObservableObject {
} catch {
captureIntakeState.support = .unavailable
captureIntakeState.operation = .idle
- captureIntakeState.lastError = error.localizedDescription
+ captureIntakeState.lastError = error.fieldRuntimeMessage
captureIntakeState.recoveryAction = nil
telemetry.captureSupportRefreshed(
support: captureIntakeState.support,
@@ -576,7 +576,7 @@ public final class AppState: ObservableObject {
)
} catch {
captureIntakeState.operation = .idle
- captureIntakeState.lastError = error.localizedDescription
+ captureIntakeState.lastError = error.fieldRuntimeMessage
captureIntakeState.recoveryAction = captureRecoveryAction(for: error)
telemetry.captureOperation(
operation: operation,
@@ -674,7 +674,7 @@ public final class AppState: ObservableObject {
let snapshot = try await service.nostrIdentitySnapshot()
apply(identity: snapshot)
} catch {
- relayLastError = error.localizedDescription
+ relayLastError = error.fieldRuntimeMessage
}
await refreshRelayStatus(using: service)
await backgroundExecution?.updateRuntimeState(
@@ -768,7 +768,7 @@ public final class AppState: ObservableObject {
userPresenceStatus = record.statusText
telemetry.userPresence(action: action, outcome: "success")
} catch {
- userPresenceStatus = error.localizedDescription
+ userPresenceStatus = error.fieldRuntimeMessage
telemetry.userPresence(action: action, outcome: FieldTelemetry.userPresenceOutcome(for: error))
throw error
}
@@ -801,7 +801,7 @@ public final class AppState: ObservableObject {
do {
try await lockRuntimeIdentityState(using: service)
} catch {
- relayLastError = error.localizedDescription
+ relayLastError = error.fieldRuntimeMessage
}
hasKey = storedIdentityAvailable
await refreshRelayStatus(using: service)
@@ -912,7 +912,7 @@ public final class AppState: ObservableObject {
externalActionStatus = record.statusText
telemetry.externalAction(operation: "open", kind: record.kind, outcome: "success")
} catch {
- externalActionStatus = error.localizedDescription
+ externalActionStatus = error.fieldRuntimeMessage
telemetry.externalAction(
operation: "open",
kind: nil,
@@ -932,7 +932,7 @@ public final class AppState: ObservableObject {
do {
try await self?.connect(using: service)
} catch {
- self?.relayLastError = error.localizedDescription
+ self?.relayLastError = error.fieldRuntimeMessage
self?.relayLight = .red
}
while !Task.isCancelled {
diff --git a/Radroots/Runtime/FieldCaptureIntake.swift b/Radroots/Runtime/FieldCaptureIntake.swift
@@ -382,7 +382,7 @@ final class FieldCaptureIntake: @unchecked Sendable {
success: success
)
} catch {
- return .failure(.permanentFailure(error.localizedDescription))
+ return .failure(.permanentFailure(error.fieldRuntimeMessage))
}
}
@@ -395,7 +395,7 @@ final class FieldCaptureIntake: @unchecked Sendable {
success: success
)
} catch {
- return .failure(.permanentFailure(error.localizedDescription))
+ return .failure(.permanentFailure(error.fieldRuntimeMessage))
}
}
diff --git a/Radroots/Runtime/FieldLocationCheckIn.swift b/Radroots/Runtime/FieldLocationCheckIn.swift
@@ -95,7 +95,7 @@ public struct FieldLocationCheckIn: Sendable {
} catch RadrootsLocationServicesError.cancelled(let message) {
return .failed(availability, message)
} catch {
- return .failed(availability, error.localizedDescription)
+ return .failed(availability, error.fieldRuntimeMessage)
}
}
diff --git a/Radroots/Runtime/FieldRuntimeError.swift b/Radroots/Runtime/FieldRuntimeError.swift
@@ -0,0 +1,58 @@
+import Foundation
+
+enum FieldRuntimeErrorCategory: String, Sendable {
+ case initialization
+ case identity
+ case secureStore
+ case relay
+ case runtime
+ case unsupported
+ case internalFailure
+}
+
+extension RadrootsAppError {
+ var fieldCategory: FieldRuntimeErrorCategory {
+ switch self {
+ case .Initialization(_):
+ .initialization
+ case .Identity(_):
+ .identity
+ case .SecureStore(_):
+ .secureStore
+ case .Relay(_):
+ .relay
+ case .Runtime(_):
+ .runtime
+ case .Unsupported(_):
+ .unsupported
+ case .Internal(_):
+ .internalFailure
+ }
+ }
+
+ var fieldMessage: String {
+ switch self {
+ case .Initialization(let message),
+ .Identity(let message),
+ .SecureStore(let message),
+ .Relay(let message),
+ .Runtime(let message),
+ .Unsupported(let message),
+ .Internal(let message):
+ message
+ }
+ }
+}
+
+extension Error {
+ var fieldRuntimeErrorCategory: FieldRuntimeErrorCategory? {
+ (self as? RadrootsAppError)?.fieldCategory
+ }
+
+ var fieldRuntimeMessage: String {
+ if let fieldError = self as? RadrootsAppError {
+ return fieldError.fieldMessage
+ }
+ return localizedDescription
+ }
+}
diff --git a/Radroots/Runtime/Radroots.swift b/Radroots/Runtime/Radroots.swift
@@ -18,7 +18,7 @@ public final class Radroots: ObservableObject {
do {
try settings.apply(bundleIdentifier: bundleId)
} catch {
- throw FieldRuntimeLoggingError.initializationFailed(error.localizedDescription)
+ throw FieldRuntimeLoggingError.initializationFailed(error.fieldRuntimeMessage)
}
telemetry.runtimeLoggingInitialized(settings: settings)
diff --git a/Radroots/Views/MarketView.swift b/Radroots/Views/MarketView.swift
@@ -23,7 +23,7 @@ final class TradeListingsViewModel: ObservableObject {
listings = items
isLoading = false
} catch {
- errorMessage = String(describing: error)
+ errorMessage = error.fieldRuntimeMessage
isLoading = false
}
}
diff --git a/Radroots/Views/PostFeedViewModel.swift b/Radroots/Views/PostFeedViewModel.swift
@@ -33,7 +33,7 @@ final class PostFeedViewModel: ObservableObject {
posts = fetched.sorted { $0.publishedAt > $1.publishedAt }
isLoading = false
} catch {
- errorMessage = String(describing: error)
+ errorMessage = error.fieldRuntimeMessage
isLoading = false
}
}
@@ -82,7 +82,7 @@ final class PostFeedViewModel: ObservableObject {
setResult("Reply Posted", "Event \(id.rawValue)")
} catch {
sendingReplyFor.remove(parentId)
- setResult("Failed to Post Reply", String(describing: error))
+ setResult("Failed to Post Reply", error.fieldRuntimeMessage)
}
}
}
@@ -97,7 +97,7 @@ final class PostFeedViewModel: ObservableObject {
do {
try await app.runtimeService?.nostrStartPostStream(sinceUnix: since)
} catch {
- errorMessage = String(describing: error)
+ errorMessage = error.fieldRuntimeMessage
}
while !Task.isCancelled {
diff --git a/Radroots/Views/RelaysView.swift b/Radroots/Views/RelaysView.swift
@@ -120,7 +120,7 @@ struct RelaysView: View {
activeExport = export
preparedExport = export
} catch {
- documentError = error.localizedDescription
+ documentError = error.fieldRuntimeMessage
}
}
@@ -137,7 +137,7 @@ struct RelaysView: View {
destinationScope: .temporary
)
} catch {
- documentError = error.localizedDescription
+ documentError = error.fieldRuntimeMessage
}
}
@@ -151,7 +151,7 @@ struct RelaysView: View {
documentMessage = "Exported \(exportResult.exportedFilename)"
documentError = nil
case .failure(let error):
- documentError = error.localizedDescription
+ documentError = error.fieldRuntimeMessage
}
}
@@ -172,7 +172,7 @@ struct RelaysView: View {
documentMessage = "Imported and applied \(importedRelays.count) relay config entries"
documentError = nil
} catch {
- documentError = error.localizedDescription
+ documentError = error.fieldRuntimeMessage
}
}
}
diff --git a/Radroots/Views/SettingsView.swift b/Radroots/Views/SettingsView.swift
@@ -141,7 +141,7 @@ struct SettingsView: View {
do {
try await app.resetLocalIdentity()
} catch {
- resetError = error.localizedDescription
+ resetError = error.fieldRuntimeMessage
}
}
}
@@ -223,7 +223,7 @@ private struct RuntimeDiagnosticsView: View {
activeExport = export
preparedExport = export
} catch {
- exportError = error.localizedDescription
+ exportError = error.fieldRuntimeMessage
}
}
@@ -237,7 +237,7 @@ private struct RuntimeDiagnosticsView: View {
exportMessage = "Exported \(exportResult.exportedFilename)"
exportError = nil
case .failure(let error):
- exportError = error.localizedDescription
+ exportError = error.fieldRuntimeMessage
}
}
}
diff --git a/Radroots/Views/SetupView.swift b/Radroots/Views/SetupView.swift
@@ -133,7 +133,7 @@ struct SetupView: View {
try await app.continueWithLocalIdentity()
onSuccess?()
} catch {
- errorMessage = error.localizedDescription
+ errorMessage = error.fieldRuntimeMessage
}
isWorking = false
}
@@ -147,7 +147,7 @@ struct SetupView: View {
try await app.createLocalIdentity()
onSuccess?()
} catch {
- errorMessage = error.localizedDescription
+ errorMessage = error.fieldRuntimeMessage
}
isWorking = false
}
@@ -163,7 +163,7 @@ struct SetupView: View {
try await app.importNostrSecret(submittedSecret)
onSuccess?()
} catch {
- errorMessage = error.localizedDescription
+ errorMessage = error.fieldRuntimeMessage
}
isWorking = false
}
diff --git a/Radroots/Views/TradeListingCreateView.swift b/Radroots/Views/TradeListingCreateView.swift
@@ -151,7 +151,7 @@ struct TradeListingCreateView: View {
dismiss()
} catch {
isPosting = false
- errorMessage = String(describing: error)
+ errorMessage = error.fieldRuntimeMessage
}
}
}
diff --git a/Radroots/Views/TradeListingDetailView.swift b/Radroots/Views/TradeListingDetailView.swift
@@ -27,7 +27,7 @@ final class TradeListingDetailViewModel: ObservableObject {
messages = items
isLoading = false
} catch {
- errorMessage = String(describing: error)
+ errorMessage = error.fieldRuntimeMessage
isLoading = false
}
}
diff --git a/Radroots/Views/TradeOrderRequestView.swift b/Radroots/Views/TradeOrderRequestView.swift
@@ -129,7 +129,7 @@ struct TradeOrderRequestView: View {
dismiss()
} catch {
isSending = false
- errorMessage = String(describing: error)
+ errorMessage = error.fieldRuntimeMessage
}
}
}
diff --git a/RadrootsFFI/Makefile b/RadrootsFFI/Makefile
@@ -6,7 +6,7 @@ SHELL := /bin/bash
SOURCE_MODE ?= git
RADROOTS_FIELD_LIB_GIT_URL ?= git@github.com:radrootslabs/field_lib.git
-RADROOTS_FIELD_LIB_GIT_REV ?= daedde8b8022190954ff4a68e34ed93b3423be34
+RADROOTS_FIELD_LIB_GIT_REV ?= 20af1b91483cf420170bfb03d12d86da10f34363
RADROOTS_FIELD_FFI_CRATE_VERSION ?= 0.1.0-alpha.1
FFI_FEATURES ?= radroots_field_core/rt,radroots_field_core/nostr-client
LOCAL_FFI_MANIFEST ?=
diff --git a/RadrootsFFI/source.lock b/RadrootsFFI/source.lock
@@ -1,5 +1,5 @@
SOURCE_MODE=git
RADROOTS_FIELD_LIB_GIT_URL=git@github.com:radrootslabs/field_lib.git
-RADROOTS_FIELD_LIB_GIT_REV=daedde8b8022190954ff4a68e34ed93b3423be34
+RADROOTS_FIELD_LIB_GIT_REV=20af1b91483cf420170bfb03d12d86da10f34363
RADROOTS_FIELD_FFI_CRATE_VERSION=0.1.0-alpha.1
FFI_FEATURES=radroots_field_core/rt,radroots_field_core/nostr-client