From 454dac041c93efd4197e0efbeda15789ebd2d674 Mon Sep 17 00:00:00 2001 From: Michal Rentka Date: Tue, 10 Oct 2023 13:58:19 +0200 Subject: [PATCH 1/2] Improved API parsing logs --- .../Models/API/AuthorizeUploadResponse.swift | 9 +- Zotero/Models/API/CollectionResponse.swift | 10 +- Zotero/Models/API/ItemResponse.swift | 199 ++++++++++++------ Zotero/Models/API/KeyResponse.swift | 2 +- Zotero/Models/API/LibraryResponse.swift | 6 +- Zotero/Models/API/LinksResponse.swift | 2 +- Zotero/Models/API/SearchesResponse.swift | 18 +- Zotero/Models/API/SettingsResponse.swift | 22 +- Zotero/Models/Parsing.swift | 7 +- 9 files changed, 178 insertions(+), 97 deletions(-) diff --git a/Zotero/Models/API/AuthorizeUploadResponse.swift b/Zotero/Models/API/AuthorizeUploadResponse.swift index 39a5436dc..afb38cd1c 100644 --- a/Zotero/Models/API/AuthorizeUploadResponse.swift +++ b/Zotero/Models/API/AuthorizeUploadResponse.swift @@ -8,6 +8,8 @@ import Foundation +import CocoaLumberjackSwift + enum AuthorizeUploadResponse { case exists(Int) case new(AuthorizeNewUploadResponse) @@ -31,14 +33,15 @@ struct AuthorizeNewUploadResponse { let params: [String: String] init(from jsonObject: [String: Any]) throws { - let urlString: String = try jsonObject.apiGet(key: "url") + let urlString: String = try jsonObject.apiGet(key: "url", errorLogMessage: "AuthorizeNewUploadResponse missing key \"url\"") guard let url = URL(string: urlString.replacingOccurrences(of: "\\", with: "")) else { + DDLogError("AuthorizeNewUploadResponse: url invalid format - \(urlString)") throw Parsing.Error.missingKey("url") } self.url = url - self.uploadKey = try jsonObject.apiGet(key: "uploadKey") - self.params = try jsonObject.apiGet(key: "params") + self.uploadKey = try jsonObject.apiGet(key: "uploadKey", errorLogMessage: "AuthorizeNewUploadResponse missing key \"uploadKey\"") + self.params = try jsonObject.apiGet(key: "params", errorLogMessage: "AuthorizeNewUploadResponse missing key \"params\"") } } diff --git a/Zotero/Models/API/CollectionResponse.swift b/Zotero/Models/API/CollectionResponse.swift index fe56e70fe..4ed06a467 100644 --- a/Zotero/Models/API/CollectionResponse.swift +++ b/Zotero/Models/API/CollectionResponse.swift @@ -22,14 +22,14 @@ struct CollectionResponse: KeyedResponse { let version: Int init(response: [String: Any]) throws { - let library: [String: Any] = try response.apiGet(key: "library") - let data: [String: Any] = try response.apiGet(key: "data") - let key: String = try response.apiGet(key: "key") + let library: [String: Any] = try response.apiGet(key: "library", errorLogMessage: "CollectionResponse missing key \"library\"") + let data: [String: Any] = try response.apiGet(key: "data", errorLogMessage: "CollectionResponse missing key \"data\"") + let key: String = try response.apiGet(key: "key", errorLogMessage: "CollectionResponse missing key \"key\"") self.key = key self.library = try LibraryResponse(response: library) self.links = try (response["links"] as? [String: Any]).flatMap({ try LinksResponse(response: $0) }) - self.version = try response.apiGet(key: "version") + self.version = try response.apiGet(key: "version", errorLogMessage: "CollectionResponse missing key \"version\"") self.data = try Data(response: data, key: key) } } @@ -41,7 +41,7 @@ extension CollectionResponse.Data { throw SchemaError.unknownField(key: key, field: unknownKey) } - self.name = try response.apiGet(key: "name") + self.name = try response.apiGet(key: "name", errorLogMessage: "CollectionResponse missing key \"name\"") self.parentCollection = response["parentCollection"] as? String self.isTrash = (response["deleted"] as? Bool) ?? ((response["deleted"] as? Int) == 1) } diff --git a/Zotero/Models/API/ItemResponse.swift b/Zotero/Models/API/ItemResponse.swift index 8087c5cea..90aefeb14 100644 --- a/Zotero/Models/API/ItemResponse.swift +++ b/Zotero/Models/API/ItemResponse.swift @@ -39,9 +39,27 @@ struct ItemResponse { let rects: [[Double]]? let paths: [[Double]]? - init(rawType: String, key: String, library: LibraryResponse, parentKey: String?, collectionKeys: Set, links: LinksResponse?, parsedDate: String?, isTrash: Bool, version: Int, - dateModified: Date, dateAdded: Date, fields: [KeyBaseKeyPair: String], tags: [TagResponse], creators: [CreatorResponse], relations: [String: Any], createdBy: UserResponse?, - lastModifiedBy: UserResponse?, rects: [[Double]]?, paths: [[Double]]?) { + init( + rawType: String, + key: String, + library: LibraryResponse, + parentKey: String?, + collectionKeys: Set, + links: LinksResponse?, + parsedDate: String?, + isTrash: Bool, + version: Int, + dateModified: Date, + dateAdded: Date, + fields: [KeyBaseKeyPair: String], + tags: [TagResponse], + creators: [CreatorResponse], + relations: [String: Any], + createdBy: UserResponse?, + lastModifiedBy: UserResponse?, + rects: [[Double]]?, + paths: [[Double]]? + ) { self.rawType = rawType self.key = key self.library = library @@ -65,15 +83,15 @@ struct ItemResponse { } init(response: [String: Any], schemaController: SchemaController) throws { - let data: [String: Any] = try response.apiGet(key: "data") - let key: String = try response.apiGet(key: "key") - let itemType: String = try data.apiGet(key: "itemType") + let data: [String: Any] = try response.apiGet(key: "data", errorLogMessage: "ItemResponse missing key \"data\"") + let key: String = try response.apiGet(key: "key", errorLogMessage: "ItemResponse missing key \"key\"") + let itemType: String = try data.apiGet(key: "itemType", errorLogMessage: "ItemResponse missing key \"itemType\"") if !schemaController.itemTypes.contains(itemType) { throw SchemaError.invalidValue(value: itemType, field: "itemType", key: key) } - let library = try LibraryResponse(response: (try response.apiGet(key: "library"))) + let library = try LibraryResponse(response: (try response.apiGet(key: "library", errorLogMessage: "ItemResponse missing key \"library\""))) let linksData = response["links"] as? [String: Any] let links = try linksData.flatMap { try LinksResponse(response: $0) } let meta = response["meta"] as? [String: Any] @@ -82,22 +100,51 @@ struct ItemResponse { let createdBy = try createdByData.flatMap { try UserResponse(response: $0) } let lastModifiedByData = meta?["lastModifiedByUser"] as? [String: Any] let lastModifiedBy = try lastModifiedByData.flatMap { try UserResponse(response: $0) } - let version: Int = try response.apiGet(key: "version") + let version: Int = try response.apiGet(key: "version", errorLogMessage: "ItemResponse missing key \"version\"") switch itemType { case ItemTypes.annotation: - try self.init(key: key, library: library, links: links, parsedDate: parsedDate, createdBy: createdBy, lastModifiedBy: lastModifiedBy, version: version, annotationData: data, - schemaController: schemaController) + try self.init( + key: key, + library: library, + links: links, + parsedDate: parsedDate, + createdBy: createdBy, + lastModifiedBy: lastModifiedBy, + version: version, + annotationData: data, + schemaController: schemaController + ) default: - try self.init(key: key, rawType: itemType, library: library, links: links, parsedDate: parsedDate, createdBy: createdBy, lastModifiedBy: lastModifiedBy, version: version, data: data, - schemaController: schemaController) + try self.init( + key: key, + rawType: itemType, + library: library, + links: links, + parsedDate: parsedDate, + createdBy: createdBy, + lastModifiedBy: lastModifiedBy, + version: version, + data: data, + schemaController: schemaController + ) } } // Init any item type except annotation - private init(key: String, rawType: String, library: LibraryResponse, links: LinksResponse?, parsedDate: String?, createdBy: UserResponse?, lastModifiedBy: UserResponse?, version: Int, - data: [String: Any], schemaController: SchemaController) throws { + private init( + key: String, + rawType: String, + library: LibraryResponse, + links: LinksResponse?, + parsedDate: String?, + createdBy: UserResponse?, + lastModifiedBy: UserResponse?, + version: Int, + data: [String: Any], + schemaController: SchemaController + ) throws { let dateAdded = data["dateAdded"] as? String let dateModified = data["dateModified"] as? String let tags = (data["tags"] as? [[String: Any]]) ?? [] @@ -133,8 +180,17 @@ struct ItemResponse { } // Init annotation - private init(key: String, library: LibraryResponse, links: LinksResponse?, parsedDate: String?, createdBy: UserResponse?, lastModifiedBy: UserResponse?, version: Int, - annotationData data: [String: Any], schemaController: SchemaController) throws { + private init( + key: String, + library: LibraryResponse, + links: LinksResponse?, + parsedDate: String?, + createdBy: UserResponse?, + lastModifiedBy: UserResponse?, + version: Int, + annotationData data: [String: Any], + schemaController: SchemaController + ) throws { let dateAdded = data["dateAdded"] as? String let dateModified = data["dateModified"] as? String let tags = (data["tags"] as? [[String: Any]]) ?? [] @@ -145,7 +201,7 @@ struct ItemResponse { self.key = key self.version = version self.collectionKeys = [] - self.parentKey = try data.apiGet(key: "parentItem") + self.parentKey = try data.apiGet(key: "parentItem", errorLogMessage: "ItemResponse missing key \"parentItem\"") self.dateAdded = dateAdded.flatMap({ Formatter.iso8601.date(from: $0) }) ?? Date() self.dateModified = dateModified.flatMap({ Formatter.iso8601.date(from: $0) }) ?? Date() self.parsedDate = parsedDate @@ -165,7 +221,7 @@ struct ItemResponse { init(translatorResponse response: [String: Any], schemaController: SchemaController) throws { let key = KeyGenerator.newKey - let rawType: String = try response.apiGet(key: "itemType") + let rawType: String = try response.apiGet(key: "itemType", errorLogMessage: "ItemResponse missing key \"itemType\"") let accessDate = (response["accessDate"] as? String).flatMap({ Formatter.iso8601.date(from: $0) }) ?? Date() let tags = (response["tags"] as? [[String: Any]]) ?? [] let creators = (response["creators"] as? [[String: Any]]) ?? [] @@ -195,47 +251,51 @@ struct ItemResponse { } func copy(libraryId: LibraryIdentifier, collectionKeys: Set, tags: [TagResponse]) -> ItemResponse { - return ItemResponse(rawType: self.rawType, - key: self.key, - library: LibraryResponse(libraryId: libraryId), - parentKey: self.parentKey, - collectionKeys: collectionKeys, - links: self.links, - parsedDate: self.parsedDate, - isTrash: self.isTrash, - version: self.version, - dateModified: self.dateModified, - dateAdded: self.dateAdded, - fields: self.fields, - tags: tags, - creators: self.creators, - relations: self.relations, - createdBy: self.createdBy, - lastModifiedBy: self.lastModifiedBy, - rects: self.rects, - paths: self.paths) + return ItemResponse( + rawType: self.rawType, + key: self.key, + library: LibraryResponse(libraryId: libraryId), + parentKey: self.parentKey, + collectionKeys: collectionKeys, + links: self.links, + parsedDate: self.parsedDate, + isTrash: self.isTrash, + version: self.version, + dateModified: self.dateModified, + dateAdded: self.dateAdded, + fields: self.fields, + tags: tags, + creators: self.creators, + relations: self.relations, + createdBy: self.createdBy, + lastModifiedBy: self.lastModifiedBy, + rects: self.rects, + paths: self.paths + ) } var copyWithAutomaticTags: ItemResponse { - return ItemResponse(rawType: self.rawType, - key: self.key, - library: self.library, - parentKey: self.parentKey, - collectionKeys: self.collectionKeys, - links: self.links, - parsedDate: self.parsedDate, - isTrash: self.isTrash, - version: self.version, - dateModified: self.dateModified, - dateAdded: self.dateAdded, - fields: self.fields, - tags: self.tags.map({ $0.automaticCopy }), - creators: self.creators, - relations: self.relations, - createdBy: self.createdBy, - lastModifiedBy: self.lastModifiedBy, - rects: self.rects, - paths: self.paths) + return ItemResponse( + rawType: self.rawType, + key: self.key, + library: self.library, + parentKey: self.parentKey, + collectionKeys: self.collectionKeys, + links: self.links, + parsedDate: self.parsedDate, + isTrash: self.isTrash, + version: self.version, + dateModified: self.dateModified, + dateAdded: self.dateAdded, + fields: self.fields, + tags: self.tags.map({ $0.automaticCopy }), + creators: self.creators, + relations: self.relations, + createdBy: self.createdBy, + lastModifiedBy: self.lastModifiedBy, + rects: self.rects, + paths: self.paths + ) } /// Parses field values from item data for given type. @@ -245,8 +305,13 @@ struct ItemResponse { /// - parameter key: Key of item. /// - parameter ignoreUnknownFields: If set to `false`, when an unknown field is encountered during parsing, an exception `Error.unknownField` is thrown. Otherwise the field is silently ignored and parsing continues. /// - returns: Parsed dictionary of fields with their values. - private static func parseFields(from data: [String: Any], rawType: String, key: String, schemaController: SchemaController, - ignoreUnknownFields: Bool = false) throws -> (fields: [KeyBaseKeyPair: String], rects: [[Double]]?, paths: [[Double]]?) { + private static func parseFields( + from data: [String: Any], + rawType: String, + key: String, + schemaController: SchemaController, + ignoreUnknownFields: Bool = false + ) throws -> (fields: [KeyBaseKeyPair: String], rects: [[Double]]?, paths: [[Double]]?) { let excludedKeys = FieldKeys.Item.knownNonFieldKeys var fields: [KeyBaseKeyPair: String] = [:] var rects: [[Double]]? @@ -294,7 +359,11 @@ struct ItemResponse { return (fields, rects, paths) } - private static func parsePositionFields(from encoded: String, key: String, fields: inout [KeyBaseKeyPair: String]) throws -> (rects: [[Double]]?, paths: [[Double]]?) { + private static func parsePositionFields( + from encoded: String, + key: String, + fields: inout [KeyBaseKeyPair: String] + ) throws -> (rects: [[Double]]?, paths: [[Double]]?) { guard let data = encoded.data(using: .utf8), let json = (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)) as? [String: Any] else { throw SchemaError.invalidValue(value: encoded, field: FieldKeys.Item.Annotation.position, key: key) } @@ -430,13 +499,13 @@ struct TagResponse { } init(response: [String: Any]) throws { - let rawType = (try? response.apiGet(key: "type")) ?? 0 + let rawType = (try? response.apiGet(key: "type", errorLogMessage: "TagResponse missing key \"type\"")) ?? 0 guard let type = RTypedTag.Kind(rawValue: rawType) else { throw Error.unknownTagType } - self.tag = try response.apiGet(key: "tag") + self.tag = try response.apiGet(key: "tag", errorLogMessage: "TagResponse missing key \"tag\"") self.type = type } @@ -452,7 +521,7 @@ struct CreatorResponse { let name: String? init(response: [String: Any]) throws { - self.creatorType = try response.apiGet(key: "creatorType") + self.creatorType = try response.apiGet(key: "creatorType", errorLogMessage: "CreatorResponse missing key \"creatorType\"") self.firstName = response["firstName"] as? String self.lastName = response["lastName"] as? String self.name = response["name"] as? String @@ -468,8 +537,8 @@ struct UserResponse { let username: String init(response: [String: Any]) throws { - self.id = try response.apiGet(key: "id") - self.name = try response.apiGet(key: "name") - self.username = try response.apiGet(key: "username") + self.id = try response.apiGet(key: "id", errorLogMessage: "UserResponse missing key \"id\"") + self.name = try response.apiGet(key: "name", errorLogMessage: "UserResponse missing key \"name\"") + self.username = try response.apiGet(key: "username", errorLogMessage: "UserResponse missing key \"username\"") } } diff --git a/Zotero/Models/API/KeyResponse.swift b/Zotero/Models/API/KeyResponse.swift index f8193a5d6..c08a54fad 100644 --- a/Zotero/Models/API/KeyResponse.swift +++ b/Zotero/Models/API/KeyResponse.swift @@ -18,7 +18,7 @@ struct KeyResponse { init(response: Any) throws { guard let data = response as? [String: Any] else { throw Parsing.Error.notDictionary } - let accessData: [String: Any] = try data.apiGet(key: "access") + let accessData: [String: Any] = try data.apiGet(key: "access", errorLogMessage: "KeyResponse missing key \"access\"") self.username = (data["username"] as? String) ?? "" self.displayName = (data["displayName"] as? String) ?? "" diff --git a/Zotero/Models/API/LibraryResponse.swift b/Zotero/Models/API/LibraryResponse.swift index 2704ca9a3..4ce85ee43 100644 --- a/Zotero/Models/API/LibraryResponse.swift +++ b/Zotero/Models/API/LibraryResponse.swift @@ -15,9 +15,9 @@ struct LibraryResponse { let links: LinksResponse? init(response: [String: Any]) throws { - self.id = try response.apiGet(key: "id") - self.name = try response.apiGet(key: "name") - self.type = try response.apiGet(key: "type") + self.id = try response.apiGet(key: "id", errorLogMessage: "LibraryResponse missing key \"id\"") + self.name = try response.apiGet(key: "name", errorLogMessage: "LibraryResponse missing key \"name\"") + self.type = try response.apiGet(key: "type", errorLogMessage: "LibraryResponse missing key \"type\"") self.links = try (response["links"] as? [String: Any]).flatMap({ try LinksResponse(response: $0) }) } diff --git a/Zotero/Models/API/LinksResponse.swift b/Zotero/Models/API/LinksResponse.swift index 932a06559..5ad902a42 100644 --- a/Zotero/Models/API/LinksResponse.swift +++ b/Zotero/Models/API/LinksResponse.swift @@ -29,7 +29,7 @@ struct LinkResponse { let length: Int? init(response: [String: Any]) throws { - self.href = try response.apiGet(key: "href") + self.href = try response.apiGet(key: "href", errorLogMessage: "LinkResponse missing key \"href\"") self.type = response["type"] as? String self.title = response["title"] as? String self.length = response["length"] as? Int diff --git a/Zotero/Models/API/SearchesResponse.swift b/Zotero/Models/API/SearchesResponse.swift index a7146cecb..c38d5e021 100644 --- a/Zotero/Models/API/SearchesResponse.swift +++ b/Zotero/Models/API/SearchesResponse.swift @@ -22,14 +22,14 @@ struct SearchResponse { let version: Int init(response: [String: Any]) throws { - let key: String = try response.apiGet(key: "key") - let library: [String: Any] = try response.apiGet(key: "library") - let data: [String: Any] = try response.apiGet(key: "data") + let key: String = try response.apiGet(key: "key", errorLogMessage: "SearchResponse missing key \"key\"") + let library: [String: Any] = try response.apiGet(key: "library", errorLogMessage: "SearchResponse missing key \"library\"") + let data: [String: Any] = try response.apiGet(key: "data", errorLogMessage: "SearchResponse missing key \"data\"") self.key = key self.library = try LibraryResponse(response: library) self.links = try (response["links"] as? [String: Any]).flatMap { try LinksResponse(response: $0) } - self.version = try response.apiGet(key: "version") + self.version = try response.apiGet(key: "version", errorLogMessage: "SearchResponse missing key \"version\"") self.data = try Data(response: data, key: key) } } @@ -41,9 +41,9 @@ extension SearchResponse.Data { throw SchemaError.unknownField(key: key, field: unknownKey) } - let conditions: [[String: Any]] = try response.apiGet(key: "conditions") + let conditions: [[String: Any]] = try response.apiGet(key: "conditions", errorLogMessage: "SearchResponse.Data missing key \"conditions\"") - self.name = try response.apiGet(key: "name") + self.name = try response.apiGet(key: "name", errorLogMessage: "SearchResponse.Data missing key \"name\"") self.conditions = try conditions.map({ try ConditionResponse(response: $0) }) self.isTrash = (response["deleted"] as? Bool) ?? ((response["deleted"] as? Int) == 1) } @@ -55,8 +55,8 @@ struct ConditionResponse { let value: String init(response: [String: Any]) throws { - self.condition = try response.apiGet(key: "condition") - self.`operator` = try response.apiGet(key: "operator") - self.value = try response.apiGet(key: "value") + self.condition = try response.apiGet(key: "condition", errorLogMessage: "ConditionResponse missing key \"condition\"") + self.`operator` = try response.apiGet(key: "operator", errorLogMessage: "ConditionResponse missing key \"operator\"") + self.value = try response.apiGet(key: "value", errorLogMessage: "ConditionResponse missing key \"value\"") } } diff --git a/Zotero/Models/API/SettingsResponse.swift b/Zotero/Models/API/SettingsResponse.swift index 84d50348a..43af66822 100644 --- a/Zotero/Models/API/SettingsResponse.swift +++ b/Zotero/Models/API/SettingsResponse.swift @@ -8,15 +8,18 @@ import Foundation +import CocoaLumberjackSwift + struct SettingsResponse { let tagColors: TagColorsResponse? let pageIndices: PageIndicesResponse init(response: Any) throws { guard let json = response as? [String: Any] else { + DDLogError("SettingsResponse: response not dictionary - \(response)") throw Parsing.Error.notDictionary } - self.tagColors = try (json["tagColors"] as? [String: Any]).flatMap({ try TagColorsResponse(response: $0) }) + self.tagColors = try (json["tagColors"] as? [String: Any]).flatMap({ $0.isEmpty ? nil : $0 }).flatMap({ try TagColorsResponse(response: $0) }) self.pageIndices = try PageIndicesResponse(response: json) } } @@ -38,20 +41,24 @@ struct PageIndexResponse { init?(key: String, data: Any) throws { guard key.contains("lastPageIndex") else { return nil } guard let dictionary = data as? [String: Any] else { + DDLogError("PageIndexResponse: response not dictionary for key \(key) - \(data)") throw Parsing.Error.notDictionary } let (key, libraryId) = try PageIndexResponse.parse(key: key) self.key = key - self.value = try Parsing.parse(key: "value", from: dictionary) - self.version = try Parsing.parse(key: "version", from: dictionary) + self.value = try Parsing.parse(key: "value", from: dictionary, errorLogMessage: "PageIndexResponse missing key \"value\"") + self.version = try Parsing.parse(key: "version", from: dictionary, errorLogMessage: "PageIndexResponse missing key \"version\"") self.libraryId = libraryId } static func parse(key: String) throws -> (String, LibraryIdentifier) { let parts = key.split(separator: "_") - guard parts.count == 3 else { throw Parsing.Error.incompatibleValue(key) } + guard parts.count == 3 else { + DDLogError("PageIndexResponse: key is invalid format - \(key)") + throw Parsing.Error.incompatibleValue(key) + } let libraryPart = parts[1] let libraryId: LibraryIdentifier @@ -66,6 +73,7 @@ struct PageIndexResponse { libraryId = .group(groupId) default: + DDLogError("PageIndexResponse: key is invalid format - \(key)") throw Parsing.Error.incompatibleValue("libraryPart=\(libraryPart)") } @@ -77,7 +85,7 @@ struct TagColorsResponse { let value: [TagColorResponse] init(response: [String: Any]) throws { - let responses: [[String: Any]] = try Parsing.parse(key: "value", from: response) + let responses: [[String: Any]] = try Parsing.parse(key: "value", from: response, errorLogMessage: "TagColorsResponse missing key \"value\"") self.value = try responses.map({ try TagColorResponse(response: $0) }) } } @@ -87,7 +95,7 @@ struct TagColorResponse { let color: String init(response: [String: Any]) throws { - self.name = try Parsing.parse(key: "name", from: response) - self.color = try Parsing.parse(key: "color", from: response) + self.name = try Parsing.parse(key: "name", from: response, errorLogMessage: "TagColorResponse missing key \"name\"") + self.color = try Parsing.parse(key: "color", from: response, errorLogMessage: "TagColorResponse missing key \"color\"") } } diff --git a/Zotero/Models/Parsing.swift b/Zotero/Models/Parsing.swift index 603e67ffb..80270d959 100644 --- a/Zotero/Models/Parsing.swift +++ b/Zotero/Models/Parsing.swift @@ -19,8 +19,9 @@ struct Parsing { case notUrl } - static func parse(key: String, from data: [String: Any]) throws -> T { + static func parse(key: String, from data: [String: Any], errorLogMessage: String) throws -> T { guard let parsed = data[key] as? T else { + DDLogError("Parsing: \(errorLogMessage)") throw Error.missingKey(key) } return parsed @@ -56,7 +57,7 @@ struct Parsing { } extension Dictionary where Key == String { - func apiGet(key: String) throws -> T { - return try Parsing.parse(key: key, from: self) + func apiGet(key: String, errorLogMessage: String) throws -> T { + return try Parsing.parse(key: key, from: self, errorLogMessage: errorLogMessage) } } From 929316ddb0f9e0e6b4b638a43b9d430b92f6d7be Mon Sep 17 00:00:00 2001 From: Michal Rentka Date: Wed, 11 Oct 2023 12:37:03 +0200 Subject: [PATCH 2/2] Simplified parser logging --- .../Models/API/AuthorizeUploadResponse.swift | 6 ++--- Zotero/Models/API/CollectionResponse.swift | 10 +++---- Zotero/Models/API/ItemResponse.swift | 26 +++++++++---------- Zotero/Models/API/KeyResponse.swift | 2 +- Zotero/Models/API/LibraryResponse.swift | 6 ++--- Zotero/Models/API/LinksResponse.swift | 2 +- Zotero/Models/API/SearchesResponse.swift | 18 ++++++------- Zotero/Models/API/SettingsResponse.swift | 10 +++---- Zotero/Models/Parsing.swift | 8 +++--- 9 files changed, 44 insertions(+), 44 deletions(-) diff --git a/Zotero/Models/API/AuthorizeUploadResponse.swift b/Zotero/Models/API/AuthorizeUploadResponse.swift index afb38cd1c..83bec739b 100644 --- a/Zotero/Models/API/AuthorizeUploadResponse.swift +++ b/Zotero/Models/API/AuthorizeUploadResponse.swift @@ -33,7 +33,7 @@ struct AuthorizeNewUploadResponse { let params: [String: String] init(from jsonObject: [String: Any]) throws { - let urlString: String = try jsonObject.apiGet(key: "url", errorLogMessage: "AuthorizeNewUploadResponse missing key \"url\"") + let urlString: String = try jsonObject.apiGet(key: "url", caller: Self.self) guard let url = URL(string: urlString.replacingOccurrences(of: "\\", with: "")) else { DDLogError("AuthorizeNewUploadResponse: url invalid format - \(urlString)") @@ -41,7 +41,7 @@ struct AuthorizeNewUploadResponse { } self.url = url - self.uploadKey = try jsonObject.apiGet(key: "uploadKey", errorLogMessage: "AuthorizeNewUploadResponse missing key \"uploadKey\"") - self.params = try jsonObject.apiGet(key: "params", errorLogMessage: "AuthorizeNewUploadResponse missing key \"params\"") + self.uploadKey = try jsonObject.apiGet(key: "uploadKey", caller: Self.self) + self.params = try jsonObject.apiGet(key: "params", caller: Self.self) } } diff --git a/Zotero/Models/API/CollectionResponse.swift b/Zotero/Models/API/CollectionResponse.swift index 4ed06a467..fd3370517 100644 --- a/Zotero/Models/API/CollectionResponse.swift +++ b/Zotero/Models/API/CollectionResponse.swift @@ -22,14 +22,14 @@ struct CollectionResponse: KeyedResponse { let version: Int init(response: [String: Any]) throws { - let library: [String: Any] = try response.apiGet(key: "library", errorLogMessage: "CollectionResponse missing key \"library\"") - let data: [String: Any] = try response.apiGet(key: "data", errorLogMessage: "CollectionResponse missing key \"data\"") - let key: String = try response.apiGet(key: "key", errorLogMessage: "CollectionResponse missing key \"key\"") + let library: [String: Any] = try response.apiGet(key: "library", caller: Self.self) + let data: [String: Any] = try response.apiGet(key: "data", caller: Self.self) + let key: String = try response.apiGet(key: "key", caller: Self.self) self.key = key self.library = try LibraryResponse(response: library) self.links = try (response["links"] as? [String: Any]).flatMap({ try LinksResponse(response: $0) }) - self.version = try response.apiGet(key: "version", errorLogMessage: "CollectionResponse missing key \"version\"") + self.version = try response.apiGet(key: "version", caller: Self.self) self.data = try Data(response: data, key: key) } } @@ -41,7 +41,7 @@ extension CollectionResponse.Data { throw SchemaError.unknownField(key: key, field: unknownKey) } - self.name = try response.apiGet(key: "name", errorLogMessage: "CollectionResponse missing key \"name\"") + self.name = try response.apiGet(key: "name", caller: Self.self) self.parentCollection = response["parentCollection"] as? String self.isTrash = (response["deleted"] as? Bool) ?? ((response["deleted"] as? Int) == 1) } diff --git a/Zotero/Models/API/ItemResponse.swift b/Zotero/Models/API/ItemResponse.swift index 90aefeb14..7012da33e 100644 --- a/Zotero/Models/API/ItemResponse.swift +++ b/Zotero/Models/API/ItemResponse.swift @@ -83,15 +83,15 @@ struct ItemResponse { } init(response: [String: Any], schemaController: SchemaController) throws { - let data: [String: Any] = try response.apiGet(key: "data", errorLogMessage: "ItemResponse missing key \"data\"") - let key: String = try response.apiGet(key: "key", errorLogMessage: "ItemResponse missing key \"key\"") - let itemType: String = try data.apiGet(key: "itemType", errorLogMessage: "ItemResponse missing key \"itemType\"") + let data: [String: Any] = try response.apiGet(key: "data", caller: Self.self) + let key: String = try response.apiGet(key: "key", caller: Self.self) + let itemType: String = try data.apiGet(key: "itemType", caller: Self.self) if !schemaController.itemTypes.contains(itemType) { throw SchemaError.invalidValue(value: itemType, field: "itemType", key: key) } - let library = try LibraryResponse(response: (try response.apiGet(key: "library", errorLogMessage: "ItemResponse missing key \"library\""))) + let library = try LibraryResponse(response: (try response.apiGet(key: "library", caller: Self.self))) let linksData = response["links"] as? [String: Any] let links = try linksData.flatMap { try LinksResponse(response: $0) } let meta = response["meta"] as? [String: Any] @@ -100,7 +100,7 @@ struct ItemResponse { let createdBy = try createdByData.flatMap { try UserResponse(response: $0) } let lastModifiedByData = meta?["lastModifiedByUser"] as? [String: Any] let lastModifiedBy = try lastModifiedByData.flatMap { try UserResponse(response: $0) } - let version: Int = try response.apiGet(key: "version", errorLogMessage: "ItemResponse missing key \"version\"") + let version: Int = try response.apiGet(key: "version", caller: Self.self) switch itemType { case ItemTypes.annotation: @@ -201,7 +201,7 @@ struct ItemResponse { self.key = key self.version = version self.collectionKeys = [] - self.parentKey = try data.apiGet(key: "parentItem", errorLogMessage: "ItemResponse missing key \"parentItem\"") + self.parentKey = try data.apiGet(key: "parentItem", caller: Self.self) self.dateAdded = dateAdded.flatMap({ Formatter.iso8601.date(from: $0) }) ?? Date() self.dateModified = dateModified.flatMap({ Formatter.iso8601.date(from: $0) }) ?? Date() self.parsedDate = parsedDate @@ -221,7 +221,7 @@ struct ItemResponse { init(translatorResponse response: [String: Any], schemaController: SchemaController) throws { let key = KeyGenerator.newKey - let rawType: String = try response.apiGet(key: "itemType", errorLogMessage: "ItemResponse missing key \"itemType\"") + let rawType: String = try response.apiGet(key: "itemType", caller: Self.self) let accessDate = (response["accessDate"] as? String).flatMap({ Formatter.iso8601.date(from: $0) }) ?? Date() let tags = (response["tags"] as? [[String: Any]]) ?? [] let creators = (response["creators"] as? [[String: Any]]) ?? [] @@ -499,13 +499,13 @@ struct TagResponse { } init(response: [String: Any]) throws { - let rawType = (try? response.apiGet(key: "type", errorLogMessage: "TagResponse missing key \"type\"")) ?? 0 + let rawType = (try? response.apiGet(key: "type", caller: Self.self)) ?? 0 guard let type = RTypedTag.Kind(rawValue: rawType) else { throw Error.unknownTagType } - self.tag = try response.apiGet(key: "tag", errorLogMessage: "TagResponse missing key \"tag\"") + self.tag = try response.apiGet(key: "tag", caller: Self.self) self.type = type } @@ -521,7 +521,7 @@ struct CreatorResponse { let name: String? init(response: [String: Any]) throws { - self.creatorType = try response.apiGet(key: "creatorType", errorLogMessage: "CreatorResponse missing key \"creatorType\"") + self.creatorType = try response.apiGet(key: "creatorType", caller: Self.self) self.firstName = response["firstName"] as? String self.lastName = response["lastName"] as? String self.name = response["name"] as? String @@ -537,8 +537,8 @@ struct UserResponse { let username: String init(response: [String: Any]) throws { - self.id = try response.apiGet(key: "id", errorLogMessage: "UserResponse missing key \"id\"") - self.name = try response.apiGet(key: "name", errorLogMessage: "UserResponse missing key \"name\"") - self.username = try response.apiGet(key: "username", errorLogMessage: "UserResponse missing key \"username\"") + self.id = try response.apiGet(key: "id", caller: Self.self) + self.name = try response.apiGet(key: "name", caller: Self.self) + self.username = try response.apiGet(key: "username", caller: Self.self) } } diff --git a/Zotero/Models/API/KeyResponse.swift b/Zotero/Models/API/KeyResponse.swift index c08a54fad..49fe10af6 100644 --- a/Zotero/Models/API/KeyResponse.swift +++ b/Zotero/Models/API/KeyResponse.swift @@ -18,7 +18,7 @@ struct KeyResponse { init(response: Any) throws { guard let data = response as? [String: Any] else { throw Parsing.Error.notDictionary } - let accessData: [String: Any] = try data.apiGet(key: "access", errorLogMessage: "KeyResponse missing key \"access\"") + let accessData: [String: Any] = try data.apiGet(key: "access", caller: Self.self) self.username = (data["username"] as? String) ?? "" self.displayName = (data["displayName"] as? String) ?? "" diff --git a/Zotero/Models/API/LibraryResponse.swift b/Zotero/Models/API/LibraryResponse.swift index 4ce85ee43..a79678ceb 100644 --- a/Zotero/Models/API/LibraryResponse.swift +++ b/Zotero/Models/API/LibraryResponse.swift @@ -15,9 +15,9 @@ struct LibraryResponse { let links: LinksResponse? init(response: [String: Any]) throws { - self.id = try response.apiGet(key: "id", errorLogMessage: "LibraryResponse missing key \"id\"") - self.name = try response.apiGet(key: "name", errorLogMessage: "LibraryResponse missing key \"name\"") - self.type = try response.apiGet(key: "type", errorLogMessage: "LibraryResponse missing key \"type\"") + self.id = try response.apiGet(key: "id", caller: Self.self) + self.name = try response.apiGet(key: "name", caller: Self.self) + self.type = try response.apiGet(key: "type", caller: Self.self) self.links = try (response["links"] as? [String: Any]).flatMap({ try LinksResponse(response: $0) }) } diff --git a/Zotero/Models/API/LinksResponse.swift b/Zotero/Models/API/LinksResponse.swift index 5ad902a42..1067c5912 100644 --- a/Zotero/Models/API/LinksResponse.swift +++ b/Zotero/Models/API/LinksResponse.swift @@ -29,7 +29,7 @@ struct LinkResponse { let length: Int? init(response: [String: Any]) throws { - self.href = try response.apiGet(key: "href", errorLogMessage: "LinkResponse missing key \"href\"") + self.href = try response.apiGet(key: "href", caller: Self.self) self.type = response["type"] as? String self.title = response["title"] as? String self.length = response["length"] as? Int diff --git a/Zotero/Models/API/SearchesResponse.swift b/Zotero/Models/API/SearchesResponse.swift index c38d5e021..a5d10fe72 100644 --- a/Zotero/Models/API/SearchesResponse.swift +++ b/Zotero/Models/API/SearchesResponse.swift @@ -22,14 +22,14 @@ struct SearchResponse { let version: Int init(response: [String: Any]) throws { - let key: String = try response.apiGet(key: "key", errorLogMessage: "SearchResponse missing key \"key\"") - let library: [String: Any] = try response.apiGet(key: "library", errorLogMessage: "SearchResponse missing key \"library\"") - let data: [String: Any] = try response.apiGet(key: "data", errorLogMessage: "SearchResponse missing key \"data\"") + let key: String = try response.apiGet(key: "key", caller: Self.self) + let library: [String: Any] = try response.apiGet(key: "library", caller: Self.self) + let data: [String: Any] = try response.apiGet(key: "data", caller: Self.self) self.key = key self.library = try LibraryResponse(response: library) self.links = try (response["links"] as? [String: Any]).flatMap { try LinksResponse(response: $0) } - self.version = try response.apiGet(key: "version", errorLogMessage: "SearchResponse missing key \"version\"") + self.version = try response.apiGet(key: "version", caller: Self.self) self.data = try Data(response: data, key: key) } } @@ -41,9 +41,9 @@ extension SearchResponse.Data { throw SchemaError.unknownField(key: key, field: unknownKey) } - let conditions: [[String: Any]] = try response.apiGet(key: "conditions", errorLogMessage: "SearchResponse.Data missing key \"conditions\"") + let conditions: [[String: Any]] = try response.apiGet(key: "conditions", caller: Self.self) - self.name = try response.apiGet(key: "name", errorLogMessage: "SearchResponse.Data missing key \"name\"") + self.name = try response.apiGet(key: "name", caller: Self.self) self.conditions = try conditions.map({ try ConditionResponse(response: $0) }) self.isTrash = (response["deleted"] as? Bool) ?? ((response["deleted"] as? Int) == 1) } @@ -55,8 +55,8 @@ struct ConditionResponse { let value: String init(response: [String: Any]) throws { - self.condition = try response.apiGet(key: "condition", errorLogMessage: "ConditionResponse missing key \"condition\"") - self.`operator` = try response.apiGet(key: "operator", errorLogMessage: "ConditionResponse missing key \"operator\"") - self.value = try response.apiGet(key: "value", errorLogMessage: "ConditionResponse missing key \"value\"") + self.condition = try response.apiGet(key: "condition", caller: Self.self) + self.`operator` = try response.apiGet(key: "operator", caller: Self.self) + self.value = try response.apiGet(key: "value", caller: Self.self) } } diff --git a/Zotero/Models/API/SettingsResponse.swift b/Zotero/Models/API/SettingsResponse.swift index 43af66822..6b3309d50 100644 --- a/Zotero/Models/API/SettingsResponse.swift +++ b/Zotero/Models/API/SettingsResponse.swift @@ -48,8 +48,8 @@ struct PageIndexResponse { let (key, libraryId) = try PageIndexResponse.parse(key: key) self.key = key - self.value = try Parsing.parse(key: "value", from: dictionary, errorLogMessage: "PageIndexResponse missing key \"value\"") - self.version = try Parsing.parse(key: "version", from: dictionary, errorLogMessage: "PageIndexResponse missing key \"version\"") + self.value = try Parsing.parse(key: "value", from: dictionary, caller: Self.self) + self.version = try Parsing.parse(key: "version", from: dictionary, caller: Self.self) self.libraryId = libraryId } @@ -85,7 +85,7 @@ struct TagColorsResponse { let value: [TagColorResponse] init(response: [String: Any]) throws { - let responses: [[String: Any]] = try Parsing.parse(key: "value", from: response, errorLogMessage: "TagColorsResponse missing key \"value\"") + let responses: [[String: Any]] = try Parsing.parse(key: "value", from: response, caller: Self.self) self.value = try responses.map({ try TagColorResponse(response: $0) }) } } @@ -95,7 +95,7 @@ struct TagColorResponse { let color: String init(response: [String: Any]) throws { - self.name = try Parsing.parse(key: "name", from: response, errorLogMessage: "TagColorResponse missing key \"name\"") - self.color = try Parsing.parse(key: "color", from: response, errorLogMessage: "TagColorResponse missing key \"color\"") + self.name = try Parsing.parse(key: "name", from: response, caller: Self.self) + self.color = try Parsing.parse(key: "color", from: response, caller: Self.self) } } diff --git a/Zotero/Models/Parsing.swift b/Zotero/Models/Parsing.swift index 80270d959..0e6b76389 100644 --- a/Zotero/Models/Parsing.swift +++ b/Zotero/Models/Parsing.swift @@ -19,9 +19,9 @@ struct Parsing { case notUrl } - static func parse(key: String, from data: [String: Any], errorLogMessage: String) throws -> T { + static func parse(key: String, from data: [String: Any], caller classType: Class.Type) throws -> T { guard let parsed = data[key] as? T else { - DDLogError("Parsing: \(errorLogMessage)") + DDLogError("Parsing: \(String(describing: classType)) missing key \"\(key)\"") throw Error.missingKey(key) } return parsed @@ -57,7 +57,7 @@ struct Parsing { } extension Dictionary where Key == String { - func apiGet(key: String, errorLogMessage: String) throws -> T { - return try Parsing.parse(key: key, from: self, errorLogMessage: errorLogMessage) + func apiGet(key: String, caller classType: Class.Type) throws -> T { + return try Parsing.parse(key: key, from: self, caller: classType) } }