Skip to content

Commit

Permalink
Add initial support for links
Browse files Browse the repository at this point in the history
  • Loading branch information
joanromano committed Jun 25, 2016
1 parent 5390747 commit 12e3c41
Show file tree
Hide file tree
Showing 6 changed files with 316 additions and 9 deletions.
20 changes: 19 additions & 1 deletion Kakapo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@

/* Begin PBXBuildFile section */
5B16AF891A00E1CEB873C5F8 /* Pods_Kakapo_iOSTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1250C40FD2117CD20E6AE31F /* Pods_Kakapo_iOSTests.framework */; };
8BE0FC801D16BD9A00FE706A /* JSONAPILinksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BE0FC7E1D16BD8300FE706A /* JSONAPILinksTests.swift */; };
8BE0FC811D16BD9B00FE706A /* JSONAPILinksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BE0FC7E1D16BD8300FE706A /* JSONAPILinksTests.swift */; };
8BE0FC821D16BD9C00FE706A /* JSONAPILinksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BE0FC7E1D16BD8300FE706A /* JSONAPILinksTests.swift */; };
8BE0FC841D172EC900FE706A /* JSONAPILinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BE0FC831D172EC900FE706A /* JSONAPILinks.swift */; };
8BE0FC851D172EC900FE706A /* JSONAPILinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BE0FC831D172EC900FE706A /* JSONAPILinks.swift */; };
8BE0FC861D172EC900FE706A /* JSONAPILinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BE0FC831D172EC900FE706A /* JSONAPILinks.swift */; };
8BE0FC871D172EC900FE706A /* JSONAPILinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BE0FC831D172EC900FE706A /* JSONAPILinks.swift */; };
AC9577F342F6730725CC9D72 /* Pods_Kakapo_tvOSTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C67882333142806D5CF9A918 /* Pods_Kakapo_tvOSTests.framework */; };
D5363674ABF8735A0A1368BD /* Pods_Kakapo_macOSTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ABE4F11D5A141DB87CD81023 /* Pods_Kakapo_macOSTests.framework */; };
DE76E1311D0DC62B009721A4 /* Kakapo.h in Headers */ = {isa = PBXBuildFile; fileRef = DE76E0FA1D0DC37E009721A4 /* Kakapo.h */; settings = {ATTRIBUTES = (Public, ); }; };
Expand Down Expand Up @@ -103,6 +110,8 @@
/* Begin PBXFileReference section */
1250C40FD2117CD20E6AE31F /* Pods_Kakapo_iOSTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Kakapo_iOSTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
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>"; };
8BE0FC7E1D16BD8300FE706A /* JSONAPILinksTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONAPILinksTests.swift; sourceTree = "<group>"; };
8BE0FC831D172EC900FE706A /* JSONAPILinks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONAPILinks.swift; sourceTree = "<group>"; };
ABE4F11D5A141DB87CD81023 /* Pods_Kakapo_macOSTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Kakapo_macOSTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
C67882333142806D5CF9A918 /* Pods_Kakapo_tvOSTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Kakapo_tvOSTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
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>"; };
Expand Down Expand Up @@ -261,21 +270,23 @@
DE76E0FD1D0DC38B009721A4 /* Tests */ = {
isa = PBXGroup;
children = (
DE76E0FE1D0DC38B009721A4 /* Info.plist */,
8BE0FC7E1D16BD8300FE706A /* JSONAPILinksTests.swift */,
DE76E1B11D0DC857009721A4 /* JSONAPITests.swift */,
DE76E1B21D0DC857009721A4 /* KakapoDBTests.swift */,
DE76E1B31D0DC857009721A4 /* PropertyPolicyTests.swift */,
DE76E1B41D0DC857009721A4 /* RouterTests.swift */,
DE76E1B51D0DC857009721A4 /* SerializerTests.swift */,
DE76E1B61D0DC857009721A4 /* URLDecomposerTests.swift */,
DE76E1B71D0DC857009721A4 /* XCTestCase+CustomAssertions.swift */,
DE76E0FE1D0DC38B009721A4 /* Info.plist */,
);
path = Tests;
sourceTree = "<group>";
};
DE76E1001D0DC395009721A4 /* Source */ = {
isa = PBXGroup;
children = (
8BE0FC831D172EC900FE706A /* JSONAPILinks.swift */,
DE76E1011D0DC395009721A4 /* JSONAPISerializer.swift */,
DE76E1021D0DC395009721A4 /* KakapoDB.swift */,
DE76E1031D0DC395009721A4 /* KakapoServer.swift */,
Expand Down Expand Up @@ -717,6 +728,7 @@
DE76E19A1D0DC755009721A4 /* PropertyPolicy.swift in Sources */,
DE76E19D1D0DC755009721A4 /* URLDecomposer.swift in Sources */,
DE76E1971D0DC755009721A4 /* KakapoDB.swift in Sources */,
8BE0FC851D172EC900FE706A /* JSONAPILinks.swift in Sources */,
DE76E19C1D0DC755009721A4 /* Serializer.swift in Sources */,
DE76E1961D0DC755009721A4 /* JSONAPISerializer.swift in Sources */,
DE76E19B1D0DC755009721A4 /* Router.swift in Sources */,
Expand All @@ -733,6 +745,7 @@
DE76E1921D0DC755009721A4 /* PropertyPolicy.swift in Sources */,
DE76E1951D0DC755009721A4 /* URLDecomposer.swift in Sources */,
DE76E18F1D0DC755009721A4 /* KakapoDB.swift in Sources */,
8BE0FC861D172EC900FE706A /* JSONAPILinks.swift in Sources */,
DE76E1941D0DC755009721A4 /* Serializer.swift in Sources */,
DE76E18E1D0DC755009721A4 /* JSONAPISerializer.swift in Sources */,
DE76E1931D0DC755009721A4 /* Router.swift in Sources */,
Expand All @@ -746,6 +759,7 @@
buildActionMask = 2147483647;
files = (
DE76E1BC1D0DC857009721A4 /* JSONAPITests.swift in Sources */,
8BE0FC811D16BD9B00FE706A /* JSONAPILinksTests.swift in Sources */,
DE76E1CB1D0DC857009721A4 /* URLDecomposerTests.swift in Sources */,
DE76E1BF1D0DC857009721A4 /* KakapoDBTests.swift in Sources */,
DE76E1C81D0DC857009721A4 /* SerializerTests.swift in Sources */,
Expand All @@ -763,6 +777,7 @@
DE76E18A1D0DC754009721A4 /* PropertyPolicy.swift in Sources */,
DE76E18D1D0DC754009721A4 /* URLDecomposer.swift in Sources */,
DE76E1871D0DC754009721A4 /* KakapoDB.swift in Sources */,
8BE0FC871D172EC900FE706A /* JSONAPILinks.swift in Sources */,
DE76E18C1D0DC754009721A4 /* Serializer.swift in Sources */,
DE76E1861D0DC754009721A4 /* JSONAPISerializer.swift in Sources */,
DE76E18B1D0DC754009721A4 /* Router.swift in Sources */,
Expand All @@ -776,6 +791,7 @@
buildActionMask = 2147483647;
files = (
DE76E1BD1D0DC857009721A4 /* JSONAPITests.swift in Sources */,
8BE0FC821D16BD9C00FE706A /* JSONAPILinksTests.swift in Sources */,
DE76E1CC1D0DC857009721A4 /* URLDecomposerTests.swift in Sources */,
DE76E1C01D0DC857009721A4 /* KakapoDBTests.swift in Sources */,
DE76E1C91D0DC857009721A4 /* SerializerTests.swift in Sources */,
Expand All @@ -793,6 +809,7 @@
DE76E1A21D0DC756009721A4 /* PropertyPolicy.swift in Sources */,
DE76E1A51D0DC756009721A4 /* URLDecomposer.swift in Sources */,
DE76E19F1D0DC756009721A4 /* KakapoDB.swift in Sources */,
8BE0FC841D172EC900FE706A /* JSONAPILinks.swift in Sources */,
DE76E1A41D0DC756009721A4 /* Serializer.swift in Sources */,
DE76E19E1D0DC756009721A4 /* JSONAPISerializer.swift in Sources */,
DE76E1A31D0DC756009721A4 /* Router.swift in Sources */,
Expand All @@ -806,6 +823,7 @@
buildActionMask = 2147483647;
files = (
DE76E1BB1D0DC857009721A4 /* JSONAPITests.swift in Sources */,
8BE0FC801D16BD9A00FE706A /* JSONAPILinksTests.swift in Sources */,
DE76E1CA1D0DC857009721A4 /* URLDecomposerTests.swift in Sources */,
DE76E1BE1D0DC857009721A4 /* KakapoDBTests.swift in Sources */,
DE76E1C71D0DC857009721A4 /* SerializerTests.swift in Sources */,
Expand Down
33 changes: 32 additions & 1 deletion README.playground/Contents.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,35 @@
//: Playground - noun: a place where people can play

import Kakapo
@testable import Kakapo

struct Dog: JSONAPIEntity {
let id: String
let name: String
let cat: Cat?
}

struct Cat: JSONAPIEntity, JSONAPILinkedEntity {
let id: String
let name: String
let topLinks: [String : JSONAPILink]? = ["one": JSONAPILink.Simple(value: "hello top"), "two": JSONAPILink.Simple(value: "world")]
let links: [String : JSONAPILink]? = ["one": JSONAPILink.Simple(value: "hello"), "two": JSONAPILink.Simple(value: "world")]
}

struct User: JSONAPIEntity, JSONAPILinkedEntity {
let id: String
let name: String
let dog: Dog
let cats: [Cat]
let links: [String : JSONAPILink]? = ["one": JSONAPILink.Simple(value: "hello"), "two": JSONAPILink.Object(href: "hello", meta: Meta())]
}

struct Meta: Serializable {
let copyright = "Copyright 2015 Example Corp."
let authors = ["Yehuda Katz", "Steve Klabnik","Dan Gebhardt","Tyler Kellen"]
}

let cats = [Cat(id: "33", name: "Stancho"), Cat(id: "44", name: "Hez")]
let dog = Dog(id: "22", name: "Joan", cat: cats[0])
let user = User(id: "11", name: "Alex", dog: dog, cats: cats)

print(JSONAPISerializer(user).serialize()!)
56 changes: 56 additions & 0 deletions Source/JSONAPILinks.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//
// JSONAPILinks.swift
// Kakapo
//
// Created by Joan Romano on 19/06/16.
// Copyright © 2016 devlucky. All rights reserved.
//

import Foundation

public enum JSONAPILink: CustomSerializable {
case Simple(value: String)
case Object(href: String, meta: Serializable)

public func customSerialize() -> AnyObject? {
switch self {
case let Object(href, meta):
var serializedObject = [String: AnyObject](dictionaryLiteral: ("href", href))
serializedObject["meta"] = meta.serialize()

return serializedObject
case let Simple(value):
return value
}
}
}

public protocol JSONAPILinkedEntity {
var links: [String : JSONAPILink]? { get }
var topLinks: [String : JSONAPILink]? { get }
}

extension JSONAPILinkedEntity {
public var links: [String : JSONAPILink]? { return nil }
public var topLinks: [String : JSONAPILink]? { return nil }
}

extension Array: JSONAPILinkedEntity {
public var topLinks: [String : JSONAPILink]? {
var returnLinks = [String : JSONAPILink]()

for linkedEntity in self {
guard let linkedEntity = linkedEntity as? JSONAPILinkedEntity,
links = linkedEntity.topLinks else { break }
returnLinks += links
}

return !returnLinks.isEmpty ? returnLinks : nil
}
}

private func += <K, V> (inout left: [K:V], right: [K:V]) {
for (k, v) in right {
left.updateValue(v, forKey: k)
}
}
33 changes: 27 additions & 6 deletions Source/JSONAPISerializer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,31 +70,41 @@ public protocol JSONAPIEntity: CustomSerializable, JSONAPISerializable {
*/
public struct JSONAPISerializer<T: JSONAPIEntity>: Serializable {

// Top level `data` member: the document’s “primary data”
private let data: AnyObject

// Top level `included` member: an array of resource objects that are related to the primary data and/or each other (“included resources”).
private let included: [AnyObject]?

// Top level `links` member: a links object related to the primary data.
private let links: AnyObject?

/**
Initialize a serializer with a single `JSONAPIEntity`

- parameter object: A `JSONAPIEntities`
- parameter object: A `JSONAPIEntity`
- parameter topLinks: A top `JSONAPILink` optional object

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

/**
Initialize a serializer with an array of `JSONAPIEntity`

- parameter objects: An array of `JSONAPIEntity`
- parameter topLinks: A top `JSONAPILink` optional object

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

Expand Down Expand Up @@ -212,16 +222,27 @@ public extension JSONAPIEntity {
var attributes = [String: AnyObject]()
var relationships = [String: AnyObject]()

if let linkedEntity = self as? JSONAPILinkedEntity,
entityLinks = linkedEntity.links where entityLinks.count > 0 {
data["links"] = linkedEntity.links.serialize()
}

for child in mirror.children {
if let label = child.label {
if let value = child.value as? JSONAPISerializable, let data = value.data(includeRelationships: false, includeAttributes: false) {
if includeRelationships {
relationships[label] = ["data": data]
var relationship = [String: AnyObject](dictionaryLiteral: ("data", data))

if let value = value as? JSONAPILinkedEntity {
relationship["links"] = value.topLinks.serialize()
}

relationships[label] = relationship
}
} else if includeAttributes {
} else if includeAttributes && !["id", "links", "topLinks"].contains(label) {
if let value = child.value as? Serializable {
attributes[label] = value.serialize()
} else if label != "id" {
} else {
assert(child.value is AnyObject)
attributes[label] = child.value as? AnyObject
}
Expand Down
Loading

0 comments on commit 12e3c41

Please sign in to comment.