Skip to content

Commit 16c35c1

Browse files
committed
Merge branch 'develop' into release/add-pr-list-tooling
2 parents 1d267b2 + f896928 commit 16c35c1

File tree

68 files changed

+12671
-152
lines changed

Some content is hidden

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

68 files changed

+12671
-152
lines changed

Networking/Networking.xcodeproj/project.pbxproj

Lines changed: 120 additions & 21 deletions
Large diffs are not rendered by default.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import Foundation
2+
3+
4+
// MARK: - Dictionary: JSON Encoding Helpers
5+
//
6+
extension Dictionary where Key: Encodable, Value: Encodable {
7+
8+
/// Returns a String with the JSON Representation of the receiver.
9+
///
10+
func toJSONEncoded() -> String? {
11+
guard let encoded = try? JSONEncoder().encode(self) else {
12+
return nil
13+
}
14+
15+
return String(data: encoded, encoding: .utf8)
16+
}
17+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import Foundation
2+
3+
4+
// MARK: - KeyedDecodingContainer: Bulletproof JSON Decoding.
5+
//
6+
extension KeyedDecodingContainer {
7+
8+
/// Decodes the specified Type for a given Key (if present).
9+
///
10+
/// This method *does NOT throw*. We want this behavior so that if a malformed entity is received, we just skip it, rather
11+
/// than breaking the entire parsing chain.
12+
///
13+
func failsafeDecodeIfPresent<T>(_ type: T.Type, forKey key: KeyedDecodingContainer<K>.Key) -> T? where T : Decodable {
14+
do {
15+
return try decodeIfPresent(type, forKey: key)
16+
} catch {
17+
return nil
18+
}
19+
}
20+
21+
/// Decodes a String for the specified key. Supported Encodings = [String, Integer]
22+
///
23+
/// This method *does NOT throw*. We want this behavior so that if a malformed entity is received, we just skip it, rather
24+
/// than breaking the entire parsing chain.
25+
///
26+
func failsafeDecodeIfPresent(stringForKey key: KeyedDecodingContainer<K>.Key) -> String? {
27+
if let string = failsafeDecodeIfPresent(String.self, forKey: key) {
28+
return string
29+
}
30+
31+
if let stringAsInteger = failsafeDecodeIfPresent(Int.self, forKey: key) {
32+
return String(stringAsInteger)
33+
}
34+
35+
return nil
36+
}
37+
38+
/// Decodes an Integer for the specified key. Supported Encodings = [Integer / String]
39+
///
40+
/// This method *does NOT throw*. We want this behavior so that if a malformed entity is received, we just skip it, rather
41+
/// than breaking the entire parsing chain.
42+
///
43+
func failsafeDecodeIfPresent(integerForKey key: KeyedDecodingContainer<K>.Key) -> Int? {
44+
if let integer = failsafeDecodeIfPresent(Int.self, forKey: key) {
45+
return integer
46+
}
47+
48+
if let integerAsString = failsafeDecodeIfPresent(String.self, forKey: key) {
49+
return Int(integerAsString)
50+
}
51+
52+
return nil
53+
}
54+
55+
/// Decodes a Boolean for the specified key. Supported Encodings = [Bool / String]
56+
///
57+
/// This method *does NOT throw*. We want this behavior so that if a malformed entity is received, we just skip it, rather
58+
/// than breaking the entire parsing chain.
59+
///
60+
func failsafeDecodeIfPresent(booleanForKey key: KeyedDecodingContainer<K>.Key) -> Bool? {
61+
if let bool = failsafeDecodeIfPresent(Bool.self, forKey: key) {
62+
return bool
63+
}
64+
65+
if let boolAsInteger = failsafeDecodeIfPresent(Int.self, forKey: key) {
66+
return boolAsInteger == DecodingConstants.booleanTrueAsInteger
67+
}
68+
69+
return nil
70+
}
71+
}
72+
73+
74+
// MARK: - Convenience Decoding Methods
75+
//
76+
extension KeyedDecodingContainer {
77+
78+
/// Decodes a NSRange entity encoded as an array of integers, under the specified key.
79+
///
80+
func decode(arrayEncodedRangeForKey key: KeyedDecodingContainer<K>.Key) throws -> NSRange {
81+
let indices = try decode([Int].self, forKey: key)
82+
guard let start = indices.first, let end = indices.last, indices.count == 2 else {
83+
throw DecodingError.dataCorruptedError(forKey: key, in: self, debugDescription: "Invalid Indices")
84+
}
85+
86+
return NSRange(location: start, length: end - start)
87+
}
88+
}
89+
90+
91+
// MARK: - Private Decoding Constants
92+
private enum DecodingConstants {
93+
static let booleanTrueAsInteger = 1
94+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import Foundation
2+
3+
4+
/// Mapper: NoteHashes Collection
5+
///
6+
struct NoteHashListMapper: Mapper {
7+
8+
/// (Attempts) to convert an instance of Data into an array of NoteHash Entities.
9+
///
10+
func map(response: Data) throws -> [NoteHash] {
11+
return try JSONDecoder().decode(NoteHashesEnvelope.self, from: response).hashes
12+
}
13+
}
14+
15+
16+
/// NoteHashesEnvelope Disposable Entity:
17+
/// This entity allows us to parse [NoteHash] with JSONDecoder.
18+
///
19+
private struct NoteHashesEnvelope: Decodable {
20+
let hashes: [NoteHash]
21+
22+
private enum CodingKeys: String, CodingKey {
23+
case hashes = "notes"
24+
}
25+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import Foundation
2+
3+
4+
/// Mapper: Notifications List
5+
///
6+
struct NoteListMapper: Mapper {
7+
8+
/// (Attempts) to convert an instance of Data into an array of Note Entities.
9+
///
10+
func map(response: Data) throws -> [Note] {
11+
let decoder = JSONDecoder()
12+
decoder.dateDecodingStrategy = .formatted(DateFormatter.Defaults.dateTimeFormatter)
13+
14+
return try decoder.decode(NotesEnvelope.self, from: response).notes
15+
}
16+
}
17+
18+
19+
/// NotesEnvelope Disposable Entity:
20+
/// `Notifications` endpoint returns the updated order document in the `notes` key. This entity
21+
/// allows us to do parse all the things with JSONDecoder.
22+
///
23+
private struct NotesEnvelope: Decodable {
24+
let notes: [Note]
25+
26+
private enum CodingKeys: String, CodingKey {
27+
case notes = "notes"
28+
}
29+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import Foundation
2+
3+
4+
/// Mapper: Success Result
5+
///
6+
struct SuccessResultMapper: Mapper {
7+
8+
/// (Attempts) to extract the `success` flag from a given JSON Encoded response.
9+
///
10+
func map(response: Data) throws -> Bool {
11+
return try JSONDecoder().decode(SuccessResult.self, from: response).success
12+
}
13+
}
14+
15+
16+
/// Success Flag Envelope
17+
///
18+
private struct SuccessResult: Decodable {
19+
20+
/// Success Flag
21+
///
22+
let success: Bool
23+
24+
/// Coding Keys!
25+
///
26+
private enum CodingKeys: String, CodingKey {
27+
case success
28+
}
29+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import Foundation
2+
3+
4+
/// WordPress.com Request Error
5+
///
6+
public struct DotcomError: Error, Decodable {
7+
8+
/// Error Code
9+
///
10+
public let code: String
11+
12+
/// Descriptive Message
13+
///
14+
public let message: String?
15+
16+
17+
/// Coding Keys!
18+
///
19+
private enum CodingKeys: String, CodingKey {
20+
case code = "error"
21+
case message
22+
}
23+
}
24+
25+
26+
/// Known Dotcom Errors
27+
///
28+
extension DotcomError {
29+
30+
/// Something went wrong. We just don't know what!
31+
///
32+
static var unknown: DotcomError {
33+
return DotcomError(code: "unknown", message: nil)
34+
}
35+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import Foundation
2+
3+
4+
// MARK: - MetaContainer: Simple API to query the "Notification Meta" Collection.
5+
//
6+
public struct MetaContainer {
7+
8+
/// The actual Meta Payload.
9+
///
10+
let payload: [String: AnyCodable]
11+
12+
13+
/// Returns the Meta Link associated with the specified key (if any).
14+
///
15+
public func link(forKey key: Keys) -> String? {
16+
let links = container(ofType: [String: String].self, forKey: .links)
17+
return links?[key.rawValue]
18+
}
19+
20+
/// Returns the Meta ID associated with the specified key (if any).
21+
///
22+
public func identifier(forKey key: Keys) -> Int? {
23+
let identifiers = container(ofType: [String: Int].self, forKey: .ids)
24+
return identifiers?[key.rawValue]
25+
}
26+
27+
/// Returns the Meta Container for a given key (if any).
28+
///
29+
private func container<T>(ofType type: T.Type, forKey key: Containers) -> T? {
30+
return payload[key.rawValue]?.value as? T
31+
}
32+
}
33+
34+
35+
// MARK: - Nested Types
36+
//
37+
extension MetaContainer {
38+
39+
/// Known Meta Containers
40+
///
41+
enum Containers: String {
42+
case ids
43+
case links
44+
case titles
45+
}
46+
47+
/// Known Meta Keys
48+
///
49+
public enum Keys: String {
50+
case comment
51+
case home
52+
case post
53+
case reply = "reply_comment"
54+
case site
55+
case user
56+
}
57+
}

0 commit comments

Comments
 (0)