Skip to content
This repository was archived by the owner on Jan 25, 2019. It is now read-only.
Open
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
1 change: 1 addition & 0 deletions Cartfile
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
github "SwiftyJSON/SwiftyJSON" ~> 3.1.4
github "Thomvis/BrightFutures" ~> 5.0
github "ZeWo/reflection" ~> 0.14
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick thought: updating to Swift 3 was a pain, and it seemed like the pain was exponential to the amount of dependencies involved in the project. Is it really worth adding a new dependency here? Is it absolutely required versus implementing this PR's particular reflection needs and wrapping it in an internal class?

5 changes: 3 additions & 2 deletions Cartfile.resolved
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
github "antitypical/Result" "3.0.0"
github "antitypical/Result" "3.1.0"
github "SwiftyJSON/SwiftyJSON" "3.1.4"
github "Thomvis/BrightFutures" "v5.0.1"
github "ZeWo/reflection" "0.14.3"
github "Thomvis/BrightFutures" "v5.1.0"
697 changes: 697 additions & 0 deletions Reflection/Reflection.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

26 changes: 26 additions & 0 deletions Reflection/Reflection/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
245 changes: 121 additions & 124 deletions Spine.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions Spine.xcworkspace/contents.xcworkspacedata

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

272 changes: 143 additions & 129 deletions Spine/DeserializeOperation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,13 @@ class DeserializeOperation: Operation {
resolveRelationships()

// Create a result
var responseDocument = JSONAPIDocument(data: nil, included: nil, errors: extractedErrors, meta: extractedMeta, links: extractedLinks as [String : URL]?, jsonapi: extractedJSONAPI)
// var responseDocument = JSONAPIDocument(data: nil, included: nil, errors: extractedErrors, meta: extractedMeta, links: extractedLinks as [String : URL]?, jsonapi: extractedJSONAPI)
var responseDocument = JSONAPIDocument()
responseDocument.data = extractedPrimaryResources
responseDocument.errors = extractedErrors
responseDocument.meta = extractedMeta
responseDocument.links = extractedLinks
responseDocument.jsonapi = extractedJSONAPI
if !extractedIncludedResources.isEmpty {
responseDocument.included = extractedIncludedResources
}
Expand Down Expand Up @@ -170,14 +175,18 @@ class DeserializeOperation: Operation {
}

// Dispense a resource
let resource = try resourceFactory.dispense(type, id: id, pool: &resourcePool, index: mappingTargetIndex)
let resource = try resourceFactory.dispenseRaw(type, id: id, pool: &resourcePool, index: mappingTargetIndex)

// Extract data
resource.id = id
resource.url = representation["links"]["self"].url
resource.meta = representation["meta"].dictionaryObject
extractAttributes(from: representation, intoResource: resource)
extractRelationships(from: representation, intoResource: resource)
// extractAttributes(from: representation, intoResource: resource)
// extractRelationships(from: representation, intoResource: resource)

for field in resource.fields {
field.extract(from: representation, intoResource: resource, withKeyFormatter: keyFormatter, withValueFormatters: valueFormatters, fromResourcePool: &resourcePool, withFactory: resourceFactory)
}

resource.isLoaded = true

Expand All @@ -191,61 +200,61 @@ class DeserializeOperation: Operation {
///
/// - parameter serializedData: The data from which to extract the attributes.
/// - parameter resource: The resource into which to extract the attributes.
fileprivate func extractAttributes(from serializedData: JSON, intoResource resource: Resource) {
for case let field as Attribute in resource.fields {
let key = keyFormatter.format(field)
if let extractedValue = extractAttribute(key, from: serializedData) {
let formattedValue = valueFormatters.unformatValue(extractedValue, forAttribute: field)
resource.setValue(formattedValue, forField: field.name)
}
}
}
// fileprivate func extractAttributes(from serializedData: JSON, intoResource resource: Resource) {
// for case let field as Attribute in resource.fields {
// let key = keyFormatter.format(field)
// if let extractedValue = extractAttribute(key, from: serializedData) {
// let formattedValue = valueFormatters.unformatValue(extractedValue, forAttribute: field)
// resource.setValue(formattedValue, forField: field.name)
// }
// }
// }

/// Extracts the value for the given key from the passed serialized data.
///
/// - parameter key: The data from which to extract the attribute.
/// - parameter serializedData: The key for which to extract the value from the data.
///
/// - returns: The extracted value or nil if no attribute with the given key was found in the data.
fileprivate func extractAttribute(_ key: String, from serializedData: JSON) -> Any? {
let value = serializedData["attributes"][key]

if let _ = value.null {
return nil
} else {
return value.rawValue
}
}
// fileprivate func extractAttribute(_ key: String, from serializedData: JSON) -> Any? {
// let value = serializedData["attributes"][key]
//
// if let _ = value.null {
// return nil
// } else {
// return value.rawValue
// }
// }


// MARK: Relationships

/// Extracts the relationships from the given data into the given resource.
///
/// - parameter serializedData: The data from which to extract the relationships.
/// - parameter resource: The resource into which to extract the relationships.
fileprivate func extractRelationships(from serializedData: JSON, intoResource resource: Resource) {
for field in resource.fields {
let key = keyFormatter.format(field)
resource.relationships[field.name] = extractRelationshipData(serializedData["relationships"][key])

switch field {
case let toOne as ToOneRelationship:
if let linkedResource = extractToOneRelationship(key, from: serializedData, linkedType: toOne.linkedType.resourceType) {
if resource.value(forField: toOne.name) == nil || (resource.value(forField: toOne.name) as? Resource)?.isLoaded == false {
resource.setValue(linkedResource, forField: toOne.name)
}
}
case let toMany as ToManyRelationship:
if let linkedResourceCollection = extractToManyRelationship(key, from: serializedData) {
if linkedResourceCollection.linkage != nil || resource.value(forField: toMany.name) == nil {
resource.setValue(linkedResourceCollection, forField: toMany.name)
}
}
default: ()
}
}
}
// fileprivate func extractRelationships(from serializedData: JSON, intoResource resource: Resource) {
// for field in resource.fields {
// let key = keyFormatter.format(field)
// resource.relationships[field.name] = extractRelationshipData(serializedData["relationships"][key])
//
// switch field {
// case let toOne as ToOneRelationshipProtocol:
// if let linkedResource = toOne.extractToOneRelationship(key, from: serializedData, linkedType: toOne.linkedType) { //extractToOneRelationship(key, from: serializedData, linkedType: toOne.linkedType.resourceType) { //
// if resource.value(forField: toOne.name) == nil || (resource.value(forField: toOne.name) as? Resource)?.isLoaded == false {
// resource.setValue(linkedResource, forField: toOne.name)
// }
// }
// case let toMany as ToManyRelationshipProtocol:
// if let linkedResourceCollection = toMany.extractToManyRelationship(key, from: serializedData) { // extractToManyRelationship(key, from: serializedData, linkedType: toMany.linkedType) {
// if linkedResourceCollection.linkage != nil || resource.value(forField: toMany.name) == nil {
// resource.setValue(linkedResourceCollection, forField: toMany.name)
// }
// }
// default: ()
// }
// }
// }

/// Extracts the to-one relationship for the given key from the passed serialized data.
/// This method supports both the single ID form and the resource object forms.
Expand All @@ -255,33 +264,33 @@ class DeserializeOperation: Operation {
/// - parameter linkedType: The type of the linked resource as it is defined on the parent resource.
///
/// - returns: The extracted relationship or nil if no relationship with the given key was found in the data.
fileprivate func extractToOneRelationship(_ key: String, from serializedData: JSON, linkedType: ResourceType) -> Resource? {
var resource: Resource? = nil

if let linkData = serializedData["relationships"][key].dictionary {
let type = linkData["data"]?["type"].string ?? linkedType

if let id = linkData["data"]?["id"].string {
do {
resource = try resourceFactory.dispense(type, id: id, pool: &resourcePool)
} catch {
resource = try! resourceFactory.dispense(linkedType, id: id, pool: &resourcePool)
}
} else {
do {
resource = try resourceFactory.instantiate(type)
} catch {
resource = try! resourceFactory.instantiate(linkedType)
}
}

if let resourceURL = linkData["links"]?["related"].url {
resource!.url = resourceURL
}
}

return resource
}
// fileprivate func extractToOneRelationship(_ key: String, from serializedData: JSON, linkedType: ResourceType) -> Resource? {
// var resource: Resource? = nil
//
// if let linkData = serializedData["relationships"][key].dictionary {
// let type = linkData["data"]?["type"].string ?? linkedType
//
// if let id = linkData["data"]?["id"].string {
// do {
// resource = try resourceFactory.dispense(type, id: id, pool: &resourcePool)
// } catch {
// resource = try! resourceFactory.dispense(linkedType, id: id, pool: &resourcePool)
// }
// } else {
// do {
// resource = try resourceFactory.instantiate(type)
// } catch {
// resource = try! resourceFactory.instantiate(linkedType)
// }
// }
//
// if let resourceURL = linkData["links"]?["related"].URL {
// resource!.url = resourceURL
// }
// }
//
// return resource
// }

/// Extracts the to-many relationship for the given key from the passed serialized data.
/// This method supports both the array of IDs form and the resource object forms.
Expand All @@ -290,72 +299,77 @@ class DeserializeOperation: Operation {
/// - parameter serializedData: The data from which to extract the relationship.
///
/// - returns: The extracted relationship or nil if no relationship with the given key was found in the data.
fileprivate func extractToManyRelationship(_ key: String, from serializedData: JSON) -> LinkedResourceCollection? {
var resourceCollection: LinkedResourceCollection? = nil
// fileprivate func extractToManyRelationship<T: Resource>(_ key: String, from serializedData: JSON, linkedType type: Resource.Type) -> LinkedResourceCollection<T>? {
// var resourceCollection: LinkedResourceCollection<T>? = nil
//
// if let linkData = serializedData["relationships"][key].dictionary {
// let resourcesURL: URL? = linkData["links"]?["related"].URL
// let linkURL: URL? = linkData["links"]?["self"].URL
//
// if let linkage = linkData["data"]?.array {
// let mappedLinkage = linkage.map { ResourceIdentifier(type: $0["type"].stringValue, id: $0["id"].stringValue) }
// resourceCollection = LinkedResourceCollection<T>(resourcesURL: resourcesURL, linkURL: linkURL, linkage: mappedLinkage)
// } else {
// resourceCollection = LinkedResourceCollection<T>(resourcesURL: resourcesURL, linkURL: linkURL, linkage: nil)
// }
// }
//
// return resourceCollection
// }


if let linkData = serializedData["relationships"][key].dictionary {
let resourcesURL: URL? = linkData["links"]?["related"].url
let linkURL: URL? = linkData["links"]?["self"].url

if let linkage = linkData["data"]?.array {
let mappedLinkage = linkage.map { ResourceIdentifier(type: $0["type"].stringValue, id: $0["id"].stringValue) }
resourceCollection = LinkedResourceCollection(resourcesURL: resourcesURL, linkURL: linkURL, linkage: mappedLinkage)
} else {
resourceCollection = LinkedResourceCollection(resourcesURL: resourcesURL, linkURL: linkURL, linkage: nil)
}
}

return resourceCollection
}

/// Extract the relationship data from the given JSON.
///
/// - parameter linkData: The JSON from which to extract relationship data.
///
/// - returns: A RelationshipData object.
fileprivate func extractRelationshipData(_ linkData: JSON) -> RelationshipData {
let selfURL = linkData["links"]["self"].url
let relatedURL = linkData["links"]["related"].url
let data: [ResourceIdentifier]?

if let toOne = linkData["data"].dictionary {
data = [ResourceIdentifier(type: toOne["type"]!.stringValue, id: toOne["id"]!.stringValue)]
} else if let toMany = linkData["data"].array {
data = toMany.map { JSON -> ResourceIdentifier in
return ResourceIdentifier(type: JSON["type"].stringValue, id: JSON["id"].stringValue)
}
} else {
data = nil
}

return RelationshipData(selfURL: selfURL, relatedURL: relatedURL, data: data)
}
// fileprivate func extractRelationshipData(_ linkData: JSON) -> RelationshipData {
// let selfURL = linkData["links"]["self"].URL
// let relatedURL = linkData["links"]["related"].URL
// let data: [ResourceIdentifier]?
//
// if let toOne = linkData["data"].dictionary {
// data = [ResourceIdentifier(type: toOne["type"]!.stringValue, id: toOne["id"]!.stringValue)]
// } else if let toMany = linkData["data"].array {
// data = toMany.map { JSON -> ResourceIdentifier in
// return ResourceIdentifier(type: JSON["type"].stringValue, id: JSON["id"].stringValue)
// }
// } else {
// data = nil
// }
//
// return RelationshipData(selfURL: selfURL, relatedURL: relatedURL, data: data)
// }

/// Resolves the relations of the fetched resources.
fileprivate func resolveRelationships() {
for resource in resourcePool {
for case let field as ToManyRelationship in resource.fields {

guard let linkedResourceCollection = resource.value(forField: field.name) as? LinkedResourceCollection else {
Spine.logInfo(.serializing, "Cannot resolve relationship '\(field.name)' of \(resource.resourceType):\(resource.id!) because the JSON did not include the relationship.")
continue
}

guard let linkage = linkedResourceCollection.linkage else {
Spine.logInfo(.serializing, "Cannot resolve relationship '\(field.name)' of \(resource.resourceType):\(resource.id!) because the JSON did not include linkage.")
continue
}

let targetResources = linkage.flatMap { (link: ResourceIdentifier) in
return resourcePool.filter { $0.resourceType == link.type && $0.id == link.id }
}

if !targetResources.isEmpty {
linkedResourceCollection.resources = targetResources
linkedResourceCollection.isLoaded = true
}

}
for field in resource.fields {
field.resolve(for: resource, withResourcePool: resourcePool)
}

// for case let field as ToManyRelationship in resource.fields {
//
// guard let linkedResourceCollection = resource.value(forField: field.name) as? LinkedResourceCollection else {
// Spine.logInfo(.serializing, "Cannot resolve relationship '\(field.name)' of \(resource.resourceType):\(resource.id!) because the JSON did not include the relationship.")
// continue
// }
//
// guard let linkage = linkedResourceCollection.linkage else {
// Spine.logInfo(.serializing, "Cannot resolve relationship '\(field.name)' of \(resource.resourceType):\(resource.id!) because the JSON did not include linkage.")
// continue
// }
//
// let targetResources = linkage.flatMap { (link: ResourceIdentifier) in
// return resourcePool.filter { $0.resourceType == link.type && $0.id == link.id }
// }
//
// if !targetResources.isEmpty {
// linkedResourceCollection.resources = targetResources
// linkedResourceCollection.isLoaded = true
// }
//
// }
}
}
}
Loading