Skip to content

Commit ffc88e5

Browse files
committed
Add initial support for links
1 parent 5390747 commit ffc88e5

File tree

5 files changed

+284
-8
lines changed

5 files changed

+284
-8
lines changed

Kakapo.xcodeproj/project.pbxproj

+19-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@
88

99
/* Begin PBXBuildFile section */
1010
5B16AF891A00E1CEB873C5F8 /* Pods_Kakapo_iOSTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1250C40FD2117CD20E6AE31F /* Pods_Kakapo_iOSTests.framework */; };
11+
8BE0FC801D16BD9A00FE706A /* JSONAPILinksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BE0FC7E1D16BD8300FE706A /* JSONAPILinksTests.swift */; };
12+
8BE0FC811D16BD9B00FE706A /* JSONAPILinksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BE0FC7E1D16BD8300FE706A /* JSONAPILinksTests.swift */; };
13+
8BE0FC821D16BD9C00FE706A /* JSONAPILinksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BE0FC7E1D16BD8300FE706A /* JSONAPILinksTests.swift */; };
14+
8BE0FC841D172EC900FE706A /* JSONAPILinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BE0FC831D172EC900FE706A /* JSONAPILinks.swift */; };
15+
8BE0FC851D172EC900FE706A /* JSONAPILinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BE0FC831D172EC900FE706A /* JSONAPILinks.swift */; };
16+
8BE0FC861D172EC900FE706A /* JSONAPILinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BE0FC831D172EC900FE706A /* JSONAPILinks.swift */; };
17+
8BE0FC871D172EC900FE706A /* JSONAPILinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BE0FC831D172EC900FE706A /* JSONAPILinks.swift */; };
1118
AC9577F342F6730725CC9D72 /* Pods_Kakapo_tvOSTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C67882333142806D5CF9A918 /* Pods_Kakapo_tvOSTests.framework */; };
1219
D5363674ABF8735A0A1368BD /* Pods_Kakapo_macOSTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ABE4F11D5A141DB87CD81023 /* Pods_Kakapo_macOSTests.framework */; };
1320
DE76E1311D0DC62B009721A4 /* Kakapo.h in Headers */ = {isa = PBXBuildFile; fileRef = DE76E0FA1D0DC37E009721A4 /* Kakapo.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -103,6 +110,8 @@
103110
/* Begin PBXFileReference section */
104111
1250C40FD2117CD20E6AE31F /* Pods_Kakapo_iOSTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Kakapo_iOSTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
105112
822EBBB73B6B4117D229CDF1 /* Pods-Kakapo tvOSTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Kakapo tvOSTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Kakapo tvOSTests/Pods-Kakapo tvOSTests.debug.xcconfig"; sourceTree = "<group>"; };
113+
8BE0FC7E1D16BD8300FE706A /* JSONAPILinksTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONAPILinksTests.swift; sourceTree = "<group>"; };
114+
8BE0FC831D172EC900FE706A /* JSONAPILinks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONAPILinks.swift; sourceTree = "<group>"; };
106115
ABE4F11D5A141DB87CD81023 /* Pods_Kakapo_macOSTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Kakapo_macOSTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
107116
C67882333142806D5CF9A918 /* Pods_Kakapo_tvOSTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Kakapo_tvOSTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
108117
C9E9EAD776E83C0F5785A0DB /* Pods-Kakapo macOSTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Kakapo macOSTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Kakapo macOSTests/Pods-Kakapo macOSTests.debug.xcconfig"; sourceTree = "<group>"; };
@@ -261,21 +270,23 @@
261270
DE76E0FD1D0DC38B009721A4 /* Tests */ = {
262271
isa = PBXGroup;
263272
children = (
273+
DE76E0FE1D0DC38B009721A4 /* Info.plist */,
274+
8BE0FC7E1D16BD8300FE706A /* JSONAPILinksTests.swift */,
264275
DE76E1B11D0DC857009721A4 /* JSONAPITests.swift */,
265276
DE76E1B21D0DC857009721A4 /* KakapoDBTests.swift */,
266277
DE76E1B31D0DC857009721A4 /* PropertyPolicyTests.swift */,
267278
DE76E1B41D0DC857009721A4 /* RouterTests.swift */,
268279
DE76E1B51D0DC857009721A4 /* SerializerTests.swift */,
269280
DE76E1B61D0DC857009721A4 /* URLDecomposerTests.swift */,
270281
DE76E1B71D0DC857009721A4 /* XCTestCase+CustomAssertions.swift */,
271-
DE76E0FE1D0DC38B009721A4 /* Info.plist */,
272282
);
273283
path = Tests;
274284
sourceTree = "<group>";
275285
};
276286
DE76E1001D0DC395009721A4 /* Source */ = {
277287
isa = PBXGroup;
278288
children = (
289+
8BE0FC831D172EC900FE706A /* JSONAPILinks.swift */,
279290
DE76E1011D0DC395009721A4 /* JSONAPISerializer.swift */,
280291
DE76E1021D0DC395009721A4 /* KakapoDB.swift */,
281292
DE76E1031D0DC395009721A4 /* KakapoServer.swift */,
@@ -717,6 +728,7 @@
717728
DE76E19A1D0DC755009721A4 /* PropertyPolicy.swift in Sources */,
718729
DE76E19D1D0DC755009721A4 /* URLDecomposer.swift in Sources */,
719730
DE76E1971D0DC755009721A4 /* KakapoDB.swift in Sources */,
731+
8BE0FC851D172EC900FE706A /* JSONAPILinks.swift in Sources */,
720732
DE76E19C1D0DC755009721A4 /* Serializer.swift in Sources */,
721733
DE76E1961D0DC755009721A4 /* JSONAPISerializer.swift in Sources */,
722734
DE76E19B1D0DC755009721A4 /* Router.swift in Sources */,
@@ -733,6 +745,7 @@
733745
DE76E1921D0DC755009721A4 /* PropertyPolicy.swift in Sources */,
734746
DE76E1951D0DC755009721A4 /* URLDecomposer.swift in Sources */,
735747
DE76E18F1D0DC755009721A4 /* KakapoDB.swift in Sources */,
748+
8BE0FC861D172EC900FE706A /* JSONAPILinks.swift in Sources */,
736749
DE76E1941D0DC755009721A4 /* Serializer.swift in Sources */,
737750
DE76E18E1D0DC755009721A4 /* JSONAPISerializer.swift in Sources */,
738751
DE76E1931D0DC755009721A4 /* Router.swift in Sources */,
@@ -746,6 +759,7 @@
746759
buildActionMask = 2147483647;
747760
files = (
748761
DE76E1BC1D0DC857009721A4 /* JSONAPITests.swift in Sources */,
762+
8BE0FC811D16BD9B00FE706A /* JSONAPILinksTests.swift in Sources */,
749763
DE76E1CB1D0DC857009721A4 /* URLDecomposerTests.swift in Sources */,
750764
DE76E1BF1D0DC857009721A4 /* KakapoDBTests.swift in Sources */,
751765
DE76E1C81D0DC857009721A4 /* SerializerTests.swift in Sources */,
@@ -763,6 +777,7 @@
763777
DE76E18A1D0DC754009721A4 /* PropertyPolicy.swift in Sources */,
764778
DE76E18D1D0DC754009721A4 /* URLDecomposer.swift in Sources */,
765779
DE76E1871D0DC754009721A4 /* KakapoDB.swift in Sources */,
780+
8BE0FC871D172EC900FE706A /* JSONAPILinks.swift in Sources */,
766781
DE76E18C1D0DC754009721A4 /* Serializer.swift in Sources */,
767782
DE76E1861D0DC754009721A4 /* JSONAPISerializer.swift in Sources */,
768783
DE76E18B1D0DC754009721A4 /* Router.swift in Sources */,
@@ -776,6 +791,7 @@
776791
buildActionMask = 2147483647;
777792
files = (
778793
DE76E1BD1D0DC857009721A4 /* JSONAPITests.swift in Sources */,
794+
8BE0FC821D16BD9C00FE706A /* JSONAPILinksTests.swift in Sources */,
779795
DE76E1CC1D0DC857009721A4 /* URLDecomposerTests.swift in Sources */,
780796
DE76E1C01D0DC857009721A4 /* KakapoDBTests.swift in Sources */,
781797
DE76E1C91D0DC857009721A4 /* SerializerTests.swift in Sources */,
@@ -793,6 +809,7 @@
793809
DE76E1A21D0DC756009721A4 /* PropertyPolicy.swift in Sources */,
794810
DE76E1A51D0DC756009721A4 /* URLDecomposer.swift in Sources */,
795811
DE76E19F1D0DC756009721A4 /* KakapoDB.swift in Sources */,
812+
8BE0FC841D172EC900FE706A /* JSONAPILinks.swift in Sources */,
796813
DE76E1A41D0DC756009721A4 /* Serializer.swift in Sources */,
797814
DE76E19E1D0DC756009721A4 /* JSONAPISerializer.swift in Sources */,
798815
DE76E1A31D0DC756009721A4 /* Router.swift in Sources */,
@@ -806,6 +823,7 @@
806823
buildActionMask = 2147483647;
807824
files = (
808825
DE76E1BB1D0DC857009721A4 /* JSONAPITests.swift in Sources */,
826+
8BE0FC801D16BD9A00FE706A /* JSONAPILinksTests.swift in Sources */,
809827
DE76E1CA1D0DC857009721A4 /* URLDecomposerTests.swift in Sources */,
810828
DE76E1BE1D0DC857009721A4 /* KakapoDBTests.swift in Sources */,
811829
DE76E1C71D0DC857009721A4 /* SerializerTests.swift in Sources */,

Source/JSONAPILinks.swift

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//
2+
// JSONAPILinks.swift
3+
// Kakapo
4+
//
5+
// Created by Joan Romano on 19/06/16.
6+
// Copyright © 2016 devlucky. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
public enum JSONAPILink: CustomSerializable {
12+
case Simple(value: String)
13+
case Object(href: String, meta: Serializable)
14+
15+
public func customSerialize() -> AnyObject? {
16+
switch self {
17+
case let Object(href, meta):
18+
var serializedObject = [String: AnyObject](dictionaryLiteral: ("href", href))
19+
serializedObject["meta"] = meta.serialize()
20+
21+
return serializedObject
22+
case let Simple(value):
23+
return value
24+
}
25+
}
26+
}
27+
28+
public protocol JSONAPILinkedEntity {
29+
var links: [String : JSONAPILink]? { get }
30+
var topLinks: [String : JSONAPILink]? { get }
31+
}
32+
33+
extension JSONAPILinkedEntity {
34+
public var links: [String : JSONAPILink]? { return nil }
35+
public var topLinks: [String : JSONAPILink]? { return nil }
36+
}
37+
38+
extension Array: JSONAPILinkedEntity {
39+
public var topLinks: [String : JSONAPILink]? {
40+
var returnLinks = [String : JSONAPILink]()
41+
42+
for linkedEntity in self {
43+
guard let linkedEntity = linkedEntity as? JSONAPILinkedEntity,
44+
links = linkedEntity.topLinks else { break }
45+
returnLinks += links
46+
}
47+
48+
return !returnLinks.isEmpty ? returnLinks : nil
49+
}
50+
}
51+
52+
private func += <K, V> (inout left: [K:V], right: [K:V]) {
53+
for (k, v) in right {
54+
left.updateValue(v, forKey: k)
55+
}
56+
}

Source/JSONAPISerializer.swift

+27-6
Original file line numberDiff line numberDiff line change
@@ -70,31 +70,41 @@ public protocol JSONAPIEntity: CustomSerializable, JSONAPISerializable {
7070
*/
7171
public struct JSONAPISerializer<T: JSONAPIEntity>: Serializable {
7272

73+
// Top level `data` member: the document’s “primary data”
7374
private let data: AnyObject
75+
76+
// Top level `included` member: an array of resource objects that are related to the primary data and/or each other (“included resources”).
7477
private let included: [AnyObject]?
78+
79+
// Top level `links` member: a links object related to the primary data.
80+
private let links: AnyObject?
7581

7682
/**
7783
Initialize a serializer with a single `JSONAPIEntity`
7884

79-
- parameter object: A `JSONAPIEntities`
85+
- parameter object: A `JSONAPIEntity`
86+
- parameter topLinks: A top `JSONAPILink` optional object
8087

8188
- returns: A serializable object that serializes a `JSONAPIEntity` conforming to JSON API
8289
*/
83-
public init(_ object: T) {
90+
public init(_ object: T, topLinks: [String: JSONAPILink]? = nil) {
8491
data = object.serialize()! // can't fail, JSONAPIEntity must always be serializable
8592
included = object.includedRelationships()
93+
links = topLinks.serialize()
8694
}
8795

8896
/**
8997
Initialize a serializer with an array of `JSONAPIEntity`
9098

9199
- parameter objects: An array of `JSONAPIEntity`
100+
- parameter topLinks: A top `JSONAPILink` optional object
92101

93102
- returns: A serializable object that serializes an array of `JSONAPIEntity` conforming to JSON API
94103
*/
95-
public init(_ objects: [T]) {
104+
public init(_ objects: [T], topLinks: [String: JSONAPILink]? = nil) {
96105
data = objects.serialize()! // can't fail, JSONAPIEntity must always be serializable
97106
included = objects.includedRelationships()
107+
links = topLinks.serialize()
98108
}
99109
}
100110

@@ -212,16 +222,27 @@ public extension JSONAPIEntity {
212222
var attributes = [String: AnyObject]()
213223
var relationships = [String: AnyObject]()
214224

225+
if let linkedEntity = self as? JSONAPILinkedEntity,
226+
entityLinks = linkedEntity.links where entityLinks.count > 0 {
227+
data["links"] = linkedEntity.links.serialize()
228+
}
229+
215230
for child in mirror.children {
216231
if let label = child.label {
217232
if let value = child.value as? JSONAPISerializable, let data = value.data(includeRelationships: false, includeAttributes: false) {
218233
if includeRelationships {
219-
relationships[label] = ["data": data]
234+
var relationship = [String: AnyObject](dictionaryLiteral: ("data", data))
235+
236+
if let value = value as? JSONAPILinkedEntity {
237+
relationship["links"] = value.topLinks.serialize()
238+
}
239+
240+
relationships[label] = relationship
220241
}
221-
} else if includeAttributes {
242+
} else if includeAttributes && !["id", "links", "topLinks"].contains(label) {
222243
if let value = child.value as? Serializable {
223244
attributes[label] = value.serialize()
224-
} else if label != "id" {
245+
} else {
225246
assert(child.value is AnyObject)
226247
attributes[label] = child.value as? AnyObject
227248
}

0 commit comments

Comments
 (0)