Skip to content

Commit 2a8e632

Browse files
committed
Add descriptions to some error cases so they include some context of where they were thrown
feature/identified-description
1 parent 311edfe commit 2a8e632

21 files changed

Lines changed: 132 additions & 44 deletions

Sources/CoreDataRepository/CoreDataError.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,15 @@ public enum CoreDataError: Error, Hashable, Sendable {
2121
/// against the correct property.
2222
/// If the `NSAttributeDescription` is not for the correct or expected `NSEntityDescription`, this error is
2323
/// returned.
24-
case propertyDoesNotMatchEntity
24+
case propertyDoesNotMatchEntity(description: String?)
2525

2626
/// CoreData may return a value of a related type to what is actually needed. If casting the value CoreData returns
2727
/// to the required type fails, this error is returned.
28-
case fetchedObjectFailedToCastToExpectedType
28+
case fetchedObjectFailedToCastToExpectedType(description: String?)
2929

3030
/// It's possible for a persisted object to be flagged as deleted but still be fetched. If that happens, this error
3131
/// is returned.
32-
case fetchedObjectIsFlaggedAsDeleted
32+
case fetchedObjectIsFlaggedAsDeleted(description: String)
3333

3434
/// If CoreData throws a `CocoaError`, it is embedded here.
3535
case cocoa(CocoaError)
@@ -48,13 +48,13 @@ public enum CoreDataError: Error, Hashable, Sendable {
4848
/// If a ``ManagedIdUrlReferencable`` value is used in a transaction where it is expected to already be persisted
4949
/// but has no `URL`
5050
/// representing the ``NSManagedObjectID``, this error is returned.
51-
case noUrlOnItemToMapToObjectId
51+
case noUrlOnItemToMapToObjectId(description: String)
5252

5353
/// If a ``ManagedIdReferencable`` value is used in a transaction where it is expected to already be persisted but
5454
/// has no `NSManagedObjectID`, this error is returned.
55-
case noObjectIdOnItem
55+
case noObjectIdOnItem(description: String)
5656

57-
case noMatchFoundWhenReadingItem
57+
case noMatchFoundWhenReadingItem(description: String)
5858

5959
public var localizedDescription: String {
6060
switch self {

Sources/CoreDataRepository/CoreDataRepository+Aggregate.swift

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@ extension CoreDataRepository {
411411
) throws -> Value {
412412
let result = try context.fetch(request)
413413
guard let value: Value = result.asAggregateValue() else {
414-
throw CoreDataError.fetchedObjectFailedToCastToExpectedType
414+
throw CoreDataError.fetchedObjectFailedToCastToExpectedType(description: nil)
415415
}
416416
return value
417417
}
@@ -426,7 +426,18 @@ extension CoreDataRepository {
426426
groupBy: NSAttributeDescription? = nil
427427
) async -> Result<Value, CoreDataError> {
428428
guard entityDesc == attributeDesc.entity else {
429-
return .failure(.propertyDoesNotMatchEntity)
429+
guard let entityName = entityDesc.name ?? entityDesc.managedObjectClassName else {
430+
return .failure(.propertyDoesNotMatchEntity(description: nil))
431+
}
432+
guard let attributeEntityName = attributeDesc.entity.name ?? attributeDesc.entity.managedObjectClassName
433+
else {
434+
return .failure(.propertyDoesNotMatchEntity(description: entityName))
435+
}
436+
return .failure(
437+
.propertyDoesNotMatchEntity(
438+
description: "\(entityName) != \(attributeDesc.name).\(attributeEntityName)"
439+
)
440+
)
430441
}
431442
return await context.performInChild { scratchPad in
432443
let request = try NSFetchRequest<NSDictionary>.request(

Sources/CoreDataRepository/CoreDataRepository+BatchRequest.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ extension CoreDataRepository {
1818
context.transactionAuthor = transactionAuthor
1919
guard let result = try scratchPad.execute(request) as? NSBatchDeleteResult else {
2020
context.transactionAuthor = nil
21-
throw CoreDataError.fetchedObjectFailedToCastToExpectedType
21+
throw CoreDataError.fetchedObjectFailedToCastToExpectedType(description: request.description)
2222
}
2323
context.transactionAuthor = nil
2424
return result
@@ -36,7 +36,7 @@ extension CoreDataRepository {
3636
context.transactionAuthor = transactionAuthor
3737
guard let result = try scratchPad.execute(request) as? NSBatchInsertResult else {
3838
context.transactionAuthor = nil
39-
throw CoreDataError.fetchedObjectFailedToCastToExpectedType
39+
throw CoreDataError.fetchedObjectFailedToCastToExpectedType(description: request.description)
4040
}
4141
context.transactionAuthor = nil
4242
return result
@@ -54,7 +54,7 @@ extension CoreDataRepository {
5454
context.transactionAuthor = transactionAuthor
5555
guard let result = try scratchPad.execute(request) as? NSBatchUpdateResult else {
5656
context.transactionAuthor = nil
57-
throw CoreDataError.fetchedObjectFailedToCastToExpectedType
57+
throw CoreDataError.fetchedObjectFailedToCastToExpectedType(description: request.description)
5858
}
5959
context.transactionAuthor = nil
6060
return result

Sources/CoreDataRepository/CoreDataRepository+Delete.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ extension CoreDataRepository {
7070
scratchPad.transactionAuthor = transactionAuthor
7171
let object = try item.readManaged(from: scratchPad)
7272
guard !object.isDeleted else {
73-
throw CoreDataError.fetchedObjectIsFlaggedAsDeleted
73+
throw CoreDataError.fetchedObjectIsFlaggedAsDeleted(description: item.errorDescription)
7474
}
7575
object.prepareForDeletion()
7676
scratchPad.delete(object)

Sources/CoreDataRepository/CoreDataRepository+Delete_Batch.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,8 @@ extension CoreDataRepository {
138138
for item in items {
139139
let object = try item.readManaged(from: scratchPad)
140140
guard !object.isDeleted else {
141-
throw CoreDataError.fetchedObjectIsFlaggedAsDeleted
141+
throw CoreDataError
142+
.fetchedObjectIsFlaggedAsDeleted(description: item.errorDescription)
142143
}
143144
object.prepareForDeletion()
144145
scratchPad.delete(object)

Sources/CoreDataRepository/CoreDataRepository+Read_Batch.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,8 @@ extension CoreDataRepository {
103103
try ids.map { id in
104104
let managed = try Model.readManaged(id: id, from: readContext)
105105
guard !managed.isDeleted else {
106-
throw CoreDataError.fetchedObjectIsFlaggedAsDeleted
106+
throw CoreDataError
107+
.fetchedObjectIsFlaggedAsDeleted(description: Model.errorDescription(for: id))
107108
}
108109
return try Model(managed: managed)
109110
}
@@ -122,7 +123,8 @@ extension CoreDataRepository {
122123
try items.map { item in
123124
let managed = try item.readManaged(from: readContext)
124125
guard !managed.isDeleted else {
125-
throw CoreDataError.fetchedObjectIsFlaggedAsDeleted
126+
throw CoreDataError
127+
.fetchedObjectIsFlaggedAsDeleted(description: item.errorDescription)
126128
}
127129
return try Model(managed: managed)
128130
}
@@ -142,7 +144,7 @@ extension CoreDataRepository {
142144
try managedIds.map { managedId in
143145
let _managed = try readContext.notDeletedObject(for: managedId)
144146
guard let managed = _managed as? Model.ManagedModel else {
145-
throw CoreDataError.fetchedObjectFailedToCastToExpectedType
147+
throw CoreDataError.fetchedObjectFailedToCastToExpectedType(description: "\(Model.self)")
146148
}
147149
return try Model(managed: managed)
148150
}
@@ -163,7 +165,7 @@ extension CoreDataRepository {
163165
let managedId = try readContext.objectId(from: managedIdUrl).get()
164166
let _managed = try readContext.notDeletedObject(for: managedId)
165167
guard let managed = _managed as? Model.ManagedModel else {
166-
throw CoreDataError.fetchedObjectFailedToCastToExpectedType
168+
throw CoreDataError.fetchedObjectFailedToCastToExpectedType(description: "\(Model.self)")
167169
}
168170
return try Model(managed: managed)
169171
}

Sources/CoreDataRepository/FetchableUnmanagedModel.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ public protocol FetchableUnmanagedModel: Sendable {
6767

6868
/// ``NSFetchRequest`` for ``ManagedModel`` with a strongly typed ``NSFetchRequest.ResultType``
6969
static func managedFetchRequest() -> NSFetchRequest<ManagedModel>
70+
71+
/// A description of the context from where an error is thrown
72+
var errorDescription: String { get }
7073
}
7174

7275
extension FetchableUnmanagedModel {
@@ -77,4 +80,9 @@ extension FetchableUnmanagedModel {
7780
.managedObjectClassName
7881
)
7982
}
83+
84+
@inlinable
85+
public var errorDescription: String {
86+
"\(Self.self)"
87+
}
8088
}

Sources/CoreDataRepository/IdentifiedUnmanagedModel.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ public protocol IdentifiedUnmanagedModel: ReadableUnmanagedModel {
1010
associatedtype UnmanagedId: Equatable
1111
var unmanagedId: UnmanagedId { get }
1212
static var unmanagedIdExpression: NSExpression { get }
13+
/// Enables including ``UnmanagedId`` in ``errorDescription``
14+
static func errorDescription(for unmanagedId: UnmanagedId) -> String
1315
}
1416

1517
extension IdentifiedUnmanagedModel {
@@ -29,10 +31,12 @@ extension IdentifiedUnmanagedModel {
2931
)
3032
let fetchResult = try context.fetch(request)
3133
guard let managed = fetchResult.first, fetchResult.count == 1 else {
32-
throw CoreDataError.noMatchFoundWhenReadingItem
34+
throw CoreDataError
35+
.noMatchFoundWhenReadingItem(description: "\(Self.self) -- id: \(errorDescription(for: id))")
3336
}
3437
guard !managed.isDeleted else {
35-
throw CoreDataError.fetchedObjectIsFlaggedAsDeleted
38+
throw CoreDataError
39+
.fetchedObjectIsFlaggedAsDeleted(description: "\(Self.self) -- id: \(errorDescription(for: id))")
3640
}
3741
return managed
3842
}

Sources/CoreDataRepository/Internal/AggregateSubscription.swift

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ final class AggregateSubscription<Value: Numeric & Sendable>: Subscription<Value
3232
}
3333

3434
guard let value: Value = result.asAggregateValue() else {
35-
self?.fail(.fetchedObjectFailedToCastToExpectedType)
35+
self?.fail(.fetchedObjectFailedToCastToExpectedType(description: nil))
3636
return
3737
}
3838
self?.send(value)
@@ -93,7 +93,20 @@ final class AggregateSubscription<Value: Numeric & Sendable>: Subscription<Value
9393
context: context,
9494
continuation: continuation
9595
)
96-
fail(.propertyDoesNotMatchEntity)
96+
guard let entityName = entityDesc.name ?? entityDesc.managedObjectClassName else {
97+
fail(.propertyDoesNotMatchEntity(description: nil))
98+
return
99+
}
100+
guard let attributeEntityName = attributeDesc.entity.name ?? attributeDesc.entity.managedObjectClassName
101+
else {
102+
fail(.propertyDoesNotMatchEntity(description: entityName))
103+
return
104+
}
105+
fail(
106+
.propertyDoesNotMatchEntity(
107+
description: "\(entityName) != \(attributeDesc.name).\(attributeEntityName)"
108+
)
109+
)
97110
return
98111
}
99112
self.init(request: request, context: context, continuation: continuation)

Sources/CoreDataRepository/Internal/AggregateThrowingSubscription.swift

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ final class AggregateThrowingSubscription<Value: Numeric & Sendable>: ThrowingSu
3636
}
3737

3838
guard let value: Value = result.asAggregateValue() else {
39-
self?.fail(.fetchedObjectFailedToCastToExpectedType)
39+
self?.fail(.fetchedObjectFailedToCastToExpectedType(description: nil))
4040
return
4141
}
4242
self?.send(value)
@@ -97,7 +97,20 @@ final class AggregateThrowingSubscription<Value: Numeric & Sendable>: ThrowingSu
9797
context: context,
9898
continuation: continuation
9999
)
100-
fail(.propertyDoesNotMatchEntity)
100+
guard let entityName = entityDesc.name ?? entityDesc.managedObjectClassName else {
101+
fail(.propertyDoesNotMatchEntity(description: nil))
102+
return
103+
}
104+
guard let attributeEntityName = attributeDesc.entity.name ?? attributeDesc.entity.managedObjectClassName
105+
else {
106+
fail(.propertyDoesNotMatchEntity(description: entityName))
107+
return
108+
}
109+
fail(
110+
.propertyDoesNotMatchEntity(
111+
description: "\(entityName) != \(attributeDesc.name).\(attributeEntityName)"
112+
)
113+
)
101114
return
102115
}
103116
self.init(request: request, context: context, continuation: continuation)

0 commit comments

Comments
 (0)