forked from parse-community/Parse-Swift
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathAPI+Commands.swift
140 lines (125 loc) · 5.15 KB
/
API+Commands.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
//
// API+Commands.swift
// ParseSwift (iOS)
//
// Created by Florent Vilmart on 17-09-24.
// Copyright © 2017 Parse. All rights reserved.
//
import Foundation
internal extension API {
struct Command<T, U>: Encodable where T: Encodable {
typealias ReturnType = U // swiftlint:disable:this nesting
let method: API.Method
let path: API.Endpoint
let body: T?
let mapper: ((Data) throws -> U)
let params: [String: String?]?
internal var data: Data? {
return try? ParseCoding.jsonEncoder().encode(body)
}
init(method: API.Method,
path: API.Endpoint,
params: [String: String]? = nil,
body: T? = nil,
mapper: @escaping ((Data) throws -> U)) {
self.method = method
self.path = path
self.body = body
self.mapper = mapper
self.params = params
}
public func execute(options: API.Options) throws -> U {
let params = self.params?.getQueryItems()
let headers = API.getHeaders(options: options)
let url = ParseConfiguration.serverURL.appendingPathComponent(path.urlComponent)
var components = URLComponents(url: url, resolvingAgainstBaseURL: false)!
components.queryItems = params
var urlRequest = URLRequest(url: components.url!)
urlRequest.allHTTPHeaderFields = headers
if let body = data {
urlRequest.httpBody = body
}
urlRequest.httpMethod = method.rawValue
let responseData = try URLSession.shared.syncDataTask(with: urlRequest)
do {
return try mapper(responseData)
} catch _ {
throw try ParseCoding.jsonDecoder().decode(ParseError.self, from: responseData)
}
}
enum CodingKeys: String, CodingKey { // swiftlint:disable:this nesting
case method, body, path
}
}
}
internal extension API.Command {
// MARK: Saving
static func saveCommand<T>(_ object: T) -> API.Command<T, T> where T: ParseObject {
if object.isSaved {
return updateCommand(object)
}
return createCommand(object)
}
// MARK: Saving - private
private static func createCommand<T>(_ object: T) -> API.Command<T, T> where T: ParseObject {
let mapper = { (data) -> T in
try ParseCoding.jsonDecoder().decode(SaveResponse.self, from: data).apply(object)
}
return API.Command<T, T>(method: .POST,
path: object.endpoint,
body: object,
mapper: mapper)
}
private static func updateCommand<T>(_ object: T) -> API.Command<T, T> where T: ParseObject {
let mapper = { (data: Data) -> T in
try ParseCoding.jsonDecoder().decode(UpdateResponse.self, from: data).apply(object)
}
return API.Command<T, T>(method: .PUT,
path: object.endpoint,
body: object,
mapper: mapper)
}
// MARK: Fetching
static func fetchCommand<T>(_ object: T) throws -> API.Command<T, T> where T: ParseObject {
guard object.isSaved else {
throw ParseError(code: .unknownError, message: "Cannot Fetch an object without id")
}
return API.Command<T, T>(method: .GET,
path: object.endpoint) { (data) -> T in
try ParseCoding.jsonDecoder().decode(T.self, from: data)
}
}
}
extension API.Command where T: ParseObject {
internal var data: Data? {
guard let body = body else { return nil }
return try? body.getEncoder().encode(body)
}
static func batch(commands: [API.Command<T, T>]) -> RESTBatchCommandType<T> {
let commands = commands.compactMap { (command) -> API.Command<T, T>? in
let path = ParseConfiguration.mountPath + command.path.urlComponent
guard let body = command.body else {
return nil
}
return API.Command<T, T>(method: command.method, path: .any(path),
body: body, mapper: command.mapper)
}
let bodies = commands.compactMap { (command) -> T? in
return command.body
}
let mapper = { (data: Data) -> [(T, ParseError?)] in
let decodingType = [BatchResponseItem<SaveOrUpdateResponse>].self
let responses = try ParseCoding.jsonDecoder().decode(decodingType, from: data)
return bodies.enumerated().map({ (object) -> (T, ParseError?) in
let response = responses[object.0]
if let success = response.success {
return (success.apply(object.1), nil)
} else {
return (object.1, response.error)
}
})
}
let batchCommand = BatchCommand(requests: commands)
return RESTBatchCommandType<T>(method: .POST, path: .batch, body: batchCommand, mapper: mapper)
}
}