Skip to content

Commit d986bfd

Browse files
otaviolimaallenhumphreys
authored andcommitted
Implement Codable & Remove SwiftyJSON dependency
1 parent 2a5d1fc commit d986bfd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1045
-1746
lines changed

ConfCore/Adapter.swift

Lines changed: 15 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,46 +7,28 @@
77
//
88

99
import Foundation
10-
import SwiftyJSON
1110

12-
enum AdapterError: Error {
13-
case invalidData
14-
case unsupported
15-
case missingKey(JSONSubscriptType)
11+
// This is needed to keep the same behavior that adapters had before
12+
// Where it could adapt array of items even if some of the individual items failed to adapt
13+
struct FailableItemArrayWrapper<T: Decodable>: Decodable {
1614

17-
var localizedDescription: String {
18-
switch self {
19-
case .invalidData:
20-
return "Invalid input data"
21-
case .unsupported:
22-
return "This type of entity is not supported"
23-
case .missingKey(let key):
24-
return "Input is missing a required key: \"\(key)\""
25-
}
26-
}
27-
}
15+
private struct Empty: Codable {}
2816

29-
protocol Adapter {
30-
associatedtype InputType
31-
associatedtype OutputType
17+
let items: [T]
3218

33-
func adapt(_ input: InputType) -> Result<OutputType, AdapterError>
34-
func adapt(_ input: [InputType]) -> Result<[OutputType], AdapterError>
35-
}
36-
37-
extension Adapter {
19+
public init(from decoder: Decoder) throws {
20+
var container = try decoder.unkeyedContainer()
21+
var items = [T]()
3822

39-
func adapt(_ input: [InputType]) -> Result<[OutputType], AdapterError> {
40-
let collection = input.compactMap { (item: InputType) -> OutputType? in
41-
let itemResult = adapt(item)
42-
switch itemResult {
43-
case .success(let resultingItem):
44-
return resultingItem
45-
default: return nil
23+
while !container.isAtEnd {
24+
if let item = try? container.decode(T.self) {
25+
items.append(item)
26+
} else {
27+
// container.currentIndex is not incremented unless something is decoded
28+
_ = try? container.decode(Empty.self)
4629
}
4730
}
4831

49-
return .success(collection)
32+
self.items = items
5033
}
51-
5234
}

ConfCore/AppleAPIClient.swift

Lines changed: 22 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
import Foundation
1010
import Siesta
11-
import SwiftyJSON
1211

1312
// MARK: - Initialization and configuration
1413

@@ -36,53 +35,45 @@ public final class AppleAPIClient {
3635
}
3736
}
3837

39-
private let jsonParser = ResponseContentTransformer { JSON($0.content as AnyObject) }
38+
4039

4140
private func configureService() {
4241
service.configure("**") { config in
43-
config.pipeline[.parsing].add(self.jsonParser, contentTypes: ["*/json"])
42+
// Parsing & Transformation is done using Codable, no need to let Siesta do the parsing
43+
config.pipeline[.parsing].removeTransformers()
4444
}
4545

46-
service.configureTransformer(environment.newsPath) { [weak self] (entity: Entity<JSON>) throws -> [NewsItem]? in
47-
guard let newsItemsJson = entity.content["items"].array else {
48-
throw APIError.adapter
46+
let decoder = JSONDecoder()
47+
decoder.dateDecodingStrategy = .formatted(.confCoreFormatter)
48+
49+
service.configureTransformer(environment.newsPath) { (entity: Entity<Data>) throws -> [NewsItem]? in
50+
struct NewsItemWrapper: Decodable {
51+
let items: FailableItemArrayWrapper<NewsItem>
4952
}
5053

51-
return try self?.failableAdaptCollection(newsItemsJson, using: NewsItemsJSONAdapter())
54+
let result = try decoder.decode(NewsItemWrapper.self, from: entity.content).items.items
55+
return result
5256
}
5357

54-
service.configureTransformer(environment.featuredSectionsPath) { [weak self] (entity: Entity<JSON>) throws -> [FeaturedSection]? in
55-
guard let sectionsJSON = entity.content["sections"].array else {
56-
throw APIError.adapter
58+
service.configureTransformer(environment.featuredSectionsPath) { (entity: Entity<Data>) throws -> [FeaturedSection]? in
59+
struct FeaturedContentWrapper: Decodable {
60+
let sections: FailableItemArrayWrapper<FeaturedSection>
5761
}
5862

59-
return try self?.failableAdaptCollection(sectionsJSON, using: FeaturedSectionsJSONAdapter())
63+
let result = try decoder.decode(FeaturedContentWrapper.self, from: entity.content).sections.items
64+
return result
6065
}
6166

62-
service.configureTransformer(environment.sessionsPath) { [weak self] (entity: Entity<JSON>) throws -> ContentsResponse? in
63-
return try self?.failableAdapt(entity.content, using: ContentsResponseAdapter())
67+
service.configureTransformer(environment.sessionsPath) { (entity: Entity<Data>) throws -> ContentsResponse? in
68+
return try decoder.decode(ContentsResponse.self, from: entity.content)
6469
}
6570

66-
service.configureTransformer(environment.videosPath) { [weak self] (entity: Entity<JSON>) throws -> SessionsResponse? in
67-
return try self?.failableAdapt(entity.content, using: SessionsResponseAdapter())
71+
service.configureTransformer(environment.videosPath) { (entity: Entity<Data>) throws -> SessionsResponse? in
72+
return try decoder.decode(SessionsResponse.self, from: entity.content)
6873
}
6974

70-
service.configureTransformer(environment.liveVideosPath) { [weak self] (entity: Entity<JSON>) throws -> [SessionAsset]? in
71-
guard let sessionsDict = entity.content["live_sessions"].dictionary else {
72-
throw APIError.adapter
73-
}
74-
75-
let sessionsArray = sessionsDict.compactMap { key, value -> JSON? in
76-
guard let id = JSON.init(rawValue: key) else { return nil }
77-
78-
var v = value
79-
80-
v["sessionId"] = id
81-
82-
return v
83-
}
84-
85-
return try self?.failableAdaptCollection(sessionsArray, using: LiveVideosAdapter())
75+
service.configureTransformer(environment.liveVideosPath) { (entity: Entity<Data>) throws -> [SessionAsset]? in
76+
return try decoder.decode(LiveVideosWrapper.self, from: entity.content).liveAssets
8677
}
8778
}
8879

@@ -199,30 +190,6 @@ public final class AppleAPIClient {
199190

200191
extension AppleAPIClient {
201192

202-
/// Convenience method to use a model adapter as a method that returns the model(s) or throws an error
203-
fileprivate func failableAdapt<A: Adapter, T>(_ input: JSON, using adapter: A) throws -> T where A.InputType == JSON, A.OutputType == T {
204-
let result = adapter.adapt(input)
205-
206-
switch result {
207-
case let .error(error):
208-
throw error
209-
case let .success(output):
210-
return output
211-
}
212-
}
213-
214-
/// Convenience method to use a model adapter as a method that returns the model(s) or throws an error
215-
fileprivate func failableAdaptCollection<A: Adapter, T>(_ input: [JSON], using adapter: A) throws -> [T] where A.InputType == JSON, A.OutputType == T {
216-
let result = adapter.adapt(input)
217-
218-
switch result {
219-
case let .error(error):
220-
throw error
221-
case let .success(output):
222-
return output
223-
}
224-
}
225-
226193
fileprivate func process<M>(_ resource: Resource, event: ResourceEvent, with completion: @escaping (Result<M, APIError>) -> Void) {
227194
switch event {
228195
case .error:

ConfCore/Bookmark.swift

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import Cocoa
1010
import RealmSwift
1111

1212
/// Bookmarks are notes the user can create while watching session videos to reference later, bookmarks can be private or shared with other users
13-
public final class Bookmark: Object, HasCloudKitFields, SoftDeletable {
13+
public final class Bookmark: Object, HasCloudKitFields, SoftDeletable, Decodable {
1414

1515
@objc public dynamic var ckFields = Data()
1616

@@ -57,4 +57,33 @@ public final class Bookmark: Object, HasCloudKitFields, SoftDeletable {
5757
return "identifier"
5858
}
5959

60+
// MARK: - Codable
61+
62+
private enum CodingKeys: String, CodingKey {
63+
case body
64+
case attributedBody
65+
case timecode
66+
case duration
67+
}
68+
69+
public required convenience init(from decoder: Decoder) throws {
70+
let container = try decoder.container(keyedBy: CodingKeys.self)
71+
72+
let body = try container.decode(String.self, forKey: .body)
73+
let timecode = try container.decode(Double.self, forKey: .timecode)
74+
let duration = try container.decode(Double.self, forKey: .duration)
75+
76+
self.init()
77+
78+
self.body = body
79+
self.timecode = timecode
80+
self.duration = duration
81+
self.isShared = true
82+
83+
if let attributedBody = try container.decodeIfPresent(String.self, forKey: .attributedBody),
84+
let attributedBodyData = Data(base64Encoded: attributedBody) {
85+
self.attributedBody = attributedBodyData
86+
}
87+
}
88+
6089
}

ConfCore/BookmarkJSONAdapter.swift

Lines changed: 0 additions & 51 deletions
This file was deleted.

ConfCore/ContributorsFetcher.swift

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
//
88

99
import Cocoa
10-
import SwiftyJSON
1110
import os.log
1211

1312
public final class ContributorsFetcher {
@@ -72,17 +71,11 @@ public final class ContributorsFetcher {
7271
}
7372

7473
fileprivate func parseResponse(_ data: Data) -> [String] {
75-
76-
guard let jsonData = try? JSON(data: data), let contributors = jsonData.array else { return [String]() }
77-
78-
var contributorNames = [String]()
79-
for contributor in contributors {
80-
if let name = contributor["login"].string {
81-
contributorNames.append(name)
82-
}
74+
guard let contributors = try? JSONDecoder().decode(Array<GitHubUser>.self, from: data) else {
75+
return [String]()
8376
}
8477

85-
return contributorNames
78+
return contributors.map { $0.login }
8679
}
8780

8881
fileprivate func buildInfoText(_ names: [String]) {
@@ -98,6 +91,10 @@ public final class ContributorsFetcher {
9891
}
9992
}
10093

94+
private struct GitHubUser: Codable {
95+
var login: String
96+
}
97+
10198
private struct GitHubPagination {
10299

103100
var first: URL?

ConfCore/DateAdapter.swift

Lines changed: 5 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -10,38 +10,15 @@ import Foundation
1010

1111
public let confCoreDateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
1212

13-
final class DateAdapter: Adapter {
14-
typealias InputType = String
15-
typealias OutputType = Date
13+
extension DateFormatter {
1614

17-
func adapt(_ input: String) -> Result<Date, AdapterError> {
18-
let formatter = DateFormatter()
19-
formatter.dateFormat = "yyyy-MM-dd"
20-
formatter.locale = Locale(identifier: "en-US")
21-
formatter.timeZone = TimeZone.current
22-
23-
if let date = formatter.date(from: input) {
24-
return .success(date)
25-
} else {
26-
return .error(.invalidData)
27-
}
28-
}
29-
}
30-
31-
final class DateTimeAdapter: Adapter {
32-
typealias InputType = String
33-
typealias OutputType = Date
34-
35-
func adapt(_ input: String) -> Result<Date, AdapterError> {
15+
static let confCoreFormatter: DateFormatter = {
3616
let formatter = DateFormatter()
3717
formatter.dateFormat = confCoreDateFormat
3818
formatter.locale = Locale(identifier: "en-US")
3919
formatter.timeZone = TimeZone.current
4020

41-
if let date = formatter.date(from: input) {
42-
return .success(date)
43-
} else {
44-
return .error(.invalidData)
45-
}
46-
}
21+
return formatter
22+
}()
23+
4724
}

0 commit comments

Comments
 (0)