Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved API parsing logs #779

Merged
merged 2 commits into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions Zotero/Models/API/AuthorizeUploadResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

import Foundation

import CocoaLumberjackSwift

enum AuthorizeUploadResponse {
case exists(Int)
case new(AuthorizeNewUploadResponse)
Expand All @@ -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", caller: Self.self)

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", caller: Self.self)
self.params = try jsonObject.apiGet(key: "params", caller: Self.self)
}
}
10 changes: 5 additions & 5 deletions Zotero/Models/API/CollectionResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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", 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")
self.version = try response.apiGet(key: "version", caller: Self.self)
self.data = try Data(response: data, key: key)
}
}
Expand All @@ -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", caller: Self.self)
self.parentCollection = response["parentCollection"] as? String
self.isTrash = (response["deleted"] as? Bool) ?? ((response["deleted"] as? Int) == 1)
}
Expand Down
199 changes: 134 additions & 65 deletions Zotero/Models/API/ItemResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,27 @@ struct ItemResponse {
let rects: [[Double]]?
let paths: [[Double]]?

init(rawType: String, key: String, library: LibraryResponse, parentKey: String?, collectionKeys: Set<String>, 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<String>,
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
Expand All @@ -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", 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")))
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]
Expand All @@ -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", caller: Self.self)

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]]) ?? []
Expand Down Expand Up @@ -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]]) ?? []
Expand All @@ -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", 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
Expand All @@ -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", 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]]) ?? []
Expand Down Expand Up @@ -195,47 +251,51 @@ struct ItemResponse {
}

func copy(libraryId: LibraryIdentifier, collectionKeys: Set<String>, 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.
Expand All @@ -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]]?
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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", caller: Self.self)) ?? 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", caller: Self.self)
self.type = type
}

Expand All @@ -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", caller: Self.self)
self.firstName = response["firstName"] as? String
self.lastName = response["lastName"] as? String
self.name = response["name"] as? String
Expand All @@ -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", caller: Self.self)
self.name = try response.apiGet(key: "name", caller: Self.self)
self.username = try response.apiGet(key: "username", caller: Self.self)
}
}
2 changes: 1 addition & 1 deletion Zotero/Models/API/KeyResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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", caller: Self.self)

self.username = (data["username"] as? String) ?? ""
self.displayName = (data["displayName"] as? String) ?? ""
Expand Down
6 changes: 3 additions & 3 deletions Zotero/Models/API/LibraryResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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", 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) })
}

Expand Down
2 changes: 1 addition & 1 deletion Zotero/Models/API/LinksResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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", caller: Self.self)
self.type = response["type"] as? String
self.title = response["title"] as? String
self.length = response["length"] as? Int
Expand Down
Loading
Loading